Development

sfBookFRmodel (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of sfBookFRmodel

Show
Ignore:
Author:
godai (IP: 64.236.227.6)
Timestamp:
07/26/06 16:12:37 (11 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • sfBookFRmodel

    v0 v1  
     1= Le Modèle dans symfony = 
     2 
     3== Présentation == 
     4 
     5Symfony utilise une couche d'abstraction objet/relationnel basée sur Propel pour se connecter aux bases de données. Propel permet d'accéder et de modifier facilement les données stockées dans une base en utilisant des objets à la place de requêtes SQL. Ce chapitre détaille l'utilisation du modèle de données objet dans Propel et illustre son utilisation au sein de symfony. 
     6 
     7== Pourquoi utiliser une couche d'abstraction ? == 
     8 
     9Les bases de données sont relationnelles, alors que PHP5 est orienté objet. Afin d'accéder à une base de données de manière objet, il nous faut utiliser une interface qui convertie la logique objet en logique relationnelle. Cette méthode se nomme ORM (Object Relationnal Mapping ou mappage objet relationnel). 
     10 
     11C'est la couche '''Modèle''' de l'architecture MVC utilisée dans symfony. Elle est constituée d'objets qui donnent accès aux données, tout en conservant la logique métier. 
     12 
     13Un des bénéfices d'une telle implémentation, c'est que vous n'avez pas à utiliser une syntaxe propre à une base de données particulière. Elle traduit automatique les appels du modèle objet en requêtes SQL, optimisées pour la base utilisée. 
     14 
     15Il est ainsi facile de changer de système de données en cours de projet. Imaginé que vous ayez à écrire rapidement un prototype pour une application, mais que client ne s'est pas encore décidé sur le système de base de données. Vous pouvez commencé le projet avec SQLite et migrer vers MySQL, PostgreSQL ou Oracle quand le client le souhaite. Il vous suffit juste pour cela de changer une ligne dans un fichier de configuration. 
     16 
     17Une couche d'abstraction gère l'extraction des données. Le reste de l'application ne nécessite pas de requêtes SQL et le SQL qui accède à la base est facile à trouver. Les développeurs spécialisés dans la programmation de base de données s'y retrouvent également. 
     18 
     19Le fait d'utiliser les objets à la place d'enregistrements et les classes à la place de tables a un autre avantage : ils permettent d'ajouter des accesseurs à vos tables. Si par exemple vous avez une table 'Client' avec deux champs 'Prénom' et 'Nom', vous pourriez être intéressé pour récupérer uniquement le nom complet. Dans un environnement orienté objet, il suffit d'ajouter un nouvelle méthode accesseur à la classe `Client`. 
     20 
     21{{{ 
     22 public function getName() 
     23 { 
     24   return $this->getFirstName.' '.$this->getLastName(); 
     25 } 
     26}}} 
     27 
     28Les fonctions qui font des appels répétitifs aux données et la logique métier des données elles-mêmes peuvent être conservés dans de tels objets. Prenons l'exemple d'une classe 'ShoppingCart' dans laquelle vous conservez les produits sous forme d'objets. Pour connaître le montant total du panier lorsque la commande est terminée, vous pouvez utiliser la méthode suivante : 
     29 
     30{{{ 
     31 public function getTotal() 
     32 { 
     33   $total = 0; 
     34   foreach ($this->getItems() as $item) 
     35   { 
     36     $total += $item->getPrice(); 
     37   } 
     38   return $total; 
     39 } 
     40}}} 
     41 
     42C'est tout. Faire la même chose avec une requête SQL aurait été plus long. 
     43 
     44[http://propel.phpdb.org/trac/ Propel] est actuellement l'une des meilleures couches d'abstraction pour PHP5. Plutôt que de la réécrire, symfony l'intègre de façon homogène au sein du framework. 
     45 
     46== Le modèle de données == 
     47 
     48=== But === 
     49 
     50Pour créer le modèle de données objet que symfony va utiliser, vous devez commencer par convertir le modèle relationnel de votre base de données. C'est le fichier `schema.yml` qui s'en charge. On y trouve les tables, leurs relations et le type de chaque colonne. 
     51 
     52Le fichier `schema.yml` doit se trouver dans le répertoire `myproject/config/`. Il utilise la syntaxe [wiki:sfBookFRconfiguration YAML], mais vous n'aurez aucun problème à le lire sans connaître ce format. Format qui, soit dit en passant, ne nécessite pas plus de 5 minutes d'apprentissage. 
     53 
     54=== Exemple === 
     55 
     56Imaginons que vous ayez une base de données 'blog' avec deux tables 'blog''article' et 'blog''comment' qui contiennent des données. La structure est la suivante : 
     57 
     58|| blog_article || blog_comment || 
     59|| id || id || 
     60|| title || article_id || 
     61|| content || author || 
     62|| created_at || content || 
     63|| created_at || 
     64 
     65Le fichier `schema.yml` correspondant ressemblerait à ça : 
     66 
     67{{{ 
     68propel: 
     69   blog_article: 
     70     _attributes: { phpName: Article } 
     71     id: 
     72     title:       varchar(255) 
     73     content:     longvarchar 
     74     created_at: 
     75   blog_comment: 
     76     _attributes: { phpName: Comment } 
     77     id: 
     78     article_id: 
     79     author:      varchar(255) 
     80     content:     longvarchar 
     81     created_at: 
     82}}} 
     83 
     84Vous pouvez remarquer que le nom de la base de données (`blog`) n'apparaît pas directement dans le fichier `schema.yml`. A la place, la base est référencée sous un nom de connexion (`propel` dans notre exemple). Ceci parce que les paramètres de connexion actuels peuvent dépendre de l'environnement dans lequel votre application fonctionne. Par exemple, lorsque vous êtes en phase de développement, vous accédez à la base de développement, mais avec le même schéma que la base de données de production qui, elle, n'utilise pas les mêmes paramètres de connexion. Ces informations seront spécifiées dans le fichier `databases.yml` (voir ci-dessous). 
     85 
     86=== La syntaxe de base pour le schéma === 
     87 
     88Dans un fichier `schema.yml`, la première clé correspond à une connexion. Plusieurs tables peuvent y figurer, chacune ayant un ensemble de colonnes. La syntaxe YAML définie que les clés se terminent par un point-virgule et que la structure est indiquée à l'aide d'une indentation (un ou plusieurs espaces, mais '''jamais''' de tabulations). 
     89 
     90Une table peut avoir des attributs spéciaux, y compris l'attribut `phpName` qui indique le nom de la classe qui sera générée. Si cet attribut n'est pas mentionné, le nom de la table sera utilisé à la place, sans les tirets bas et avec une lettre majuscule pour chacun des mots, excepté pour le premier. Dans notre exemple, le nom `phpName` par défaut serait `blogArticle` et `blogComment`). Les colonnes ont elles aussi un nom `phpName`, qui est la version majuscule du `nom` (`ID`, `TITLE`, `CONTENT`, etc.) et qui, dans la majorité des cas, n'a pas a être réécrit. 
     91 
     92La plupart du temps, les colonnes n'ont besoin que d'un seul attribut : le type. Il peut avoir pour valeur `boolean`, `integer`, `float`, `date`, `varchar(taille)`, `longvarchar`, etc. Pour les champs texte de plus de 256 caractères, vous devez utiliser le type `longvarchar` qui n'a pas de taille. Selon les spécifications de MySQL, celui-ci ne peut excéder 65 Ko. Les types `date` et `timestamp` ont les mêmes limitations que les dates Unix et ne peuvent donc pas être définies avec une date antérieure au 1er janvier 1970 (1970-01-01). Pour définir des dates plus anciennes (par exemple des dates de naissance), un format de date 'before Unix' existe et s'utilise avec `bu_date` et `bu_timestamp`. 
     93 
     94Bien sûr, si vous souhaitez spécifier vos propres clés primaires, clés étrangères, valeurs par défaut ou index, vous pouvez utiliser la syntaxe de schéma étendue (voir ci-dessous). 
     95 
     96=== Conventions utilisées pour les schémas === 
     97 
     98Les colonnes qui se nomment `id` sont considérées comme étant des clés primaires. 
     99 
     100Les colonnes se terminant par `_id` sont considérées comme étant des clés étrangères. La table qui s'y rattache est déterminée automatiquement, à partir de la première partie du nom de la colonne. 
     101 
     102Les colonnes qui se nomment `created_at` sont automatiquement défini comme étant de type `timestamp`. 
     103 
     104Pour toutes ces colonnes vous n'avez pas à spécifier leur type. Symfony déduit leur format à partir de leur nom. C'est pour cette raison que le fichier `schema.yml` est si facile à écrire. 
     105 
     106== Les fichiers du modèle de données objet == 
     107 
     108=== Génération du code === 
     109 
     110Maintenant que nous avons le schéma de données, les classes du modèle peuvent être générées. Dans le répertoire du projet, saisissez la commande suivante : 
     111 
     112{{{ 
     113$ symfony propel-build-model 
     114}}} 
     115 
     116'''Note''': Propel utilise [http://phing.info/trac/ Phing]. Vous aurez donc besoin de l'installer si cela n'a pas déjà été fait. Pour cela, tapez : 
     117 
     118{{{ 
     119$ pear install http://phing.info/pear/phing-current.tgz 
     120}}} 
     121 
     122Les classes de base d'accès aux données seront automatiquement créées dans le répertoire `myproject/lib/model/om/` : 
     123 
     124{{{ 
     125BaseArticle.php  
     126 BaseArticlePeer.php  
     127 BaseComment.php 
     128 BaseCommentPeer.php 
     129}}} 
     130 
     131En outre, les classes réelles d'accès aux données seront créées dans `myproject/lib/model` : 
     132 
     133{{{ 
     134Article.php  
     135 ArticlePeer.php  
     136 Comment.php 
     137 CommentPeer.php 
     138}}} 
     139 
     140Vous n'avez défini que deux tables et vous vous retrouver avec huit fichiers. Pourquoi donc ?? 
     141 
     142=== Le modèle de base et le modèle courant === 
     143 
     144Pourquoi avoir deux version du modèle de données objet, l'un dans `model/om/` et l'autre dans `model/` ? 
     145 
     146Vous aurez certainement besoin d'ajouter vos propres méthodes et attributs aux objets du modèle (pensez à la méthode `->getName()` qui permet de joindre les champs Prénom et Nom). De même, au fil du développement de votre projet, vous aurez probablement à ajouter des tables et des colonnes. A chaque fois que vous modifiez le fichier `schema.yml`, vous devrez génerer à nouveau les classes du modèle objet, à l'aide de la commande `symfony propel-build-model`. Si vos méthodes ont été rajoutées à celles précédemment générées, elles seront alors effacées à chaque fois. 
     147 
     148Les classes `Base` qui se trouvent dans le répertoire `model/om/` sont celles générées par Propel. Vous ne devriez '''jamais''' les modifier, car à chaque fois que vous générez le modèle, ces fichiers seront effacés. Par contre, les classes qui se trouvent dans le répertoire `model/`, qui héritent des classes précédentes, ne sont pas réécrites. C'est donc là que vous devez ajouter vos propres méthodes. 
     149 
     150Par exemple, voici le contenu du fichier `model/Article.php` nouvellement créé : 
     151 
     152{{{ 
     153 require_once 'model/om/BaseArticle.php'; 
     154 class Article extends BaseArticle 
     155 { 
     156 } 
     157}}} 
     158 
     159Cette classe hérite de toutes les méthodes de la classe `BaseArticle`, mais elle ne sera pas affectée si vous touchez au modèle. Cette structure est à la fois personnalisable et évolutive. 
     160 
     161Ce mécanisme permet de commencer à coder sans pour autant connaître le modèle relationnel final de votre base de données. 
     162 
     163=== Les classes Peer === 
     164 
     165Les classes `Article` et `Comment` vont vous permettrent d'accéder aux attributs d'un enregistrement. Vous pouvez ainsi connaître le titre d'un article en appelant la méthode appropriée d'un objet `Article` : 
     166 
     167{{{ 
     168 $article = new Article(); 
     169 ... 
     170 $title = $article->getTitle(); 
     171}}} 
     172 
     173Mais vous aurez également besoin de trouver des enregistrements. C'est là qu'interviennent les classes `Peer` qui offrent des méthodes pour cela. Celles-ci retournent un objet ou une liste d'objets correspondant à la classe sans la mention `Peer`. 
     174 
     175{{{ 
     176 $articles = ArticlePeer::retrieveByPks(array(123, 124, 125)); 
     177 // $articles est un tableau d'objets de la classe Article 
     178}}} 
     179 
     180D'un point de vue du modèle de données, vous noterez qu'il ne peut pas y avoir d'objet `Peer`. C'est pourquoi les méthodes se trouvant dans les classes `Peer` seront appelées avec la syntaxe `::` (pour un appel de méthode de classe) plutôt que l'habituel `->` (pour un appel de méthode d'objet). 
     181 
     182== Accès aux données == 
     183 
     184Avec Propel, vos données sont accessibles à travers des objets. Si vous êtes rodé au modèle relationnel et au langage SQL, les méthodes Propel vous sembleront sans doute compliquées. Mais une fois que vous aurez goûté à la puissance du mappage objet, vous ne pourrez plus vous en passer. Jetez donc un oeil aux [http://propel.phpdb.org/trac/wiki/Users/Documentation/BasicCRUD exemples] donnés dans la documentation de Propel. 
     185 
     186=== Accès aux champs === 
     187 
     188Propel fourni une classe pour chacune des tables définies dans le fichier `schema.yml`. Ces classes ont des créateurs, accesseurs et manipulateurs par défaut. Les méthodes `new`, `get` et `set` donnent accès aux colonnes de la table. 
     189 
     190{{{ 
     191 $article = new Article(); 
     192 $article->setTitle('Mon premier article'); 
     193 $article->setContent('C\'est mon tout premier article.\n J'espère que vous l\'appréciez !'); 
     194 $title   = $article->getTitle(); 
     195 $content = $article->getContent(); 
     196}}} 
     197 
     198Notez que l'attribut `phpName` de la `table` est utilisé pour le nom de la classe. Les accesseurs utilisent la notation [http://fr.wikipedia.org/wiki/CamelCase CamelCase] (la première lettre de chaque mot est en majuscule) en se basant sur le nom des colonnes et en supprimant les tirets bas. 
     199 
     200Pour créer un enregistrement dans une table liée à une autre, il vous suffit de passer l'objet comme clé étrangère : 
     201 
     202{{{ 
     203 $comment = new Comment(); 
     204 $comment->setAuthor('Steve'); 
     205 $comment->setContent('C'est le meilleur article que j'ai jamais lu !'); 
     206 $comment->setArticle($article); 
     207}}} 
     208 
     209La dernière ligne place automatiquement la valeur de l'attribut `Id` de l'objet `$article` à l'attribut `ArticleId` de l'objet `Comment`. Vous auriez également pu l'écrire manuellement : 
     210 
     211{{{ 
     212 $comment->setArticleId($article->getId()); 
     213}}} 
     214 
     215Notez qu'en appelant le constructeur `new`, vous avez créé un nouvel objet, mais pas un nouvel enregistrement dans la table `Article`. Pour enregistrer les données dans la base, vous devez appeler la méthode de votre objet : 
     216 
     217{{{ 
     218 $article->save(); 
     219}}} 
     220 
     221Grâce au principe en cascade de Propel, vous n'avez pas à enregistrer l'objet `$comment`. Étant donné que l'enregistrement est lié à celui que vous traitez, il sera sauvegardé automatiquement. 
     222 
     223'''Astuce''': Pour définir plusieurs champs d'un seul coup, vous pouvez utiliser les méthodes `->fromArray()` des objets Propel : 
     224 
     225{{{ 
     226$article->fromArray(array( 
     227    'title'   =>   'Mon premier article', 
     228    'content' =>   'C\'est mon tout premier article.\n J'espère que vous l\'appréciez !', 
     229 )); 
     230}}} 
     231 
     232=== Accès aux enregistrements liés === 
     233 
     234Vérifions tous les commentaires de notre nouvel article : 
     235 
     236{{{ 
     237 $comments = $article->getComments(); 
     238}}} 
     239 
     240Propel voit la colonne `article_id` de la table `Comment` et comprend qu'un article peut avoir plusieurs commentaires (relation un à plusieurs). Lors de la génération du code, une méthode `getComments()` est créée pour l'objet `Article`, afin de récupérer un tableau contenant les commentaires correspondants. Vous noterez ici l'utilisation du pluriel dans le nom de la méthode `get`. Ainsi, la variable `$comments` contient un tableau d'objets de la classe `Comment`. Vous pouvez afficher le premier élément de ce tableau en utilisant : 
     241 
     242{{{ 
     243 print_r($comments[0]); 
     244}}} 
     245 
     246Si vous lisez les commentaires laissés sur vos articles, vous pourriez changer d'avis concernant l'intérêt de les voir publier sur Internet. De même, si vous n'appréciez pas l'ironie de leurs auteurs, vous pouvez facilement les supprimer à l'aide de la méthode `delete()` : 
     247 
     248{{{ 
     249 foreach($comments as $comment) 
     250 { 
     251   $comment->delete(); 
     252 } 
     253}}} 
     254 
     255Pour les relations plusieurs à un, c'est encore plus simple : 
     256 
     257{{{ 
     258 echo $comment->getArticle()->getTitle(); 
     259}}} 
     260 
     261La méthode `getArticle()` renvoie un objet de la classe `Article` qui bénéficie de l'accesseur `getTitle()`. C'est bien mieux que de faire la jointure soi-même; ce qui prendrait quelques lignes de code (à partir de l'appel de la méthode `$comment->getArticleId()`). 
     262 
     263Vous en saurez plus sur ces méthodes et leur syntaxe dans la [http://propel.phpdb.org/docs/user_guide/chapters/ManipulatingObjects.html documentation de Propel]. 
     264 
     265=== Accès aux enregistrements === 
     266 
     267Si vous connaissez la valeur de la clé primaire d'un enregistrement, vous pouvez utiliser la méthode `retrieveByPk()` de la classe `Peer` pour obtenir l'objet correspondant. Par exemple : 
     268 
     269{{{ 
     270 $article = ArticlePeer::retrieveByPk(7); 
     271}}} 
     272 
     273Le fichier `schema.yml` défini le champ `id` comme étant la clé primaire de la table `Article`. La variable va ainsi retourner l'article qui correspond au champ `id` qui a la valeur 7. Comme nous utilisons la clé primaire, nous savons qu'un seul enregistrement sera retourné. La variable `$article` contient alors un objet de la classe `Article`. 
     274 
     275Afin de permettre l'abstraction des bases de données, Propel ne nécessite pas de SQL pour les requêtes simples. A la place, il utilise un objet `Criteria`, qui offre la même puissance. 
     276 
     277Par exemple, pour rapatrier tous les articles, il suffit d'écrire : 
     278 
     279{{{ 
     280 $c = new Criteria(); 
     281 $articles = ArticlePeer::doSelect($c); 
     282}}} 
     283 
     284La méthode `doSelect()` applique comme filtre ce qu'elle reçoit en paramètre. Dans cette exemple, l'objet `$c` est vide. L'appel à `doSelect()` renverra donc tous les enregistrements de la table `Article` (ce qui est équivaut en SQL à '`SELECT *`'). La variable `$articles` contiendra un tableau de tous les objets de la classe `Article`. Pour y accéder un par un, il vous suffit d'utiliser l'instruction `foreach` : 
     285 
     286{{{ 
     287 foreach ($articles as $article) 
     288 { 
     289   echo $article->getTitle(); 
     290 } 
     291}}} 
     292 
     293Mais on peut aller plus loin. L'objet `Criteria` vous permet d'ajouter des règles, comme vous le feriez en SQL avec les instructions '`WHERE`' ou '`ORDER BY`'. 
     294 
     295Pour récupérer tous les commentaires écrits par Steve, classés par date, utilisez : 
     296 
     297{{{ 
     298 $c = new Criteria(); 
     299 $c->add(CommentPeer::AUTHOR, 'Steve'); 
     300 $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT); 
     301 $comments = CommentPeer::doSelect($c); 
     302}}} 
     303 
     304Ou, à l'inverse, pour ne récupérer que les articles qui n'ont pas été écrits par Steve : 
     305 
     306{{{ 
     307 $c = new Criteria(); 
     308 $c->add(CommentPeer::AUTHOR, 'Steve', Criteria::NOT_EQUAL); 
     309 $c->addAscendingOrderByColumn(CommentPeer::DATE); 
     310 $comments = CommentPeer::doSelect($c); 
     311}}} 
     312 
     313Notez les constantes utilisées ici : il s'agit des noms `phpNames` des colonnes correspondant au modèle de données objet. La méthode `::doSelect()` génère la requête SQL la plus appropriée selon le type de votre base de données, l'exécute et retourne une liste d'objets; ce qui est bien mieux que de récupérer un jeu d'enregistrements. 
     314 
     315'''Note''': Pourquoi écrire `CommentPeer::AUTHOR` plutôt que `comment.AUTHOR`, que nous aurions utilisé avec la requête SQL ? Imaginez que vous devez remplacer dans la base de données le champ `auteur` par `contributeur`. Si vous utilisez `comment.AUTHOR`, vous devrez le modifier à chaque appel aux objets Propel. En utilisant `CommentPeer::AUTHOR`, il vous suffit juste de changer le nom de la colonne dans le fichier `schema.yml`, et de garder le nom `phpName` `AUTHOR` afin d'éviter de multiples modifications. 
     316 
     317Référez-vous à la [http://propel.phpdb.org/docs/user_guide/chapters/FindingObjects.html Documentation Propel] pour plus de détails concernant l'objet `Criteria` et ces méthodes. 
     318 
     319=== Les colonnes `created_at` et `updated_at` === 
     320 
     321Habituellement, lorsqu'une table possède une colonne `created_at`, celle-ci sert à stocker l'heure et la date à laquelle l'enregistrement a été créé. La même logique s'applique pour la colonne `updated_at`, qui est généralement modifiée, avec l'heure et la date courante, à chaque fois que l'enregistrement est mis à jour. 
     322 
     323La bonne nouvelle est que symfony sait reconnaître le nom de ces tables. Vous n'avez pas à définir manuellement les valeurs des colonnes `created_at` et `updated_at`; elles seront automatiquement mise à jour. 
     324 
     325{{{ 
     326 $comment = new Comment(); 
     327 $comment->setAuthor('Steve'); 
     328 $comment->save(); 
     329 // affiche la date de création 
     330 echo $comment->getCreatedAt(); 
     331}}} 
     332 
     333Ces colonnes accepte également un format date en argument : 
     334 
     335{{{ 
     336 echo $comment->getCreatedAt('Y-m-d'); 
     337}}} 
     338 
     339== Configuration de l'accès à la base de données == 
     340 
     341Le modèle de données est indépendant de la base de données utilisée, mais vous devrait impérativement en utiliser une. Les informations nécessaires à symfony pour gérer les requêtes d'une base sont : son nom, ses codes d'accès et son type. Ces données doivent être indiquées dans le fichier `databases.yml` qui se trouve dans le répertoire `myproject/config/` : 
     342 
     343{{{ 
     344prod: 
     345   propel: 
     346     param: 
     347       host:               mydataserver 
     348       username:           myusername 
     349       password:           xxxxxxxxxx   
     350 all: 
     351   propel: 
     352     class:                sfPropelDatabase 
     353     param: 
     354       phptype:            mysql 
     355       host:               localhost 
     356       database:           blog 
     357       username:           root 
     358       password:            
     359       compat_assoc_lower: true 
     360       # datasource:       propel 
     361}}} 
     362 
     363L'accès à la base est lié à l'environnement dans lequel vous travaillez. Vous pouvez définir des paramètres différents pour les environnements `prod`, `dev`, `test` ou n'importe quel autre environnement que vous avez défini. L'entête `all` défini les paramètres pour tous les environnements. Cette configuration peut également être modifiée pour chaque application. On indique pour cela des valeurs différentes, dans un fichier spécifique à une application, par exemple dans `myproject/apps/myapp/config/databases.yml`. 
     364 
     365'''Note:''' Le paramètre `host` peut également être défini sous le nom `hostspec`. 
     366 
     367Vous pouvez définir plusieurs connexions pour chaque environnement. Chacune de ces connexions est liées au fichier `schema.yml` par le paramètre `datasource`. Il réfère à la première clé de ce fichier. Si vous ne spécifiez pas ce paramètre, il prend la valeur du label indiqué dans les paramètres de connexion. 
     368 
     369Les valeurs autorisées pour le paramètre `phptype` correspondent aux systèmes de bases de données que supporte Propel : 
     370 
     371  * `mysql` 
     372  * `sqlserver` 
     373  * `pgsql` 
     374  * `sqlite` 
     375  * `oracle` 
     376 
     377Les valeurs `host`, `database`, `username` et `password` définissent les paramètres nécessaires pour se connecter à la base de données. 
     378 
     379Dans l'exemple ci-dessous, les valeurs pour tous les environnements (`all`) peuvent également être présentées avec la syntaxe suivante, plus compacte : 
     380 
     381{{{ 
     382all: 
     383   propel: 
     384     class:          sfPropelDatabase 
     385     param: 
     386       dsn:          mysql://root:@localhost/blog 
     387}}} 
     388 
     389'''Note''': Si vous utilisez une base de données SQLite, le paramètre `host` doit être défini avec le chemin d'accès au fichier de la base. Par exemple, si vous stocker la base de données de votre blog dans `myproject/data/blog.db`, le fichier `databases.yml` sera : 
     390 
     391> 
     392 
     393{{{ 
     394all: 
     395   propel: 
     396     class:          sfPropelDatabase 
     397     param: 
     398       dsn:          sqlite://./../data/blog.db 
     399}}} 
     400 
     401== Syntaxe de schéma étendu == 
     402 
     403=== Attributs === 
     404 
     405La base de données et les tables peuvent avoir des attributs différents. Ils sont définis avec la clé `_attribute:`. 
     406 
     407Si vous souhaitez que votre schéma soit validé avant de générer le code, il suffit de désactiver l'attribut `noXSD`. 
     408 
     409{{{ 
     410propel: 
     411   _attributes:   { noXsd: false } 
     412}}} 
     413 
     414L'élément principal supporte l'attribut `defaultIdMethod`. Dans le cas où celui-ci n'est pas défini, la méthode native de génération des champs IDs sera utilisée -- ex. auto incrémentation pour MySQL, séquences pour PostgreSQL. L'autre valeur possible est `none` : 
     415 
     416{{{ 
     417propel: 
     418   _attributes:   { defaultIdMethod: none } 
     419}}} 
     420 
     421Nous avons déjà vu l'attribut de table `phpName`, utilisé pour définir le nom des classes générées convertissant la table : 
     422 
     423{{{ 
     424propel: 
     425   blog_article: 
     426     _attributes: { phpName: Article } 
     427}}} 
     428 
     429Les tables ayant un contenu localisé (c'est-à-dire plusieurs versions d'un contenu pour l'internationalisation) se voient adjoindre deux attributs supplémentaires (voir le [wiki:sfBookFRi18n chapitre internationalisation] pour les détails) : 
     430 
     431{{{ 
     432propel: 
     433   blog_article: 
     434     _attributes: { isI18N: true, i18nTable: db_group_i18n } 
     435}}} 
     436 
     437=== Détails des colonnes === 
     438 
     439La syntaxe de base vous laisse deux possibilités : laisser à symfony le soin de déduire les caractéristiques des colonnes à partir de leur nom (en n'indiquant pas de valeur) ou définir le type avec l'un des mots-clés autorisés : 
     440 
     441{{{ 
     442propel: 
     443   blog_article: 
     444     id:                 # laissons symfony faire le travail 
     445     title: varchar(50)  # spécifions le type nous-même 
     446}}} 
     447 
     448Mais vous pouvez définir bien plus pour une colonne, et dans ce cas, vous aurez à définir les valeurs de la colonne sous forme d'un tableau associatif : 
     449 
     450{{{ 
     451propel: 
     452   blog_article: 
     453     id:       { type: integer, required: true, primaryKey: true, autoincrement: true } 
     454     name:     { type: varchar , size: 50, defaultValue: foobar, index: true } 
     455     group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade } 
     456}}} 
     457 
     458Les paramètres de colonnes sont : 
     459 
     460  * `type`: Type de colonne Propel. Voir [http://propel.phpdb.org/docs/user_guide/ Liste de types de Propel] pour plus de détails. 
     461  * `size`: Défini la longueur d'une chaîne pour les colonnes de type `VARCHAR`. Notez qu'une colonne définie comme `varchar(50)` dans la syntaxe de base correspond à `{ type: varchar , size: 50 }` dans la syntaxe étendue. 
     462  * `required`: `false` par défaut, définir à `true` si vous souhaitez que la colonne soit requise 
     463  * `defaultValue` 
     464  * `primaryKey`: booléen, définir à `true` pour les clés primaires 
     465  * `autoincrement`: booléen, définir à `true` pour les colonnes de type `integer` qui nécessitent d'être auto incrémentées 
     466  * `index`: booléen, définir à `true` si vous souhaitez créer un index sur cette colonne 
     467  * `foreignTable`: pour créer une clé étrangère avec une autre table 
     468  * `foreignReference`: le nom de la colonne liée si une clé étrangère est définie via `foreignTable` 
     469  * `onDelete`: dans le cas d'une clé étrangère, avec la valeur `cascade`, les données de cette table seront effacées lorsqu'un enregistrement lié de la table étrangère sera supprimée. 
     470  * `isCulture`: définir à `true` pour les colonnes culture dans les tables de contenus localisés (voir le [wiki:sfBookFRi18n chapitre internationalisation]) 
     471 
     472=== Clés étrangères === 
     473 
     474Une alternative à l'utilisation des attributs de colonne `foreignTable` et `foreignReference` pour une table consiste à ajouter manuellement une ou plusieurs clés étrangères dans la section `_foreign_keys` : 
     475 
     476{{{ 
     477propel: 
     478   blog_article: 
     479     id:                 
     480     title:   varchar(50)  
     481     user_id: { type: integer } 
     482     _foreign_keys: 
     483       blog_user: 
     484         _attributes: { onDelete: cascade } 
     485         references: 
     486           - { local: user_id, foreign: id } 
     487}}} 
     488 
     489Cela créera une clé étrangère pour la colonne `user_id`, correspondant à la colonne `id` de la table `blog_user`. 
     490 
     491Comparer à l'utilisation des attributs de colonne, cela n'a d'intérêt que pour des clés étrangères avec de multiple références : 
     492 
     493{{{ 
     494    _foreign_keys: 
     495       db_user: 
     496         _attributes: { onDelete: cascade } 
     497         references: 
     498           - { local: user_id, foreign: id } 
     499           - { local: post_id, foreign: id } 
     500}}} 
     501 
     502=== Index === 
     503 
     504Pour une table, comme alternative à l'attribut de colonne `index`, vous pouvez ajouter manuellement un ou plusieurs index dans la section `_indexes`: 
     505 
     506{{{ 
     507propel: 
     508   blog_article: 
     509     id:                 
     510     title:            varchar(50)  
     511     created_at: 
     512     _indexes: 
     513       my_index:       [title, user_id] 
     514       my_other_index: [created_at] 
     515}}} 
     516 
     517De même que la syntaxe pour les clés étrangères, cela ne se révèle utile que pour les index construits sur plus d'une colonne. 
     518 
     519=== Automatismes liés aux colonnes === 
     520 
     521Lorsque symfony rencontre une colonne sans valeur, il ajoute ces propres informations, comme par magie : 
     522 
     523{{{ 
     524id:         { type: integer, required: true, primaryKey: true, autoincrement: true } 
     525 foobar_id:  { type: integer, foreignTable: db_foobar, foreignReference: id } 
     526 created_at: { type: timestamp } 
     527 updated_at: { type: timestamp } 
     528}}} 
     529 
     530Pour les clés étrangères, symfony va chercher une table ayant le même nom `phpName` que le début du nom de la colonne. S'il en trouve une, il prendre le nom de la table comme valeur pour l'attribut `foreignTable`. 
     531 
     532=== Automatismes liés aux tables (I18n) === 
     533 
     534Symfony supporte la [i18n.txt localisation de contenu] dans les tables liées. Cela signifie que lorsque vous avez du contenu sujet à localisation, celui est stocké dans deux tables séparées : l'une avec des colonnes invariables, et une seconde avec des colonnes propres à la localisation. 
     535 
     536Dans un fichier `schema.yml`, cela est pris en compte lorsque vous nommez une table `foobar_i18n`, comme suit : 
     537 
     538{{{ 
     539propel: 
     540   db_group: 
     541     id:          - 
     542     created_at:  - 
     543   db_group_i18n:  
     544     name:        varchar(50) 
     545}}} 
     546 
     547Symfony va automatiquement ajouter les attributs de colonnes et de table pour permettre au mécanisme qui gère le contenu localisé de fonctionner. Par conséquent, le schéma précédent est équivalent à : 
     548 
     549{{{ 
     550propel: 
     551   db_group: 
     552     _attributes: { isI18N: true, i18nTable: db_group_i18n } 
     553     id:          - 
     554     created_at:  - 
     555   db_group_i18n:  
     556     id:          { type: integer, required: true, primaryKey: true, foreignTable: db_group, foreignReference: id } 
     557     culture:     { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true } 
     558     name:        varchar(50) 
     559}}} 
     560 
     561=== Au delà de `schema.yml`: Le fichier `schema.xml` === 
     562 
     563Le format `schema.yml` est interne à symfony. Lorsque vous exécutez une commande `propel-`, symfony génère en réalité un fichier `generated-schema.xml`, qui correspond au type de fichier attendu par Propel pour exécuter des tâches sur le modèle. 
     564 
     565Le fichier `schema.xml` contient exactement les même informations que son équivalent YAML. Voici ce que donne au format XML le schéma Article/Comment défini précédemment : 
     566 
     567{{{ 
     568[xml] 
     569 <?xml version="1.0" encoding="UTF-8"?> 
     570  <database name="propel" defaultIdMethod="native" noxsd="true"> 
     571     <table name="blog_article" phpName="Article"> 
     572       <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" /> 
     573       <column name="title" type="varchar" size="255" /> 
     574       <column name="content" type="longvarchar" /> 
     575       <column name="created_at" type="timestamp" /> 
     576     </table> 
     577     <table name="blog_comment" phpName="Comment"> 
     578       <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" /> 
     579       <column name="article_id" type="integer" /> 
     580       <foreign-key foreignTable="blog_article"> 
     581         <reference local="article_id" foreign="id"/> 
     582       </foreign-key> 
     583       <column name="author" type="varchar" size="255" /> 
     584       <column name="content" type="longvarchar" /> 
     585       <column name="created_at" type="timestamp" /> 
     586     </table>     
     587  </database> 
     588}}} 
     589 
     590La description du format `schema.xml` se trouve dans les sections [http://propel.phpdb.org/docs/user_guide/ documentation] et [http://propel.phpdb.org/docs/user_guide/chapters/GettingStarted.html bien démarrer] du site du projet Propel. 
     591 
     592Le format YAML a été conçu pour que les schémas demeurent facilent à lire et à écrire. Mais de fait, les schémas plus complexes ne peuvent pas être décrits dans un fichier `schema.yml`. Le format XML permet, par contre, une description complexe du schéma, quelque soit la complexité de celui-ci. Il intègre également les paramètres spécifiques aux différents types de données, la transmission des tables, etc. 
     593 
     594Symfony peut lire les schémas écrits au format XML. Si votre schéma est trop complexe pour la syntaxe YAML, si vous avez déjà un schéma au format XML, ou si vous connaissez déjà Propel et que vous ne souhaitez pas réécrire votre schéma en YAML, il vous suffit de mettre votre fichier `schema.xml` dans le répertoire `config/` de votre projet, puis de construire le modèle. 
     595 
     596== Ne créez pas deux fois le modèle == 
     597 
     598=== Construire une structure SQL de base de données basé sur un schéma existant === 
     599 
     600Si vous commencez votre application en écrivant le fichier `schema.yml`, vous n'aurez pas envie de le faire une seconde fois en SQL pour créer les tables dans la base de données. Symfony peut générer une requête SQL qui va directement créer les tables à partir du modèle de données défini dans le fichier YAML. Pour cela, placez-vous à la racine du répertoire de votre projet et tapez : 
     601 
     602{{{ 
     603$ symfony propel-build-sql 
     604}}} 
     605 
     606You can connexion that you want to use in the `propel.ini`. 
     607 
     608Un fichier `schema.sql` sera créé dans le répertoire `myproject/data/sql/`. Le code SQL généré sera optimisé pour le système de base de données défini par le paramètre `phptype` du fichier `databases.yml`. Vous pouvez également précisez la connexion que vous souhaitez utiliser dans le fichier `propel.ini`. 
     609 
     610Vous pouvez utiliser directement le fichier `schema.sql` pour construire les tables. Par exemple avec MySQL : 
     611 
     612{{{ 
     613$ mysqladmin -u root -p create blog 
     614$ mysql -u root -p blog < data/sql/schema.sql 
     615}}} 
     616 
     617Cette commande est également utile pour reconstruire la base dans un autre environnement, ou pour changer de système de base de données. 
     618 
     619'''Note''': Si les paramètres de connexion sont correctement définis dans votre fichier `propel.ini`, vous pouvez utiliser la commande `symfony propel-insert-sql` pour effectuer cette tâche automatiquement. 
     620 
     621=== Générer un modèle de données XML à partir d'une base de données existante === 
     622 
     623Symfony peut utiliser la couche d'accès aux bases de données de Creole pour générer un fichier `schema.xml` à partir d'une base existante. Cela peut se révéler particulièrement utile lorsque vous faites de la rétro ingénierie (reverse engineering), ou si vous préférez travailler sur la base de données avant de vous attaquez au modèle objet. 
     624 
     625Vous devez pour cela vous assurez que le fichier `propel.ini` de votre projet pointe sur la bonne base de données et contient tous les paramètres de connexion. 
     626 
     627Vous pouvez ensuite appeler la commande `propel-build-schema` : 
     628 
     629{{{ 
     630$ symfony propel-build-schema 
     631}}} 
     632 
     633Un nouveau fichier `schema.xml` sera généré dans votre répertoire `myproject/config/`, basé sur la structure de votre base de données. Vous pouvez construire votre modèle à partir de cette structure. 
     634 
     635La commande qui génère les schémas est assez puissante. Elle peut ajouter à votre schéma de nombreuses informations propres à la base de données. Parce que le format YAML ne gère pas les informations relatives aux différents types de bases de données du marché, vous devez générer un schéma XML pour bénéficier de ces informations. Pour cela, il suffit d'ajouter un argument à la commande `build-schema` : 
     636 
     637{{{ 
     638$ symfony propel-build-schema xml 
     639}}} 
     640 
     641Plutôt que de générer un fichier `schema.yml`, cette commande va créer un fichier `schema.xml`, compatible avec Propel, qui contiendra les informations relatives au type de la base. Soyez conscient que les schémas XML générés de la sorte tendent à être assez chargés et difficile à lire. 
     642 
     643== Bases de données multiples == 
     644 
     645Vous pouvez définir plusieurs bases de données pour un même projet et répartir vos données sur plusieurs bases. Il faut pour cela écrire plusieurs fichiers `schema.yml` en indiquant un préfixe spécifique à chaque nom, tout en conservent l'appellation `schema.yml` à la fin, afin qu'ils soient reconnus. 
     646 
     647Par exemple, si vous souhaitez séparer les connexions pour les parties `blog` et `dépôt d'images` de votre site, créez deux fichiers dans votre répertoire `config/`: 
     648 
     649{{{ 
     650//dans le fichier blog_schema.yml 
     651 blog: 
     652   table1: 
     653     column1: ... 
     654     column2: ... 
     655 //dans le fichier image_schema.yml 
     656 imagerep: 
     657   table1: 
     658     column1: ... 
     659     column2: ... 
     660}}} 
     661 
     662== Refactoriser la couche de données == 
     663 
     664Lors du développement d'un projet symfony, vous démarrez souvent en écrivant le code du domaine logique dans les actions. Plus les projets deviennent complexes, plus les actions contiennent de code PHP et SQL. Ils deviennent alors moins lisible. 
     665 
     666La logique liée aux données doit alors être déplacée vers la couche Modèle. Toutes les fois où vous avez à faire la même requête à plus d'un endroit au sein de vos actions, pensez à transférer le code correspondant au Modèle. Cela aide à garder les actions compactes et lisibles. 
     667 
     668Imaginez, par exemple, le code nécessaire dans un weblog pour récupérer les 10 articles les plus populaires pour un tag donné (passé en paramètre de requête). Ce code ne devrait pas se trouver dans une action, mais dans le modèle. Si vous devez afficher cette liste dans un template, l'action devrait ressembler à ceci : 
     669 
     670{{{ 
     671 public function executeShowPopularArticlesForTag() 
     672 { 
     673   $tag = TagPeer::retrieveByName($this->getRequestParameter('tag')); 
     674   $this->articles = $tag->getPopularArticles(10); 
     675 } 
     676}}} 
     677 
     678L'action crée un objet de la classe `Tag` à l'aide du paramètre de la requête. Le code nécessaire pour faire une requête à la base est situé dans la méthode `->getPopularArticles()` de cette classe. Mettre le code de cette méthode dans l'action rendrait le code moins lisible. 
     679 
     680Déplacer le code dans un endroit plus approprié est l'une des techniques de refactorisation. Si vous le faites souvent, votre code sera plus facile à maintenir et à comprendre des autres développeurs. Une bonne règle de conduite pour savoir quand effectuer une refactorisation vers la couche de données, c'est lorsque le code d'une action compte plus de dix lignes de code PHP. 
     681 
     682== Propel dans symfony == 
     683 
     684Tous les détails donnés ci-dessus ne sont pas spécifique à symfony. Vous n'êtes pas obligé d'utiliser Propel comme couche d'abstraction objet/relationnelle, mais symfony fonctionne de façon plus homogène avec Propel du fait que : 
     685 
     686  * Toutes les classes du modèle de données objet et les classes `Criteria` sont des classes chargées automatiquement. Dès que vous les utiliserez, symfony inclura les bons fichiers, vous n'aurez pas à gérer vous même à la main les inclusions de fichiers. 
     687  * Dans symfony, Propel n'a pas besoin d'être lancé ou initialisé. Lorsqu'un objet utilise Propel, la bibliothèque l'initialise d'elle-même. 
     688  * Les résultats d'une requête peuvent être facilement paginées (pour plus de détails reportez-vous au [wiki:sfBookFRpager chapitre pager]). 
     689  * Les objets Propel permettent le prototypage rapide et la génération d'un backend pour votre application (pour plus de détails reportez vous au [wiki:sfBookFRscaffolding chapitre scaffolding]). 
     690  * Le fichier `schema.xml` est plus rapide à écrire via le fichier `schema.yml`. 
     691 
     692Enfin, parce que Propel est indépendant de la base de données utilisée, symfony l'est aussi.