avr
19
2010

Introduction au ZF – Partie 5 : Doctrine

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.

 SQL |  copy code |? 
001
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
002
 
003
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
004
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
005
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
006
/*!40101 SET NAMES utf8 */;
007
 
008
--
009
-- Base de données: `tutosite`
010
--
011
CREATE DATABASE `tutosite` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
012
USE `tutosite`;
013
 
014
-- --------------------------------------------------------
015
--
016
-- Structure de la table `article`
017
--
018
 
019
CREATE TABLE IF NOT EXISTS `article` (
020
  `idArticle` INT(11) NOT NULL AUTO_INCREMENT,
021
  `idUtilisateur` INT(11) NOT NULL,
022
  `idRubrique` INT(11) NOT NULL,
023
  `titre` VARCHAR(45) NOT NULL,
024
  `corps` text NOT NULL,
025
  `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
026
  PRIMARY KEY (`idArticle`),
027
  KEY `fk_Article_Utilisateurs` (`idUtilisateur`),
028
  KEY `fk_Article_Rubrique` (`idRubrique`)
029
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
030
 
031
--
032
-- Contenu de la table `article`
033
--
034
-- --------------------------------------------------------
035
--
036
-- Structure de la table `commentaire`
037
--
038
 
039
CREATE TABLE IF NOT EXISTS `commentaire` (
040
  `idCommentaire` INT(11) NOT NULL AUTO_INCREMENT,
041
  `idArticle` INT(11) NOT NULL,
042
  `pseudo` VARCHAR(45) NOT NULL,
043
  `email` VARCHAR(45) DEFAULT NULL,
044
  `message` text NOT NULL,
045
  `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
046
  PRIMARY KEY (`idCommentaire`),
047
  KEY `fk_Commentaire_Article` (`idArticle`)
048
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
049
 
050
--
051
-- Contenu de la table `commentaire`
052
--
053
-- --------------------------------------------------------
054
--
055
-- Structure de la table `rubrique`
056
--
057
 
058
CREATE TABLE IF NOT EXISTS `rubrique` (
059
  `idRubrique` INT(11) NOT NULL,
060
  `Nom` VARCHAR(45) NOT NULL,
061
  PRIMARY KEY (`idRubrique`)
062
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
063
 
064
--
065
-- Contenu de la table `rubrique`
066
--
067
-- --------------------------------------------------------
068
--
069
-- Structure de la table `tag`
070
--
071
 
072
CREATE TABLE IF NOT EXISTS `tag` (
073
  `idTag` INT(11) NOT NULL,
074
  `Nom` VARCHAR(45) NOT NULL,
075
  `Couleur` VARCHAR(7) NOT NULL DEFAULT '#000000',
076
  `Hicolor` VARCHAR(7) NOT NULL DEFAULT '#000000',
077
  PRIMARY KEY (`idTag`)
078
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
079
 
080
--
081
-- Contenu de la table `tag`
082
--
083
-- --------------------------------------------------------
084
--
085
-- Structure de la table `tagarticle`
086
--
087
 
088
CREATE TABLE IF NOT EXISTS `tagarticle` (
089
  `idTag` INT(11) NOT NULL,
090
  `idArticle` INT(11) NOT NULL,
091
  PRIMARY KEY (`idTag`,`idArticle`),
092
  KEY `fk_Tag_has_Article_Tag` (`idTag`),
093
  KEY `fk_Tag_has_Article_Article` (`idArticle`)
094
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
095
 
096
--
097
-- Contenu de la table `tagarticle`
098
--
099
-- --------------------------------------------------------
100
--
101
-- Structure de la table `utilisateurs`
102
--
103
 
104
CREATE TABLE IF NOT EXISTS `utilisateurs` (
105
  `idutilisateur` INT(11) NOT NULL AUTO_INCREMENT,
106
  `login` VARCHAR(50) NOT NULL,
107
  `passwd` VARCHAR(100) NOT NULL,
108
  PRIMARY KEY (`idutilisateur`)
109
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
110
 
111
--
112
-- Contenu de la table `utilisateurs`
113
--
114
 
115
INSERT INTO `utilisateurs` (`idutilisateur`, `login`, `passwd`) VALUES
116
(1, 'admin', 'ab4f63f9ac65152575886860dde480a1 ');
117
 
118
--
119
-- Contraintes pour les tables exportées
120
--
121
--
122
-- Contraintes pour la table `article`
123
--
124
ALTER TABLE `article`
125
  ADD CONSTRAINT `fk_Article_Rubrique` FOREIGN KEY (`idRubrique`) REFERENCES `rubrique` (`idRubrique`) ON DELETE NO ACTION ON UPDATE NO ACTION,
126
  ADD CONSTRAINT `fk_Article_Utilisateurs` FOREIGN KEY (`idUtilisateur`) REFERENCES `utilisateurs` (`idutilisateur`) ON DELETE NO ACTION ON UPDATE NO ACTION;
127
--
128
-- Contraintes pour la table `commentaire`
129
--
130
ALTER TABLE `commentaire`
131
  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

 INI |  copy code |? 
01
; ---
02
; Doctrine
03
; ---
04
 
05
; configuration de base
06
; Le dsn correspond aux infos de connexion. Ici nous appelons une base mysql
07
; SGBD : MySqL
08
; Login : Root
09
; Password : azerty //si aucun password mettre juste root@localhost/bdd
10
; Serveur : localhost
11
; BDD : tutosite
12
doctrine.dsn                = "mysql://root:azerty@localhost/tutosite"
13
doctrine.data_fixtures_path = APPLICATION_PATH "/configurations/data/fixtures"
14
doctrine.sql_path           = APPLICATION_PATH "/configurations/data/sql"
15
doctrine.migrations_path    = APPLICATION_PATH "/configurations/migrations"
16
doctrine.yaml_schema_path   = APPLICATION_PATH "/configurations/schema.yml"
17
doctrine.models_path = APPLICATION_PATH "/../library/App/Models"
18
 
19
; règles lors de la génération des classes
20
; on utilise la convention de nommage PEAR
21
doctrine.generate_models_options.pearStyle  = true
22
; on génère les classes de table
23
doctrine.generate_models_options.generateTableClasses  = true
24
; on génère les classes de base. Ces classes contiennent les définitions de nos tables
25
doctrine.generate_models_options.generateBaseClasses  = true
26
; on définit un préfixe pour les classes de base
27
doctrine.generate_models_options.baseClassPrefix  = "Base_"
28
; on ne pet rien ici, avec la convention pear, ils seront ajoutés dans le sous dossier Base
29
doctrine.generate_models_options.baseClassesDirectory  =
30
; on ne met pas le préfixe au nom de fichier
31
doctrine.generate_models_options.classPrefixFiles  = false
32
; toutes les classes commenceront par ce préfixe
33
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 :
 PHP |  copy code |? 
01
protected function _initDoctrine() 
02
 {
03
 //on met Doctrine en autoload
04
        $this->getApplication()
05
             ->getAutoloader()
06
             ->pushAutoloader ( array ('Doctrine', 'autoload' ) );
07
        spl_autoload_register(array('Doctrine', 'modelsAutoload'));
08
 
09
        //on récupère une instance de Doctrine
10
        $manager = Doctrine_Manager::getInstance ();
11
 
12
        //permet de valider automatiquement l'intégrité des données
13
        //ce qui veut dire que l'on ne peut pas mettre une variable de type string
14
        //dans une variable de type int.
15
        $manager->setAttribute (Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
16
        //l’AUTO_ACCESSOR_OVERRIDE va nous permettre de personnaliser l’assignation de données.
17
        $manager->setAttribute ( Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true );
18
        //Doctrine permet de personnaliser également les classes de table en permettant 
19
        //de créer des méthodes propres à une table. 
20
        //Ce paramètre permet de charger le fichier contenant nos méthodes personnalisées.
21
        $manager->setAttribute (
22
         Doctrine::ATTR_MODEL_LOADING, 
23
         Doctrine::MODEL_LOADING_CONSERVATIVE
24
        );
25
        //on permet le chargement des classes table
26
        $manager->setAttribute ( Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true );
27
 
28
        //on récupère toutes les options doctrine du fichier app.ini
29
        $doctrineConfig = $this->getOption('doctrine');
30
 
31
        //on récupère la variable doctrine.models_path du fichier app.ini
32
        //afin d'avoir le répertoire des models
33
        //pour que Doctrine les charge
34
        Doctrine::loadModels($doctrineConfig['models_path']);
35
 
36
        //on récupère la connexion à mysql et on la nomme doctrine
37
        $conn = Doctrine_Manager::connection($doctrineConfig['dsn'],'doctrine');
38
        //je sais plus ce que sa veut dire, mais il le faut
39
        $conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM,true);
40
 
41
        //on définit la sortie encodée en UTF-8
42
        $conn->setCharset('utf8');
43
        $conn->setCollate('utf8_general_ci');
44
 
45
        //on retourne la connexion
46
        return $conn;
47
    }

Et pour que tout soit chargé directement, il suffit d’ajouter ces lignes dans la fonction _initNamSpaces() :
 PHP |  copy code |? 
1
$autoloader->registerNamespace('Doctrine_');
2
$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

 PHP |  copy code |? 
01
// Define path to application directory
02
defined('APPLICATION_PATH')
03
    || define('APPLICATION_PATH',
04
              realpath(dirname(__FILE__) . '/..'));
05
 
06
// Define application environment
07
defined('APPLICATION_ENV')
08
    || define('APPLICATION_ENV',
09
              (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
10
                                         : 'production'));
11
 
12
// Typically, you will also want to add your library/ directory
13
// to the include_path, particularly if it contains your ZF install
14
set_include_path(implode(PATH_SEPARATOR, array(
15
    realpath(APPLICATION_PATH . '/../library'),
16
    get_include_path(),
17
)));
18
 
19
require_once 'Doctrine.php';
20
 
21
/** Zend_Application */
22
require_once 'Zend/Application.php';
23
 
24
// Create application, bootstrap, and run
25
$application = new Zend_Application(
26
    APPLICATION_ENV,
27
    APPLICATION_PATH . '/configurations/app.ini'
28
);
29
 
30
$application->getBootstrap()->bootstrap('doctrine');
31
$config = $application->getOption('doctrine');
32
 
33
$cli = new Doctrine_Cli($config);
34
$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 se faire, allez dans votre invite de commande et rendez vous dans le dossier qui contient le script.

Tapez juste la commande suivante :

 Bash |  copy code |? 
1
php Doctrine.php

Elle devrait vous retourner le résultat suivant :

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

 Bash |  copy code |? 
1
php doctrine.php generate-models-db
2
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.

 PHP |  copy code |? 
01
/**
02
     * Select de base
03
     * 
04
     * @param string $where
05
     * @param string $order
06
     * @param int $limit
07
     * @param int $offset
08
     * @return Collection
09
     */
10
 public function fetchAll($where=null, $order=null, $limit=null, $offset=null)
11
 {
12
 $select = Doctrine_Query::create()
13
 ->select('*')
14
 //dans le from on met le nom de la classe
15
 //Doctrine s'occupe du reste
16
 ->from('App_Models_Utilisateurs');
17
 
18
 if(isset($where)){ $select->where($where); }
19
 if(isset($order)){ $select->orderBy($order); }
20
 if(isset($limit)){ $select->limit($limit); }
21
 if(isset($offset)){ $select->offset($offset); }
22
 
23
 return $select->execute();
24
 }

Ensuite pour utiliser cette requête, nous allons éditier le controller Admin_AccueilController. Dans d’action d’index ajoutez les lignes suivantes :
 PHP |  copy code |? 
01
//on récupère la classe table de App_Models_Utilisateurs
02
$table = Doctrine_Core::getTable('App_Models_Utilisateurs');
03
//on liste tout le monde
04
$liste = $table->fetchAll();
05
//on traite le résultat
06
foreach($liste as $unuser)
07
{
08
 print_r($unuser->login);
09
 print_r($unuser->passwd);
10
}

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.

Articles Connexes

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.

7 Commentaires Donnez votre avis

  • 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 ?

Laisser un commentaire

Mots-Clefs