Navigation

Tutorial - Search

Spryker provides a convenient way to search product data. You’re enabled to easily setup full-text and facet search. In this tutorial we will show how to implement a simple product search using Spryker. This tutorial will not cover all aspects in detail to keep it focused. There will be links throughout the article that will lead you to in depth articles on related topics. If you want to dive deeper into the search topic there’s a number of additional articles that cover various topics.

Search Collector

If you’re not familiar with the concept of collectors yet I highly recommend reading the article on Collectors. In order to provide data for the search index you will need to implement a dedicated collector and a processor for the product data mapping.

Collector

The collector that collects data and pushes it to Elasticsearch is ProductCollector (Pyz/Zed/Collector/Business/Search).

The main operations performed by the search collector are :

  • createQuery() - creates the query for gathering the data from the Sql database
  • processData() - transforms the result of the database query into the format that’s stored in the Elasticsearch storage

Product Processor

When processing the query result, the mapping of the query result to the Elasticsearch structure is done through the ProductSearchProcessor.

Example :

$baseProduct['integer-sort']['size'] = isset($attributes['size']) ? $attributes['size'] : 0;

The line above would allow to perform an integer sort based on the values of the size field, by setting this attribute in the Elasticsearch data storage.

After you have set up the collector and processor you need to hook it up as a plugin to the collector:search:export console command. Details about how to setup console commands can be found in the Console article.

Introduction to the Search Client

You can access the search from within Yves using the Catalog module’s client Spryker\Client\Catalog\CatalogClient. This client provides two methods that can be used to query the search index

  • createFulltextSearch() - the search is made on all products; multiple filter criteria can be applied
  • createFacetSearch() - the search is made over a category of products; multiple filter criteria can be applied

SimpleSearch module (full-text search)

To exemplify how the search functionality works, we’ll add a new module that will contain these examples, called SimpleSearch. The new module will be added on the project side, in the Yves folder (because it’s a functionality we’ll exemplify in Yves).

These are the steps we will cover:

  • Set up a controller to handle requests and communicate with the search client
  • Set up a template to show results
  • Set up URL routing to access the controller

Controller and Template

User interface interactions will be forwarded to a dedicated controller that will take care of the communication with the search client.

Controller

mkdir -p src/Pyz/Yves/SimpleSearch/Communication/Controller
touch src/Pyz/Yves/SimpleSearch/Communication/Controller/SimpleSearchController.php

Add an action in the controller that submits a full-text search and returns the results :

<?php
namespace Pyz\Yves\SimpleSearch\Communication\Controller;

use Spryker\Yves\Application\Communication\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;

class SimpleSearchController extends AbstractController
{

    /**
     * @param Request $request
     *
     * @return array
     */
    public function fullTextSearchAction(Request $request)
    {
        $search = $this->getLocator()->catalog()->client()->createFulltextSearch($request);
        $searchResults = $search->getResult();

        return $searchResults;
    }

}

Template

Next, add the view template for showing the list of products :

mkdir -p src/Pyz/Yves/SimpleSearch/Theme/default/simple-search
touch src/Pyz/Yves/SimpleSearch/Theme/default/simple-search/full-text-search.twig

The template will be simple ( it will only show the name and price of the products, with a link to the products page). The price will be formatted by using the price twig extension. Place the following content into the full-text-search.twig file :

{% extends "@application/layout/layout.twig" %}
{% block content %}
    </br>
    <div>
        <section >
            {% for product in products %}
                <article>
                    <a href="{{product.url}}">
                        <h2>{{product.abstract_name}}</h2>
                        <h2>{{ product.valid_price | price }}</h2>
                    </a>
                </article>
            {% endfor %}
        </section>
    </div>
{% endblock %}

You can find in-depth coverage on controllers and templates in the Controllers and Actions article.

Routing

There are a couple of steps involved to set up routing to the newly created controller. We will need to add some dependencies to our SimpleSearchBundle that will be required by the router. Please read the article URL Routing to get more in-depth coverage on this topic. For brevity sake of this article we will skip over details and show the implementation to get routing for the URL /full-text-search to the fullTextSearchAction controller action of SimpleSearchController working.

Add a router to map the URL to the FullTextSearch action:

mkdir -p src/Pyz/Yves/SimpleSearch/Communication/Plugin/Router
touch src/Pyz/Yves/SimpleSearch/Communication/Plugin/Router/FullTextSearchRouter.php

Route the URL to the action you added in the SimpleSearchController:

Register the new added router in YvesBootstrap :

<?php
/**
 * @param Application $app
 *
 * @return RouterInterface[]
 */
protected function getRouters(Application $app)
{
    $locator = $this->getLocator($app);

    return [
        //..
        $locator->simplesearch()->pluginRouterFulltextSearchRouter()->setSsl(false),
        new SilexRouter($app),
    ];
}

To have access to the necessary dependencies, add a communication factory under the Communication layer:

touch src/Pyz/Yves/SimpleSearch/Communication/SimpleSearchCommunicationFactory.php

Place the following code in your communication factory:

Testing the Implementation

If you navigate to http://www.de.demoshop.local/full-text-search?q=brown in your browser of choice you will see search results for products that match the term brown.

You can also add filters or sort the results on various criteria, as in the examples below :

Adding Pagination

Paginating results is already built in and easy to set up. You only need to slightly change the implementation of the fullTextSearchAction in SimpleSearchController. Add the following line of code somewhere before $searchResults = $search->getResult(); in fullTextSearchActio

$search->setItemsPerPage(10);

In the search view template, add navigation buttons to browse the results :

<div class="catalog__pagination">
    <button class="pagination__button js-pagination-prev">catalog.prev</button>
    <button class="pagination__button js-pagination-next">catalog.next</button>
</div>

Facets and Sorting

After the data is correctly collected and stored in Elasticsearch in the best suited format, Yves must be able to build a query request that the Elasticsearch service can understand in order to get the correct search results.

Each attribute after which we need to build a search query must be configured in FacetConfig, so that the request is correctly being built.

Example :

<?php
'size' => [
    static::KEY_FACET_ACTIVE => true,
    static::KEY_SORT_ACTIVE => true,
    static::KEY_FACET_FIELD_NAME => static::FIELD_INTEGER_FACET,
    static::KEY_TYPE => static::TYPE_ENUMERATION,
    static::KEY_PARAM => 'size',
]

The configuration above allows to perform a search using the size attribute, which is stored under the integer facet and to sort the results on this value.