Tutos & Astuces

Intégrer Google Maps dans vos Entités sur Symfony2 avec Doctrine2

Il arrive souvent que dans certains projets web, vous vous retrouvez avec de nombreux types de données tels que des personnes, des entreprises, ou tout simplement des lieux devant contenir...

Il arrive souvent que dans certains projets web, vous vous retrouvez avec de nombreux types de données tels que des personnes, des entreprises, ou tout simplement des lieux devant contenir des données géographiques. On se retrouve rapidement avec des schémas de bdd très redondants car contenant les mêmes informations.

Nous allons voir dans cette article comment palier à cela proprement, grâce aux Mapped Superclasses de Doctrine2.

Une Mapped Superclass est une sorte de classe modèle permettant de définir des attributs spécifiques pour plusieurs classes enfants.
Si vous ne connaissez pas ce principe, je vous conseille vivement de jeter un œil sur la documentation officiel avant de lire la suite, une traduction est disponible ici pour les francophiles. 🙂

La classe modèle

Pour commencer, nous allons définir notre Mapped Supperclass Google Maps :

<?php
namespace Test\TestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Test\TestBundle\Entity\AbstractGMapEntity
 *
 * @author Sullivan SENECHAL
 *
 * @ORM\MappedSuperclass
 */
abstract class AbstractGMapEntity
{
    /**
     * @var string
     *
     * @ORM\Column(name="address", type="string", length=255, nullable=true)
     */
    protected $address;

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

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

    /**
     * @var float     Latitude of the position
     *
     * @ORM\Column(name="lat", type="float", nullable=true)
     */
    protected $lat;

    /**
     * @var float     Longitude of the position
     *
     * @ORM\Column(name="lng", type="float", nullable=true)
     */
    protected $lng;

    public function setAddress($address)
    {
        $this->address = $address;
    }

    public function getAddress()
    {
        return $this->address;
    }

    public function setLocality($locality)
    {
        $this->locality = $locality;
    }

    public function getLocality()
    {
        return $this->locality;
    }

    public function setCountry($country)
    {
        $this->country = $country;
    }

    public function getCountry()
    {
        return $this->country;
    }

    public function getLat()
    {
        return $this->lat;
    }

    public function setLat($lat)
    {
        if (is_string($lat)) {
            $lat = floatval($lat);
        }
        $this->lat = $lat;
    }

    public function getLng()
    {
        return $this->lng;
    }

    public function setLng($lng)
    {
        if (is_string($lng)) {
            $lng = floatval($lng);
        }
        $this->lng = $lng;
    }
}
?>

Notons certaines choses :

  • La classe est abstraite, en effet, il s’agit d’une classe modèle qui ne pourra pas être utilisée en tant qu’entité propre.
  • Il faut impérativement préciser à Doctrine qu’il s’agit d’une MappedSuperclass dans le commentaire d’en-tête.
  • L’adresse, la longitude et la longitude sont des champs obligatoires pour que les données soit exploitables par Google Maps.
    La ville est le pays sont ajoutés à titre d’exemple et seront utiles pour mes prochains articles… 🙂
  • Attention, la longitude et la latitude sont des floats ! Veillez à faire en sorte que les informations soient enregistrées correctement comme dans l’exemple ci-dessus.

Intégrer le modèle à nos entités

Maintenant que notre modèle est fait, il faut l’appliquer sur nos véritables entités !
Pour ce faire, rien de plus simple ! Il suffit de faire hériter notre classe par notre modèle.

Voici un exemple avec une entité contenant des informations sur une personne :

<?php

namespace Test\TestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

use Test\TestBundle\Entity\AbstractGMapEntity;

/**
 * Person
 *
 * @author Sullivan SENECHAL
 *
 * @ORM\Table(name="person")
 */
class Person extends AbstractGMapEntity
{
    /**
     * @var string
     *
     * @ORM\Column(name="first_name", type="string", length=255, nullable=false)
     */
    protected $firstName;

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

    // Vos autres attibuts et méthodes...
}
?>
  • N’oubliez pas d’importer la classe modèle Google Maps via le mot clé use !
  • Intégrez les attributs et méthodes de la classe modèle par un héritage de classe (extends)

Mettez à jour votre bdd via la console symfony, et le tour est joué !

Vous n’avez plus qu’à faire de même pour les autres entités correspondantes…

