From 6b1c5eb6d774ac47830f4cb50f74abed89f28f92 Mon Sep 17 00:00:00 2001 From: Tyler Trout Date: Wed, 9 Feb 2022 15:40:16 -0500 Subject: [PATCH] NEW Allow methods to be used for flexible searchable_fields (#10199) * Allow methods to be used for flexible searchable_fields * match_any key * Documentation * Update docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md Co-authored-by: GuySartorelli <36352093+GuySartorelli@users.noreply.github.com> * Search fields test * Newlines * Update src/ORM/Search/SearchContext.php Co-authored-by: Steve Boyd * Update docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md Co-authored-by: Steve Boyd * Removed comments and whitespace. Linting fixes Co-authored-by: GuySartorelli <36352093+GuySartorelli@users.noreply.github.com> Co-authored-by: Steve Boyd --- .../00_Model/11_Scaffolding.md | 25 ++++++++++++++++ src/ORM/DataObject.php | 3 ++ src/ORM/Search/SearchContext.php | 22 +++++++++++++- tests/php/ORM/Search/SearchContextTest.php | 17 +++++++++++ tests/php/ORM/Search/SearchContextTest.yml | 13 ++++++++ .../ORM/Search/SearchContextTest/Address.php | 15 ++++++++++ .../ORM/Search/SearchContextTest/Customer.php | 15 ++++++++++ .../ORM/Search/SearchContextTest/Order.php | 30 +++++++++++++++++++ 8 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 tests/php/ORM/Search/SearchContextTest/Address.php create mode 100644 tests/php/ORM/Search/SearchContextTest/Customer.php create mode 100644 tests/php/ORM/Search/SearchContextTest/Order.php diff --git a/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md b/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md index cfa6b1ead..18a90bbc2 100644 --- a/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md +++ b/docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md @@ -154,6 +154,31 @@ class Player extends DataObject ``` +Use a single search field that matches on multiple database fields with `'match_any'` + +```php +class Order extends DataObject +{ + private static $has_one = [ + 'Customer' => Customer::class, + 'ShippingAddress' => Address::class, + ]; + + private static $searchable_fields = [ + 'CustomFirstName' => [ + 'title' => 'First Name', + 'field' => TextField::class, + 'filter' => 'PartialMatchFilter', + 'match_any' => [ + // Searching with the "First Name" field will show Orders matching either Customer.FirstName or ShippingAddress.FirstName + 'Customer.FirstName', + 'ShippingAddress.FirstName', + ] + ] + ]; +} +``` + ### Summary Fields Summary fields can be used to show a quick overview of the data for a specific [DataObject](api:SilverStripe\ORM\DataObject) record. The most common use diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index 99181735d..a21f55211 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -3754,6 +3754,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity if (is_int($name)) { // Format: array('MyFieldName') $rewrite[$identifier] = []; + } elseif (is_array($specOrName) && (isset($specOrName['match_any']))) { + $rewrite[$identifier] = $fields[$identifier]; + $rewrite[$identifier]['match_any'] = $specOrName['match_any']; } elseif (is_array($specOrName) && ($relObject = $this->relObject($identifier))) { // Format: array('MyFieldName' => array( // 'filter => 'ExactMatchFilter', diff --git a/src/ORM/Search/SearchContext.php b/src/ORM/Search/SearchContext.php index a47b2f714..05ed9eb65 100644 --- a/src/ORM/Search/SearchContext.php +++ b/src/ORM/Search/SearchContext.php @@ -5,6 +5,7 @@ namespace SilverStripe\ORM\Search; use SilverStripe\Control\HTTPRequest; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Injector\Injectable; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FormField; use SilverStripe\ORM\DataObject; @@ -179,7 +180,26 @@ class SearchContext $filter->setModel($this->modelClass); $filter->setValue($value); if (!$filter->isEmpty()) { - $query = $query->alterDataQuery([$filter, 'apply']); + $modelObj = Injector::inst()->create($this->modelClass); + if (isset($modelObj->searchableFields()[$key]['match_any'])) { + $query = $query->alterDataQuery(function ($dataQuery) use ($modelObj, $key, $value) { + $searchFields = $modelObj->searchableFields()[$key]['match_any']; + $sqlSearchFields = []; + foreach ($searchFields as $dottedRelation) { + $relation = substr($dottedRelation, 0, strpos($dottedRelation, '.')); + $relations = explode('.', $dottedRelation); + $fieldName = array_pop($relations); + $relationModelName = $dataQuery->applyRelation($relation); + $relationPrefix = $dataQuery->applyRelationPrefix($relation); + $columnName = $modelObj->getSchema() + ->sqlColumnForField($relationModelName, $fieldName, $relationPrefix); + $sqlSearchFields[$columnName] = $value; + } + $dataQuery = $dataQuery->whereAny($sqlSearchFields); + }); + } else { + $query = $query->alterDataQuery([$filter, 'apply']); + } } } } diff --git a/tests/php/ORM/Search/SearchContextTest.php b/tests/php/ORM/Search/SearchContextTest.php index 7b968f4fe..18010b273 100644 --- a/tests/php/ORM/Search/SearchContextTest.php +++ b/tests/php/ORM/Search/SearchContextTest.php @@ -26,6 +26,9 @@ class SearchContextTest extends SapphireTest SearchContextTest\Deadline::class, SearchContextTest\Action::class, SearchContextTest\AllFilterTypes::class, + SearchContextTest\Customer::class, + SearchContextTest\Address::class, + SearchContextTest\Order::class, ]; public function testResultSetFilterReturnsExpectedCount() @@ -251,4 +254,18 @@ class SearchContextTest extends SapphireTest $nothing = $list->find('Field', 'Nothing'); $this->assertNull($nothing); } + + public function testMatchAnySearch() + { + $order1 = $this->objFromFixture(SearchContextTest\Order::class, 'order1'); + $context = $order1->getDefaultSearchContext(); + + // Search should match Order's customer FirstName + $results = $context->getResults(['CustomFirstName' => 'Bill']); + $this->assertEquals(1, $results->Count()); + + // Search should match Order's shipping address FirstName + $results = $context->getResults(['CustomFirstName' => 'Bob']); + $this->assertEquals(1, $results->Count()); + } } diff --git a/tests/php/ORM/Search/SearchContextTest.yml b/tests/php/ORM/Search/SearchContextTest.yml index 72ccd0b84..5eb4d4022 100644 --- a/tests/php/ORM/Search/SearchContextTest.yml +++ b/tests/php/ORM/Search/SearchContextTest.yml @@ -70,3 +70,16 @@ SilverStripe\ORM\Tests\Search\SearchContextTest\AllFilterTypes: StartsWith: 12345-6789 CamelCase EndsWith: abcd-efgh-ijkl FulltextField: one two three + +SilverStripe\ORM\Tests\Search\SearchContextTest\Customer: + customer1: + FirstName: Bill + +SilverStripe\ORM\Tests\Search\SearchContextTest\Address: + address1: + FirstName: Bob + +SilverStripe\ORM\Tests\Search\SearchContextTest\Order: + order1: + Customer: =>SilverStripe\ORM\Tests\Search\SearchContextTest\Customer.customer1 + ShippingAddress: =>SilverStripe\ORM\Tests\Search\SearchContextTest\Address.address1 diff --git a/tests/php/ORM/Search/SearchContextTest/Address.php b/tests/php/ORM/Search/SearchContextTest/Address.php new file mode 100644 index 000000000..f13f60064 --- /dev/null +++ b/tests/php/ORM/Search/SearchContextTest/Address.php @@ -0,0 +1,15 @@ + 'Text' + ]; +} diff --git a/tests/php/ORM/Search/SearchContextTest/Customer.php b/tests/php/ORM/Search/SearchContextTest/Customer.php new file mode 100644 index 000000000..4efe9efa9 --- /dev/null +++ b/tests/php/ORM/Search/SearchContextTest/Customer.php @@ -0,0 +1,15 @@ + 'Text' + ]; +} diff --git a/tests/php/ORM/Search/SearchContextTest/Order.php b/tests/php/ORM/Search/SearchContextTest/Order.php new file mode 100644 index 000000000..6aae69b44 --- /dev/null +++ b/tests/php/ORM/Search/SearchContextTest/Order.php @@ -0,0 +1,30 @@ + Customer::class, + 'ShippingAddress' => Address::class, + ]; + + private static $searchable_fields = [ + 'CustomFirstName' => [ + 'title' => 'First Name', + 'field' => TextField::class, + 'filter' => 'PartialMatchFilter', + 'match_any' => [ + // Searching with "First Name" will show Orders with matching Customer or Address names + 'Customer.FirstName', + 'ShippingAddress.FirstName', + ] + ] + ]; +}