keyboard

Hugo Soltys

Symfony developer

Since 2013

Use ElasticSearch in your Symfony project

Posted on by Hugo - 1235 views - 5 comments


ElasticSearch is an open source clusterised search engine. Based on Apache Lucene and Java technologies, ElasticSearch allows you to search in a pretty huge amount of document in a really short time.

 

In this FOSElasticaBundle tutorial, we will learn how to use ElasticSearch in your Symfony project with the help of Elastica and more precisely.


Let's go ! 

Here you will find the steps you have to follow to use ElasticSearch in your Symfony project.

I am using an Ubuntu system so the following console commands will be Linux based, so I apologize for non-Linux users.

 

INSTALLING JAVA

 

Installing ElasticSearch requires Java on your computer, so if you haven't installed it yet follow those steps.

 

Open a new terminal and type :

sudo apt-get update

 

Now you can install OpenJDK

sudo apt-get install openjdk-7-jre

 

Verify that your JRE is correctly installed with this command

java -version

 

You should see something like

Output of java -version
java version "1.7.0_79"
OpenJDK Runtime Environment (IcedTea 2.5.6) (7u79-2.5.6-0ubuntu1.14.04.1)
OpenJDK 64-Bit Server VM (build 24.79-b02, mixed mode)

 

DOWNLOADING AND INSTALLING ELASTICSEARCH

 

In your terminal, type the following command to download the version 1.7.2 of ElasticSearch.

wget https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.7.2.deb

 

Then, install it by using the dpkg command like this

sudo dpkg -i elasticsearch-1.7.2.deb

 

That's it ! ElasticSearch is now installed. But you must configure it and secure it correctly before using it. I recommend you to follow the Digital Ocean tutorial about ElasticSearch to getting started.

 

DOWNLOADING AND INSTALLING THE FOSELASTICABUNDLE 

 

Open a new terminal and in your project directory type the following command to dowload the lastest stable version :

composer require friendsofsymfony/elastica-bundle

 

In your AppKernel.php file, enable the bundle by adding this line 

<?php

//app/AppKernel.php

//...

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            //...
            new FOS\ElasticaBundle\FOSElasticaBundle(),
        );

        //...
    }
}

 

You must now configure the bundle. In your app/config/config.yml add this basic configuration for one client with one single index.

#app/config/config.yml

fos_elastica:
    clients:
        default: { host: locahost, port: 9200 }
    indexes:
        app: ~

 

Here, an Elastica index called app is available as a service with the key fos_elastica.index.app

 

Now we have to define our types.

By default, FOSElasticaBundle requires each type to be mapped. Each type defined is available as a service with the key fos_elastica.index.my_index.my_type.

FOSElasticaBundle also requires a provider for each type that will notify when an object that maps to a type has been modified.

 

Here is a config example with Doctrine ORM :

#app/config/config.yml

fos_elastica:
    clients:
        default: { host: localhost, port: 9200 }
    indexes:
        app:
            types:
                user:
                    mappings:
                        username: ~
                        firstname: ~
                        lastname: ~
                        email: ~
                    persistence:
                        driver: orm
                        model: Acme\AppBundle\Entity\User
                        provider: ~
                        listener: ~
                        finder: ~

 

Once your mapping is done, you have to populate your new index with the command 

php app/console fos:elastica:populate

 

And... that's all ;)

You are now reasy to go with ElasticSearch on your Symfony Project !

 

MAKING A SIMPLE SEARCH WITH FOSELASTICABUNDLE

 

To make simple searches with ElasticSearch, we will need to create a search repository for our User entity and a search object that will help us to search more easily.

 

Let's start with our search object, UserModel.php, which will contain properties from our app index.

<?php

namespace Acme\AppBundle\Model;

class UserModel
{
    protected $username;

    protected $firstname;

    protected $lastname;

    protected $email;

    public function getUsername()
    {
        return $this->username;
    }

    public function setUsername($username)
    {
        $this->username = $username;

        return $this;
    }

