Introduction au ZF – Partie 5 : Doctrine

Lundi 19 avril 2010, 19:20

Pour les Models, je pensais vous montrer l’exemple concret de l’usine à gaz présentée par Zend. Mais vu que le Zend Framework 2.0 va le changer pour doctrine, j’ai décidé de passer dessus. Doctrine a plusieurs points positifs :

  • Il utilise PDO donc il y a toujours une couche d’abstraction
  • Il génère automatiquement les models (si on utilise l’utilitaire)
  • Il est plus performant que Zend_Db
  • Tout est objet
  • Conforme au pattern MVC

Pour ces raisons, Doctrine semble être le choix le plus judicieux. Maintenant il ne reste plus qu’à l’intégrer à notre projet. Pour informations, j’utilise la version 1.2.2 de Doctrine qui est la dernière version stable en date. Vous pouvez la récupérer à cette adresse.

Je vous conseil aussi d’avoir la documentation officielle sous le bras pour bien comprendre tout ce qu’il va se dire ici. Avant de continuer, voici la base de donnée finale que nous allons utiliser tout au long de ce tutoriel. Je mets le contenu final ici pour que l’on génère les Models complets dès le début.

C’est avec une application bien pensée qu’on arrive à un bon résultat.

Et voici maintenant le script avec un utilisateur par défaut. Le mot de passe décrypté est azerty.

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;

--
-- Base de données: `tutosite`
--
CREATE DATABASE `tutosite` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `tutosite`;

-- --------------------------------------------------------
--
-- Structure de la table `article`
--

