Commentaires
Symfony2 : Faites le plein de Propel
Qu'est-ce que Propel ?
Propel est un ORM
(Object Relational Mapping) pour les bases de type
SQL (MySQL, SQLite, Oracle, PostgreSQL) construit sur le design pattern
ActiveRecord. Il vous permet donc d'accéder à vos bases avec un
ensemble de classes représentant vos tables. Et, avec l'aide d'une API
simple, il vous permet de manipuler facilement vos données.
Mais ce n'est pas tout, Propel réalise aussi vos migrations de bases de
données, pour prendre en compte les mises à jour ou annulations de structure
lors de vos passages de mise en production.
Enfin, Propel vous permet de générer vos objets à partir d'une base de
données existante. Idéal pour reprendre un bon vieux code que vous auriez pu
écrire en PDO ou avec un autre ORM.
Propel ce n'est plus new Criteria()
Si vous êtes resté à la version 1.4 de Propel embarqué par défaut avec
symfony 1.x, alors vous avez loupé l'introduction de l'ActiveQuery.
Pour que vous puissiez vous en mordre les doigts après l'illustration
suivante, sachez que c'était déjà disponible dans symfony 1 avec le plugin
sfPropelORPMlugin.
Disons donc avant :
<?php
$c = new Criteria();
$c->add(JobeetJobPeer::CREATED_AT, time() - 86400 * 30, Criteria::GREATER_THAN);
$jobs = JobeetJobPeer::doSelect($c);
Et après :
<?php
$jobs = JobeetJobQuery::create()
->recentlyCreated(30)
->find()
;
Idem pour les jointures et les sous-requêtes :
<?php
$c = new Criteria();
$c->add(JobeetJobPeer::CREATED_AT, time() - 86400 * 30, Criteria::GREATER_THAN);
$c->addJoin(JobeetJobPeer::CATEGORY_ID, JobCategoryPeer::ID, Criteria::INNER_JOIN);
$c->add(JobCategoryPeer::NAME, 'truc%', Criteria::LIKE);
$jobs = JobeetJobPeer::doSelect($c);
Et après :
<?php
$jobs = JobeetJobQuery::create()
->useJobCategoryQuery()
->filterByName('truc%') // Va automatiquement passer en mode LIKE
->endUse()
->recentlyCreated(30)
->find()
;
On a donc aujourd'hui une écriture de nos requêtes très proche de nos objets
et très facile à manipuler.
Propel dans Symfony2 au quotidien
Non, il n'y a pas que Doctrine2. Propel existe et est intégré nativement
dans Symfony2 grâce aux classes de l'espace de nommage Symfony\Bridge\Propel1
.
William Durand, actuel responsable du projet Propel, a rédigé de nombreux
« How-to avec Symfony2 »
dans la documentation officielle de Propel afin d'apprendre les usages de
Propel avec les formulaires, ou bien le composant sécurité de Symfony, etc.
Pour les bundles du quotidien, c'est pareil. La
plupart des bundles que nous utilisons
(FOSUserBundle
, AdminGeneratorBundle
,
SonataAdminBundle
, FakerBundle
,
PagerfantaBundle
etc.) fonctionnent comme un charme avec
Propel.
Et avec ses comportements spécifiques
(behaviors), c'est encore plus simple :
gestion d'événements Symfony2, tagger ces objets, etc.
Prêt à l'utiliser en 3 étapes
1. Installation avec Composer
Ajouter dans votre Composer :
"require": {
"propel/propel-bundle": "1.2.*",
}
2. Création du schéma
Dans votre bundle, créez votre fichier
Resources/config/schema.xml.
<database name="default"
defaultIdMethod="native"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://xsd.propelorm.org/1.6/database.xsd"
namespace="Compagny\ModelBundle\Propel\Bookstore">
<table name="book" phpName="Book">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
<column name="title" type="varchar" size="255" required="true" />
<column name="isbn" type="varchar" size="24" required="true" phpName="ISBN"/>
</table>
</database>
Ensuite on exécute simplement les commandes suivantes :
php app/console propel:database:drop --force --connection=default
php app/console propel:database:create --connection=default
php app/console propel:model:build --verbose
php app/console propel:sql:build
php app/console propel:sql:insert --force --connection=default
3. Génération de fixtures
Vous pouvez même utiliser Faker en natif, avec les fixtures du PropelBundle.
Créez le fichier Resources/fixtures/001-books.yml :
Compagny\ModelBundle\Propel\Bookstore\Book:
book_1:
title: Hello
isbn: 1225
book_2:
title: <?php $faker('text', 50); ?>
isbn: 1225
Et exécutez simplement :
php app/console propel:fixtures:load @CompagnyModelBundle
4. Vous êtes prêt !
Il ne vous reste plus qu'à créer votre contrôleur pour lister vos livres :
<?php
/**
* (c) Cedric LOMBARDOT <cedric.lombardot@youcare.fr>
*
* This file is a part of youcare package
* This file is the property of Youcare and can't be used without permission
*/
namespace Compagny\ModelBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Compagny\ModelBundle\Propel\Bookstore\BookQuery;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class ListController extends Controller
{
/**
* List of books
* @return array
* @Template()
*/
public function sidebarAction()
{
return array(
'books' => BookQuery::create()->find(),
);
}
}
Performances
La performance de nos applications PHP est de plus en plus souvent une
question importante. Alors oui, Propel n'est pas aussi performant que
PDO puisqu'il est basé dessus. Mais ne
comparons pas l'incomparable. Regardons plutôt ce qu'il en est entre Propel
et Doctrine2. Pour ne pas être accusé de publicité mensongère, je n'ai pas
réalisé moi même le test !
Librairie |
Insert (ms) |
findPk (ms) |
complex (ms) |
hydrate (ms) |
with (ms) |
real memory usage (bytes) |
temps total (s) |
PDO |
71 |
78 |
58 |
57 |
55 |
768,832 |
0.32 |
Propel16 |
181 |
121 |
85 |
151 |
166 |
9,699,328 |
0.71 |
Propel16WithCache |
117 |
67 |
76 |
129 |
130 |
9,699,328 |
0.53 |
Propel17 |
167 |
105 |
82 |
151 |
166 |
9,437,184 |
0.68 |
Propel17WithCache |
118 |
69 |
70 |
117 |
109 |
9,699,328 |
0.49 |
Propel20 |
184 |
118 |
88 |
179 |
180 |
5,767,168 |
0.75 |
Propel20WithCache |
126 |
76 |
79 |
150 |
144 |
5,767,168 |
0.58 |
Propel20FormatOnDemand |
129 |
76 |
78 |
146 |
145 |
5,767,168 |
0.58 |
Doctrine24 |
305 |
260 |
152 |
458 |
461 |
16,252,928 |
1.67 |
Doctrine24WithCache |
305 |
251 |
81 |
283 |
211 |
16,777,216 |
1.17 |
Doctrine24ArrayHydrate |
304 |
256 |
82 |
158 |
137 |
15,728,640 |
0.97 |
Doctrine24ScalarHydrate |
306 |
252 |
82 |
127 |
114 |
15,728,640 |
0.92 |
Doctrine24WithoutProxies |
304 |
252 |
83 |
214 |
307 |
15,990,784 |
1.20 |
Plus les valeurs de ces colonnes sont faibles, plus la librairie est
performante sur la base des tests effectués. Nous visualisons donc bien dans
ce tableau que Propel est un ORM rapide et peu consommateur de mémoire. (source https://github.com/propelorm/Propel2/issues/478).
Vous pouvez d'ailleurs rejouer vous même ce test.
Le truc, c'est que la plupart du temps, l'origine du problème à la base, ce
n'est pas l'ORM, mais le développeur ! Oui, vous savez quand on oublie un
index ou quand on remonte toute la base pour récupérer la première ligne !
Pour mieux gérer tout cela, Propel nous permet plusieurs choses :
Un Explain plan
L'explain plan est une requête
SQL un peu spécifique
qui permet de mieux visualiser les index que nous avons utilisé et de mieux
les comprendre. Ce dernier est inclus dans la barre de débogage de Symfony2,
mais aussi dans celle de symfony1 avec le sfPropelORMPlugin
.
Vous pouvez aussi exécuter cette requête d'explain hors symfony :
$explainArray = BookQuery::create()->filterByAuthor('me')->explain()
Des méthodes simples
Propel vous aide à faire la différence entre find
et
findOne
ou encore count
.
Vous n'avez donc plus de raisons d'écrire :
function countBook() {
$books = BookQuery::create()->find();
return count($books);
}
La requête ci-dessus a pour effet de remonter toute la base, de créer autant
d'objets PHP que de lignes dans votre base. Si votre administrateur de bases
de données voit cela et décide de vous assassiner, nous considérerons que
c'était un suicide ! La solution :
$count = BookQuery::create()->count();
Idem pour avoir le premier livre, nous n'écrirons surtout pas :
BookQuery::create()->find()->getFirst();
mais tout simplement :
BookQuery::create()->findOne();
Du code généré
Le fait que Propel ne fasse pas d'introspection à l'exécution, mais se base
sur un code généré, lui permet de s'exécuter rapidement. Cela vous permet
d'apprendre à l'utiliser tout aussi rapidement, en ouvrant ses fichiers ou
en laissant simplement votre IDE les lire.
Des requêtes faciles à lire et à déboguer
Propel, contrairement à Doctrine2, ne réécrit pas vos alias de table ou de
colonnes, ce qui permet de plus facilement lire vos requêtes et donc de
comprendre le pourquoi du comment votre requête ne fonctionne pas.
Propel2 : Le retour
Le voilà enfin, la version 2 de Propel pointe le bout de son nez. Que
contient-elle de nouveau ?
-
Une version avec
des standards de code
PSR-0, PSR-1, PSR-2, PSR-3
-
La suppression des classes
Peer
: un reste de
l'époque de Criteria
de la version 1.4, plein de
méthode statiques, enfin révolue.
-
Un conteneur de service permettant de manipuler plus efficacement les
objets transverses tels que les connexions à la base de données ou le
système de logs.
- Plusieurs remaniements du coeur.
En conclusion
Propel est simple, rapide et efficace, l'essayez sur votre prochain projet,
c'est l'adopter !