Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 134 additions & 1 deletion docs/search-and-filters/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,6 @@ for example. Instead of displaying all the product variants, you may group them
->addFilter(Condition::search('product title'))
->distinct('product_id')
->getResult();
}

.. note::

Expand All @@ -529,6 +528,140 @@ for example. Instead of displaying all the product variants, you may group them

--------------

Serializing searches
--------------------

SEAL keeps the core ``SearchBuilder``, conditions and facets focused on building searches. If you
want to exchange search state with a UI, for example through a single request query parameter such
as ``?search=<json>``, use the ``ArraySearchBuilderFactory``.

The factory translates between a ``SearchBuilder`` and an array or JSON payload. The index name is
not part of the serialized payload. Your application still decides which index to target.

.. code-block:: php

<?php

use CmsIg\Seal\Search\Condition\Condition;
use CmsIg\Seal\Search\Facet\Facet;
use CmsIg\Seal\Search\SearchBuilderFactory\ArraySearchBuilderFactory;

$searchBuilderFactory = new ArraySearchBuilderFactory($this->engine);

$searchBuilder = $this->engine->createSearchBuilder('blog')
->addFilter(Condition::search('product title'))
->addFilter(Condition::equal('tags', 'php'))
->addSortBy('rating', 'desc')
->limit(20)
->addFacet(Facet::count('tags'));

$json = $searchBuilderFactory->toJson($searchBuilder);

You can also work with arrays directly instead of JSON:

.. code-block:: php

<?php

$payload = $searchBuilderFactory->toArray($searchBuilder);

To build a ``SearchBuilder`` back from external input, pass a
``SearchBuilderFactoryConfig`` explicitly. This is required on purpose. The config defines exactly
which parts of the search language your endpoint wants to expose.

Nothing is allowed by default.

Only after a field or feature is allowed in the config can it be used. Even then, SEAL still checks
the index schema. So a field must be allowed by your config and configured in the schema as
``filterable``, ``sortable``, ``facet``, ``distinct`` or ``searchable`` where applicable.

This is especially important for public APIs. An index may support internal fields such as
``authorEmail`` or ``internalStatus`` for back office workflows, while a public endpoint should only
accept a small safe subset.

.. code-block:: php

<?php

use CmsIg\Seal\Search\SearchBuilderFactory\ArraySearchBuilderFactory;
use CmsIg\Seal\Search\SearchBuilderFactory\SearchBuilderFactoryConfig;

$searchBuilderFactory = new ArraySearchBuilderFactory($this->engine);

$config = new SearchBuilderFactoryConfig(
filterFields: ['tags', 'rating'],
sortFields: ['rating'],
facetFields: ['tags'],
highlightFields: ['title', 'article'],
allowSearch: true,
maxLimit: 100,
);

$result = $searchBuilderFactory
->buildFromJson('blog', $config, $_GET['search'])
->getResult();

If your input already arrives as an array, for example from a custom request
mapping, use ``build()`` instead:

.. code-block:: php

<?php

$result = $searchBuilderFactory
->build('blog', $config, $requestData)
->getResult();

If you accept regular form submissions and work with ``$_POST``, normalize
string values before building the search. This allows you to build a tiny
controller while giving your designer's the full flexibility once they understand
how to build the form inputs:

.. code-block:: php

<?php

use CmsIg\Seal\Search\SearchBuilderFactory\FormSearchInputNormalizer;

$requestData = FormSearchInputNormalizer::normalize($_POST);

$result = $searchBuilderFactory
->build('blog', $config, $requestData)
->getResult();

Example HTML form for a more complex nested structure (top-level ``or`` with a nested
``and``):

.. code-block:: html

<form method="post">
<input type="text" name="limit" value="20">
<input type="text" name="offset" value="10">

<input type="text" name="filters[0][type]" value="or">

<input type="text" name="filters[0][conditions][0][type]" value="geoDistance">
<input type="text" name="filters[0][conditions][0][field]" value="location">
<input type="text" name="filters[0][conditions][0][latitude]" value="45.472735">
<input type="text" name="filters[0][conditions][0][longitude]" value="9.184019">
<input type="text" name="filters[0][conditions][0][distance]" value="2000">

<input type="text" name="filters[0][conditions][1][type]" value="and">

<input type="text" name="filters[0][conditions][1][conditions][0][type]" value="geoBoundingBox">
<input type="text" name="filters[0][conditions][1][conditions][0][field]" value="location">
<input type="text" name="filters[0][conditions][1][conditions][0][northLatitude]" value="45.494181">
<input type="text" name="filters[0][conditions][1][conditions][0][eastLongitude]" value="9.214024">
<input type="text" name="filters[0][conditions][1][conditions][0][southLatitude]" value="45.449484">
<input type="text" name="filters[0][conditions][1][conditions][0][westLongitude]" value="9.179175">

<input type="text" name="filters[0][conditions][1][conditions][1][type]" value="equal">
<input type="text" name="filters[0][conditions][1][conditions][1][field]" value="tags">
<input type="text" name="filters[0][conditions][1][conditions][1][value]" value="php">
</form>

--------------

Counting documents
------------------

Expand Down
Loading
Loading