CREATE TABLE IF NOT EXISTS `article` (
  `idArticle` int(11) NOT NULL AUTO_INCREMENT,
  `idUtilisateur` int(11) NOT NULL,
  `idRubrique` int(11) NOT NULL,
  `titre` varchar(45) NOT NULL,
  `corps` text NOT NULL,
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`idArticle`),
  KEY `fk_Article_Utilisateurs` (`idUtilisateur`),
  KEY `fk_Article_Rubrique` (`idRubrique`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

--
-- Contenu de la table `article`
--
-- --------------------------------------------------------
--
-- Structure de la table `commentaire`
--

CREATE TABLE IF NOT EXISTS `commentaire` (
  `idCommentaire` int(11) NOT NULL AUTO_INCREMENT,
  `idArticle` int(11) NOT NULL,
  `pseudo` varchar(45) NOT NULL,
  `email` varchar(45) DEFAULT NULL,
  `message` text NOT NULL,
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`idCommentaire`),
  KEY `fk_Commentaire_Article` (`idArticle`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

--
-- Contenu de la table `commentaire`
--
-- --------------------------------------------------------
--
-- Structure de la table `rubrique`
--

CREATE TABLE IF NOT EXISTS `rubrique` (
  `idRubrique` int(11) NOT NULL,
  `Nom` varchar(45) NOT NULL,
  PRIMARY KEY (`idRubrique`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Contenu de la table `rubrique`
--
-- --------------------------------------------------------
--
-- Structure de la table `tag`
--

CREATE TABLE IF NOT EXISTS `tag` (
  `idTag` int(11) NOT NULL,
  `Nom` varchar(45) NOT NULL,
  `Couleur` varchar(7) NOT NULL DEFAULT '#000000',
  `Hicolor` varchar(7) NOT NULL DEFAULT '#000000',
  PRIMARY KEY (`idTag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Contenu de la table `tag`
--
-- --------------------------------------------------------
--
-- Structure de la table `tagarticle`
--

CREATE TABLE IF NOT EXISTS `tagarticle` (
  `idTag` int(11) NOT NULL,
  `idArticle` int(11) NOT NULL,
  PRIMARY KEY (`idTag`,`idArticle`),
  KEY `fk_Tag_has_Article_Tag` (`idTag`),
  KEY `fk_Tag_has_Article_Article` (`idArticle`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

--
-- Contenu de la table `tagarticle`
--
-- --------------------------------------------------------
--
-- Structure de la table `utilisateurs`
--

CREATE TABLE IF NOT EXISTS `utilisateurs` (
  `idutilisateur` int(11) NOT NULL AUTO_INCREMENT,
  `login` varchar(50) NOT NULL,
  `passwd` varchar(100) NOT NULL,
  PRIMARY KEY (`idutilisateur`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

--
-- Contenu de la table `utilisateurs`
--

INSERT INTO `utilisateurs` (`idutilisateur`, `login`, `passwd`) VALUES
(1, 'admin', 'ab4f63f9ac65152575886860dde480a1 ');

--
-- Contraintes pour les tables exportées
--
--
-- Contraintes pour la table `article`
--
ALTER TABLE `article`
  ADD CONSTRAINT `fk_Article_Rubrique` FOREIGN KEY (`idRubrique`) REFERENCES `rubrique` (`idRubrique`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  ADD CONSTRAINT `fk_Article_Utilisateurs` FOREIGN KEY (`idUtilisateur`) REFERENCES `utilisateurs` (`idutilisateur`) ON DELETE NO ACTION ON UPDATE NO ACTION;
--
-- Contraintes pour la table `commentaire`
--
ALTER TABLE `commentaire`
  ADD CONSTRAINT `fk_Commentaire_Article` FOREIGN KEY (`idArticle`) REFERENCES `article` (`idArticle`) ON DELETE NO ACTION ON UPDATE NO ACTION;

Voilà pour la partie base de données SQL. Maintenant nous allons nous attaquer à la configuration. Nous allons tout d’abord créer les répertoires suivants :

  • Dossier contenant les données des tables en script YAML générés par Doctrine /application/configurations/data/fixtures
  • Dossier contenant les scripts SQL générés par Doctrine /application/configurations/data/sql
  • Dossier de migration de la base de données /application/configurations/migration
  • Dossier contenant le script de génération des cmodels /application/scripts
  • Dossier contenant nos models générés /library/App/Models

Quand la création des dossiers est faite, vous allez télécharger Doctrine 1.2.2. La version sandbox est inutile ici vu que les scripts donnés pour la génération doivent être complètement refait pour Zend.

Lorsque vous avez téléchargé Doctrine, vous allez extraire son contenu dans /library. Vous devriez alors avoir le dossier /library comme la capture sur votre droite. Le dossier vendor permet d’utiliser les scripts YAML quand au dossier Doctrine, il contient tout le cœur de Doctrine.

Maintenant que Doctrine a été correctement ajouté au dossier Library, Que la base de donnée a bien été ajoutée au SGBD, nous pouvons passer à la suite qui va consister à configurer proprement Zend.

Tout d’abord, nous allons rajouter les lignes suivantes au fichier /application/configurations/app.ini

; ---
; Doctrine
; ---

; configuration de base
; Le dsn correspond aux infos de connexion. Ici nous appelons une base mysql
; SGBD : MySqL
; Login : Root
; Password : azerty //si aucun password mettre juste root@localhost/bdd
; Serveur : localhost
; BDD : tutosite
doctrine.dsn                = "mysql://root:azerty@localhost/tutosite"
doctrine.data_fixtures_path = APPLICATION_PATH "/configurations/data/fixtures"
doctrine.sql_path           = APPLICATION_PATH "/configurations/data/sql"
doctrine.migrations_path    = APPLICATION_PATH "/configurations/migrations"
doctrine.yaml_schema_path   = APPLICATION_PATH "/configurations/schema.yml"
doctrine.models_path = APPLICATION_PATH "/../library/App/Models"

; règles lors de la génération des classes
; on utilise la convention de nommage PEAR
doctrine.generate_models_options.pearStyle  = true
; on génère les classes de table
doctrine.generate_models_options.generateTableClasses  = true
; on génère les classes de base. Ces classes contiennent les définitions de nos tables
doctrine.generate_models_options.generateBaseClasses  = true
; on définit un préfixe pour les classes de base
doctrine.generate_models_options.baseClassPrefix  = "Base_"
; on ne pet rien ici, avec la convention pear, ils seront ajoutés dans le sous dossier Base
doctrine.generate_models_options.baseClassesDirectory  =
; on ne met pas le préfixe au nom de fichier
doctrine.generate_models_options.classPrefixFiles  = false
; toutes les classes commenceront par ce préfixe
doctrine.generate_models_options.classPrefix = "App_Models_"

Ensuite, nous allons nous intéresser au /application/Bootstrap.php et nous allons ajouter la fonction d’initialisation de Doctrine :

protected function _initDoctrine() 
{
    //on met Doctrine en autoload
    $this->getApplication()
         ->getAutoloader()
         ->pushAutoloader ( array ('Doctrine', 'autoload' ) );
    spl_autoload_register(array('Doctrine', 'modelsAutoload'));

    //on récupère une instance de Doctrine
    $manager = Doctrine_Manager::getInstance ();

    //permet de valider automatiquement l'intégrité des données
    //ce qui veut dire que l'on ne peut pas mettre une variable de type string
    //dans une variable de type int.
    $manager->setAttribute (Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
    //l’AUTO_ACCESSOR_OVERRIDE va nous permettre de personnaliser l’assignation de données.
    $manager->setAttribute ( Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true );
    //Doctrine permet de personnaliser également les classes de table en permettant 
    //de créer des méthodes propres à une table. 
    //Ce paramètre permet de charger le fichier contenant nos méthodes personnalisées.
    $manager->setAttribute (
     Doctrine::ATTR_MODEL_LOADING, 
     Doctrine::MODEL_LOADING_CONSERVATIVE
    );
    //on permet le chargement des classes table
    $manager->setAttribute ( Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true );

    //on récupère toutes les options doctrine du fichier app.ini
    $doctrineConfig = $this->getOption('doctrine');

    //on récupère la variable doctrine.models_path du fichier app.ini
    //afin d'avoir le répertoire des models
    //pour que Doctrine les charge
    Doctrine::loadModels($doctrineConfig['models_path']);

    //on récupère la connexion à mysql et on la nomme doctrine
    $conn = Doctrine_Manager::connection($doctrineConfig['dsn'],'doctrine');
    //je sais plus ce que sa veut dire, mais il le faut
    $conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM,true);

    //on définit la sortie encodée en UTF-8
    $conn->setCharset('utf8');
    $conn->setCollate('utf8_general_ci');

    //on retourne la connexion
    return $conn;
}

Et pour que tout soit chargé directement, il suffit d’ajouter ces lignes dans la fonction _initNamSpaces() :

$autoloader->registerNamespace('Doctrine_');
$autoloader->registerNamespace('Doctrine');

Bien, maintenant Doctrine est correctement intégré à notre framework. Il ne reste plus qu’à générer les classes. Pour ce faire, il nous fait le fichier script suivant /application/scripts/Doctrine.php :

// Define path to application directory
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH',
              realpath(dirname(__FILE__) . '/..'));

// Define application environment
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV',
              (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
                                         : 'production'));

// Typically, you will also want to add your library/ directory
// to the include_path, particularly if it contains your ZF install
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path(),
)));

require_once 'Doctrine.php';

/** Zend_Application */
require_once 'Zend/Application.php';

// Create application, bootstrap, and run
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configurations/app.ini'
);

$application->getBootstrap()->bootstrap('doctrine');
$config = $application->getOption('doctrine');

$cli = new Doctrine_Cli($config);
$cli->run($_SERVER['argv']);

Vous l’aurez sans doute deviné, il s’agit du fichier /public/index.php avec à la fin l’instanciation de Doctrine. Cette instanciation nous permet d’utiliser, par le biais de la ligne de commande, Doctrine et, ainsi, générer nos classes.

Pour ce faire, allez dans votre invite de commande et rendez vous dans le dossier qui contient le script. Tapez juste la commande suivante :

php Doctrine.php

Elle devrait vous retourner le résultat suivant :

Il ne nous reste plus qu’à taper la commande :

php doctrine.php generate-models-db
php doctrine.php generate-yaml-db

Dans le dossier /application/configurations/ nous trouverons le dossier schema.yml contenant le script YAML de notre base de données. Et maintenant, si nous regardons dans le dossier /library/App/Models, nous avons pleins de classes de créées.

Prenons par exemple la table Utilisateurs. Elle aura 3 classes dédiées :

  • App_Models_Utilisateurs : permet de définir des fonction sur un objet utilisateur
  • App_Models_UtilisateursTable : permet de définir les différentes requêtes à faire sur notre table
  • App_Models_Base_Utilisateurs : contient la définition de la table Utilisateurs.

Justement, afin de tester et de bien comprendre ces classes, nous allons ajouter une fonction à la classe App_Models_UtilisateursTable. La fonction en question n’est rien d’autre qu’un fetchAll permettant de faire un Select de base.

/**
 * Select de base
 * 
 * @param string $where
 * @param string $order
 * @param int $limit
 * @param int $offset
 * @return Collection
 */
 public function fetchAll($where=null, $order=null, $limit=null, $offset=null)
 {
     $select = Doctrine_Query::create()->select('*')
         //dans le from on met le nom de la classe
         //Doctrine s'occupe du reste
         ->from('App_Models_Utilisateurs');

     if(isset($where)){ $select->where($where); }
     if(isset($order)){ $select->orderBy($order); }
     if(isset($limit)){ $select->limit($limit); }
     if(isset($offset)){ $select->offset($offset); }

     return $select->execute();
 }

Ensuite pour utiliser cette requête, nous allons éditier le controller Admin_AccueilController. Dans d’action d’index ajoutez les lignes suivantes :

//on récupère la classe table de App_Models_Utilisateurs
$table = Doctrine_Core::getTable('App_Models_Utilisateurs');
//on liste tout le monde
$liste = $table->fetchAll();
//on traite le résultat
foreach($liste as $unuser)
{
    print_r($unuser->login);
    print_r($unuser->passwd);
}

Normalement, si vous vous rendez sur la page concernée, vous devriez voir afficher le login et le mot de passe des utilisateurs. Voilà, maintenant vous pouvez utiliser Doctrine au sein du Framework Zend. La prochaine fois, nous verrons la création des premiers articles de notre Blog.

Throrïn
A propos de l'auteur
Benjamin Besse

Je suis Analyste Développeur chez Goomeo et je suis passioné par tout ce qui touche aux technologies du Web. J'ai commencé par apprendre l'utilisation du Framework Zend et j'ai continué naturellement via Android. Le tout seulement avec les bases acquises en DUT et Licence professionnelle Informatique.

Laisser un commentaire

Commentaires

J’ai un problème lorsque j’utilise php Doctrine.php :

Fatal error: Uncaught exception ‘Zend_Config_Exception’ with message ‘Section ‘p
roduction’ cannot be found in C:\xampp\htdocs\zftest\application/configurations/
app.ini’ in C:\xampp\htdocs\zftest\library\Zend\Config\Ini.php:151
Stack trace:
#0 C:\xampp\htdocs\zftest\library\Zend\Application.php(386): Zend_Config_Ini->__
construct(‘C:\xampp\htdocs…’, ‘production’)
#1 C:\xampp\htdocs\zftest\library\Zend\Application.php(85): Zend_Application->_l
oadConfig(‘C:\xampp\htdocs…’)
#2 C:\xampp\htdocs\zftest\application\scripts\Doctrine.php(36): Zend_Application
->__construct(‘production’, ‘C:\xampp\htdocs…’)
#3 {main}
thrown in C:\xampp\htdocs\zftest\library\Zend\Config\Ini.php on line 151

Bonjour,
L’erreur est pas trop dur à corriger. En fait tu utilise ton site en mode production APP_ENV = production, hors, dans ton fichier de config, tu ne déclare pas production. Rajoute en fin de ton config.ini [production : development] et le tour est joué.
Attention tout de même. En faisant comme ça, tu remet les configs de développement pour la production ce qui n’est pas très recommandé.

Bonjour;
JE sais pas pourquoi le PhpMyAdmin me retourne cette erreur apres avoir recopier la requete SQL:

Erreur
Requête SQL:

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;

MySQL a répondu:

#1064 – You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */’ at line 1

Bonjour,

Lors de l’exécution du script Doctrine.php, j’obtiens l’erreur suivante : « unexpected T_STRING »
, qui correspond dans le script à la ligne » defined(‘APPLICATION_ENV’).

php Doctrine.php > PHP Parse error: syntax error, unexpected T_STRING in Doctrine.php on line 9.

Mon fichier est identique à votre Doctrine.php pourtant.

Je précise que j’utilise un Xampp pour faire tourner mon Zend.

Merci d’avance

Ok je viens de corriger.

En fait ton fichier contient plein d’espaces dans les inter-lignes, d’où le problème de syntaxe détecté. (à moins que ça soit le copier/collier avec ton widget pour afficher le code PHP qui foire)

Effectivement cela provient bien du module affichant le code source. As tu essayé via le bouton copy si ça remédie au problème ?

Bonjour,

Lien mort concernant le téléchargement de Doctrine.

Merci pour le tuto au passage !