Skip to content

Latest commit

 

History

History
138 lines (103 loc) · 3.65 KB

01-implementing.md

File metadata and controls

138 lines (103 loc) · 3.65 KB

Implementing

SearchEngine receives a Criteria which describes how the data should be retrieved.

For example it could ask for all profiles:

$criteria = Criteria::fromQueryParameters('profile', [
    // Nothing here means "find me everything you can"
]);

It could also ask for all purchases for the given profile:

$criteria = Criteria::fromQueryParameters('purchasing', [
    // `profile_id` is a field we'd like to use as a filter
    'profile_id' => '66f856fb-186a-4f2a-8f79-8351ad0fea70',
]);

SearchEngine returns a Porpaginas\Result, allowing us to choose between getting all results or a subset of it (paginating):

$result = $searchEngine->match($criteria);

$allResults = iterator_to_array($result->getIterator());

$paginatedResults = iterator_to_array($result->take(
    $criteria->paginating->offset
    $criteria->paginating->itemsPerPage
)->getIterator());

Selecting, Filtering, Ordering and Embeding logic cannot be written directly in SearchEngine implementations, otherwise for each new resource or filter we'd need to cram more code in them.

So SearchEngine implementations are likely to follow the Strategy design pattern:

  • "Strategies" can be registered in it
  • when receiving Criteria, SearchEngine will call "Strategies" that support it

Array implementation example

The tests directory provides an example of an Array implementation, let's go through it:

<?php

namespace Gnugat\SearchEngine\Test\ArrayImplementation;

use Porpaginas\Arrays\ArrayResult;
use Porpaginas\Result;
use Gnugat\SearchEngine\{Criteria, SearchEngine};

class ArraySearchEngine implements SearchEngine
{
    private $builders = [];
    private $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function add(Builder $builder, int $priority = 0)
    {
        $this->builders[$priority][] = $builder;
    }

    public function match(Criteria $criteria) : Result
    {
        $data = $this->data;
        foreach ($this->builders as $priority => $builders) {
            foreach ($builders as $builder) {
                if (true === $builder->supports($criteria)) {
                    $data = $builder->build($criteria, $data);
                }
            }
        }

        return new ArrayResult($data);
    }
}

As we can see it doesn't do any logic with Criteria, it entirely relies on Buidlers:

<?php

namespace Gnugat\SearchEngine\Test\ArrayImplementation;

use Gnugat\SearchEngine\Criteria;

interface Builder
{
    public function supports(Criteria $criteria) : bool;
    public function build(Criteria $criteria, array $data) : array;
}

Builder is our "Strategy", with this the Array implementation is complete: filtering a resource by a field or ordering them by IDs in descending order is project specific so developers need to create their own Builder implementations.

For example they could write a Builder that filters "profiles" by "name":

<?php

namespace Gnugat\SearchEngine\Test\ArrayBuilder;

use Gnugat\SearchEngine\Test\ArrayImplementation\Builder;
use Gnugat\SearchEngine\Criteria;

class ProfileNameFilteringBuilder implements Builder
{
    public function supports(Criteria $criteria) : bool
    {
        return 'profile' === $criteria->resourceName && isset($criteria->filtering->fields['name']);
    }

    public function build(Criteria $criteria, array $data) : array
    {
        $name = $criteria->filtering->fields['name'];

        return array_values(array_filter($data, function ($profile) use ($name) {
            return $name === $profile['name'];
        }));
    }
}