keyboard

Hugo Soltys

Symfony developer

Since 2013

Use ElasticSearch in your Symfony project

Posted on by Hugo - 2339 views - 0 Comments


Warning : Please notice this article is more than 1 year old. Some mechanics may have changed due to the newer versions of the used tools.

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.


Older articles