Technically correct website architecture is the basis for effective marketing activities. In stores offering an extensive range of products, it is a real challenge. So how to organize the navigation on the site to help the user to navigate efficiently in the store (UX) and at the same time to ensure effective use of indexing by the Google robot (SEO)? The answer might be product filtering.

It is one of the most neglected areas in eCommerce platforms. Although it is easy to implement, stores often do not have any filters on the product listing; or are unintuitive; or even distract the buyer. Filters are designed to improve usability, not to complicate the navigation in the store. The consequence of poorly optimized filters is the frustration of customers that causes them to leave the site. Therefore, when designing this tool, every detail matters.

Product filtering is nothing more than narrowing down the number of products in a given category that matches the applied rules (filters).

The most popular filters are:

  • Brand/manufacturer
  • Type/color
  • Price
  • Size

Well-designed filters:

  • support the navigation in the store,
  • have a positive effect on conversion and, as a result – may increase sales (here you will find an article about boosting sales in your online store by taking simple actions),
  • optimized filters have an impact on the SEO of the store:
  • internal linking of the store’s website gets stronger,
  • the site’s visibility for keywords increases,

Users want to access the data immediately, which might get hard when dealing with many relations in a classic database engine like MySQL. Having an external engine that does not depend on relations and allows you to access the data fast & easy might be a way to go. For this purpose, Elasticsearch is often the right answer.

How does Elasticsearch work with Sylius?

We created a Sylius Elasticsearch integration that works with all the Product related fields. It is based on FOSElasticaBundle, which is a Symfony wrapper for a commonly used PHP library – Elastica. The bundle itself integrates with Doctrine and allows you to work with the whole Symfony & Doctrine stack quite easily. What we have done is we prepared an Elasticsearch indexes on top of Sylius models including Product, Taxon, Attribute and Option.

You can find the SyliusElasticsearchPlugin on our GitHub >>

In a nutshell, it transforms the relation data into something like this:

Having such data in the Elasticsearch gives us the possibility to filter a huge amount of data in real time.

By default there are 3 indexes created:

  • bitbag_shop_products for product-related data,
  • bitbag_attribute_taxons for attributes available within a specific taxon,
  • bitbag_option_taxons for options available within a specific taxon.

The architecture

If you follow the installation guide, as a result, you will get the nice feature set from the main image of this blog post representing the products filter, which will override the default Sylius route for products list with the one pointing to our listing.

Property builders

As mentioned previously, the plugin is based on FOS Elastica Bundle. Friends Of Symfony Elastica offers a nice event-based architecture for mapping Doctrine Entities (or models) including Sylius ones. By default, the plugin listens to all Doctrine events for each entity configured for a specific index. In order to map the entity object to an Elasticsearch document, we introduced something we called property builders.

Here’s an example of what a property builder looks like:

<?php

/*
 * This file has been created by developers from BitBag.
 * Feel free to contact us once you face any issues or want to start
 * another great project.
 * You can find more information about us on https://bitbag.shop and write us
 * an email on [email protected].
 */

declare(strict_types=1);

namespace BitBagSyliusElasticsearchPluginPropertyBuilder;

use BitBagSyliusElasticsearchPluginPropertyBuilderMapperProductTaxonsMapperInterface;
use ElasticaDocument;
use FOSElasticaBundleEventTransformEvent;
use SyliusComponentCoreModelProductInterface;

final class ProductTaxonsBuilder extends AbstractBuilder
{
    /** @var ProductTaxonsMapperInterface */
    private $productTaxonsMapper;

    /** @var string */
    private $taxonsProperty;

    public function __construct(ProductTaxonsMapperInterface $productTaxonsMapper, string $taxonsProperty)
    {
        $this->productTaxonsMapper = $productTaxonsMapper;
        $this->taxonsProperty = $taxonsProperty;
    }

    public function consumeEvent(TransformEvent $event): void
    {
        $this->buildProperty($event, ProductInterface::class,
            function (ProductInterface $product, Document $document): void {
                $taxons = $this->productTaxonsMapper->mapToUniqueCodes($product);

                $document->set($this->taxonsProperty, $taxons);
            }
        );
    }
}

It is a simple tagged service that is an event subscriber at the same time, which means that it needs to be tagged with kernel.event_subscriber in case you don’t use auto-wire. Take a look at some default property builders here.

Radosław Żurawski - CSO at BitBag
Need help with a plugin implementation in your Sylius project? Talk to our expert!

Query builders

What we also introduced in this plugin are query builders. Query builders are basically simple services that wrap the Elasticsearch queries into Symfony services on top of available Elastica query objects. If you take a look at the source files of ruflin/Elastica library and compare it with this Elastica documentation page you will quickly notice that the Elastica library’s query part is an object-oriented wrapper for Elasticsearch queries that you’d normally need to build in JSON and execute after you open the ES connection and do all the boring stuff. Kudos to Nicolas Ruflin, who is the author of this package!

