· Tech watch

Quoi de neuf dans Doctrine 2 ?

Il y a environ 1 mois de cela, la première version bêta de Doctrine 2 a été rendue publique, une petite prise en main rapide s’imposait !

Je ne vais pas parler ici de l’intégration de Doctrine dans le framework MVC symfony, le plugin sfDoctrine2Plugin n’étant à l’heure actuelle pas testable en profondeur. Cet article porte donc sur Doctrine 2, l’ORM, uniquement.

Le schéma de description du modèle

Le classique schema.yml pour commencer, apporte son lot de nouveautés côté syntaxe. Il y a trois méthodes : XML, Yaml et DocBlock (sous forme d’annotations PHP). À vous de configurer le bon Driver (setMetadataDriverImpl(), dans votre configuration Doctrine), et donc de décrire votre modèle selon votre humeur :

La méthode recommandée par Doctrine est celle des annotations DocBlock. Leur écriture n’est pas très sexy, mais elles peuvent être générée depuis les autres formats :
<?php
namespace Entities ;

/** @Entity @Table(name="users") */
class User
<em>/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
private $id ;
/** @Column(type="string", length=50) */
private $name ;
/**
* @OneToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address ;
</em>
 ?>

La notation XML (si vous utilisez un IDE XML-friendly, vous aurez l’auto-complétion pour toutes les balises et attributs) :

<?xml version="1.0" encoding="UTF-8" ?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

<entity name="Entities\User" table="users">
<id name="id" type="integer">
<generator strategy="AUTO"/>
</id>
<field name="name" type="string" length="50"/>
<one-to-one field="address" target-entity="Address">
<join-column name="address_id" referenced-column-name="id"/>
</one-to-one>
</entity>

</doctrine-mapping>

et, à l’ancienne, en Yaml :

Entities\User :
type : entity
table : users
id :
id :
type : integer
generator :
strategy : AUTO
fields :
name :
type : string
length : 50
oneToOne :
address :
targetEntity : Address
joinColumn :
name : address_id
referencedColumnName : id

Vous noterez que la syntaxe est légèrement différente sur le format Yaml, mais pas de panique, la migration de vos projet de Doctrine 1 à Doctrine 2 est prévue, et on a donc une task ./doctrine orm:convert-d1-schema qui se charge de la conversion.

Les Entities et le EntityManager

Comme vous pouvez le voir dans la première méthode, la classe User que j’ai définie est une Entity ! C’est cette classe que je vais manipuler, et elle est vide. Pas de getter, pas de setter, pas de save(), elle n’étend rien…

C’est un des intérêts de Doctrine 2, tuer la magie (et ainsi améliorer les performances), c’est à vous d’écrire ce dont vous avez besoin, de façon explicite et ainsi plus simple à débugger.

À savoir que la majorité des IDE permettent de générer les getters/ setters de vos propriétés automatiquement.
_ Sachez qu’il existe aussi une classe nommée ActiveEntity que vous trouverez dans le namespace DoctrineExtensions, qui implémente sur les Entities qui l’étendent les getters / setters magique, le save(), le ArrayAccess, le toArray()… mais il n’est pas recommandé de l’utiliser (ce n’est pas « la façon de faire » en Doctrine 2).

Pour chaque connexion à votre base de donnée, vous avez un EntityManager.

$em = EntityManager ::create($connectionOptions, $config) ;

C’est cette classe qui permet de récupérer et de sauver nos Entities. Ainsi pour créer un User dans ma base, je procède de cette façon :

$user = new \Entities\User() ;
$user->setName('Damien') ;
$em->persist($user) ;
$em->flush() ;

On donne des Entities à notre EntityManager (via la méthode persist()), et quand on souhaite mettre à jour la base de donnée, on flush(). Doctrine abandonne donc le pattern Active record au profit d’une approche de Data Mapping (qui profite de Reflection, introduit en PHP 5.3) :
because we think it hurts testability, project maintainability and is not a suitable abstraction (80/20) for models that exceed the complexity of a blog or otherwise simple web application[Voir [cette annonce.]]

Côté requête, la magie des findBy* est toujours de la partie :

$user = $em->getRepository('Entities\User')->findOneByName('romanb') ;

(À noter que le Repository est commun à toutes les Entities par défaut, il est ensuite possible d’en créer des spécifique pour chaque Entity, et ainsi d’avoir des classes de type « UserTable » comme dans Doctrine 1).

La construction d’une requête plus complexe (avec des jointures par exemple) demandera de faire appel au DQL. C’est la méthode la plus puissante et la plus flexible pour récupérer des objets depuis la base de données. Il permet d’interroger la base dans un langage objet, avec nos noms de classes, nos champs, nos héritages et associations sans nous soucier du nom des champs effectivement en base. Il est très similaire au SQL, mais ce n’est PAS du SQL :

$em->createQuery('SELECT COUNT(a.id) FROM Entities\User u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id') ;

Le DQL de Doctrine 2 est très proche de ce qu’on faisait déjà avec Doctrine 1, avec quelques améliorations, comme par exemple la possibilité d’étendre le langage avec des fonctions non native.

Essayer Doctrine 2

À l’heure où j’écris ces lignes, la sandbox de Doctrine 2 beta 1 ne fonctionne pas très bien. La meilleure solution pour jouer avec Doctrine 2 est de télécharger les sources depuis le dépôt GitHub et de résoudre les dépendances comme expliqué ici. Un package entièrement prêt à l’emploi pour la sandbox est en cours de préparation. Il y a aussi un petit [quick-start dans la documentation officielle->http://www.doctrine-project.org/projects/orm/2.0/docs/reference/introduction/en#sandbox-quickstart].

Si vous souhaiter utiliser Mysql, modifiez les options comme ci-dessous dans index.php et cli-config.php :

$connectionOptions = array(
'driver' => 'pdo_mysql',
'user' => 'login',
'password' => 'pass',
'dbname' => 'dbname'
) ;

La documentation est très bien avancée, elle couvre déjà de nombreux domaines et c’est, pour l’instant, une des seules ressource viable.

Pour finir, en l’état le plugin sfDoctrine2Plugin (qui permet d’utiliser Doctrine 2 dans symfony 1, avec admin-generator, etc) n’est pas fonctionnel actuellement et il faudra encore attendre un peu avant de pouvoir l’utiliser entièrement.