Doctrine et son Behavior Geographical
Retour un peu à la technique, mais n’oubliez pas, il vous reste seulement jusqu’à demain pour gagner une BD.
En ce qui nous concerne, avec les annonces faites lors de la dernière Symfony Conference Live, il faut bien commencer à se lancer dans Doctrine. Et c’est l’occasion de découvrir petit à petit cet ORM qui change beaucoup de Propel, pas toujours en bien à mon goût, mais il y a quand même des choses très bien pensées.
En parcourant la doc, je suis donc tombé sur un behavior que j’ai trouvé fort intéressant: Geographical.
Je me suis donc laissé tenté par un test de celui-ci!
Le principe est simple, il rajoute 2 champs à notre table, latitude et longitude, qui outre le fait de donner les coordonnées GPS de notre adresse, pouvant ainsi alimenter une googlemap, permet également de faire des calculs de distance (à vol d’oiseau évidemment).
Et d’ailleurs apparemment, c’est sa seule fonction, car le remplissage des champs est à notre charge. Mais ça reste une fonctionnalité intéressante.
On se lance donc dans une application à un projet, symfony bien évidemment, avec une table classique d’adresse avec le behavior de déclaré.
Voici notre schema.yml
adresses:
actAs: [ Geographical]
tableName: adresses
columns:
aid: { type: integer(4), primary: true, autoincrement: true }
adresse1: { type: string(255), notnull: true }
adresse2: { type: string(255) }
cp: { type: string(5), notnull: true }
ville: { type: string(100), notnull: true }Ce qui donne en base une fois notre table créé:
+------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | aid | int(11) | NO | PRI | NULL | auto_increment | | adresse1 | varchar(255) | NO | | NULL | | | adresse2 | varchar(255) | YES | | NULL | | | cp | varchar(5) | NO | | NULL | | | ville | varchar(100) | NO | | NULL | | | latitude | double | YES | | NULL | | | longitude | double | YES | | NULL | | +------------+--------------+------+-----+---------+----------------+
Maintenant que les bases sont posées, on va créer une petite classe rapide qui va nous permettre d’alimenter les champs latitude et longitude de notre table. Pour cela on va se servir de l’inévitable googlemap. Il vous faudra pour cela une clé pour utiliser leur API.
class Geoloc { protected $adr = null, $key = null, $url = null, $context = null, $format = null, $x = null, $y = null; } public function execute() { // On utilise un stream_context pour avoir la main sur certaines options plus facilement, comme le timeout de notre connexion $context = stream_context_create(array( 'http' => array( 'method' => 'GET', 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'timeout' => 5, ), )); // Google n'autorise que les requêtes en GET, on construit donc notre liste de paramètre prêt à emploi $param = http_build_query(array( 'q' => $this->adr, 'output' => $this->format, 'oe' => 'utf8', 'sensor' => 'false', 'key' => $this->key )); $content = file_get_contents($this->url.'?'.$param, false, $context); if ($content) { // Le traitement est prévu ici pour du csv $retour = explode(',',$content); // Si le retour est bon, on récupère les coordonnées if ($retour[0] == '200') { $this->x = $retour[2]; $this->y = $retour[3]; return array($this->x,$this->y); } } return false; } public function getX() { return $this->x; } public function getY() { return $this->y; } }
Voilà, il suffit de placer cette classe dans un dossier Lib de Symfony, où on bon vous semble. Maintenant on va créer dans notre fichier adresses.class.php 2 méthodes:
- la première qui lancera la mise à jour des champs via google
- la deuxième qui automatisera cette mise à jour, lors d’une nouvelle insertion et seulement dans ce cas. (et non pas à chaque mise à jour)
public function updateCoordonnees() { $completeAdr = $this->;adresse1.' '.(!empty($this->adresse2)?$this->adresse2.' '.:'').$this->cp.' '.$this->ville; // Ajout adresse2 seulement si non vide $geoloc = new Geoloc($completeAdr,'maCleGoogleMap'); if ($data = $geoloc->execute()) { $this->latitude = $data[0]; $this->longitude = $data[1]; } } /** * Méthode appelé avant l'insertion d'un nouvel élément. Elle est à surcharger au besoin. */ public function preInsert($event) { $this->updateCoordonnees(); }
Et voilà, avec un fichier de fixtures.yml du genre:
adresses:
adresse_1:
adresse1: 165 avenue du prado
cp: 13008
ville: Marseille
adresse_2:
adresse1: 7 rue de verdun
cp: 13005
ville: Marseille
adresse_3:
adresse1: 57 Bd Romain Rolland
cp: 13010
ville: Marseille
adresse_4:
adresse1: Route de Gemenos
cp: 13400
ville: AubagneEn le chargeant via symfony, on obtient en base:
select * from adresses ; +-----+----------------------+----------+-------+-----------+------------+-----------+ | aid | adresse1 | address2 | cp | ville | latitude | longitude | +-----+----------------------+----------+-------+-----------+------------+-----------+ | 1 | 165 avenue du prado | NULL | 13008 | Marseille | 43.2783515 | 5.3883703 | | 2 | 7 rue de verdun | NULL | 13005 | Marseille | 43.296542 | 5.3958405 | | 3 | 57 Bd Romain Rolland | NULL | 13010 | Marseille | 43.2774579 | 5.4202946 | | 4 | Route de Gemenos | NULL | 13400 | Aubagne | 43.2975544 | 5.5888222 | +-----+----------------------+----------+-------+-----------+------------+-----------+
Un petit bout de code dans une action, rapidement pour tester:
public function executeTest(sfWebRequest $request) { $adr1 = Doctrine::getTable('adresses')->find(1); $adr2 = Doctrine::getTable('adresses')->find(2); echo number_format($adr1->getDistance($adr2, true),2,',','').'Km'; // Par défaut, retourne en miles, il faut mettre le 2e paramètre à true pour avoir le résultat en km return sfView::NONE; }
Et on obtient la distance en km entre nos 2 premières adresses. (Ici la distance entre chez moi et mon boulot :p)
Comme l’indique la doc, on peut aller plus loin, en récupérant pour une adresse donnée, les N adresses les plus proches, ce qui peut donner lieu à des fonctionnalités très intéressantes:
public function executeTest2(sfWebRequest $request) { $adr1 = Doctrine::getTable('adresses')->find(1); $q = $adr1->getDistanceQuery(); $result = $q->orderby('miles asc') // On les trie par distance ->addWhere($q->getRootAlias(). '.aid != ?',$adr1->aid) // On exclut l'adresse de référence ->execute(); foreach ($result as $res) { echo $res->cp . " - " . $res->kilometers . "<br/>"; } return sfView::NONE; }
A noter que le code du behavior est très simple en fait, il ajoute juste une formule mathématique pour ces calculs de distance. Mais c’est ce genre de petites fonctionnalités qui me font apprécier la découverte de Doctrine!
Tags: behavior, doctrine, Symfony, Tutorials
2 Réponses
Laisser un message

Je pense qu’il manque un petit construct sur la classe pour initialiser la variable $adr
protected
$adr = null,
$url = null,
$context = null,
$format = null,
$x = null,
$y = null;
public function __construct($adr)
{
$this->adr = $adr;
}
Excellent tutorial qui permet de comprendre en un coup d’oeuil cette behavior geographical.
Bien vu!
Merci ;)