1 - Démarrage d'Elasticsearch
Sur la VM Elasticsearch, on démarre ce dernier et on récupère l'ip de la VM pour l'utiliser plus tard dans la configuration de l'application(Tutorial concernant l'installation d'Elasticsearch).
Démarrage :
cd /home/elasticsearch-1.3.0/ bin/elasticsearch
Récupération de l'ip de la VM :
ifconfig
2 - Installation de Symfony 2.3
Sur notre serveur web (ici MAMP) on va créer un hôte et un socle SF 2.3 pour notre application address book :
sudo php composer.phar create-project symfony/framework-standard-edition /Applications/MAMP/htdocs/sf_address_book/ 2.3.0
3 - Installation du bundle FOSElastica, GedmoDoctrineExtensions et JmsSerializerBundle
Ajout du bundle dans le composer.json :
nano composer.json
//...
"friendsofsymfony/elastica-bundle": "3.0.*@dev",
"jms/serializer-bundle": "0.13.*@dev",
"gedmo/doctrine-extensions": "v2.3.9"
//...
Mise à jour des vendors :
sudo php composer.phar update
Enregistrement du bundle dans le kernel :
nano app/config/AppKernel.php
// ...
// elastica
new FOS\ElasticaBundle\FOSElasticaBundle(),
new JMS\SerializerBundle\JMSSerializerBundle(),
// ...
4 - Creation du MainBundle
On va créer un MainBundle pour y stocker l'entité que l'on souhaite indexer. Dans le cas présent on va créer une entité contact avec laquelle on va pouvoir faire un carnet d'adresse avec recherche.
On va pouvoir créer, modifier, lire et supprimer des contacts. L'index d'Elasticsearch sera automatiquement mis à jour lors d'évènements Doctrine lors de la création, modification ou suppression de contacts.
Création du bundle :
php app/console generate:bundle --namespace=AddressBook/MainBundle
Choisir le format du fichier de config en yml et laisser les valeurs par défaut pour les autres choix :
Configuration format (yml, xml, php, or annotation): yml
Création de l'entité contact :
php app/console doctrine:generate:entity
Création des propriétés de l'entité :
The Entity shortcut name: AddressBookMainBundle:Contact Configuration format (yml, xml, php, or annotation) [annotation]: Instead of starting with a blank entity, you can add some fields now. Note that the primary key will be added automatically (named id). Available types: array, simple_array, json_array, object, boolean, integer, smallint, bigint, string, text, datetime, datetimetz, date, time, decimal, float, blob, guid. New field name (press to stop adding fields): firstname Field type [string]: Field length [255]: New field name (press to stop adding fields): lastname Field type [string]: Field length [255]: New field name (press to stop adding fields): gender Field type [string]: boolean New field name (press to stop adding fields): address Field type [string]: text New field name (press to stop adding fields): zipCode Field type [string]: Field length [255]: New field name (press to stop adding fields): city Field type [string]: Field length [255]: New field name (press to stop adding fields): phone Field type [string]: Field length [255]: New field name (press to stop adding fields): email Field type [string]: Field length [255]: New field name (press to stop adding fields): Do you want to generate an empty repository class [no]? yes
Création de la base de données et mise à jour du schéma :
php app/console doctrine:database:create php app/console doctrine:schema:update --force
Génération des routes, controller et vues pour le CRUD de l'entité Contact : php app/console doctrine:generate:crud --entity=AddressBookMainBundle:Contact --route-prefix=contact --with-write --format=yml
5 - Implémentation de FOSElastica et de GedmoDoctrineExtensions sur l'entité Contact de notre MainBundle
On modifie notre entité Contact comme suit :
<?php
namespace AddressBook\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use FOS\ElasticaBundle\Configuration\Search;
/**
* Contact
* @ORM\Table(name="contact")
* @ORM\Entity(repositoryClass="AddressBook\MainBundle\Entity\ContactRepository")
* @Search(repositoryClass="AddressBook\MainBundle\Entity\SearchRepository\ContactRepository")
* @ORM\HasLifecycleCallbacks
*/
class Contact
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var \DateTime $created
*
* @Gedmo\Timestampable(on="create")
* @ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* @var \DateTime $updated
*
* @Gedmo\Timestampable(on="update")
* @ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
/**
* @var string
*
* @ORM\Column(name="firstname", type="string", length=255)
*/
private $firstname;
/**
* @var string
*
* @ORM\Column(name="lastname", type="string", length=255)
*/
private $lastname;
/**
* @var boolean
*
* @ORM\Column(name="gender", type="boolean")
*/
private $gender;
/**
* @var string
*
* @ORM\Column(name="address", type="text")
*/
private $address;
/**
* @var string
*
* @ORM\Column(name="zipCode", type="string", length=255)
*/
private $zipCode;
/**
* @var string
*
* @ORM\Column(name="city", type="string", length=255)
*/
private $city;
/**
* @var string
*
* @ORM\Column(name="phone", type="string", length=255)
*/
private $phone;
/**
* @var string
*
* @ORM\Column(name="email", type="string", length=255)
*/
private $email;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set createdAt
*
* @param \DateTime $createdAt
* @return Contact
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Get createdAt
*
* @return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Set updatedAt
*
* @param \DateTime $updatedAt
* @return Contact
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* @return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set firstname
*
* @param string $firstname
* @return Contact
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
/**
* Get firstname
*
* @return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* Set lastname
*
* @param string $lastname
* @return Contact
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
return $this;
}
/**
* Get lastname
*
* @return string
*/
public function getLastname()
{
return $this->lastname;
}
/**
* Set gender
*
* @param boolean $gender
* @return Contact
*/
public function setGender($gender)
{
$this->gender = $gender;
return $this;
}
/**
* Get gender
*
* @return boolean
*/
public function getGender()
{
return $this->gender;
}
/**
* Set address
*
* @param string $address
* @return Contact
*/
public function setAddress($address)
{
$this->address = $address;
return $this;
}
/**
* Get address
*
* @return string
*/
public function getAddress()
{
return $this->address;
}
/**
* Set zipCode
*
* @param string $zipCode
* @return Contact
*/
public function setZipCode($zipCode)
{
$this->zipCode = $zipCode;
return $this;
}
/**
* Get zipCode
*
* @return string
*/
public function getZipCode()
{
return $this->zipCode;
}
/**
* Set city
*
* @param string $city
* @return Contact
*/
public function setCity($city)
{
$this->city = $city;
return $this;
}
/**
* Get city
*
* @return string
*/
public function getCity()
{
return $this->city;
}
/**
* Set phone
*
* @param string $phone
* @return Contact
*/
public function setPhone($phone)
{
$this->phone = $phone;
return $this;
}
/**
* Get phone
*
* @return string
*/
public function getPhone()
{
return $this->phone;
}
/**
* Set email
*
* @param string $email
* @return Contact
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Get email
*
* @return string
*/
public function getEmail()
{
return $this->email;
}
}
Création du search repository :
nano src/AddressBook/MainBundle/Entity/SearchRepository/ContactRepository.php
<php
/**
* Created by PhpStorm.
* User: gerard
* Date: 05/10/2014
* Time: 20:06
*/
namespace AddressBook\MainBundle\Entity\SearchRepository;
use FOS\ElasticaBundle\Repository;
class ContactRepository extends Repository
{
}
6 - Création du fichier de mapping
Le fichier de mapping va définir quels champs de notre model seront indexés.
Ajout des paramètres de connexion pour ElasticSearch dans le fichier parameters.yml.dist et parameter.yml :
parameters: elastic_host : localhost elastic_port : 9200
Création d'un fichier de config pour FOSElastica :
nano app/config/fos_elastica.yml
fos_elastica:
clients:
default: { host: %elastic_host%, port: %elastic_port% }
indexes:
addressbook:
client: default
types:
contact:
mappings:
id:
type: integer
createdAt :
type : date
updatedAt :
type : date
gender:
type: boolean
firstname : ~
lastname : ~
address : ~
zipCode : ~
city: ~
phone: ~
email: ~
persistence:
driver: orm
model: AddressBook\MainBundle\Entity\Contact
finder: ~
provider: ~
listener: ~
Ajout du fichier de conf. à config.yml :
nano app/config/config.yml
// ...
imports:
- { resource: fos_elastica.yml }
// ...
7 - Création d'un objet de recherche pour l'entité Contact
On va créer un Model pour la recherche sur notre entité Contact :
nano src/AddressBook/MainBundle/Model/ContactSearch.php
<?php
/**
* Created by PhpStorm.
* User: gerard
* Date: 05/10/2014
* Time: 20:18
*/
namespace AddressBook\MainBundle\Model;
use Symfony\Component\HttpFoundation\Request;
class ContactSearch
{
protected $dateFrom;
protected $dateTo;
protected $firstname;
protected $lastname;
protected $gender;
protected $address;
protected $zipCode;
protected $city;
protected $phone;
protected $email;
public function __construct()
{
// init dateFrom
$date = new \DateTime();
$month = new \DateInterval('P1Y');
$date->sub($month);
$date->setTime('00', '00', '00');
$this->dateFrom = $date;
$this->dateTo = new \DateTime();
$this->dateTo->setTime('23', '59', '59');
}
/**
* @param \DateTime $dateFrom
* @return $this
*/
public function setDateFrom(\DateTime $dateFrom)
{
if ($dateFrom != '')
{
$dateFrom->setTime('00', '00', '00');
$this->dateFrom = $dateFrom;
}
return $this;
}
/**
* @return \DateTime
*/
public function getDateFrom()
{
return $this->dateFrom;
}
/**
* @param \DateTime $dateTo
* @return $this
*/
public function setDateTo(\DateTime $dateTo)
{
if ($dateTo != '')
{
$dateTo->setTime('23', '59', '59');
$this->dateTo = $dateTo;
}
return $this;
}
/**
* @return \DateTime
*/
public function getDateTo()
{
return $this->dateTo;
}
/**
* clear dates
*/
public function clearDates()
{
$this->dateFrom = null;
$this->dateTo = null;
}
/**
* @return mixed
*/
public function getAddress()
{
return $this->address;
}
/**
* @param mixed $address
*/
public function setAddress($address)
{
$this->address = $address;
}
/**
* @return mixed
*/
public function getCity()
{
return $this->city;
}
/**
* @param mixed $city
*/
public function setCity($city)
{
$this->city = $city;
}
/**
* @return mixed
*/
public function getEmail()
{
return $this->email;
}
/**
* @param mixed $email
*/
public function setEmail($email)
{
$this->email = $email;
}
/**
* @return mixed
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* @param mixed $firstname
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
}
/**
* @return mixed
*/
public function getGender()
{
return $this->gender;
}
/**
* @param mixed $gender
*/
public function setGender($gender)
{
$this->gender = $gender;
}
/**
* @return mixed
*/
public function getLastname()
{
return $this->lastname;
}
/**
* @param mixed $lastname
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
}
/**
* @return mixed
*/
public function getPhone()
{
return $this->phone;
}
/**
* @param mixed $phone
*/
public function setPhone($phone)
{
$this->phone = $phone;
}
/**
* @return mixed
*/
public function getZipCode()
{
return $this->zipCode;
}
/**
* @param mixed $zipCode
*/
public function setZipCode($zipCode)
{
$this->zipCode = $zipCode;
}
}
8 - Création du formulaire de recherche
Création du FormType :
nano src/AddressBook/MainBundle/Form/Type/ContactSearchType.php
<?php
/**
* Created by PhpStorm.
* User: gerard
* Date: 05/10/2014
* Time: 20:24
*/
namespace AddressBook\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use AddressBook\MainBundle\Model\ContactSearch;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ContactSearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('gender', 'text' , array(
'required' => false
))
->add('firstname', 'text', array(
'required' => false
))
->add('lastname', 'text', array(
'required' => false
))
->add('address', 'textarea', array(
'required' => false
))
->add('zipCode', 'text', array(
'required' => false
))
->add('city', 'text', array(
'required' => false
))
->add('phone', 'text', array(
'required' => false
))
->add('email', 'text', array(
'required' => false
))
->add('search','submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$resolver->setDefaults(array(
// avoid to pass the csrf token in the url (but it's not protected anymore)
'csrf_protection' => false,
'data_class' => 'AddressBook\MainBundle\Model\ContactSearch'
));
}
public function getName()
{
return 'Contact_search_type';
}
}
Création du service pour le formulaire :
nano src/AddressBook/MainBundle/Resources/config/services.yml
Contact.form.search.type:
class: AddressBook\MainBundle\Form\Type\ContactSearchType
tags:
- { name: form.type, alias: Contact_search_type }
9 - Ajout des routes pour la recherche et les résultats
nano src/AddressBook/MainBundle/Resources/config/routing.yml
// ...
search_contact:
path: /rechercher
defaults: { _controller: AddressBookMainBundle:Search:searchContact }
requirements:
_method: GET|POST// ...
10 - Créations des vues pour l'affichage du formulaire de recherche et de la liste des résultats
Création du layout :
nano src/AddressBook/MainBundle/Ressources/views/layout.html.twig
{% extends '::base.html.twig' %}
Création de la vue search-contact-form :
nano src/AddressBook/MainBundle/Ressources/views/Search/search-contact-form.html.twig
{% extends 'AddressBookMainBundle::layout.html.twig' %}
{% block body %}
<form id="search-form-contact" action="{{ path('search_contact') }}">
{{ form_errors(form) }}
{{ form_rest(form) }}
</form>
<h1>Résultats :</h1>
<div>
<table>
<thead>
<tr>
<td>Nom</td>
<td>Prénom</td>
<td>Adresse</td>
<td>Code postal</td>
<td>Ville</td>
<td>Tel.</td>
<td>Email</td>
</tr>
</thead>
<tbody id="results">
</tbody>
</table>
</div>
{% endblock %}
{% block javascripts %}
{% javascripts output='js/main-front.js'
'@AddressBookMainBundle/Resources/public/js/jquery-1.11.1.min.js'
'@AddressBookMainBundle/Resources/public/js/search.js' %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
Création de la vue result-contact-form :
nano src/AddressBook/MainBundle/Ressources/views/Search/result-contact-form.html.twig
{% for contact in contacts %}
<tr>
<td>{{ contact.firstname }}</td>
<td>{{ contact.lastname }}</td>
<td>{{ contact.address }}</td>
<td>{{ contact.zipCode }}</td>
<td>{{ contact.city }}</td>
<td>{{ contact.phone }}</td>
<td>{{ contact.email }}</td>
</tr>
{% endfor %}
Copie de jQuery : src/AddressBook/MainBundle/Ressources/public/js/jquery-1.11.1.min.js
Création du js executant la call ajax :
nano src/AddressBook/MainBundle/Ressources/public/js/search.js
$(document).ready(function(){
// SCRIPT
// listener search button
$('#search-form-contact').on('submit', function(event){
event.preventDefault();
// retrieve form values
var dataForm = $(this).serialize();
// todo-pa change ajax route for prod
// call ajax search
$.post($(this).attr('action'), dataForm, function(data){
$('#results').html(data);
return false;
});
return false;
});
});
Ajout du MainBundle dans la config d'assetic :
nano app/config/config.yml
assetic:
bundles: [ AddressBookMainBundle ]
11 - Création de la méthode search du search repository de l'entité Contact
nano src/AddressBook/MainBundle/Entity/SearchRepository/ContactRepository.php
<?php
/**
* Created by PhpStorm.
* User: gerard
* Date: 05/10/2014
* Time: 20:06
*/
namespace AddressBook\MainBundle\Entity\SearchRepository;
use FOS\ElasticaBundle\Repository;
use AddressBook\MainBundle\Model\ContactSearch;
class ContactRepository extends Repository
{
public function searchFull(ContactSearch $contactSearch)
{
if ($contactSearch->getLastname() == '' && $contactSearch->getFirstname() == '' && $contactSearch->getAddress() == '' && $contactSearch->getPhone() == '' && $contactSearch->getEmail() == '') {
// query
$query = new \Elastica\Query\MatchAll();
} else {
// query
$query = new \Elastica\Query\Bool();
// lastname
if ($contactSearch->getLastname() != '') {
$lastnameQuery = new \Elastica\Query\QueryString();
$lastnameQuery->setFields(array('lastname'));
$lastnameQuery->setQuery($contactSearch->getLastname());
$query->addMust($lastnameQuery);
}
// firstname
if ($contactSearch->getFirstname() != '') {
$firstnameQuery = new \Elastica\Query\QueryString();
$firstnameQuery->setFields(array('firstname'));
$firstnameQuery->setQuery($contactSearch->getFirstname());
$query->addMust($firstnameQuery);
}
// address
if ($contactSearch->getAddress() != '') {
$addressQuery = new \Elastica\Query\QueryString();
$addressQuery->setFields(array('address'));
$addressQuery->setQuery($contactSearch->getAddress());
$query->addMust($addressQuery);
}
// phone
if ($contactSearch->getPhone() != '') {
$phoneQuery = new \Elastica\Query\QueryString();
$phoneQuery->setFields(array('phone'));
$phoneQuery->setQuery("\"".$contactSearch->getPhone()."\"");
$query->addMust($phoneQuery);
}
// email
if ($contactSearch->getEmail() != null && $contactSearch->getEmail() != '') {
$emailQuery = new \Elastica\Query\QueryString();
$emailQuery->setFields(array('email'));
$emailQuery->setQuery("\"".$contactSearch->getEmail()."\"");
$query->addMust($emailQuery);
}
}
return $this->find($query);
}
}
12 - Création du controller Search nano src/AddressBook/MainBundle/Controller/SearchController.php
<?php
/**
* Created by PhpStorm.
* User: gerard
* Date: 05/10/2014
* Time: 20:37
*/
namespace AddressBook\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use AddressBook\MainBundle\Model\ContactSearch;
use AddressBook\MainBundle\Form\Type\ContactSearchType;
use Symfony\Component\HttpFoundation\Response;
class SearchController extends Controller
{
public function searchContactAction()
{
// create Contact model
$ContactSearch = new ContactSearch();
// create form
$ContactSearchForm = $this->createForm(
new ContactSearchType(),
$ContactSearch
);
//die('coucou');
// check if is ajax request
if ($this->getRequest()->isXmlHttpRequest()) {
// bind data
$ContactSearchForm->handleRequest($this->getRequest());
// get form data
$ContactSearch = $ContactSearchForm->getData();
// call elastic manager
$elasticManager = $this->container->get('fos_elastica.manager.orm');
// retrieve results
$results = $elasticManager->getRepository('AddressBookMainBundle:Contact')->searchFull($ContactSearch);
// send response
return $this->render('AddressBookMainBundle:Search:result-contact-form.html.twig', array(
'contacts' => $results
));
}
// send view
return $this->render('AddressBookMainBundle:Search:search-contact-form.html.twig', array(
'form' => $ContactSearchForm->createView()
));
}
}
Peupler l'index d'elasticseach :
php app/console fos:elastica:populate
Source : Obtao.com