Manuel d'apprentissage
⁂
Modèle de données
Une part importante d'une application est la manipulation des
données, sa facilité, son uniformité et sa pérennité. Dans ce
chapitre, nous allons présenter la bibliothèque Hoa\Database
ainsi que la bibliothèque Hoa\Model.
Table des matières
Base de données
Les bases de données sont des outils permettant de stocker et d'extraire des données de différentes formes et de différentes tailles. Nous parlons plutôt de gestionnaires de bases de données. Nous pouvons distinguer deux familles de bases de données : relationnelles et objets. Actuellement, les usages les plus courants se tournent vers des bases de données relationnelles qui utilisent la théorie des ensembles pour stocker et extraire leurs données.
Le langage SQL (pour Structured Query Language) permet de communiquer avec ces bases de données. Plusieurs standards ont été dictés, comme les normes SQL-92 (ou SQL2) et SQL-99 (ou SQL3) pour ne citer que les plus connues et répandues. Le langage SQL est partagé en trois objectifs :
- DDL (pour Data Definition Language) permet d'ajouter, de
modifier ou de supprimer des définitions de tables (
CREATE TABLE,ALTER TABLE,DROP TABLE…) ; - DML (pour Data Manipulation Language) permet de sélectionner,
d'insérer, de mettre à jour ou de supprimer des données d'une table
(
INSERT,DELETE,UPDATE,SELECT…) ; - DCL (pour Data Control Language) permet de gérer les droits
d'accès aux données (
GRANT,DENY,REVOKE…).
La structure des données est appelée un schéma.
Dans une application, pour communiquer avec un gestionnaire de bases de données, nous devrons nous y connecter, puis y exécuter des requêtes (à l'aide du langage SQL). La base de données nous calculera un résultat qu'elle nous renverra et que nous pourrons exploiter. Le maximum de calculs doit être effectué par la base de données qui est optimisée pour ce type de traitement ! Nous verrons par la suite comme faciliter l'exploitation de ces données qui peut devenir un processus complexe si le schéma des données évolue ou n'est pas trivial.
Abstraction des diverses bases de données
Chaque gestionnaire de bases de données fournit un pilote pour pouvoir communiquer avec sa base de données. Comme aucun standard n'existe pour ces pilotes, ils sont tous différents les uns des autres. Cela peut se comprendre car ils n'apportent pas tous le même degré d'optimisation selon les usages. Cependant, nous aimerions avoir une utilisation uniforme de tous ces pilotes. C'est pourquoi il existe des couches d'abstraction de bases de données, ou DBAL (pour Database Abstract Layer). PHP en propose une : PDO (pour PHP Data Objects), mais il en existe d'autres, comme par exemple : ODBC (pour Open Database Connectivity).
Hoa définit une couche d'abstraction par dessus ces couches d'abstraction,
grâce aux classes Hoa\Database\Dal et
Hoa\Database\DalStatement. Cette couche d'abstraction permet
simplement de choisir quelle couche nous voulons utiliser (ou alors plonger
directement sur un pilote sans passer par une couche d'abstraction).
Dans un premier temps, nous conseillons d'utiliser PDO car c'est une couche native à PHP qui ne nécessite aucune installation particulière. Dans la suite de ce chapitre, nous allons utiliser PDO avec le gestionnaire de bases de données SQLite, lui aussi intégré à PHP (ainsi qu'à de nombreux outils). Ce dernier est très léger et travaille sur des fichiers contenant des petites base de données, ce qui le rend facilement portable.
Pour illuster les premiers exemples, nous allons créer une petite base de
données et la pré-remplir de quelques données. Dans un fichier, n'importe où
(par exemple /tmp/Foo.sql), nous écrivons nos requêtes SQL :
CREATE TABLE user (
id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
firstname VARCHAR(32),
lastname VARCHAR(32),
birthday INTEGER
);
INSERT INTO user VALUES ( NULL, 'Gordon', 'Freeman', 1973 );
INSERT INTO user VALUES ( NULL, 'Alyx', 'Vance', 1995 );
INSERT INTO user VALUES ( NULL, 'Cave', 'Johnson', 1947 );
Puis nous allons créer notre base de données :
$ sqlite3 -init /tmp/Foo.sql -echo /var/db/Foo.sqlite
Se connecter à une base de données
Une application peut vouloir communiquer avec plusieurs
bases de données. C'est pourquoi la classe Hoa\Database\Dal
définit une liste de profils de connexions potentielles
représentés par des identifiants uniques. Chaque profil de connexion est une
structure qui porte les données suivantes :
id, unique et représente le profil ;dsn(pour Data Source Name), décrit une connexion vers une base de données (spécifique à chaque DBAL) ;username, un nom d'utilisateur pour se connecter à la base de données (inutile avec SQLite) ;password, un mot de passe se couplant avec le nom d'utilisateur (inutile avec SQLite) ;options, un tableau d'options spécifiques au gestionnaire de bases de données utilisé.
Cette liste de profils fait partie des paramètres de notre classe à partir
de Hoa\Core\Parameter (une des couches du noyau). Toutefois,
Hoa\Database\Dal étant un multiton un peu particulier comme nous
le verrons rapidement, une méthode intermédiaire est proposée pour
initialiser les paramètres de cette classe : initializeParameters
Par exemple, si nous utilisons une base de données SQLite située dans le
fichier /var/db/Foo.sqlite sous le profil default,
alors nous aurons le code suivant :
from('Hoa')
-> import('Database.Dal');
Hoa\Database\Dal::initializeParameters(array(
'connection.list.default.dal' => Hoa\Database\Dal::PDO,
'connection.list.default.dsn' => 'sqlite:/var/db/Foo.sqlite'
));
Toutefois, dans une application, la connexion vers une base de données peut
être difficile à retrouver. En effet, les applications sont de plus en plus
découpées et il est difficile de conserver un chemin vers notre connexion.
C'est pourquoi Hoa\Database\Dal propose la méthode
getInstance qui permet de retrouver la connexion
associée à un identifiant ou de l'ouvrir si c'est la première
fois. Ainsi, nous ferons :
Hoa\Database\Dal::getInstance('default');
Mais il existe aussi des cas où nous voulons simplement utiliser la
connexion courante sans savoir laquelle c'est, en particulier
si nous manipulons plusieurs connexions en même temps. C'est pourquoi
Hoa\Database\Dal définit la méthode getLastInstance.
Cette méthode va retrouver la dernière connexion utilisée.
Toutefois, si aucune n'a été précédemment ouverte, il serait agréable qu'elle
sache en ouvrir une. C'est pourquoi Hoa\Database\Dal propopose le
paramètre connexion.autoload pour choisir un profil à charger par
défaut. L'exemple suivant va enrichir les paramètres que nous avons déjà
écrits puis ouvrir la connexion attachée au profil default comme
si la connexion avait déjà été ouverte :
Hoa\Database\Dal::initializeParameters(array(
'connection.list.default.dal' => Hoa\Database\Dal::PDO,
'connection.list.default.dsn' => 'sqlite:/var/db/Foo.sqlite',
'connection.autoload' => 'default'
));
// Open profile “default”.
$connexion = Hoa\Database\Dal::getLastInstance();
Les avantages sont nombreux : plus besoin de garder un lien vers une connexion de base de données, plus besoin de savoir quel profil a été utilisé dernièrement pour re-travailler dessus, plus besoin de se reconnecter en cherchant à nouveau les options etc.
Exécuter des requêtes sur une base de données
Exécuter une requête sur une base de données suit en général les étapes
suivantes : écrire une requête complète ou pas, la compléter si nécessaire,
l'exécuter puis récupérer les résultats. Pour exécuter écrire et exécuter une
requête directement, nous pouvons utiliser la méthode
Hoa\Database\Dal::query qui prend pour seul argument une requête
SQL. Exécuter une requête va nous retourner un objet de type
Hoa\Database\DalStatement qui représente une requête d'une
manière générale. Pour récupérer les résultats de notre requête, nous
utiliserons la méthode Hoa\Database\DalStatement::fetchAll.
Ainsi :
$dal = Hoa\Database\Dal::getLastInstance();
$statement = $dal->query('SELECT * FROM foo LIMIT 2');
print_r($statement->fetchAll());
// Will output:
// Array
// (
// [0] => Array
// (
// [id] => 1
// [firstname] => Gordon
// [lastname] => Freeman
// [birthday] => 1973
// )
//
// [1] => Array
// (
// [id] => 2
// [firstname] => Alyx
// [lastname] => Vance
// [birthday] => 1995
// )
//
// )
Le résultat ici est un tableau des tuples indexés par notre sélection.
Maintenant, imaginons que nous souhaitons que la limite (ici le nombre
maximum de tuples à sélectionner) soit variable : nous
pouvons soit construire la requête par concaténation à une variable
$limit, soit en utilisant des requêtes
préparées. Les avantages des requêtes préparées sont tout
d'abord une optimisation de la part du gestionnaire de bases
de données mais aussi une meilleure sécurité. En effet, les
paramètres (ou les variables) d'une requête SQL sont protégés automatiquement
contre les injections, il est donc toujours préférable d'utiliser ce
mécanisme. Pour exécuter une requête paramétrée, nous devons commencer par la
préparer à l'aide de la méthode Hoa\Database\Dal::prepare qui
retourne un objet Hoa\Database\DalStatement comme attendu. Puis
nous l'exécutons à l'aide de la méthode
Hoa\Database\DalStatement::execute, qui attend en argument,
reçoit un tableau de valeurs pour chaque paramètre. Les paramètres sont
préfixés par deux-points (symbole « : ») et ne doivent pas
contenir d'espaces. Voyons plutôt :
$dal = Hoa\Database\Dal::getLastInstance();
$statement = $dal->prepare('SELECT * FROM foo LIMIT :my_limit');
$limit = 2;
$statement->execute(array('my_limit' => $limit));
print_r($statement->fetchAll());
Mieux encore, au lieu de préciser la valeur des paramètres dans la méthode
execute, nous pouvons utiliser la méthode
Hoa\Database\Dal::bindParameter qui permet de faire des choses
intéressantes. Nous ne verrons que deux des quatres arguments disponibles, qui
permettent d'associer une valeur à un paramètre :
$statement = $dal->prepare('SELECT * FROM foo LIMIT :my_limit');
$limit = 2;
$statement->bindParameter('my_limit', $limit);
$statement->execute();
Ce qui est intéressant est que la variable qui porte la valeur du paramètre
est passée en référence ! Ce qui signifie que nos paramètres
sont modifiables en modifiant simplement la valeur de cette variable sans
avoir besoin d'appeler à nouveau la méthode bindParameter :
$statement = $dal->prepare('SELECT * FROM foo LIMIT :my_limit');
$limit = 1;
$statement->bindParameter('my_limit', $limit);
++$limit; // $limit and :my_limit are now set to 2.
$statement->execute();
Et même après exécution de notre requête, nous pouvons modifier un
paramètre et ré-exécuter notre requête, sur le même objet
Hoa\Database\Dal. Le gestionnaire de bases de données quant à lui
doit être en mesure de fournir des optimisations pour calculer plus rapidement
le résultat attendu (parfois même faire du cache paramétré). Ce mécanisme
n'est pas à négliger lorsque nous avons des requêtes paramétrées.
Bien plus d'opérations
La bibliothèque Hoa\Database propose bien d'autres
opérations comme la gestion des transactions, quelques opérations sur les
identifiants, d'autres sur la sécurité, les erreurs etc. Nous n'allons pas
tout détailler ici.
Modéliser des données
L'Informatique est la science de l'information. Le meilleur moyen de se représenter une information est de la modéliser. Une modélisation ne correspond pas toujours à l'implémentation de l'information, i.e. la représentation de l'information lors de son utilisation peut différer de sa modélisation mais peu importe. Une information bien modélisée permet de mieux se la représenter et ainsi pouvoir raisonner automatiquement dessus. Par exemple, selon la précision des informations portées par le modèle, nous pouvons automatiser la génération de données de tests ou carrément de tests !
Premier pas avec un modèle
Il y a plusieurs façon de représenter un modèle : soit
textuellement avec du code, soit
graphiquement (graphiques qui seront probablement compilés
vers du code). Nous avons fait le choix de représenter un modèle avec du code,
à travers la bibliothèque Hoa\Model. Nous profitons du paradigme
objet de PHP en choisissant de représenter chaque entité du
modèle par une classe, et ainsi, les données sont portées par
les attributs de classes. Toute entitée aura comme parent la classe
Hoa\Model pour profiter de ses services. Par exemple pour
représenter un utilisateur qui a un identifiant, un prénom, un nom et un âge,
nous aurions :
from('Hoa')
-> import('Model.~');
class User extends Hoa\Model {
public $_id;
public $_firstname;
public $_lastname;
public $_birthday;
}
Pour instancier un utilisateur, nous allons travailler sur ses attributs en
faisant abstraction du underscore (symbole « _ »),
ainsi :
$user = new User();
$user->id = 42;
var_dump($user->id);
// Will output:
// int(42)
C'est un bon début mais c'est très naïf. Nous aimerions faire le lien entre modèle et données.
Lien entre modèle et données
Un modèle représente une information, ou plutôt un ensemble de
données. Ces données peuvent venir de plusieurs endroits différents,
peu importe d'où. C'est pourquoi Hoa\Model propose entre autre un
mécanisme pour ouvrir un modèle et l'enregistrer. À l'utilisateur ensuite
d'écrire le lien entre son modèle et ses données.
Commençons par la méthode qui permet d'ouvrir un modèle : open
qui prend en argument un tableau de contraintes portant par
exemple sur les attributs (ce sera la sémantique la plus répandue pour les
contraintes mais rien ne nous empêche d'en donner une autre). Nous allons
extraire les données stockées dans /var/db/Foo.sqlite à travers
Hoa\Database que nous allons définir comme lien entre les données
et le modèle — ou mapping layer — dans le constructeur spécial
construct réservé au modèle. Enfin, nous allons effectuer le lien
entre les données extraites et notre modèle grâce à la méthode
map qui reconnaît des données de plusieurs formes courantes et
arrive à les associer à nos attributs :
from('Hoa')
-> import('Model.~')
-> import('Model.Exception')
-> import('Database.Dal');
class User extends Hoa\Model {
public $_id;
public $_firstname;
public $_lastname;
public $_birthday;
public function construct ( ) {
// 1. set mapping layer.
$this->setMappingLayer(Hoa\Database\Dal::getLastInstance());
return;
}
public function open ( Array $constraints = array() ) {
// 2. ensure that the constraint “id” exists.
if(!isset($constraints['id']))
throw new Hoa\Model\Exception('The constraint “id” is needed.', 0);
// 3. extract data.
$data = $this->getMappingLayer()
->prepare(
'SELECT id, firstname, lastname, birthday ' .
'FROM user ' .
'WHERE id = :id'
)
->execute($constraints)
->fetchAll();
// 4. map data to our model.
$this->map($data[0]);
return;
}
}
Nous pouvons tester ce modèle de cette façon :
$user = new User();
$user->open(array('id' => 1));
var_dump($user->firsname);
// Will output:
// string(6) "Gordon"
Il existe une autre manière de définir des contraintes en définissant des
valeurs aux attributs avant d'ouvrir le modèle. Pour assurer les deux
approches, nous pouvons fusionner les contraintes données
dans la méthode open avec les contraintes définies par les
attributs. Nous ajoutons alors juste avant le point 2 :
$constraints = array_merge($this->getConstraints(), $constraints);
Puis, pour arriver au même résultat que précédemment :
$user = new User();
$user->id = 1;
$user->open();
var_dump($user->firstname);
Notons que l'ordre des arguments de la fonction array_merge
aura une influence sur la priorité des contraintes.
Validation des données
Il est agréable de pouvoir valider automatiquement ses
données. C'est pourquoi Hoa\Model propose un mécanisme de
validation. En effet, à chaque donnée du modèle, i.e. à chaque attribut,
nous pouvons associer une méthode de validation dont la
convention de nommage est validateData et qui prend en
unique argument une valeur. Cette méthode se comporte comme un
prédicat, c'est à dire une fonction qui retourne
true ou false. L'assignation de la valeur à la
donnée ne s'effectue pas dans les méthodes de validations.
Précisons pour la convention de nommage que l'attribut
aBc_DeF_GHI sera renommée AbcDefGhi pour s'ajouter
au nom de la méthode de validation (nous faisons une transformation vers du CamelCase).
Ainsi, si nous voulons ajouter une validation sur l'année de naissance qui doit être compris entre 1970 et l'année courante, nous ferions :
public function validateBirthday ( $birthday ) {
return 1970 <= $birthday && ((int) date('Y')) >= $birthday;
}
Si nous testons notre validateur en assignant des valeurs à la donnée
birthday, nous aurons :
$user = new User();
$user->birthday = 1973; // all right.
$user->birthday = 65537; // an exception is thrown.
Nous pouvons désactiver la validation à l'aide de la méthode
setEnableValidation de cette manière :
$user->setEnableValidation(false);
Notons que comme notre modèle est représenté par un ensemble de classe, nous pouvons avoir de l'héritage et ainsi hériter des propriétés de validations. Nous sommes ainsi capable d'en surcharger certaines et ainsi de les affiner. Ceci peut être mécanisme intéressant à ne pas négliger.
Un peu de Praspel pour valider et même plus
Dans l'optique d'automatiser la génération de tests, Hoa propose le langage
Praspel. Ce dernier repose sur des structures algébriques,
appelées domaines réalistes, avec des propriétés
particulières. Praspel exploite le paradigme des contrats à travers plusieurs
clauses (i.e. plusieurs contraintes formelles). Il est prématuré d'expliquer
Praspel dans ce chapitre et c'est pourquoi nous allons nous restreindre à une
clause particulière : @invariant qui est adaptée pour spécifier
les données portées par notre modèle.
Praspel permet d'utiliser les domaines réalistes de manière simple et
permet de caractériser assez finement la forme des données. Dans la majorité
des cas, une clause Praspel peut remplacer une méthode validation vue
précédémment. L'idée est alors d'écrire des méthodes de validations pour
ajouter des contraintes supplémentaires sur la forme des données uniquement.
Notons qu'il est très facile d'écrire son propre domaine réaliste (voir la
bibliothèque Hoa\Realdom) et qu'il est préférable de passer par
là s'il sera utilisé souvent car les domaines réalistes offrent deux
propriétés intéressants qui sont la prédicabilité (vérification) et
générabilité (génération). En effet, nous sommes capable de générer
des données automatiquement et si notre modèle est annoté
avec le langage Praspel, nous pourrons générer automatiquement des données
pour remplir notre modèle. Les conséquences immédiates sont : construire
plusieurs modèles valides rapidement, l'enregistrer et ainsi construire des
données (par exemple remplir une base de données) ou générer des cas de tests
automatiquement.
Même si nous n'expliquons pas Praspel dans le détail, nous allons montrer comment valider notre modèle :
class User extends Hoa\Model {
/**
* @invariant id: boundinteger(1);
*/
public $_id;
/**
* @invariant firstname: regex('[\w\'\- ]+', boundinteger(1, 42));
*/
public $_firstname;
/**
* @invariant lastname: regex('[\w\'\- ]+', boundinteger(1, 42));
*/
public $_lastname;
/**
* @invariant birthday: date('Y', boundinteger(0, timestamp('now')));
*/
public $_birthday;
// …
}
Cet exemple signifie que l'attribut id doit être un entier de
1 minimum (le maximum n'étant pas précisé, il dépend de la plateforme),
l'attribut firstname et lastname sont des chaînes de
caractères correspondants à des expressions régulières et dont la taille est
comprise entre 1 et 42, et l'attribut birthday correspond à une
année comprise entre 1970 (0 en timestamp) et l'année actuelle.
Nous rappelons que les méthodes validateData peuvent
venir en soutient à Praspel. Nous rappelons également que nous sommes sur des
exemples simples et que Praspel peut exprimer des contraintes plus fines que
ça.
Gérer les relations
Les relations entre les différentes entités du modèle sont une composante à
ne pas négliger. En effet, si nous avons notre entité User qui
peut être reliée à disons l'entité Mail, cette relation peut
avoir une cardinalité et un sens : par exemple « combien de mails un
utilisateur peut avoir ? ».
Une relation en Praspel s'exprime avec le domaine réaliste
relation qui est une simplification syntaxique du domaine
réaliste array. Nous devons préciser à quelle entité du modèle
est reliée notre entitée courant et sa cardinalité. Par exemple, pour dire
qu'« un utilisateur peut avoir zéro ou vingt mails », nous écririons :
/**
* @invariant mails: relation('Mail', boundinteger(0, 20));
*/
public $mails;
Enfin, pour faire le lien entre les données et la relation de notre modèle,
nous devons nous positionner sur l'attribut qui va accueillir la relation et
ensuite utiliser la méthode map :
$this->mails->map(…);
Nous ne détaillons pas toutes les possibilités de Hoa\Model,
juste suffisamment pour démarrer.
Application : Gordon's blog
Nous allons ajouter des données au Gordon's blog. Pour cela, nous
allons créer une base de données, puis l'exploiter à travers notre modèle
Application\Model\*
utilisés dans notre contrôleur Application\Controller\Blog.
Schéma des données
Nous voulons représenter un blog. Le blog sera constitué d'articles auxquels seront associés des commentaires. Un article est caractérisé par un identifiant, un titre, une date et un contenu. Un commentaire est caractérisé par d'un identifiant, un auteur, une date et un contenu. Un lien existe entre les commentaires et les articles à travers leurs identifiants respectifs.
Le schéma complet au format SQL peut être retrouvé dans
le dépôt Sandbox/
dans le fichier GordonsBlog/Data/Variable/Database/Blog.sql. Nous
donnons ici la description des tables :
CREATE TABLE article (
id INTEGER,
title VARCHAR(255),
posted TIMESTAMP,
content LONGVARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE comment (
id INTEGER,
article INTEGER,
author VARCHAR(31),
posted TIMESTAMP,
content LONGVARCHAR,
PRIMARY KEY(id)
FOREIGN KEY(article) REFERENCES article(id)
);
Pour créer la base de données, nous allons nous aider de
sqlite3 en étant préalablement placé dans le dossier
Data/Variable/Database/ :
$ cd Data/Variable/Database
$ sqlite3 -init Blog.sql -echo Blog.sqlite
Notre base de données est maintenant créée. Nous allons écrire le modèle correspondant.
Modèle des données
Nous allons créer deux fichiers, un pour chaque entité de notre modèle dans
le dossier Application/Model.
Nous commençons par écrire l'entité Comment comme étant la
classe Application\Model\Comment en fonction de notre
schéma :
namespace {
from('Hoa')
-> import('Model.~')
-> import('Database.Dal');
}
namespace Application\Model {
class Comment extends \Hoa\Model {
/**
* @invariant id: boundinteger(0);
*/
protected $_id;
/**
* @invariant article: relation('Application\Model\Article', 1);
*/
protected $_article;
/**
* @invariant author: regex('[\w\d\'\- ]+', boundinteger(1, 42));
*/
protected $_author;
/**
* @invariant posted: boundinteger(
* timestamp('1 january 1999'),
* timestamp('now')
* );
*/
protected $_posted;
/**
* @invariant content: string(boundinteger(1, 4096));
*/
protected $_content;
protected function construct ( ) {
$this->setMappingLayer(\Hoa\Database\Dal::getLastInstance());
return;
}
}
}
Nous avons écrit les contraintes de validation pour chaque donnée et nous
avons défini le lien vers nos données. Maintenant voyons l'entité
Article comme étant la classe
Application\Model\Article toujours en fonction de notre
schéma :
namespace {
from('Hoa')
-> import('Model.~')
-> import('Model.Exception')
-> import('Database.Dal');
}
namespace Application\Model {
class Article extends \Hoa\Model {
/**
* @invariant id: boundinteger(0);
*/
protected $_id;
/**
* @invariant title: string(boundinteger(1, 255));
*/
protected $_title;
/**
* @invariant posted: boundinteger(
* timestamp('1 january 1999'),
* timestamp('now')
* );
*/
protected $_posted;
/**
* @invariant content: string(boundinteger(1));
*/
protected $_content;
/**
* @invariant comments: relation('Application\Model\Comment', boundinteger(0));
*/
protected $_comments;
protected function construct ( ) {
$this->setMappingLayer(\Hoa\Database\Dal::getLastInstance());
return;
}
public function open ( Array $constraints = array() ) {
$constraints = array_merge($this->getConstraints(), $constraints);
if(!isset($constraints['id']))
throw new \Hoa\Model\Exception('The constraint “id” is needed.', 0);
$data = $this->getMappingLayer()
->prepare(
'SELECT id, title, content ' .
'FROM article ' .
'WHERE id = :id'
)
->execute($constraints)
->fetchAll();
$this->map($data[0]);
$this->comments->map(
$this->getMappingLayer()
->prepare(
'SELECT id, posted, author, content ' .
'FROM comment '.
'WHERE article = :article'
)
->execute(array('article' => $constraints['id']))
->fetchAll()
);
return;
}
public function getShortList ( ) {
return $this->getMappingLayer()->query(
'SELECT id, title, posted FROM article ORDER BY id DESC'
)->fetchAll();
}
}
}
À nouveau, nous avons écrit les contraintes de validation pour chaque
donnée et nous avons défini le lien vers nos données. En plus, nous avons
écrit le comportement de la méthode open avec la gestion des
contraintes et des relations (à travers l'attribut comments).
Enfin, nous avons la méthode getShortList qui exploite l'entité
sans la modifier.
Nous allons maintenant exploiter notre modèle dans notre contrôleur.
Depuis un contrôleur
Notre contrôleur Application\Controller\Blog comporte déjà
deux actions sous la forme de méthodes : IndexAction et
ArticleAction. Nous allons les enrichir pour utiliser le modèle
et afficher les données brutes.
Pour la méthode IndexAction, nous allons afficher la liste des
articles présents sur le blog. Pour cela, nous allons nous aider de la méthode
getShortList sur Application\Model\Article :
public function IndexAction ( ) {
$article = new \Application\Model\Article();
$list = $article->getShortList();
echo "\n", str_repeat(' ', 31) . '★ Gordon\'s blog ★', "\n\n",
'Here is the list of all articles I have written:', "\n";
foreach($list as $l) {
echo ' #' . $l['id'], ' ', date('d/m/Y', $l['posted']), ' — ',
wordwrap($l['title'], 59, "\n" . str_repeat(' ', 20), true),
"\n";
}
echo "\n";
return;
}
Pour la méthode ArticleAction, nous allons afficher un article
avec ses commentaires en fonction de l'identifiant de l'article demandé :
public function ArticleAction ( $id ) {
$article = new \Application\Model\Article();
$article->open(array('id' => $id));
echo "\n", str_repeat(' ', 31) . '★ Gordon\'s blog ★', "\n\n",
$article->title, "\n\n",
wordwrap($article->content, 80, "\n", true), "\n\n",
str_repeat(' ', 34), str_repeat('-', 10), "\n\n";
foreach($article->comments as $comment)
echo $comment->author, ' • ', date('d/m/Y', $comment->posted), "\n",
wordwrap($comment->content, 70, "\n", true), "\n\n";
echo "\n";
return;
}
Testons !
N'oublions pas de modifier notre fichier d'amorçage index.php
pour lui ajouter la connexion à notre base de données qui sera exploitée par
notre modèle (à l'aide de la méthode getLastInstance de
Hoa\Database\Dal) ; ainsi :
from('Hoa')
-> import('Database.Dal.~')
-> import('Dispatcher.Basic')
-> import('Router.Http');
Hoa\Database\Dal::initializeParameters(array(
'connection.list.default.dal' => Hoa\Database\Dal::PDO,
'connection.list.default.dsn' => 'sqlite:hoa://Data/Variable/Database/Blog.sqlite',
'connection.autoload' => 'default'
));
$dispatcher = new Hoa\Dispatcher\Basic();
$router = new Hoa\Router\Http();
$router->get('i', '/', 'blog', 'index')
->get('a', '/article-(?<id>\d+)\.html', 'blog', 'article');
try {
$dispatcher->dispatch($router);
}
catch ( Hoa\Router\Exception\NotFound $e ) {
echo 'Your page seems to be not found /o\.', "\n";
}
Enfin, nous allons tester notre application un peu plus enrichie ! Nous commençons par démarrer Bhoa :
$ myapp bhoa --root hoa://Application/Public
À l'aide de cURL, nous allons afficher le résultat de notre index et d'un article :
$ curl 127.0.0.1:8888/
★ Gordon's blog ★
Here is the list of all articles I have written:
#5 07/01/2001 — G-Man or the hard deal
#4 03/01/2001 — ping -f Nihilanth
#3 16/12/2000 — I think I have made a blunder at Black Mesa
#2 15/12/2000 — Great party in the Anomalous Materials department with
collegues!
#1 05/05/2000 — Dr. Isaac Kleiner, my mentor, has hired me at Black Mesa
Research Facility
#0 18/11/1999 — Call me Ph.D!
$ curl 127.0.0.1:8888/article-0.html
★ Gordon's blog ★
Call me Ph.D!
My thesis was accepted. What a long labor! Well, it is finally over. The harder
part was to resume the title. You know, in Theoretical Physics, thesis have
always very short and concise titles. Mine is quite simple; here it is:
Observation of Einstein-Podolsky-Rosen Entanglement on Supraquantum Structures
by Induction Through Nonlinear Transuranic Crystal of Extremely Long Wavelength
(ELW) Pulse from Mode-Locked Source Array. I can gladly send you a copy if you
want.
----------
Isaac Kleiner • 19/12/1999
Nice work Gordon. Hope we will work together in a near future!
Mummy • 19/12/1999
I really donnot understand what you do but I am very proud of my
little Gordy :-). xoxo, your Mummy.
Notre application exploite plusieurs couches. Nous avons
les données qui sont stockées dans une base de données. Notre
abstraction avec Hoa\Database\Dal et une
utilisation standard du langage SQL nous permettent de
changer de gestionnaire de bases de données sans modifier le
code de notre application. De plus, les données sont manipulées par notre
application à travers un modèle les réprésentant (à l'aide de
Hoa\Model). Si les données viennent à changer d'emplacement ou de
format, mettre à jour le modèle sera la seule tâche à faire.
Elle peut nécessiter une quantité de travail non négligeable mais la tâche est
isolée : seul le modèle sera à modifier, pas le reste de
l'application. Enfin, Hoa\Model peut servir à l'élaboration
d'outils de gestion de données plus complexes de part sa conception.
