icanboogie / facets
Makes it easy to implement faceted search.
Installs: 1 215
Dependents: 2
Suggesters: 2
Security: 0
Stars: 1
Watchers: 2
Forks: 0
Open Issues: 0
pkg:composer/icanboogie/facets
Requires
- php: >=7.2
- icanboogie/activerecord: ^5.0
Requires (Dev)
- icanboogie/event: ^4.0
- phpunit/phpunit: ^8.5
README
Together with the icanboogie/activerecord package, this library makes it easy to implement faceted search. The library makes it especially easy to parse query strings (bag of words), use serialized criterion values such as sets (e.g. "1|2|3") or intervals (.e.g. "1990..2010"), and fetch records matching an array of conditions.
Fetching records matching conditions
A BasicFetcher instance can be used to fetch records matching a set of conditions. The fetcher takes care of the various steps required to build the query and fetch the matching records. These steps can be summarized as follows:
- Parse the specified modifiers and extract conditions, offset, limit, order and query string.
- Build the initial query.
- Invoke criteria to alter the query.
- Alter the query with the conditions.
- Count the total number of records that match the query.
- Alter the query with the order.
- Alter the query with the offset and limit.
- Fetch the records matching the query.
- Invoke criteria to alter the records.
- Return a RecordCollection instance containing the records.
The following example demonstrates how a BasicFetcher instance can be used to fetch online articles that are classified in the "music" category, and were published between 2010 and 2014. A maximum of 10 articles can be fetched, and they are ordered starting with the most recent:
<?php use ICanBoogie\ActiveRecord; use ICanBoogie\Facets\Fetcher\BasicFetcher; $model = ActiveRecord\get_model('articles'); $fetcher = new BasicFetcher($model); $records = $fetcher([ 'year' => "2010..2014", 'is_online' => true, 'category' => "music", 'order' => "-date", 'limit' => 10 ]);
Fetch records using a model
The package adds the fetch_records() and fetch_record() methods to Model instances,
which allow for records to be fetched directly from the model, without requiring a
BasicFetcher instance to be built.
$records = $model->fetch_records([ 'year' => "2010..2014", 'is_online' => true, 'category' => "music", 'order' => "-date", 'limit' => 10 ]);
Note that the BasicFetcher instance created to fetch the records can be obtained using the second argument of the methods.
$records = $model->fetch_records($conditions, $fetcher);
Properties of interest
Once the records have been returned the following properties might be of interest:
query_string: A QueryString instance resolved from theqmodifier.conditions: An array of conditions used to filter the fetched records.order: The order in which records are fetched, as defined by theordermodifier.count: The number of records matching the query before the offset and limit were applied.
Altering the fetched records
Fetched records are returned as a RecordCollection instance, such an instance can be used to
fire the alter event of class RecordCollection\AlterEvent. Event hooks may use this event to
alter the records of the collection, for instance fetching the images associated with a
collection of articles using a single query.
<?php use ICanBoogie\Facets\RecordCollection; $records = $fetcher(…); new RecordCollection\AlterEvent($records);
Fetching records using a CriterionList instance
If using a BasicFetcher instance is not enough of a challenge for you, you can use a CriterionList instead and do all the hard work yourself:
<?php use ICanBoogie\Facets\CriterionList; use App\Modules\Vehicles; $criterion_list = new CriterionList([ 'family' => Vehicles\Families\FamilyCriterion::class, 'brand' => Vehicles\Brands\BrandCriterion::class, 'category' => Vehicles\Categories\CategoryCriterion::class, 'color' => Vehicles\Colors\ColorCriterion::class, 'energy' => Vehicles\Energies\EnergyCriterion::class, 'engine' => Vehicles\Engines\EngineCriterion::class, 'doors' => Vehicles\DoorsCriterion::class, 'year' => Vehicles\YearCriterion::class, 'price' => Vehicles\PriceCriterion::class ]); $modifiers = $_GET + [ 'q' => null, // reserved keyword for query string 'order' => null // reserved keyword for records order ]; if ($modifiers['q']) { $q = $criterion_list->parse_query_string($modifiers['q']); echo "The following words were matched: " . implode(' ', $q->matched) . '<br />'; echo "The following words were not matched: " . implode(' ', $q->not_matched) . '<br />'; // we choose to _OR_ criterion values $modifiers += array_map(function($v) { return implode('|', $v); }, $q->matches); } # # Parameters are passed by reference, $values and $query are likely to be modified. # $conditions = []; $criterion_list ->alter_conditions($conditions, $modifiers) ->alter_query($query) ->alter_query_with_conditions($query, $conditions); if ($modifiers['order']) { $criterion_list->alter_query_with_order($query, $modifiers['order']); } $count = $query->count; // count all the records matching the query $records = $query->limit(20)->all; // fetch a maximum of 20 records
Criterion values
Criterion values are usually created when a CriterionList instance alters a query with values. If a value's key matches a criterion identifier, the parse_value() of that criterion is invoked to retrieve a criterion value, which might be the exact same value, or a CriterionValue instance if the value is complex, for instance an interval or a set.
The resulting criterion value is used to alter the query during alter_query_with_value().
Interval values result in BETWEEN ? AND ?, => ?, and <= ? conditions; while set values
result in IN(?) conditions.
Interval values
Interval values are represented by IntervalCriterionValue instances. When specified as a string
two dots .. are used to separate the lower and the upper bound. An interval value can
be created with any of the following statements:
<?php use ICanBoogie\Facets\CriterionValue\IntervalCriterionValue; $value = IntervalCriterionValue::from('123..456'); // between 123 and 456 $value = IntervalCriterionValue::from('123..'); // >= 123 $value = IntervalCriterionValue::from('..456'); // <= 456 $value = IntervalCriterionValue::from([ 'min' => '123', 'max' => '456' ]); // between 123 and 456 $value = IntervalCriterionValue::from([ 'min' => '123', 'max' => null ]); // >= 123 $value = IntervalCriterionValue::from([ 'min' => null, 'max' => '456' ]); // <= 456 $value = new IntervalCriterionValue(123, 456); // between 123 and 456 $value = new IntervalCriterionValue(null, 456); // >= 123 $value = new IntervalCriterionValue(123, null); // <= 456
IntervalCriterionValue instances can also be used as strings:
<?php use ICanBoogie\Facets\CriterionValue\IntervalCriterionValue; echo new IntervalCriterionValue(123, 456); // "123..456" echo new IntervalCriterionValue(123, null); // "123.." echo new IntervalCriterionValue(null, 456); // "..456" echo new IntervalCriterionValue(123, 123); // "123" echo new IntervalCriterionValue(null, null); // ""
IntervalCriterionValue instances can be used by criteria to create BETWEEN ? AND ?, >= ?,
and <= ? conditions while they alter the query.
Set values
Set values are represented by SetCriterionValue instances. When specified as a string the pipe character "|" is used to separate the values. A set value can be created with any of the following statements:
<?php use ICanBoogie\Facets\CriterionValue\SetCriterionValue; $value = SetCriterionValue::from('1|2'); // 1 or 2 $value = SetCriterionValue::from([ 1 => 'on', 2 => 'on' ]); // 1 or 2 $value = new SetCriterionValue([ 1, 2 ]); // 1 or 2
SetCriterionValue instances can also be used as strings:
<?php use ICanBoogie\Facets\CriterionValue\SetCriterionValue; echo new SetCriterionValue([ 1, 2, 3 ]); // "1|2|3" echo new SetCriterionValue([ 1 ]); // "1" echo new SetCriterionValue([ ]); // ""
SetCriterionValue instances can be used by criteria to create IN(?) conditions while they
alter the query.
Associating criteria with models
Criteria are associated with models using activerecord config fragments and the facets key.
The criteria for a model are specified using the model's identifier.
The activerecord_facets config is synthesized from fragments. The criteria and criterion_list
getters are added to the Model class by the package and are used to respectively retrieve the
config criteria and the CriterionList instance associated with a model.
Note: This feature currently requires the ICanBoogie framework and the
icanboogie/bind-facets package. A similar feature can be implemented using only the
icanboogie/prototype package. In which case, you only need to define the criteria and
criterion_list getters for the Model class.
The following example demonstrates how the nid and slug criteria are associated with the
nodes model, and how the month and year criteria are associated with the articles model:
<?php // config/activerecord.php return [ 'facets' => [ 'nodes' => [ 'nid' => Icybee\Modules\Nodes\NidCriterion::class, 'slug' => Icybee\Modules\Nodes\SlugCriterion::class ], 'articles' => [ 'month' => Icybee\Modules\Articles\MonthCriterion::class, 'year' => Icybee\Modules\Articles\YearCriterion::class ] ] ];
Note that criteria are inherited. In our example, because articles extends nodes it inherits
its nid and slug criteria.
Obtaining the criteria associated with a model
The criteria array can be retrieved from a model using the criteria getter that is added by the
package. The getter returns the criteria and inherited criteria as they are defined in the
activerecord_facets config.
<?php use ICanBoogie\ActiveRecord; $model = ActiveRecord\get_model('articles'); array_keys($model->criteria); # [ 'nid', 'slug', 'month', 'year' ]
The criteria and criterion_list getters are added to the Model class
Obtaining the CriterionList instance associated with a model
The CriterionList instance associated with a model can be retrieved from a model using the
criterion_list getter that is added by the package. The getter returns a CriterionList
instance created from the criteria obtained through the criteria getter.
<?php $criterion_list = $model->criterion_list;
Requirements
The package requires PHP 5.5 or later.
Installation
composer require icanboogie/facets
Documentation
The package is documented as part of the ICanBoogie framework
documentation. You can generate the documentation for the package and its dependencies with
the make doc command. The documentation is generated in the build/docs directory.
ApiGen is required. The directory can later be cleaned with
the make clean command.
Testing
Run make test-container to create and log into the test container, then run make test to run the
test suite. Alternatively, run make test-coverage to run the test suite with test coverage. Open
build/coverage/index.html to see the breakdown of the code coverage.
License
icanboogie/facets is released under the New BSD License.