mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
New getSummary() API for SearchContext
This commit is contained in:
parent
18863f0916
commit
74873096bd
@ -10,30 +10,34 @@ use SilverStripe\Forms\FormField;
|
|||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\ORM\DataList;
|
use SilverStripe\ORM\DataList;
|
||||||
use SilverStripe\ORM\Filters\SearchFilter;
|
use SilverStripe\ORM\Filters\SearchFilter;
|
||||||
|
use SilverStripe\ORM\ArrayList;
|
||||||
|
use SilverStripe\View\ArrayData;
|
||||||
|
use SilverStripe\Forms\SelectField;
|
||||||
|
use SilverStripe\Forms\CheckboxField;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages searching of properties on one or more {@link DataObject}
|
* Manages searching of properties on one or more {@link DataObject}
|
||||||
* types, based on a given set of input parameters.
|
* types, based on a given set of input parameters.
|
||||||
* SearchContext is intentionally decoupled from any controller-logic,
|
* SearchContext is intentionally decoupled from any controller-logic,
|
||||||
* it just receives a set of search parameters and an object class it acts on.
|
* it just receives a set of search parameters and an object class it acts on.
|
||||||
*
|
*
|
||||||
* The default output of a SearchContext is either a {@link SQLSelect} object
|
* The default output of a SearchContext is either a {@link SQLSelect} object
|
||||||
* for further refinement, or a {@link SS_List} that can be used to display
|
* for further refinement, or a {@link SS_List} that can be used to display
|
||||||
* search results, e.g. in a {@link TableListField} instance.
|
* search results, e.g. in a {@link TableListField} instance.
|
||||||
*
|
*
|
||||||
* In case you need multiple contexts, consider namespacing your request parameters
|
* In case you need multiple contexts, consider namespacing your request parameters
|
||||||
* by using {@link FieldList->namespace()} on the $fields constructor parameter.
|
* by using {@link FieldList->namespace()} on the $fields constructor parameter.
|
||||||
*
|
*
|
||||||
* Each DataObject subclass can have multiple search contexts for different cases,
|
* Each DataObject subclass can have multiple search contexts for different cases,
|
||||||
* e.g. for a limited frontend search and a fully featured backend search.
|
* e.g. for a limited frontend search and a fully featured backend search.
|
||||||
* By default, you can use {@link DataObject->getDefaultSearchContext()} which is automatically
|
* By default, you can use {@link DataObject->getDefaultSearchContext()} which is automatically
|
||||||
* scaffolded. It uses {@link DataObject::$searchable_fields} to determine which fields
|
* scaffolded. It uses {@link DataObject::$searchable_fields} to determine which fields
|
||||||
* to include.
|
* to include.
|
||||||
*
|
*
|
||||||
* @see http://doc.silverstripe.com/doku.php?id=searchcontext
|
* @see http://doc.silverstripe.com/doku.php?id=searchcontext
|
||||||
*/
|
*/
|
||||||
class SearchContext
|
class SearchContext
|
||||||
{
|
{
|
||||||
use Injectable;
|
use Injectable;
|
||||||
@ -61,6 +65,13 @@ class SearchContext
|
|||||||
*/
|
*/
|
||||||
protected $filters;
|
protected $filters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key/value pairs of search fields to search terms
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $searchParams = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The logical connective used to join WHERE clauses. Defaults to AND.
|
* The logical connective used to join WHERE clauses. Defaults to AND.
|
||||||
* @var string
|
* @var string
|
||||||
@ -110,10 +121,10 @@ class SearchContext
|
|||||||
$baseTable = DataObject::getSchema()->baseDataTable($this->modelClass);
|
$baseTable = DataObject::getSchema()->baseDataTable($this->modelClass);
|
||||||
$fields = array("\"{$baseTable}\".*");
|
$fields = array("\"{$baseTable}\".*");
|
||||||
if ($this->modelClass != $classes[0]) {
|
if ($this->modelClass != $classes[0]) {
|
||||||
$fields[] = '"'.$classes[0].'".*';
|
$fields[] = '"' . $classes[0] . '".*';
|
||||||
}
|
}
|
||||||
//$fields = array_keys($model->db());
|
//$fields = array_keys($model->db());
|
||||||
$fields[] = '"'.$classes[0].'".\"ClassName\" AS "RecordClassName"';
|
$fields[] = '"' . $classes[0] . '".\"ClassName\" AS "RecordClassName"';
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +132,7 @@ class SearchContext
|
|||||||
* Returns a SQL object representing the search context for the given
|
* Returns a SQL object representing the search context for the given
|
||||||
* list of query parameters.
|
* list of query parameters.
|
||||||
*
|
*
|
||||||
* @param array $searchParams Map of search criteria, mostly taked from $_REQUEST.
|
* @param array $searchParams Map of search criteria, mostly taken from $_REQUEST.
|
||||||
* If a filter is applied to a relationship in dot notation,
|
* If a filter is applied to a relationship in dot notation,
|
||||||
* the parameter name should have the dots replaced with double underscores,
|
* the parameter name should have the dots replaced with double underscores,
|
||||||
* for example "Comments__Name" instead of the filter name "Comments.Name".
|
* for example "Comments__Name" instead of the filter name "Comments.Name".
|
||||||
@ -160,20 +171,14 @@ class SearchContext
|
|||||||
|
|
||||||
/** @var DataList $query */
|
/** @var DataList $query */
|
||||||
$query = $query->sort($sort);
|
$query = $query->sort($sort);
|
||||||
|
$this->setSearchParams($searchParams);
|
||||||
|
|
||||||
// hack to work with $searchParems when it's an Object
|
foreach ($this->searchParams as $key => $value) {
|
||||||
if ($searchParams instanceof HTTPRequest) {
|
|
||||||
$searchParamArray = $searchParams->getVars();
|
|
||||||
} else {
|
|
||||||
$searchParamArray = $searchParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($searchParamArray as $key => $value) {
|
|
||||||
$key = str_replace('__', '.', $key);
|
$key = str_replace('__', '.', $key);
|
||||||
if ($filter = $this->getFilter($key)) {
|
if ($filter = $this->getFilter($key)) {
|
||||||
$filter->setModel($this->modelClass);
|
$filter->setModel($this->modelClass);
|
||||||
$filter->setValue($value);
|
$filter->setValue($value);
|
||||||
if (! $filter->isEmpty()) {
|
if (!$filter->isEmpty()) {
|
||||||
$query = $query->alterDataQuery(array($filter, 'apply'));
|
$query = $query->alterDataQuery(array($filter, 'apply'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,7 +204,7 @@ class SearchContext
|
|||||||
*/
|
*/
|
||||||
public function getResults($searchParams, $sort = false, $limit = false)
|
public function getResults($searchParams, $sort = false, $limit = false)
|
||||||
{
|
{
|
||||||
$searchParams = array_filter((array)$searchParams, array($this,'clearEmptySearchFields'));
|
$searchParams = array_filter((array)$searchParams, array($this, 'clearEmptySearchFields'));
|
||||||
|
|
||||||
// getQuery actually returns a DataList
|
// getQuery actually returns a DataList
|
||||||
return $this->getQuery($searchParams, $sort, $limit);
|
return $this->getQuery($searchParams, $sort, $limit);
|
||||||
@ -311,4 +316,72 @@ class SearchContext
|
|||||||
{
|
{
|
||||||
$this->fields->removeByName($fieldName);
|
$this->fields->removeByName($fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $searchParams
|
||||||
|
*/
|
||||||
|
public function setSearchParams($searchParams)
|
||||||
|
{
|
||||||
|
// hack to work with $searchParams when it's an Object
|
||||||
|
if ($searchParams instanceof HTTPRequest) {
|
||||||
|
$this->searchParams = $searchParams->getVars();
|
||||||
|
} else {
|
||||||
|
$this->searchParams = $searchParams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getSearchParams()
|
||||||
|
{
|
||||||
|
return $this->searchParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of what fields were searched and the values provided
|
||||||
|
* for each field. Returns an ArrayList of ArrayData, suitable for
|
||||||
|
* rendering on a template.
|
||||||
|
*
|
||||||
|
* @return ArrayList
|
||||||
|
*/
|
||||||
|
public function getSummary()
|
||||||
|
{
|
||||||
|
$list = ArrayList::create();
|
||||||
|
foreach ($this->searchParams as $searchField => $searchValue) {
|
||||||
|
if (empty($searchValue)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$filter = $this->getFilter($searchField);
|
||||||
|
if (!$filter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$field = $this->fields->fieldByName($filter->getFullName());
|
||||||
|
if (!$field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For dropdowns, checkboxes, etc, get the value that was presented to the user
|
||||||
|
// e.g. not an ID
|
||||||
|
if ($field instanceof SelectField) {
|
||||||
|
$source = $field->getSource();
|
||||||
|
if (isset($source[$searchValue])) {
|
||||||
|
$searchValue = $source[$searchValue];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For checkboxes, it suffices to simply include the field in the list, since it's binary
|
||||||
|
if ($field instanceof CheckboxField) {
|
||||||
|
$searchValue = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$list->push(ArrayData::create([
|
||||||
|
'Field' => $field->Title(),
|
||||||
|
'Value' => $searchValue,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,11 @@ use SilverStripe\Dev\SapphireTest;
|
|||||||
use SilverStripe\Forms\TextField;
|
use SilverStripe\Forms\TextField;
|
||||||
use SilverStripe\Forms\TextareaField;
|
use SilverStripe\Forms\TextareaField;
|
||||||
use SilverStripe\Forms\NumericField;
|
use SilverStripe\Forms\NumericField;
|
||||||
|
use SilverStripe\Forms\DropdownField;
|
||||||
|
use SilverStripe\Forms\CheckboxField;
|
||||||
use SilverStripe\Forms\FieldList;
|
use SilverStripe\Forms\FieldList;
|
||||||
use SilverStripe\ORM\Filters\PartialMatchFilter;
|
use SilverStripe\ORM\Filters\PartialMatchFilter;
|
||||||
|
use SilverStripe\ORM\Search\SearchContext;
|
||||||
|
|
||||||
class SearchContextTest extends SapphireTest
|
class SearchContextTest extends SapphireTest
|
||||||
{
|
{
|
||||||
@ -28,13 +31,13 @@ class SearchContextTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
$person = SearchContextTest\Person::singleton();
|
$person = SearchContextTest\Person::singleton();
|
||||||
$context = $person->getDefaultSearchContext();
|
$context = $person->getDefaultSearchContext();
|
||||||
$results = $context->getResults(array('Name'=>''));
|
$results = $context->getResults(array('Name' => ''));
|
||||||
$this->assertEquals(5, $results->Count());
|
$this->assertEquals(5, $results->Count());
|
||||||
|
|
||||||
$results = $context->getResults(array('EyeColor'=>'green'));
|
$results = $context->getResults(array('EyeColor' => 'green'));
|
||||||
$this->assertEquals(2, $results->Count());
|
$this->assertEquals(2, $results->Count());
|
||||||
|
|
||||||
$results = $context->getResults(array('EyeColor'=>'green', 'HairColor'=>'black'));
|
$results = $context->getResults(array('EyeColor' => 'green', 'HairColor' => 'black'));
|
||||||
$this->assertEquals(1, $results->Count());
|
$this->assertEquals(1, $results->Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +121,7 @@ class SearchContextTest extends SapphireTest
|
|||||||
$project = singleton(SearchContextTest\Project::class);
|
$project = singleton(SearchContextTest\Project::class);
|
||||||
$context = $project->getDefaultSearchContext();
|
$context = $project->getDefaultSearchContext();
|
||||||
|
|
||||||
$params = array("Name"=>"Blog Website", "Actions__SolutionArea"=>"technical");
|
$params = array("Name" => "Blog Website", "Actions__SolutionArea" => "technical");
|
||||||
|
|
||||||
$results = $context->getResults($params);
|
$results = $context->getResults($params);
|
||||||
|
|
||||||
@ -184,4 +187,67 @@ class SearchContextTest extends SapphireTest
|
|||||||
$this->assertEquals(1, $results->Count());
|
$this->assertEquals(1, $results->Count());
|
||||||
$this->assertEquals("Filtered value", $results->First()->HiddenValue);
|
$this->assertEquals("Filtered value", $results->First()->HiddenValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSearchContextSummary()
|
||||||
|
{
|
||||||
|
$filters = [
|
||||||
|
'KeywordSearch' => PartialMatchFilter::create('KeywordSearch'),
|
||||||
|
'Country' => PartialMatchFilter::create('Country'),
|
||||||
|
'CategoryID' => PartialMatchFilter::create('CategoryID'),
|
||||||
|
'Featured' => PartialMatchFilter::create('Featured'),
|
||||||
|
'Nothing' => PartialMatchFilter::create('Nothing'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$fields = FieldList::create(
|
||||||
|
TextField::create('KeywordSearch', 'Keywords'),
|
||||||
|
TextField::create('Country', 'Country'),
|
||||||
|
DropdownField::create('CategoryID', 'Category', [
|
||||||
|
1 => 'Category one',
|
||||||
|
2 => 'Category two',
|
||||||
|
]),
|
||||||
|
CheckboxField::create('Featured', 'Featured')
|
||||||
|
);
|
||||||
|
|
||||||
|
$context = SearchContext::create(
|
||||||
|
SearchContextTest\Person::class,
|
||||||
|
$fields,
|
||||||
|
$filters
|
||||||
|
);
|
||||||
|
|
||||||
|
$context->setSearchParams([
|
||||||
|
'KeywordSearch' => 'tester',
|
||||||
|
'Country' => null,
|
||||||
|
'CategoryID' => 2,
|
||||||
|
'Featured' => 1,
|
||||||
|
'Nothing' => 'empty',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$list = $context->getSummary();
|
||||||
|
|
||||||
|
$this->assertEquals(3, $list->count());
|
||||||
|
// KeywordSearch should be in the summary
|
||||||
|
$keyword = $list->find('Field', 'Keywords');
|
||||||
|
$this->assertNotNull($keyword);
|
||||||
|
$this->assertEquals('tester', $keyword->Value);
|
||||||
|
|
||||||
|
// Country should be skipped over
|
||||||
|
$country = $list->find('Field', 'Country');
|
||||||
|
$this->assertNull($country);
|
||||||
|
|
||||||
|
// Category should be expressed as the label
|
||||||
|
$category = $list->find('Field', 'Category');
|
||||||
|
$this->assertNotNull($category);
|
||||||
|
$this->assertEquals('Category two', $category->Value);
|
||||||
|
|
||||||
|
// Featured should have no value, since it's binary
|
||||||
|
$featured = $list->find('Field', 'Featured');
|
||||||
|
$this->assertNotNull($featured);
|
||||||
|
$this->assertNull($featured->Value);
|
||||||
|
|
||||||
|
// "Nothing" should come back null since there's no field for it
|
||||||
|
$nothing = $list->find('Field', 'Nothing');
|
||||||
|
$this->assertNull($nothing);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user