Here’s an example of what a HasTaxonQueryBuilder looks like:

<?php

/*
 * This file has been created by developers from BitBag.
 * Feel free to contact us once you face any issues or want to start
 * another great project.
 * You can find more information about us on https://bitbag.shop and write us
 * an email on [email protected].
 */

declare(strict_types=1);

namespace BitBagSyliusElasticsearchPluginQueryBuilder;

use ElasticaQueryAbstractQuery;
use ElasticaQueryTerms;

final class HasTaxonQueryBuilder implements QueryBuilderInterface
{
    /** @var string */
    private $taxonsProperty;

    public function __construct(string $taxonsProperty)
    {
        $this->taxonsProperty = $taxonsProperty;
    }

    public function buildQuery(array $data): ?AbstractQuery
    {
        if (!$taxonCode = $data[$this->taxonsProperty]) {
            return null;
        }

        $taxonQuery = new Terms();
        $taxonQuery->setTerms($this->taxonsProperty, [$taxonCode]);

        return $taxonQuery;
    }
}

Straightforward, isn’t it?

Customization

Here comes the fun part. What if you want to customize the plugin and add some data to the default index, build the query or create some new filter forms?

Services

You can customize each part of this plugin by decorating the Symfony services. You can take a look at what services are available with our plugin by executing the bin/console debug:container | grep bitbag.sylius_elasticsearch_plugin command. If you don’t know how to use the decorator pattern in Symfony yet, take a look here. It is extremely useful while working with Sylius.

Parameters

When it comes to the parameters, things are no different here. You can execute the bin/console debug:container --parameters | grep bitbag command and create corresponding parameters in your local config file, for instance _sylius.yaml . You might want to do it if you want to override the default localhost:9200 Elasticsearch connection.

Property builders

As for the property builders, you can either decorate the existing ones or create a new one and tag it with kernel.event_describer . Take a look at the examples of the services mentioned in the property builders architecture section above.

Query builders

If you want to customize the query builders, you can decorate the existing ones, especially the ShopProductsQueryBuilder. It is the main query for the products list. Currently, only decoration is the way to customize it, but in the future, we plan to introduce a cleaner tag oriented architecture.

Forms

You can customize forms by using the Symfony form extension pattern (again, quite useful while working with Sylius). You can take a look at what forms are included by default here. You might also want to decorate the default ListProductsAction, which is the controller responsible for all form & request data handling.

If you want to create a new form, you’d need to extend the AbstractFilterType proxy class and tag the form as a form service (in case you don’t auto-wire). The NameFilterType might be a good example.

Templates

There is a bunch of templates that you can override as well. Take a look at these source files. If you’re not familiar with the customization process yet, read this official Sylius documentation page. The target plugin directory you will need to create in your bundles/templates or theme folder is BitBagSyliusElasticsearchPlugin.

Helpful tips for filtering products

  1. Plan first, then execute.
  2. Pay attention to the order of filters. Sometimes it is better to display filters in terms of popularity than in alphabetical order. In determining which filters are more likely to be searched and applied, you can use, for example, data from Google Analytics.
  3. Bet on simple, intuitive and uncomplicated filters. The minimalist design makes it easy to navigate the website.
  4. Use A / B tests to select filter placement. In one store, the left sidebar will work better, while the right side may be more effective in another store. There may also be a filtering section on top. It all depends on the design of your website.
  5. Allow the user to select multiple values from a given range. For example, it may be possible to choose several colors.
  6. A small but essential detail is displaying information about the number of products that meet the filter criteria.
  7. Don’t be limited to patterns. Price or color are well-known filters. If you’re selling clothes, you may want to consider filtering your clothes according to the season or occasion. Put yourself in the user’s shoes and think about what might be useful.
  8. Don’t forget about mobile devices! The filter section view should also be seamlessly operated on the smaller screen of the mobile device.

Find more tips for your eCommerce in a FREE e-book!


Summary

As you can see, filtering products in the online store is a basic function that is often a key element on the store page. Unfortunately, about 3/4 of the stores do not have an effective section with product filtration. 

In Sylius-standard projects, we use the plugin mentioned above. It’s proven to work under a huge amount of data, like 200k products. At some point, you might need to adjust the Elasticsearch a little to deal with a large amount of data, but overall — the implementation is there. It is tested with Behat & PHPSpec, so as long as the Travis build is green, you’re quite safe while using it. The plugin is currently considered the official Sylius integration with Elasticsearch.

You can support the plugin development in various ways. Obviously, we’d appreciate creating issues and opening interesting discussions on Github alongside with pull requests. Leaving a star is also a nice way to say that you appreciate our work. 

If you have any questions, feel free to contact us using the following link