Conclusion

Pensez régulièrement à factoriser vos entités Doctrine si vous vous retrouvez face à des données communes, cela aide beaucoup à avoir un code propre et organisé ! 😉

Je vous conseille de séparer vos classes modèles, par exemple dans un bundle à part nommé UtilsBundle ! 😉

Une meilleure solution ? Une astuce à ajouter ? N’hésitez pas à m’en parler ! 😀

Voyons maintenant comment intégrer ceci dans nos formulaires avec jQuery Address Picker

Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0
  • Soullivaneuh

    Bientot le nouveau blog !

  • Sylvain

    Je n’ai qu’une question, où est la suite ? !

  • Soullivaneuh

    @2b08d143c890cd78b6109b98632453c9:disqus : Je compte prochainement publier un astuce pour faire un formType utilisant jquery address picker, mais je travail en meme temps sur la creation d’un blog sur symfony 2.1, wordpress ne repondant pas a toute mes attentes…

    Je fais au plus vite, merci de ton interet pour cet article ! 😉

  • Sylvain

    Je dois t’avouer que je suis en train de chercher comment mettre en place une Google map sur mon site internet. Mon utilisateur (qui hérite de FOSUserBundle) posséde un logement qui a lui même une adresse. Le problème est qu’avec ta solution il faut une lat et une long. Donc je vais me chercher un petit bundle qui me fait tout ça… J’espère le trouver rapidement.
    Au plaisir 🙂

  • Soullivaneuh

    En attendant mon prochain post (vais-je rester sur wordpress ? je ne sais point… :p), je peux te filer quelques tuyaux :

    – J’utilise ce plugin : http://xilinus.com/jquery-addresspicker/demos/
    – Je te conseil pour les utilisations multiples de faire un type vitual : http://symfony.com/fr/doc/current/cookbook/form/use_virtuals_forms.html
    – Voici un gist d’exemple pour te situer, j’espere ne pas avoir fait d’erreur : https://gist.github.com/3742134

    Tu n’as plus qu’a l’utiliser comme un type de formulaire classique dans ton formbuilder (voir le gist) ! 😉

    Si tu as d’autres questions n’hesites pas ! 🙂

  • trekiteasy

    Question : Lorsque je mets à jour ma bdd, la console m’indique qu’un « drop » va être effectué sur mes colonnes address / lat / lng que j’avais avant de créer cette super classe. Je pensais que puisque j’hérite de celle-ci mes colonnes devrait toujours être présentes, même si les variables associées ne sont plus déclarées dans mon entité Person par exemple (elles le sont dans la super class GMap)… ou j’ai manqué un épisode (ce qui est bien probable)

    • Soullivaneuh

      Donc avant tu avais une classe regroupant tout les champs, puis tu as separé les champs Gmap dans une MappedSuperclass ?

      Si ton entité qui hérite de ta MappedSuperclass a les même propriétés normalement non, ça devrait rester pareil…

      Aurais-tu les codes sources d’avant et d’après ce changement sous la main que je puisse jeter un oeil ? 🙂

      • trekiteasy

        Merci pour la réactivité !
        Alors ta question m’a fait relire plus attentivement mon code… et j’avais tout simplement oublié le extends AbstractGMapEntity… ceci explique cela (quel nul!) … Désolé pour le dérangement… et merci pour le tuto ! Je vais de ce pas me frotter à l’intégration avec jquery.ui.addresspicker.

        • Soullivaneuh

          Au moins ton problème est vite réglé ! 😉

          Si tu as besoin d’aide pour l’intégration, hésite pas à poster sur le blog, je suis souvent là ! 🙂

  • Timothée Moulin

    Hello,
    j’ai une question de compréhension (en reprenant ton exemple:)

    maintenant que nous avons notre MappedSuperClass ainsi qu’une classe l’étendant j’aimerai continuer sur cette lancée et en créer une seconde (pas de souci jusque là).

    j’ai donc mes 2 classe extended (Person et Company).

    j’aimerai que ces 2 classes soient liées à une autre Contact par exemple

    je peux le faire en ajoutant 2 attributs dans ma class Company et en utilisant un getPeople et getCompanies récupérer toutes mes relations (OnetToMany)

    mais ce que je trouverai bien serait de n’avoir qu’un seul attribut (addresses) et de récupérer toutes mes relations vers ma MappedSuperClass avec un getAddresses

    est-ce possible de créer ce genre de relations? j’ai vu quelque part dans la doc que non, mais je ne suis pas certain d’avoir tout compris..

    sinon, as-tu une autre solution?

    merci infiniment
    tim

    • Soullivaneuh

      Alors si je ne dis pas de betise, tu ne peux pas. Enfin dans ton cas tu ne peux pas, tu peux faire apparement une relation seulement si ta MappedSuperClass est utilisée que par une seule entité.

      Il faut surtout retenir qu’une MappedSuperClass n’est pas une entité (beaucoup font l’erreur), c’est une classe abstraite est aucune table a son nom ne sera crée, elle sert juste de modele.

      Il existe d’autre type d’héritage, mais je ne suis pas un expert non plus, as-tu jeter un oeil sur cette doc ? http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html

      Bon dev ! 😉

      • Timothée Moulin

        Oui pour finir j’ai utilisé une Single Table Inheritance et ça marche comme je voulais 😉

  • Guest

    Salut,

    Voilà j’aurais une petite question à te poser car j’ai suivit ton tuto pour faire une MappedSuperClass afin d’utiliser tout ça dans mon projet. Et j’ai aussi suivit la suite de ton tuto qui ce trouve ici : http://devyourdream.net/2012/09/24/jquery-address-picker-symfony2-google-map-dans-vos-formulaires/

    Dans la forme je dirais que j’ai pas eu de soucis à suivre tout ça, ma BDD est à jour et mon entity asset possède bien tout les champs cependant quand je lance mon projet j’ai une petite erreur mais je ne comprends pourquoi il me dit ça, l’erreur est la suivant :

    FatalErrorException: Compile Error: Cannot use TsUtilsBundleEntityAbstractGMapEntity as AbstractGMapEntity because the name is already in use in /home/kir/mywww/BookApart/src/Ts/BookingBundle/Entity/Asset.php line 9

    Ce qui je comprends pas la dedans c’est que si je l’écoute je peux pas appeler AbstractGMapEntity car elle est déjà appelé… ouais mais justement elle est appelé qu’une fois à la dites ligne 9 de mon projet.

    Pourrais tu m’éclairer un peu ?

  • Antoine

    Bonjour,

    J’ai suivi ton tuto il y a quelques mois et cela fonctionnai à merveille sur symfony 2.1. Depuis que je suis passé à la 2.4, lorsque je persiste mon objet, toutes les données de la « Mapped Superclasse » sont vides.. je ne sais vraiment pas pourquoi. En fait, par exemple j’arrive à les afficher en faisant $monobjet->getAdress()->getCountry(); alors qu’avant j’y accédai directement en faisant $monobjet->getCountry();

    Une idée? ( si j’ai été assez clair…)

    Merci bcp

    • Antoine

      Rebonjour,

      Quelqu’un aurai t-il une idée?

      Voici l’erreur que j’ai lorsque je valide mon formulaire : PHP Catchable fatal error: Object of class RpfAnnonceBundleEntityAnnonce could not be converted to string in /vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php on line 120

      Merci!

      • Marc

        Bonjour Antoine

        j’ai aussi cette erreur depuis la MAJ vers Symfony2.3

        en fait, il y a un soucis au niveau de data_class

        au lieu de remplacer les champs de l’entity principal, il crée l’objet dans le champs address et ne remplace plus les champs de l’entity

        et le message est plus clair lors d’une modification.

        J’ai essayé de contacter Soullivaneuh pour linformer que sa version de tuto n’est plusa jour sans succès.

        Je continue de chercher, si je trouve, je le mettrai ici

      • Marc

        en fait, virtual n’existe plus à partir de la version Symfony2.3
        courage 😉

      • Alex

        Bonjour,

        J’ai le meme soucis que toi et de toute évidence Marc a raison… Quelqu’un a t’il trouvé une solution? Je planche dessus depuis plus d’une semaine c’est un peu la galère…

      • Alex

        got it, il faut enlever le champ virtual qui n’existe plus depuis symfony 2.3 et a été remplacé par ‘inherit_data’ => true.

  • pape

    je suis pape dieng etudiant ucad/akar senegal je vourais voir le controlleur qui faity le traitement avec la base de donnes parceque j’ai projet qui doit l’utiliser.