Le Blog

Zend Framework 1 + Doctrine 2

Alors que Doctrine s'installe comme un module dans la dernière version du ZF2, ce n'est pas le cas dans la première version du framework.

Zend Framework 1 ne propose pas de véritable ORM. Le composant Zend_Db est une couche d'abstration permettant tout au plus de manipuler les données d'une table via le momdule de gestion de table Zend_Db_Table.

Doctrine (http://www.doctrine-project.org/ )

Le projet Doctrine est principalement connu pour son ORM (Object Relational Mapper) qui permet d'une part d'effectuer des requêtes sur la base de données dans un langage objet (DQL Doctrine Query Language) et d'autre part de travailler avec les données sous la forme d'objet. 

Cet ORM repose sur une couche d'abstraction (DBAL Doctrine Database Abstraction Layer) qui fait également partie du projet Doctine et qui elle même utilise la célèbre extension PHP Data Objects PDO. La couche d'abstration fournie par Doctrine permet en plus d'inspecter et de manipuler le schema de la base, ce qui permet de générer automatiquement les tables de la base de données ou le code php des classes dans le cas où la base existe déjà.

Mise en place

Dans cette exemple, nous avons utilisé Zend Framework 1.12.0 et Doctrine 2.2.2

Pour commencer vous devez installer les bibliothèques Doctrine soit en téléchargrant directement l'archive (la dernière version est disponible à l'adresse suivante : http://www.doctrine-project.org/projects/orm.html ), soit en l'installant via Pear :

pear channel-discover pear.doctrine-project.org

pear install doctrine/DoctrineORM-2.3.3

Dossiers d'installation :

Les bibliothèques Doctrine seront installées dans un sous dossier du dossier « library » de l'architecture classique de Zend (Voir à droite).




Le dossier « models » contiendra les 3 sous dossiers utilisés par Doctrine : « Entities », « Proxies » et « Repositories ». La dénomination de ces 3 dossiers est configurée dans le Bootstrap (_initDoctrine)

Le dossier « tools » contient le fichier doctrine.php (attention à configurer les chemins dans ce fichier) permettant de générer les entités, les dépôts, le schéma de la base de données. Nous verrons dans un second article des exemples de commande à exécuter en interface de ligne de commande.









Configuration.

Dans le fichier /application/config/application.ini on pourra définir les variables de connexion à la base de données, par exemple de la manière suivante :

/application/config/application.ini 

; Databases
doctrine.conn.0.host = '127.0.0.1'
doctrine.conn.0.username = 'root'
doctrine.conn.0.password = ''
doctrine.conn.0.driver = 'pdo_mysql'
doctrine.conn.0.charset = 'utf8'
doctrine.conn.0.dbname = 'database'
doctrine.path.0.models = APPLICATION_PATH "/models"

Dans le bootstrap, on ajoute une méthode pour initialiser Doctrine :

/application/Bootstrap.php

protected function _initDoctrine() {

         $options = $this->getOptions();
         // On inclus l'autoloader Doctrine
         $doctrinePath = $options['includePaths']['library'];
         require_once $doctrinePath . '/Doctrine/Common/ClassLoader.php';
         $classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
         $classLoader->register();

         // Autoloader pour les Entities
         $entitiesLoader = new \Doctrine\Common\ClassLoader('Entities', realpath($options['doctrine']['path'][0]['models']));
         $entitiesLoader->register();

        // Autoloader pour les repositories
         $repoLoader = new \Doctrine\Common\ClassLoader('Repositories', realpath($options['doctrine']['path'][0]['models']));
         $repoLoader->register();

         // Exemple de configuration du cache
         if (APPLICATION_ENV == "development") {
            $cache = new \Doctrine\Common\Cache\ArrayCache();
         } else {
             $cacheOptions = $options['cache']['backendOptions'];
             $cache = new \Doctrine\Common\Cache\MemcacheCache();
             $memcache = new Memcache;
             $memcache->connect($cacheOptions['servers']['host'], $cacheOptions['servers']['port']);
             $cache->setMemcache($memcache);
         }
        
         $config = new \Doctrine\ORM\Configuration();
         $config->setMetadataCacheImpl($cache);

         // Utiliser les annotations pour la description du modèle
         $driverImpl = $config->newDefaultAnnotationDriver($options['doctrine']['path'][0]['models']);
         $config->setMetadataDriverImpl($driverImpl);        
         $config->setQueryCacheImpl($cache);
         $config->setProxyDir($options['doctrine']['path'][0]['models']. '/Proxies');
         $config->setProxyNamespace('Proxies');

         // Ne générer les class proxy qu'en developpement
         $config->setAutoGenerateProxyClasses(APPLICATION_ENV == "development");

         // Connexion à la BDD (valeurs définies dans le fichier application.ini)
         $connectionOptions = array(
            'driver' => $options['doctrine']['conn'][0]['driver'],
            'dbname' => $options['doctrine']['conn'][0]['dbname'],
            'user' => $options['doctrine']['conn'][0]['username'],
            'password' => $options['doctrine']['conn'][0]['password'],
            'charset' => $options['doctrine']['conn'][0]['charset'],
            'driverOptions' => array( 1002=>'SET NAMES utf8' )
        );

         $entityManager = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
         Zend_Registry::set('em', $entityManager);
         return $entityManager; 
     }
Définition de modèle 

Comme indiqué dans le bootstrap, nous utilisons les annotations pour décrire les modèles. Par exemple :
/application/models/Entities/brand.php

namespace Entities;
/**
 * @Entity(repositoryClass="Repositories\BrandRepository")
 * @Table(name="brands")
 */
class Brand
{
    /**
     * @var integer $_id
     *
     * @Column(name="id", type="integer", nullable=false)
     * @Id
     * @GeneratedValue(strategy="IDENTITY")
     */
     private $_id;
	 
    /**
     * @var string $_title
     *
     * @Column(name="title", type="string", length=64, nullable=true)
     */
    private $_title;
	
    /**
     * @OneToMany(targetEntity="Model", mappedBy="_brand", cascade={"remove", "persist"})
     * @var _models[]
     **/
    private $_models = null;
	
    const _INACTIF  = 0;
    const _ACTIF    = 1;
	
    …
Exemple de définition de repository:
 /application/models/Repositories/BrandRepository.php
namespace Repositories;
use Doctrine\ORM\EntityRepository;
use Entities;

class BrandRepository extends EntityRepository
{
    public function getAllActiveBrandWithModels() {

        $qb = $this->createQueryBuilder('b');
        $qb->select(array('b', 'm'))
            ->leftJoin('b._models', 'm')
            ->where('b._status = '.\Entities\Brand::_ACTIF)
            ->orderBy('b._title');

        return $qb->getQuery()->getResult();
    }
}
Utilisation dans un controlleur :
 public function indexAction()
    {
        $em = Zend_Registry::get('em');
                 
        $repository = $em->getRepository('Entities\Brand');
        $brand = $repository->getAllActiveBrandWithModels();
        foreach ($brand as $b) {
           …
        }    
    }

2 commentaires

  • #3
    Netsive a écrit :
    18/04/24 11:07
    Sans avoir testé le code, je détecte quelques incohérences et bugs.

    D'abord dans la configuration du ClassLoader :
    - vous définissez dans le application.ini un paramètre : doctrine.path.0.models = APPLICATION_PATH "/models". Il serait donc logique d'utiliser ce paramètre plutôt qu'un chemin en dur dans le Bootstrap.
    - pourquoi ne pas définir le driver "pdo_mysql" également dans le application.ini tout comme le "charset". De manière générale, il serait judicieux de reprendre les mêmes paramètres que ceux disponibles pour la ressource db de Zend.
    - En suivant le raisonnement ci-dessus, nous pourrions par idéalement définir un nouveau plugin de ressource pour Doctrine ou toute la configuration se ferait dans application.ini
    - enfin le retour de la fonction _initDoctrine devrait être $entityManager et non $em qui correspond à la clé de registre contenant le gestionnaire d'entités.

    Cordialement
  • #4
    christophe a écrit :
    18/04/24 11:07
    Merci pour ces remarques judicieuses.
    Des corrections ont été apportés à l'article.

Laisser un Commentaire

Les champs précédés d'un * sont obligatoires.

Copyrights © 2013 & All Rights Reserved by aTouTdev