    public function getFirstname()
    {
        return $this->firstname;
    }

    public function setFirstname($firstname)
    {
        $this->firstname = $firstname;
    
        return $this;
    }

    public function getLastname()
    {
        return $this->lastname;
    }

    public function setLastname($lastname)
    {
        $this->lastname = $lastname;

        return $this;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }        
}


Like all searches, we need a form to collect the informations that we want to search in our index. 

NB : In our example, we will create a form with one field by indexed property, but you can also do a single input form which will search in all the indexed informations.

<?php

namespace Acme\AppBundle\Form;

use Acme\AppBundle\Model\UserModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;

class UserSearchType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('username', TextType::class, ['required' => false])
            ->add('firstname', TextType::class, ['required' => false])
            ->add('lastname', TextType::class, ['required' => false])
            ->add('email', EmailType::class, ['required' => false])
            ->add('submit', SubmitType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'csrf_protection' => false,
            'data_class' => 'Acme\AppBundle\Model\UserModel'
        ]);
    }
}

 

And now our repository that will use our model :

<?php

namespace Acme\AppBundle\Entity\SearchRepository;

use FOS\ElasticaBundle\Repository;
use Acme\AppBundle\Model\UserModel;
use Elastica\Query\BoolQuery;
use Elastica\Query\Terms;
use Elastica\Query;

class UserRepository extends Repository
{
    $query = new BoolQuery();

    // This searchUser function will build the elasticsearch query to get a list of users that match our criterias
    public function searchUser(UserModel $search)
    {
        if ($search->getUsername() != null && $search->getUsername() != '') {
            $query->addMust(new Terms('username', [$search->getUsername()]));
        }
        if ($search->getFirstname() != null && $search->getFirstname() != '') {
            $query->addMust(new Terms('firstname', [$search->getFirstname()]));
        }
        if ($search->getLastname() != null && $search->getLastname() != '') {
            $query->addMust(new Terms('lastname', [$search->getLastname()]));
        }
        if ($search->getEmail() != null && $search->getEmail() != '') {
            $query->addMust(new Terms('email', [$search->getEmail()]));
        }

        $query = Query::create($query);

        return $this->find($query);
    }
}


Important : In your User entity, you must specify that the above repository will be used for our elasticsearch searches. To do this, you must add the following annotation in your entity.

 

//src/Acme/UserBundle/Entity/User.php

use FOS\ElasticaBundle\Configuration\Search;

/**
 * @ORM\Table()
 * @Search(repositoryClass="Acme\UserBundle\Entity\SearchRepository\UserRepository")
 *
 */
class User
{
    // your entity...
}

 

 

And now the controller, here it is :

<?php

namespace Acme\UserBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Acme\UserBundle\Model\UserModel;

class UserController extends Controller
{
    public function indexAction(Request $request)
    {
        $userSearch = new UserModel();

        $form = $this->createForm(UserSearchType::class, $userSearch);
        $form->handleRequest($request);

        $userSearch = $form->getData();

        $elasticaManager = $this->get('fos_elastica.manager');
        $results = $elasticaManager->getRepository('AcmeUserBundle:User')->searchUser($userSearch);

        return $this->render('AcmeUserBundle:User:list.html.twig', [
            'form' => $form->createView(),
            'users' => $results
        ]);
    }
}

 

You now have a working elasticsearch user search form which will return an user list based on your search criterias. It may be a bit complicated at first sight but with a some practice you'll be able to use Elasticsearch for all your searches.

 

Thanks for reading, I hope this article interested you ! 

As always if you have any questions do not hesitate to use the comment form.

Hugo.


Hugo Soltys

My name is Hugo, I'm 25 and I'm a Symfony developer since 2013. I love to create websites by myself to learn new technologies or increase my skills.
I love movies, books, music and video games. I also like to drink a few beers with my friends. I'm from Lille (France) and I currently work as Symfony developer at Decathlon since 2016. Before that, I worked as Symfony developer for the IT Room company, in Roubaix, France.