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 <emteknetnz@gmail.com>

* Update docs/en/02_Developer_Guides/00_Model/11_Scaffolding.md

Co-authored-by: Steve Boyd <emteknetnz@gmail.com>

* Removed comments and whitespace. Linting fixes

Co-authored-by: GuySartorelli <36352093+GuySartorelli@users.noreply.github.com>
Co-authored-by: Steve Boyd <emteknetnz@gmail.com>
This commit is contained in:
Tyler Trout 2022-02-09 15:40:16 -05:00 committed by GitHub
parent 9b93399f1f
commit 6b1c5eb6d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 139 additions and 1 deletions

View File

@ -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
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 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

View File

@ -3754,6 +3754,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if (is_int($name)) { if (is_int($name)) {
// Format: array('MyFieldName') // Format: array('MyFieldName')
$rewrite[$identifier] = []; $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))) { } elseif (is_array($specOrName) && ($relObject = $this->relObject($identifier))) {
// Format: array('MyFieldName' => array( // Format: array('MyFieldName' => array(
// 'filter => 'ExactMatchFilter', // 'filter => 'ExactMatchFilter',

View File

@ -5,6 +5,7 @@ namespace SilverStripe\ORM\Search;
use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormField; use SilverStripe\Forms\FormField;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
@ -179,10 +180,29 @@ class SearchContext
$filter->setModel($this->modelClass); $filter->setModel($this->modelClass);
$filter->setValue($value); $filter->setValue($value);
if (!$filter->isEmpty()) { if (!$filter->isEmpty()) {
$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']); $query = $query->alterDataQuery([$filter, 'apply']);
} }
} }
} }
}
if ($this->connective != "AND") { if ($this->connective != "AND") {
throw new Exception("SearchContext connective '$this->connective' not supported after ORM-rewrite."); throw new Exception("SearchContext connective '$this->connective' not supported after ORM-rewrite.");

View File

@ -26,6 +26,9 @@ class SearchContextTest extends SapphireTest
SearchContextTest\Deadline::class, SearchContextTest\Deadline::class,
SearchContextTest\Action::class, SearchContextTest\Action::class,
SearchContextTest\AllFilterTypes::class, SearchContextTest\AllFilterTypes::class,
SearchContextTest\Customer::class,
SearchContextTest\Address::class,
SearchContextTest\Order::class,
]; ];
public function testResultSetFilterReturnsExpectedCount() public function testResultSetFilterReturnsExpectedCount()
@ -251,4 +254,18 @@ class SearchContextTest extends SapphireTest
$nothing = $list->find('Field', 'Nothing'); $nothing = $list->find('Field', 'Nothing');
$this->assertNull($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());
}
} }

View File

@ -70,3 +70,16 @@ SilverStripe\ORM\Tests\Search\SearchContextTest\AllFilterTypes:
StartsWith: 12345-6789 CamelCase StartsWith: 12345-6789 CamelCase
EndsWith: abcd-efgh-ijkl EndsWith: abcd-efgh-ijkl
FulltextField: one two three 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

View File

@ -0,0 +1,15 @@
<?php
namespace SilverStripe\ORM\Tests\Search\SearchContextTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
class Address extends DataObject implements TestOnly
{
private static $table_name = 'SearchContextTest_Address';
private static $db = [
'FirstName' => 'Text'
];
}

View File

@ -0,0 +1,15 @@
<?php
namespace SilverStripe\ORM\Tests\Search\SearchContextTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
class Customer extends DataObject implements TestOnly
{
private static $table_name = 'SearchContextTest_Customer';
private static $db = [
'FirstName' => 'Text'
];
}

View File

@ -0,0 +1,30 @@
<?php
namespace SilverStripe\ORM\Tests\Search\SearchContextTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataObject;
class Order extends DataObject implements TestOnly
{
private static $table_name = 'SearchContextTest_Order';
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 "First Name" will show Orders with matching Customer or Address names
'Customer.FirstName',
'ShippingAddress.FirstName',
]
]
];
}