Doctrine 2 et Zend Framework

Fidèle utilisateur de doctrine depuis plusieurs années, j'ai réussi à l'imposer dans mon entreprise en tant que 'best practice' dans le développement Php au quotidien.

La grande majorité de nos projets sont fait en Zend Frameork (ZF), principalement pour l'utilisation de ZendAmf. Nous avons donc logiquement utilisé Doctrine en complément de ZF.

Avec l'avènement de Doctrine 2, nous nous intéressons au changement Doctrine 1.x vers Doctrine 2.

Après un certain temps passé à tenter de faire fonctionner ZF et Doctrine 2 de concert, j'en suis arrivé à la conclusion : autant avec Doctrine 1.x il avait été plus facile d'utiliser l'auto loader de ZF, autant avec Doctrine 2, cela relève de l'exploit.

Utiliser l'auto-loader de Doctrine

Doctrine 2 tire partie de php 5.3 le plus possible. Il utilise donc naturellement les espaces de noms. La syntaxe est particulière, il va falloir s'y faire.

Prérequis : configurer l'include_path

Pour un projet appelé NL, admettons l'arborescence suivante :

 
/
  /application
    /nl
      /models
        /doctrine
  /htdocs
  /inline
  /library
    /Doctrine      
    /Zend
 

J'ai pour habitude de mettre mes bootstrap en dehors du répertoire /projet : dans le répertoire /inline
D'autre part, je places les classes spécifiques à mon projet dans /application/nl/models et pour les classes utilisant doctrine dans /application/nl/models/doctrine

Je configure ainsi l'include-path de mes projets comme suit :

 
// Define path to application directory
defined('APPLICATION_PATH')
	|| define('APPLICATION_PATH', realpath(__DIR__ . '/../application/nl'));
defined('PROJECT_PATH')
	|| define('PROJECT_PATH', realpath(__DIR__ . '/..'));
 
 
ini_set("display_errors", 1);
ini_set("display_startup_errors", 1);
error_reporting(E_ALL);
 
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
	realpath(PROJECT_PATH.DIRECTORY_SEPARATOR .'library'),
	realpath(APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'),
	realpath(APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'.DIRECTORY_SEPARATOR .'doctrine'),	
	get_include_path(),
)));
 
 

Charger ZF avec l'auto-loader de Doctrine

 
 
/**
 * Doctrine Autoloader
 */
require_once 'Common/ClassLoader.php';
 
/**
 * Zend Class
 */
$classLoader = new \Doctrine\Common\ClassLoader('Zend', PROJECT_PATH.DIRECTORY_SEPARATOR .'library'.DIRECTORY_SEPARATOR );
$classLoader->setNamespaceSeparator('_');
$classLoader->register(); 
 

Petite explication : dans un premier temps, on charge le fichier contenant la classe d'auto-load de Doctrine. C'est al première référence à Doctrine dans le code, Doctrine lui même n'est pas encore chargé.

On instancie ensuite l'auto-loader en lui donnant le nom de l'instance (Zend) et le répertoire dans lequel il doit aller chercher. (Le nom de l'instance permet de récupérer celle-ci par la suite si le besoin s'en faisait sentir et de la distinguer des autres instances que nous allons créer).

Doctrine utiliser la nomenclature UpperCamelCase pour nommer ses classes et ses fichiers. ZF en revanche sépare les mots par des "_". Il convient donc de dire à l'auto-loader de Doctrine la syntaxe à utiliser pour retrouver le nom d'un fichier en fonctino du nom de la classe appelée. Cela se fait avec :

$classLoader->setNamespaceSeparator('_');

Enfin, on enregistre l'instance.

Il est donc maintenant possible d'appeler les classes de Zend.
Notamment pour charger le fichier de configuration :

$config = new Zend_Config_Ini(APPLICATION_PATH . DIRECTORY_SEPARATOR .'configs'.DIRECTORY_SEPARATOR .'application.ini', 'production');

Charger Doctrine

Jusqu'ici Doctrine n'est pas encore chargé. Seul la classe d'auto-load (ClassLoader) a été chargée explicitement et utilisée.

Pour charger Doctrine, nous allons procéder comme suit :

 
/**
 * Docrine Class
 */
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', PROJECT_PATH.DIRECTORY_SEPARATOR .'library'.DIRECTORY_SEPARATOR .'Doctrine');
$classLoader->register();
 

Cette fois-ci, pas besoin de spécifier le 'NamespaceSeparator'.

Chargements complémentaires

Afin de pouvoir utiliser Doctrine en ligne de commande ainsi que le mapping YML, il convient d'enregistrer le namespace suivant :

 
/**
 * Doctrine classes for symfony.
 * Provide yml and console support
 */
$classLoader = new \Doctrine\Common\ClassLoader('Symfony', PROJECT_PATH.DIRECTORY_SEPARATOR .'library'.DIRECTORY_SEPARATOR .'Doctrine');
$classLoader->register();
 

Et pour les classes natives de mon projet :
 
/**
 * My project (NL) classes
 */
