This is where the magic happens. You will construct the search terms and other parameters required to form a SearchQuery object, and pass that into a SearchIndex to get results.
First, you'll need to construct a new SearchQuery object:
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = SearchQuery::create();You can then alter the SearchQuery with a number of methods:
The simplest - pass through a string to search your index for.
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = SearchQuery::create()
->addSearchTerm('fire');You can also limit this to specific fields by passing an array as the second argument, specified in the form of {table}_{field}:
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use Page;
$query = SearchQuery::create()
->addSearchTerm('on fire', [Page::class . '_Title']);Pass through a string to search your index for, with "fuzzier" matching - this means that a term like "fishing" would also likely find results containing "fish" or "fisher". Otherwise behaves the same as addSearchTerm().
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
$query = SearchQuery::create()
->addFuzzySearchTerm('fire');Only query a specific class in the index, optionally including subclasses.
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use My\Namespace\PageType\SpecialPage;
$query = SearchQuery::create()
->addClassFilter(SpecialPage::class, false); // only return results from SpecialPages, not subclassesMost values can be expressed as ranges, most commonly dates or numbers. To search for a range of values rather than an exact match,
use the SearchQuery_Range class. The range can include bounds on both sides, or stay open-ended by simply leaving the argument blank.
It takes arguments in the form of SearchQuery_Range::create($start, $end)):
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery_Range;
use My\Namespace\Index\MyIndex;
use Page;
$query = SearchQuery::create()
->addSearchTerm('fire')
// Only include documents edited in 2011 or earlier
->addFilter(Page::class . '_LastEdited', SearchQuery_Range::create(null, '2011-12-31T23:59:59Z'));
$results = MyIndex::singleton()->search($query);The Solr index updater only includes dates with values, so the field might not exist in all your index entries. A simple bounded range query (<field>:[* TO <date>]) will fail in this case. In order to query the field, reverse the search conditions and exclude the ranges you don't want:
// Wrong: Filter will ignore all empty field values
$query->addFilter('fieldname', SearchQuery_Range::create('*', 'somedate'));
// Right: Exclude the opposite range
$query->addExclude('fieldname', SearchQuery_Range::create('somedate', '*'));Note: At the moment, the date format is specific to the search implementation.
Since there's a type conversion between the SilverStripe database, object properties
and the search index persistence, it's often not clear which condition is searched for.
Should it equal an empty string, or only match if the field wasn't indexed at all?
The SearchQuery API has the concept of a "missing" and "present" field value for this:
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use My\Namespace\Index\MyIndex;
use Page;
$query = SearchQuery::create()
->addSearchTerm('fire');
// Needs a value, although it can be false
->addFilter(Page::class . '_ShowInMenus', SearchQuery::$present);
$results = MyIndex::singleton()->search($query);Once you have your query constructed, you need to run it against your index.
use SilverStripe\FullTextSearch\Search\Queries\SearchQuery;
use My\Namespace\Index\MyIndex;
$query = SearchQuery::create()->addSearchTerm('fire');
$results = MyIndex::singleton()->search($query);The return value of a search() call is an object which contains a few properties:
Matches:ArrayListof the current "page" of search results.Suggestion: (optional) Any suggested spelling corrections in the original query notationSuggestionNice: (optional) Any suggested spelling corrections for display (without query notation)SuggestionQueryString(optional) Link to repeat the search with suggested spelling corrections
SearchCriteriaInterface: Interface forSearchCriterionandSearchCriteriaclasses.SearchCriterion: An object containing a single field filter (target field, comparison value, comparison type).SearchCriteria: An object containing a collection ofSearchCriterionand/orSearchCriteriawith conjunctions (IE:AND,OR) between each.SearchQueryWriter: A class used to generate a query string based on aSearchCriterion.SearchAdapterInterface: An Interface for our SearchAdapters. This adapter will control whatSearchQueryWriteris used for eachSearchCriteria.
We need 3 things to create a SearchCriterion:
Target: EG the field in our Search Index that we want to filter against.Value: The value we want to use for comparison.Comparison: The type of comparison (EG:EQUAL,IN, etc).
All currently supported comparisons can be found as constants in SearchCriterion.
// `EQUAL` is the default comparison for `SearchCriterion`, so no third param is required.
$criterion = new SearchCriterion('Product_Title', 'My Product');
// Or use the `create` static method.
$criterion = SearchCriterion::create('Product_Title', 'My Product');SearchCriteria has a property called $clauses which is a collection of SearchCriterion (above) and/or SearchCriteria (allowing for infinite nesting of clauses), along with the conjunction used between each clause (IE: AND, OR). We want to build up our SearchCriteria by adding to it's $clauses collection.
SearchCriteria can either be passed an object that implements SearchCriteriaInterface, or it can be passed the Target, Value, and Comparison (like above).
Instantiate a new SearchCriteria by providing an already instantiated SearchCriterion object. This $criterion will be added as the first item in the $clauses collection.
$criteria = SearchCriteria::create($criterion);Instantiate a new SearchCriteria objects and define the Target, Value, and Comparison. SearchCriteria will create a new SearchCriterion object based on the values, and add it to the $clauses collection.
$criteria = SearchCriteria::create('Product_CatID', array(21, 24, 25), AbstractCriterion::IN);When you want to add more complexity to your SearchCriteria, there are two methods available:
addAnd: Add a newSearchCriterionorSearchCriteriawith anANDconjunction.addOr: Add a newSearchCriterionorSearchCriteriawith anORconjunction.
Use method chaining to create a SearchCriterion with two clauses.
// Filter by products with stock that are in either of these 3 categories.
$criteria = SearchCriteria::create('Product_CatID', array(21, 24, 25), AbstractCriterion::IN)
->addAnd('Product_Stock', 0, AbstractSearchCriterion::GREATER_THAN);Systematically add clauses to your already instantiated SearchCriteria.
// Filter by products in either of these 3 categories.
$criteria = SearchCriteria::create('Product_CatID', array(21, 24, 25), AbstractCriterion::IN);
... other stuff
// Filter by products with stock.
$criteria->addAnd('Product_StockLevel', 0, AbstractCriterion::GREATER_THAN);SearchCriteria also allows you to pass in other SearchCriteria objects as you instantiate it and as you use the addAnd and addOr methods.
// Filter by products that are in either of these 3 categories with stock.
$stockCategoryCriteria = SearchCriteria::create('Product_CatID', array(21, 24, 25), AbstractCriterion::IN)
->addAnd('Product_Stock', 0, AbstractSearchCriterion::GREATER_THAN);
// Filter by products in Category ID 1 with stock over 5.
$legoCriteria = SearchCriteria::create('Product_CatID', 1, AbstractCriterion::EQUAL)
->addAnd('Product_Stock', 5, AbstractSearchCriterion::GREATER_THAN);
// Combine the two criteria with an `OR` conjunction
$criteria = SearchCriteria::create($stockCategoryCriteria)
->addOr($legoCriteria);Our SearchQuery class now has a property called $criteria which holds all of our SearchCriteria. You can add new SearchCriteria by using SearchQuery::filterBy().
Pass in an already instantiated SearchCriteria object. If you implemented complex filtering (above), you will probably need to follow this method - fully creating your SearchCriteria first, and then passing it to the SearchQuery.
$query->filterBy($criteria);Where basic (single level) filtering is ok, the SearchQuery::filterBy() method can be used to create your SearchCriterion and SearchCriteria object.
$query->filterBy('Product_CatID', array(21, 24, 25), AbstractCriterion::IN);The filterBy() method will return the current SearchCriteria, this allows you to method chain the addAnd and addOr methods.
// Filter by products with stock that are in either of these 3 categories.
$searchQuery->filterBy('Product_CategoryID', array(21, 24, 25), AbstractCriterion::IN)
->addAnd('Product_StockLevel', 0, AbstractCriterion::GREATER_THAN);Each item in the $criteria collection are treated with an AND conjunction (matching current filter/exclude functionality).
Provided are 3 different SearchQueryWriters for Solr:
SolrSearchQueryWriter_BasicSolrSearchQueryWriter_InSolrSearchQueryWriter_Range
When these Writers are provided a SearchCriterion, they will generate the desired query string.
Search Adapters need to provide the following information:
- What is the search engine's conjunction strings? (EG: are they "AND" and "OR", or are they "&&" and "||", etc).
- What is the desired comparison container string? (EG: "+( query here )") for Solr).
- Most importantly - how to generate the query string from a
SearchCriterion.
The SolrSearchAdapter uses SearchQueryWriters (above) to generate query strings from a SearchCriterion.
If you find that you do not want your SearchCriterion being parsed by one of the default SearchQueryWriters (for whatever reason), you can optionally pass your own SearchQueryWriter to your SearchCriterion either as the fourth parameter when instantiating it, or by calling setSearchQueryWriter().
If this value is set, then the (default Solr) Adapter will always use the provided SearchQueryWriter, rather than deciding for itself.
This should allow you to have full control over how your query strings are being generated if the default SearchQueryWriters are not cutting it for you.