Skip to content

Commit

Permalink
fix: Fixed and refactored SQL search query building on Repository and…
Browse files Browse the repository at this point in the history
… Paginator levels
  • Loading branch information
ambroisemaupate committed Jun 23, 2023
1 parent 0bfc469 commit b5d320b
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 109 deletions.
10 changes: 0 additions & 10 deletions lib/RoadizCoreBundle/src/DataCollector/RequestDataCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,6 @@ public static function getTemplate(): ?string
return '@RoadizCore/DataCollector/request.html.twig';
}

public function getMethod()
{
return $this->data['method'];
}

public function getAcceptableContentTypes()
{
return $this->data['acceptable_content_types'];
}

/**
* {@inheritdoc}
*/
Expand Down
47 changes: 26 additions & 21 deletions lib/RoadizCoreBundle/src/ListManager/Paginator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace RZ\Roadiz\CoreBundle\ListManager;

use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
Expand Down Expand Up @@ -96,19 +97,19 @@ public function setDisplayingAllNodesStatuses(bool $displayAllNodesStatuses)
}

/**
* @return string
* @return string|null
*/
public function getSearchPattern()
public function getSearchPattern(): ?string
{
return $this->searchPattern;
}

/**
* @param string $searchPattern
* @param string|null $searchPattern
*
* @return $this
*/
public function setSearchPattern($searchPattern)
public function setSearchPattern(?string $searchPattern)
{
$this->searchPattern = $searchPattern;

Expand All @@ -135,8 +136,9 @@ public function getTotalCount(): int
/*
* Use QueryBuilder for non-roadiz entities
*/
$qb = $this->getSearchQueryBuilder();
$qb->select($qb->expr()->countDistinct('o'));
$alias = 'o';
$qb = $this->getSearchQueryBuilder($alias);
$qb->select($qb->expr()->countDistinct($alias));
try {
return (int)$qb->getQuery()->getSingleScalarResult();
} catch (NoResultException | NonUniqueResultException $e) {
Expand All @@ -163,7 +165,7 @@ public function getPageCount(): int
/**
* Return entities filtered for current page.
*
* @param array $order
* @param array $order
* @param int $page
*
* @return array|\Doctrine\ORM\Tools\Pagination\Paginator
Expand All @@ -186,10 +188,10 @@ public function findByAtPage(array $order = [], int $page = 1)
/**
* Use a search query to paginate instead of a findBy.
*
* @param array $order
* @param array $order
* @param int $page
*
* @return array
* @return array|\Doctrine\ORM\Tools\Pagination\Paginator
*/
public function searchByAtPage(array $order = [], int $page = 1)
{
Expand All @@ -207,12 +209,13 @@ public function searchByAtPage(array $order = [], int $page = 1)
/*
* Use QueryBuilder for non-roadiz entities
*/
$qb = $this->getSearchQueryBuilder();
$alias = 'o';
$qb = $this->getSearchQueryBuilder($alias);
$qb->setMaxResults($this->getItemsPerPage())
->setFirstResult($this->getItemsPerPage() * ($page - 1));

foreach ($order as $key => $value) {
$qb->addOrderBy('o.' . $key, $value);
$qb->addOrderBy($alias . '.' . $key, $value);
}

return $qb->getQuery()->getResult();
Expand All @@ -223,7 +226,7 @@ public function searchByAtPage(array $order = [], int $page = 1)
*
* @return $this
*/
public function setItemsPerPage(int $itemsPerPage)
public function setItemsPerPage(int $itemsPerPage): self
{
$this->itemsPerPage = $itemsPerPage;

Expand All @@ -237,29 +240,31 @@ public function getItemsPerPage(): int
return $this->itemsPerPage;
}

protected function getSearchQueryBuilder(): QueryBuilder
protected function getSearchQueryBuilder(string $alias): QueryBuilder
{
$searchableFields = $this->getSearchableFields();
if (count($searchableFields) === 0) {
throw new \RuntimeException('Entity has no searchable field.');
}
$qb = $this->getRepository()->createQueryBuilder('o');
$qb = $this->getRepository()->createQueryBuilder($alias);
$orX = [];
foreach ($this->getSearchableFields() as $field) {
$orX[] = $qb->expr()->like('o.' . $field, $qb->expr()->literal('%' . $this->searchPattern . '%'));
$orX[] = $qb->expr()->like(
'LOWER(' . $alias . '.' . $field . ')',
$qb->expr()->literal('%' . mb_strtolower($this->searchPattern) . '%')
);
}
$qb->andWhere($qb->expr()->orX(...$orX));
return $qb;
}

protected function getSearchableFields(): array
{
return array_filter(
['name', 'title', 'slug'],
function (string $fieldName) {
return $this->em->getClassMetadata($this->entityName)->hasField($fieldName);
}
);
$metadata = $this->em->getClassMetadata($this->entityName);
if (!($metadata instanceof ClassMetadataInfo)) {
throw new \RuntimeException('Entity has no metadata.');
}
return EntityRepository::getSearchableColumnsNames($metadata);
}

/**
Expand Down
12 changes: 4 additions & 8 deletions lib/RoadizCoreBundle/src/Repository/DocumentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,15 +222,11 @@ protected function createSearchBy(
*/
$qb->leftJoin($alias . '.documentTranslations', 'dt');
$criteriaFields = [];
$metadata = $this->_em->getClassMetadata(DocumentTranslation::class);
$cols = $metadata->getColumnNames();
foreach ($cols as $col) {
$field = $metadata->getFieldName($col);
$type = $metadata->getTypeOfField($field);
if (in_array($type, $this->searchableTypes)) {
$criteriaFields[$field] = '%' . strip_tags((string) $pattern) . '%';
}

foreach (self::getSearchableColumnsNames($this->_em->getClassMetadata(DocumentTranslation::class)) as $field) {
$criteriaFields[$field] = '%' . strip_tags(mb_strtolower($pattern)) . '%';
}

foreach ($criteriaFields as $key => $value) {
$fullKey = sprintf('LOWER(%s)', 'dt.' . $key);
$qb->orWhere($qb->expr()->like($fullKey, $qb->expr()->literal($value)));
Expand Down
67 changes: 40 additions & 27 deletions lib/RoadizCoreBundle/src/Repository/EntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Query;
Expand Down Expand Up @@ -75,14 +76,6 @@ public function __construct(
*/
public const NODETYPE_ALIAS = 'nt';

/**
* Doctrine column types that can be search
* with LIKE feature.
*
* @var array
*/
protected array $searchableTypes = ['string', 'text'];

/**
* @param QueryBuilder $qb
* @param class-string $entityClass
Expand All @@ -97,7 +90,7 @@ protected function dispatchQueryBuilderEvent(QueryBuilder $qb, string $entityCla
* @param string $property
* @param mixed $value
*
* @return object|QueryBuilderBuildEvent
* @return QueryBuilderBuildEvent
*/
protected function dispatchQueryBuilderBuildEvent(QueryBuilder $qb, string $property, mixed $value): object
{
Expand All @@ -113,7 +106,7 @@ protected function dispatchQueryBuilderBuildEvent(QueryBuilder $qb, string $prop
/**
* @param Query $query
*
* @return object|QueryEvent
* @return QueryEvent
*/
protected function dispatchQueryEvent(Query $query): object
{
Expand All @@ -128,7 +121,7 @@ protected function dispatchQueryEvent(Query $query): object
* @param string $property
* @param mixed $value
*
* @return object|QueryBuilderApplyEvent
* @return QueryBuilderApplyEvent
*/
protected function dispatchQueryBuilderApplyEvent(QueryBuilder $qb, string $property, mixed $value): object
{
Expand Down Expand Up @@ -234,6 +227,40 @@ public function countBy(mixed $criteria): int
return 0;
}

/**
* @param ClassMetadataInfo $metadata
* @return array
*/
public static function getSearchableColumnsNames(ClassMetadataInfo $metadata): array
{
/*
* Get fields needed for a search query
*/
$criteriaFields = [];
$cols = $metadata->getColumnNames();
foreach ($cols as $col) {
$field = $metadata->getFieldName($col);
$type = $metadata->getTypeOfField($field);
if (
in_array($type, ['string', 'text']) &&
!in_array($field, [
'color',
'folder',
'childrenOrder',
'childrenOrderDirection',
'password',
'salt',
'token',
'confirmationToken'
])
) {
$criteriaFields[] = $field;
}
}

return $criteriaFields;
}

/**
* Create a LIKE comparison with entity texts colunms.
*
Expand All @@ -247,23 +274,9 @@ protected function classicLikeComparison(
QueryBuilder $qb,
string $alias = EntityRepository::DEFAULT_ALIAS
): QueryBuilder {
/*
* Get fields needed for a search query
*/
$metadata = $this->_em->getClassMetadata($this->getEntityName());
$criteriaFields = [];
$cols = $metadata->getColumnNames();
foreach ($cols as $col) {
$field = $metadata->getFieldName($col);
$type = $metadata->getTypeOfField($field);
if (
in_array($type, $this->searchableTypes) &&
$field != 'folder' &&
$field != 'childrenOrder' &&
$field != 'childrenOrderDirection'
) {
$criteriaFields[$field] = '%' . strip_tags((string) $pattern) . '%';
}
foreach (static::getSearchableColumnsNames($this->getClassMetadata()) as $field) {
$criteriaFields[$field] = '%' . strip_tags(mb_strtolower($pattern)) . '%';
}

foreach ($criteriaFields as $key => $value) {
Expand Down
16 changes: 5 additions & 11 deletions lib/RoadizCoreBundle/src/Repository/FolderRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,24 +209,18 @@ protected function createSearchBy(
* Search in translations
*/
$qb->leftJoin('obj.translatedFolders', 'tf');

$criteriaFields = [];
$metadata = $this->_em->getClassMetadata(FolderTranslation::class);
$cols = $metadata->getColumnNames();
foreach ($cols as $col) {
$field = $metadata->getFieldName($col);
$type = $metadata->getTypeOfField($field);
if (in_array($type, $this->searchableTypes)) {
$criteriaFields[$field] = '%' . strip_tags((string) $pattern) . '%';
}
foreach (self::getSearchableColumnsNames($this->_em->getClassMetadata(FolderTranslation::class)) as $field) {
$criteriaFields[$field] = '%' . strip_tags(mb_strtolower($pattern)) . '%';
}

foreach ($criteriaFields as $key => $value) {
$fullKey = sprintf('LOWER(%s)', 'tf.' . $key);
$qb->orWhere($qb->expr()->like($fullKey, $qb->expr()->literal($value)));
}

$qb = $this->prepareComparisons($criteria, $qb, $alias);

return $qb;
return $this->prepareComparisons($criteria, $qb, $alias);
}

/**
Expand Down
10 changes: 2 additions & 8 deletions lib/RoadizCoreBundle/src/Repository/NodeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -1001,14 +1001,8 @@ protected function createSearchBy(
*/
$qb->innerJoin($alias . '.nodeSources', self::NODESSOURCES_ALIAS);
$criteriaFields = [];
$metadatas = $this->_em->getClassMetadata(NodesSources::class);
$cols = $metadatas->getColumnNames();
foreach ($cols as $col) {
$field = $metadatas->getFieldName($col);
$type = $metadatas->getTypeOfField($field);
if (in_array($type, $this->searchableTypes)) {
$criteriaFields[$field] = '%' . strip_tags((string) $pattern) . '%';
}
foreach (self::getSearchableColumnsNames($this->_em->getClassMetadata(NodesSources::class)) as $field) {
$criteriaFields[$field] = '%' . strip_tags(mb_strtolower($pattern)) . '%';
}
foreach ($criteriaFields as $key => $value) {
$fullKey = sprintf('LOWER(%s)', self::NODESSOURCES_ALIAS . '.' . $key);
Expand Down
16 changes: 3 additions & 13 deletions lib/RoadizCoreBundle/src/Repository/PrefixAwareRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
use RZ\Roadiz\CoreBundle\Doctrine\ORM\SimpleQueryBuilder;
use RZ\Roadiz\CoreBundle\Entity\FolderTranslation;

/**
* Class PrefixAwareRepository for defining join-queries prefixes.
Expand Down Expand Up @@ -314,20 +315,9 @@ protected function classicLikeComparison(
/*
* Get fields needed for a search query
*/
$metadatas = $this->_em->getClassMetadata($this->getEntityName());
$criteriaFields = [];
$cols = $metadatas->getColumnNames();
foreach ($cols as $col) {
$field = $metadatas->getFieldName($col);
$type = $metadatas->getTypeOfField($field);
if (
in_array($type, $this->searchableTypes) &&
$field != 'folder' &&
$field != 'childrenOrder' &&
$field != 'childrenOrderDirection'
) {
$criteriaFields[$field] = '%' . strip_tags((string) $pattern) . '%';
}
foreach (static::getSearchableColumnsNames($this->getClassMetadata()) as $field) {
$criteriaFields[$field] = '%' . strip_tags(mb_strtolower($pattern)) . '%';
}

foreach ($criteriaFields as $key => $value) {
Expand Down
15 changes: 4 additions & 11 deletions lib/RoadizCoreBundle/src/Repository/TagRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -562,24 +562,17 @@ protected function createSearchBy(
* Search in translations
*/
$qb->leftJoin($alias . '.translatedTags', 'tt');

$criteriaFields = [];
$metadatas = $this->_em->getClassMetadata(TagTranslation::class);
$cols = $metadatas->getColumnNames();
foreach ($cols as $col) {
$field = $metadatas->getFieldName($col);
$type = $metadatas->getTypeOfField($field);
if (in_array($type, $this->searchableTypes)) {
$criteriaFields[$field] = '%' . strip_tags((string) $pattern) . '%';
}
foreach (self::getSearchableColumnsNames($this->_em->getClassMetadata(TagTranslation::class)) as $field) {
$criteriaFields[$field] = '%' . strip_tags(mb_strtolower($pattern)) . '%';
}
foreach ($criteriaFields as $key => $value) {
$fullKey = sprintf('LOWER(%s)', 'tt.' . $key);
$qb->orWhere($qb->expr()->like($fullKey, $qb->expr()->literal($value)));
}

$qb = $this->prepareComparisons($criteria, $qb, $alias);

return $qb;
return $this->prepareComparisons($criteria, $qb, $alias);
}

/**
Expand Down

0 comments on commit b5d320b

Please sign in to comment.