How to create your search engine with Elasticsearch 7 and FOSElasticaBundle in Symfony 5

How to create your search engine with Elasticsearch 7 and FOSElasticaBundle in Symfony 5

Elasticsearch is a useful tool that allows you to store, search and analyze huge volumes of data quickly and in real-time, providing answers in milliseconds. You can think of Elasticsearch as a server that can process JSON requests and give you back JSON data according to your parameters. Elasticsearch is extremely useful for applications that rely heavily on a search platform for the access, retrieval, and reporting of data, as well as for effective and accurate searches.

In this article, I will explain to you how to interact with your Elasticsearch server in your Symfony 5 project.

Requirements

To follow this article, we'll assume that you have already an Elasticsearch server up and running. In our case, we will use a local instance of Elasticsearch v7.12 running through Docker and available at http://localhost:9200/.

Keeping this in mind, let's get started with the tutorial.

1. Install FOSElasticaBundle

In order to interact with your Elasticsearch server in Symfony, the best way to do it is through the FOSElasticaBundle. This bundle helps you to:

  • Integrates the Elastica library into a Symfony environment (Elastica by itself is a PHP client for Elasticsearch).
  • Use JmsSerializer or Symfony Serializer to convert between PHP objects and Elasticsearch data.
  • Index configuration for Elasticsearch, or send data without configuration to use the dynamic mapping feature of Elasticsearch
  • Listeners for Doctrine events for automatic indexing

To date, the support for Elasticsearch v7 is only available in the beta version of the FOSElasticaBundle (v6.0.0-beta3). To install this version, run the following command (note that till the date, the v6 release isn't available, if it's available, install that one instead):

composer require friendsofsymfony/elastica-bundle v6.0.0-beta3

Once the installation finishes, proceed with the configuration steps. For more information about this bundle, please visit the official repository at Github here.

2. Configure access URL

In your .env file, you will find a new property that will indicate the endpoint of Elasticsearch:

# project/.env
###> friendsofsymfony/elastica-bundle ###
ELASTICSEARCH_URL=http://localhost:9200/
###< friendsofsymfony/elastica-bundle ###

In case it isn't, define it and replace its value with the access endpoint.

3. Create example Entity

The idea of the search engine in this example is to search some articles with a given keyword. The articles are stored on a MySQL database and Doctrine is used as ORM, the entity looks like this:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Articles
 *
 * @ORM\Table(name="articles")
 * @ORM\Entity(repositoryClass="App\Repository\ArticlesRepository")
 */
class Articles
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, nullable=false)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="slug", type="string", length=255, nullable=false)
     */
    private $slug;
    
    /**
     * @var string|null
     *
     * @ORM\Column(name="preview", type="string", length=255, nullable=true, options={"default"="NULL"})
     */
    private $preview;
    
    /**
     * @var string
     *
     * @ORM\Column(name="content", type="text", length=0, nullable=false)
     */
    private $content;

    /**
     * @var string|null
     *
     * @ORM\Column(name="tags", type="string", length=255, nullable=true, options={"default"="NULL"})
     */
    private $tags;
}

The articles will be populated in Elasticsearch by indicating this entity later. Be sure to have some articles persisted in your database.

4. Configure indexes

In Elasticsearch, an index can be thought of as an optimized collection of documents and each document is a collection of fields, which are the key-value pairs that contain your data. By default, Elasticsearch indexes all data in every field and each indexed field has a dedicated, optimized data structure. In this case, we can create an index for the articles entity, that will store the defined properties in my entity, using the Articles entity as the model. You can define all of your indexes in the fos_elastica.yaml file like this:

# app/config/packages/fos_elastica.yaml
# Read the documentation: https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/doc/setup.md
fos_elastica:
    clients:
        default: { url: '%env(ELASTICSEARCH_URL)%' }
    indexes:
        articles:
            properties:
                name: ~
                slug: ~
                preview: ~
                content: ~
                tags: ~
            persistence:
                driver: orm
                model: App\Entity\Articles

5. Create indexes

Now, you need to create the indexes in Elasticsearch. This can be easily done running the following command:

php bin/console fos:elastica:create

6. Populate data

Finally, you can populate the information of the table in Elasticsearch running the following command:

php bin/console fos:elastica:populate

This will generate an output similar to the following one:

Resetting articles
    0/1480 [>---------------------------]   0%
  200/1480 [===>------------------------]  13%
  300/1480 [=====>----------------------]  20%
  500/1480 [=========>------------------]  33%
  600/1480 [===========>----------------]  40%
  800/1480 [===============>------------]  54%
  900/1480 [=================>----------]  60%
 1100/1480 [====================>-------]  74%
 1200/1480 [======================>-----]  81%
 1400/1480 [==========================>-]  94%
 1480/1480 [============================] 100%
Populating articles Refreshing articles

As you can see, in the articles table of my database, there are 1480 Articles entities that have been now indexed in Elasticsearch, so we can now run some queries on the engine within the controllers or services of the Symfony project.

7. Register finder

There are multiple ways in which you can run some queries in Elasticsearch through Elastica, you can check the detailed information about them on this page. In this example, we are going to proceed with the most basic example, so you can research more about advanced searching later.

The first thing you need to do is to register the finder for your index in your services.yaml. Depending on your needs, you may register the finder on a single controller or service like this:

# app/config/services.yaml
App\Controller\ExampleController:
        tags: [controller.service_arguments]
        bind:
            # replace "articles" with the name of your index
            # and replace the class of the Finder 
            FOS\ElasticaBundle\Finder\TransformedFinder $articlesFinder: '@fos_elastica.finder.articles'

So you can obtain the service only in the specific controller:

<?php

// app/src/Controller/ExampleController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use FOS\ElasticaBundle\Finder\TransformedFinder;
use Elastica\Util;

class ExampleController extends AbstractController
{
    public function someAction(TransformedFinder $articlesFinder) : Response
    {
        // do something with $articlesFinder
    }
}

Alternatively, you may bind the finder everywhere you need it with:

# app/config/services.yaml
services:
    _defaults:
        # replace "articles" with the name of your index
        # and replace the class of the Finder 
        bind:
            FOS\ElasticaBundle\Finder\TransformedFinder $articlesFinder: '@fos_elastica.finder.articles'

8. Running a search

As the last step, you may now test if everything is working as expected running some queries. This example will run a query on the articles index and will return all the entities that match your search terms. In this case, it will run inside a controller:

<?php

// app/src/Controller/ExampleController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use FOS\ElasticaBundle\Finder\TransformedFinder;
use Elastica\Util;

class ExampleController extends AbstractController
{
    public function someAction(TransformedFinder $articlesFinder) : Response
    {
        $search = Util::escapeTerm("Bleu de channel parfum");
        
        $result = $articlesFinder->findHybrid($search, 10);
        
        dump($result);
        
        return $this->render("views/empty.html.twig");
    }
}

The dumped content looks like this, so all the articles that match with the search "Bleu de channel parfum":

Elasticsearch Results Hybrid

The documentation of Elasticsearch and Elastica is quite extensive, so don't forget to check out the documentation for more advanced examples of how to interact with Elasticsearch in your Symfony 5 application.

Happy coding ❤️!

This could interest you

Become a more social person