$classLoader = new \Doctrine\Common\ClassLoader('NL', APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'.DIRECTORY_SEPARATOR .'doctrine');
$classLoader->register(); 
 

Instanciation

Maintenant que tout est en place pour pouvoir utiliser Doctrine et Zend, la première chose à faire est d'instancier une occurrence de l'EntityManager de Doctrine. Il s'agit en fait du point d'entrée aux fonctionnalités d'ORM fournies par Doctrine (dixit la doc de Doctrine).

Pour cela nous allons :
1. lister les namespaces à utiliser
2. implémenter un cache (la documentation de Doctrine est très précise : il est très fortement déconseillé de ne pas utiliser de cache)
3. préparer la configuration de l'EntityManager
4. instancier l'EntityManager

/**
 * 1. list namespaces
 */
use Doctrine\ORM\EntityManager,
        Doctrine\ORM\Configuration;
 
/**
 * 2. implement cache 
 */
$cache = new \Doctrine\Common\Cache\ArrayCache;
 
/**
 * 3. prepare conf 
 */
// Implement Configuration
$configDoctrine = new Configuration;
// Add cache to config
$configDoctrine->setMetadataCacheImpl($cache);
$configDoctrine->setQueryCacheImpl($cache);
// Where to find Doctrine objects for my project
$driverImpl = $configDoctrine->newDefaultAnnotationDriver(APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'. DIRECTORY_SEPARATOR .'doctrine');
$configDoctrine->setMetadataDriverImpl($driverImpl);
// Where to generate proxy classes
$configDoctrine->setProxyDir(APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'. DIRECTORY_SEPARATOR .'doctrine'. DIRECTORY_SEPARATOR .'NL'. DIRECTORY_SEPARATOR .'proxies');
$configDoctrine->setProxyNamespace('NL\Proxies');
$configDoctrine->setAutoGenerateProxyClasses(false);
// database access
$connectionOptions = array(
	'driver' => 'pdo_mysql',
	'dbname' => $config->resources->db->dbname,
	'user' => $config->resources->db->username,
	'password' => $config->resources->db->password
);
/**
 * 4. implement EntityManager 
 */
$em = EntityManager::create($connectionOptions, $configDoctrine);
 

Et voila, il ne reste pus qu'a coder la partie métier avec Doctrine2 !

Mon fichier de bootstrap en entier :

<?php
// Define path to application directory
defined('APPLICATION_PATH')
	|| define('APPLICATION_PATH', realpath(__DIR__ . '/../application/nl'));
defined('PROJECT_PATH')
	|| define('PROJECT_PATH', realpath(__DIR__ . '/..'));
 
 
ini_set("display_errors", 1);
ini_set("display_startup_errors", 1);
error_reporting(E_ALL);
 
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
	realpath(PROJECT_PATH.DIRECTORY_SEPARATOR .'library'),
	realpath(APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'),
	realpath(APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'.DIRECTORY_SEPARATOR .'doctrine'),	
	get_include_path(),
)));
 
/**
 * Doctrine Autoloader
 */
require_once 'Common/ClassLoader.php';
 
/**
 * Zend Class
 */
$classLoader = new \Doctrine\Common\ClassLoader('Zend', PROJECT_PATH.DIRECTORY_SEPARATOR .'library'.DIRECTORY_SEPARATOR );
$classLoader->setNamespaceSeparator('_');
$classLoader->register(); 
 
$config = new Zend_Config_Ini(APPLICATION_PATH . DIRECTORY_SEPARATOR .'configs'.DIRECTORY_SEPARATOR .'application.ini', 'production');
 
/**
 * Docrine Class
 */
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', PROJECT_PATH.DIRECTORY_SEPARATOR .'library'.DIRECTORY_SEPARATOR .'Doctrine');
$classLoader->register();
 
/**
 * Doctrine classes for symfony.
 * Provide yml and console support
 */
$classLoader = new \Doctrine\Common\ClassLoader('Symfony', PROJECT_PATH.DIRECTORY_SEPARATOR .'library'.DIRECTORY_SEPARATOR .'Doctrine');
$classLoader->register();
 
/**
 * My project (NL) classes
 */
$classLoader = new \Doctrine\Common\ClassLoader('NL', APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'.DIRECTORY_SEPARATOR .'doctrine');
$classLoader->register(); 
 /**
 * 1. list namespaces
 */
use Doctrine\ORM\EntityManager,
        Doctrine\ORM\Configuration;
 
/**
 * 2. implement cache 
 */
$cache = new \Doctrine\Common\Cache\ArrayCache;
 
/**
 * 3. prepare conf 
 */
// Implement Configuration
$configDoctrine = new Configuration;
// Add cache to config
$configDoctrine->setMetadataCacheImpl($cache);
$configDoctrine->setQueryCacheImpl($cache);
// Where to find Doctrine objects for my project
$driverImpl = $configDoctrine->newDefaultAnnotationDriver(APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'. DIRECTORY_SEPARATOR .'doctrine');
$configDoctrine->setMetadataDriverImpl($driverImpl);
// Where to generate proxy classes
$configDoctrine->setProxyDir(APPLICATION_PATH . DIRECTORY_SEPARATOR .'models'. DIRECTORY_SEPARATOR .'doctrine'. DIRECTORY_SEPARATOR .'NL'. DIRECTORY_SEPARATOR .'proxies');
$configDoctrine->setProxyNamespace('NL\Proxies');
$configDoctrine->setAutoGenerateProxyClasses(false);
// database access
$connectionOptions = array(
	'driver' => 'pdo_mysql',
	'dbname' => $config->resources->db->dbname,
	'user' => $config->resources->db->username,
	'password' => $config->resources->db->password
);
/**
 * 4. implement EntityManager 
 */
 $em = EntityManager::create($connectionOptions, $configDoctrine);

postheadericon Commentaires

  • Thomas Sunday 1 May à 20h35

    Salut,

    En cherchant comment intégrer Zend Framework et Doctrine 2 je suis tombé sur ça http://borisguery.github.com/bgylibrary/#bgylib-components-bgy-application-resource-doctrine2

    Disponible ici : https://github.com/borisguery/bgylibrary

    Pour ma part ça fonctionne super bien