Skip to content

Commit

Permalink
bug #5823 Allow searching by associated fields with the same name (Lu…
Browse files Browse the repository at this point in the history
…stmored)

This PR was squashed before being merged into the 4.x branch.

Discussion
----------

Allow searching by associated fields with the same name

This little change handles the case when we have custom search fields defined in CRUD configuration, but each pointing to a field with the same name. Example taken from my daily project:

Entity `Department` has (among others) 2 associations - `City` and `Company`. In CRUD I'd like to easily search by department name and both company name and city name, so:
`->setSearchFields(['id', 'name', 'city.name', 'company.name'])`

The way it's done in `EntityRepository` makes it only search by `company.name`, as field name is taken as the key for search fields configuration. I moved the field name to a property and return a list from `getSearchablePropertiesConfig()` to mitigate the issue.

This time around I was able to work with tests - I have extended `BlogPost` fixture to include optional publisher association and then search by both `author` and `publisher`. It fails without this little change and works as expected after.

Commits
-------

903abf1 Allow searching by associated fields with the same name
  • Loading branch information
javiereguiluz committed Jul 18, 2023
2 parents 07a14e1 + 903abf1 commit ff0d9ff
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 12 deletions.
13 changes: 7 additions & 6 deletions src/Orm/EntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private function addSearchClause(QueryBuilder $queryBuilder, SearchDto $searchDt
'text_query' => '%'.$lowercaseQueryTerm.'%',
];

foreach ($searchablePropertiesConfig as $propertyName => $propertyConfig) {
foreach ($searchablePropertiesConfig as $propertyConfig) {
$entityName = $propertyConfig['entity_name'];

// this complex condition is needed to avoid issues on PostgreSQL databases
Expand All @@ -96,19 +96,19 @@ private function addSearchClause(QueryBuilder $queryBuilder, SearchDto $searchDt
|| ($propertyConfig['is_numeric'] && $isNumericQueryTerm)
) {
$parameterName = sprintf('query_for_numbers_%d', $queryTermIndex);
$queryBuilder->orWhere(sprintf('%s.%s = :%s', $entityName, $propertyName, $parameterName))
$queryBuilder->orWhere(sprintf('%s.%s = :%s', $entityName, $propertyConfig['property_name'], $parameterName))
->setParameter($parameterName, $dqlParameters['numeric_query']);
} elseif ($propertyConfig['is_guid'] && $isUuidQueryTerm) {
$parameterName = sprintf('query_for_uuids_%d', $queryTermIndex);
$queryBuilder->orWhere(sprintf('%s.%s = :%s', $entityName, $propertyName, $parameterName))
$queryBuilder->orWhere(sprintf('%s.%s = :%s', $entityName, $propertyConfig['property_name'], $parameterName))
->setParameter($parameterName, $dqlParameters['uuid_query'], 'uuid' === $propertyConfig['property_data_type'] ? 'uuid' : null);
} elseif ($propertyConfig['is_ulid'] && $isUlidQueryTerm) {
$parameterName = sprintf('query_for_ulids_%d', $queryTermIndex);
$queryBuilder->orWhere(sprintf('%s.%s = :%s', $entityName, $propertyName, $parameterName))
$queryBuilder->orWhere(sprintf('%s.%s = :%s', $entityName, $propertyConfig['property_name'], $parameterName))
->setParameter($parameterName, $dqlParameters['uuid_query'], 'ulid');
} elseif ($propertyConfig['is_text'] || $propertyConfig['is_json']) {
$parameterName = sprintf('query_for_text_%d', $queryTermIndex);
$queryBuilder->orWhere(sprintf('LOWER(%s.%s) LIKE :%s', $entityName, $propertyName, $parameterName))
$queryBuilder->orWhere(sprintf('LOWER(%s.%s) LIKE :%s', $entityName, $propertyConfig['property_name'], $parameterName))
->setParameter($parameterName, $dqlParameters['text_query']);
}
}
Expand Down Expand Up @@ -263,9 +263,10 @@ private function getSearchablePropertiesConfig(QueryBuilder $queryBuilder, Searc
}
}

$searchablePropertiesConfig[$propertyName] = [
$searchablePropertiesConfig[] = [
'entity_name' => $entityName,
'property_data_type' => $propertyDataType,
'property_name' => $propertyName,
'is_boolean' => $isBoolean,
'is_small_integer' => $isSmallIntegerProperty,
'is_integer' => $isIntegerProperty,
Expand Down
11 changes: 6 additions & 5 deletions tests/Controller/Search/CustomCrudSearchControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public static function provideSearchTests(): iterable
// properties used by the search engine. That's why results are not the default ones
$totalNumberOfPosts = 20;
$numOfPostsWrittenByEachAuthor = 4;
$numOfPostsPublishedByEachUser = 2;

yield 'search by blog post title yields no results' => [
'blog post',
Expand All @@ -50,19 +51,19 @@ public static function provideSearchTests(): iterable
0,
];

yield 'search by author email' => [
yield 'search by author or publisher email' => [
'@example.com',
$totalNumberOfPosts,
];

yield 'quoted search by author email' => [
yield 'quoted search by author or published email' => [
'"user4@"',
$numOfPostsWrittenByEachAuthor,
$numOfPostsWrittenByEachAuthor + $numOfPostsPublishedByEachUser,
];

yield 'multiple search by author email (partial or complete)' => [
yield 'multiple search by author or publisher email (partial or complete)' => [
'"user2@example.com" "user4@"',
2 * $numOfPostsWrittenByEachAuthor,
2 * $numOfPostsWrittenByEachAuthor + 2 * $numOfPostsPublishedByEachUser,
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public static function getEntityFqcn(): string
public function configureCrud(Crud $crud): Crud
{
return parent::configureCrud($crud)
->setSearchFields(['id', 'author.email']);
->setSearchFields(['id', 'author.email', 'publisher.email']);
}
}
6 changes: 6 additions & 0 deletions tests/TestApplication/src/DataFixtures/AppFixtures.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public function load(ObjectManager $manager)
->addCategory($this->getReference('category'.($i % 10), Category::class))
->setAuthor($this->getReference('user'.($i % 5), User::class));

if ($i < 10) {
$blogPost->setPublisher(
$this->getReference('user'.(($i + 1) % 5), User::class)
);
}

$manager->persist($blogPost);
}

Expand Down
13 changes: 13 additions & 0 deletions tests/TestApplication/src/Entity/BlogPost.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class BlogPost
#[ORM\JoinColumn(nullable: false)]
private $author;

#[ORM\ManyToOne(targetEntity: User::class)]
private $publisher;

public function __construct()
{
$this->categories = new ArrayCollection();
Expand Down Expand Up @@ -141,4 +144,14 @@ public function setAuthor(?User $author): self

return $this;
}

public function getPublisher()
{
return $this->publisher;
}

public function setPublisher(?User $publisher): void
{
$this->publisher = $publisher;
}
}

0 comments on commit ff0d9ff

Please sign in to comment.