Section: Fichier DAO de base
^ jDao : mapping objet relationnel | Utilisation d'une factory et d'un record DAO » |
− Table des matières
Pour utiliser jDao, il faut d'abord décrire dans un fichier XML le mapping objet-relationnel, c'est à dire indiquer dans ce fichier la correspondance entre les propriétés de l'objet DAO et les champs d'une ou plusieurs tables.
Création automatique ¶
Le fichier d'un DAO peut être généré par le script dev.php
en ligne de commande,
à partir d'une table de base de données existante.
php dev.php module:create-dao <nom_module> <nom_dao> [<nom_table> [<nom_sequence>]]
Par exemple, si vous voulez créer dans le module "myshop", un DAO "product" qui sera basé sur la table "shop_product", vous taperez donc :
php dev.php module:create-dao myshop product shop_product
Dans le répertoire daos
du module myshop vous allez donc avoir un
fichier XML product.dao.xml
qui va contenir la description du mapping. Le
nom de la table est optionnel si vous voulez utiliser le même nom de dao que
celui de la table, et que vous n'avez pas de séquence. Indiquez le nom de la
séquence utilisée pour auto incrémenter la clé primaire (paramètre nécessaire
pour les bases oracles et parfois postgresql).
Bien que la génération automatique permette de gagner du temps, il faut souvent retoucher le fichier pour que le mapping corresponde mieux à ce que l'on veut faire, le générateur ne pouvant tout deviner. Voyez alors la suite pour le compléter, ou en créer un à la main.
Détails sur le format XML ¶
La structure d'un fichier DAO ressemble à cela :
<dao xmlns="http://jelix.org/ns/dao/1.0">
<datasources>
section datasources
</datasources>
<record>
section properties
</record>
<factory>
section methodes
</factory>
</dao>
Il y a trois sections, sachant que la section <factory>
(ou "methodes")
est facultative et est décrite dans un autre chapitre dédiée.
<datasources>
: indique les tables sur lequel reposera l'objet.<record>
: indique la correspondance entre les propriétés de l'objet et les champs des tables et définit donc les propriétés qu'il y aura sur l'objet record.
Correspondance simple ¶
Déclaration de la table ¶
On appelle correspondance simple, une correspondance où un record est mappé sur
une seule table. Pour déclarer la table sur laquelle reposera le DAO, on utilise
la balise <primarytable>
, avec les attributs suivants :
name
: alias donné à la table et qui sera utilisé dans les requêtesrealname
(facultatif) : nom réel de la table dans la base de données. Si cet attribut n'est pas précisé, il prend la même valeur que l'attributname
. Dans ce casname
doit être le nom réel de la table.primarykey
indique la clé primaire. Vous pouvez indiquer plusieurs champs en les séparant par un espace ou une virgule.
<datasources>
<primarytable name="p" realname="products" primarykey="id_product" />
</datasources>
On déclare ici que le record sera basé sur la table "products", qui a pour alias "p", et dont la clé primaire est "id_product".
Il n'y a toujours qu'une seule table "primaire" dans un DAO (donc une seule
balise <primarytable>
). Vous verrez que l'on peut indiquer des tables
annexes (étrangères) plus loin.
Ensuite, il faut déclarer la correspondance propriété - champs.
Déclaration des propriétés ¶
La section <record>
déclare les propriétés d'un objet record
(enregistrement). Chaque propriété correspond à l'un des champs de la table
primaire, ou l'un de ceux des tables étrangères comme vous le verrez plus loin.
Bien sûr, vous n'êtes pas obligés de déclarer une propriété pour tous les champs
existants. On peut ainsi faire plusieurs DAO qui travaillent sur une même table
mais qui sont destinés à des usages différents. Par exemple faire un DAO
spécifique pour récupérer des listes légères d'enregistrement (on ne déclarera
que les propriétés essentielles), et un autre pour les gérer de manière complète
(on y indiquera tous les champs).
La section record
doit donc contenir une ou plusieurs balises
<property>
. Voici une balise <property>
avec la liste des attributs
possibles :
<property
name="nom simplifié"
fieldname="nom du champ"
datatype="" required="true/false" minlength="" maxlength="" regexp=""
sequence="nom de la sequence" autoincrement="true/false"
updatepattern="" insertpattern="" selectpattern=""
default=""
comment=""
/>
L'attribut name
est le nom de la propriété de l'objet.
L'attribut fieldname
est le nom du champ qui correspond. Si name
et
fieldname
sont égaux, on peut omettre fieldname
.
Les attributs datatype
, required
, minlength
, maxlength
,
et regexp
sont des contraintes. Cela permet par la suite d'appeler la
méthode check()
sur un record pour vérifier les valeurs des propriétés
(avant son stockage par exemple).
L'attribut default
permet d'indiquer une valeur par défaut.
L'attribut datatype
indique le type SQL du champs. Voici les types de
champs reconnus par jDao, qui sont bien plus nombreux que dans les versions
précédentes de jelix :
varchar
,varchar2
,nvarchar2
,character
,character varying
,char
,nchar
,name
,longvarchar
,string
(deprecated),int
,integer
,tinyint
,smallint
,mediumint
,bigint
,autoincrement
,serial
,bigserial
,bigautoincrement
,double
,double precision
,float
,real
,number
,binary_float
,binary_double
,money
,numeric
,decimal
,dec
,date
,time
,datetime
,timestamp
,utimestamp
,year
,interval
boolean
,bool
,bit
tinytext
,text
,mediumtext
,longtext
,long
,clob
,nclob
tinyblob
,blob
,mediumblob
,longblob
,bfile
,varbinary
,bytea
,binary
,raw
,long raw
enum
,set
,xmltype
,point
,line
,lsed
,box
,path
,polygon
,circle
,cidr
,inet
,macaddr
,bit varyong
,arrays
,complex types
Les bases de données ne reconnaissent pas tous ces types de champs. Ne vous
inquietez pas, indiquez le type que vous voulez, et jDao utilisera le type qui
correspond le mieux pour la base de donnée utilisée. Par exemple, si vous
indiquez bytea
, c'est un type utilisé par postgresql. Si vous utilisez
mysql, jDao utilisera en fait le type longblob
.
Les valeurs des champs seront convertis dans le type PHP le plus approprié, la plupart du temps, une chaine.
Pour les champs auto incrementés, vous pouvez indiquer le type serial
ou
autoincrement
. Mais dans certain cas, il est préférable d'indiquer le type
d'entier (integer
, tinyint
..) et de mettre un attribut
autoincrement
à true
. Sur certaines bases, on peut associer une
séquence à un champ. L'attribut sequence
doit alors indiquer son nom.
L'attribut comment
permet d'avoir un commentaire sur la propriété. Sa valeur
peut être utilisée par la commande createform pour générer les labels des champs de
saisie.
Les attributs updatepattern
, insertpattern
et selectpattern
permettent d'indiquer un "motif" à appliquer lors de la mise à jour, l'insertion
ou la lecture de la valeur du champ dans la table. Ce motif doit en fait être
une expression SQL, contenant éventuellement la chaîne "%s" qui sera remplacée
par la valeur ou le nom du champ. Par défaut leurs valeurs vaut "%s". Si on
indique une valeur vide, cela correspond à une opération nulle (le champ n'est
pas lu, inséré ou mis à jour). Vous pouvez utiliser insertpattern
et
selectpattern
sur des clés primaires, mais pas updatepattern
.
Exemple 1 ¶
Pour un champ qui contient une date de mise à jour, on pourra indiquer :
<property name="date_update" datatype="datetime" insertpattern="NOW()" updatepattern="NOW()" />
Ainsi chaque fois qu'un INSERT
ou un UPDATE
sera fait, la valeur insérée
sera la date du jour (et non celle que l'on aurait indiquée dans la propriété
date_update
du record).
Exemple 2 ¶
On peut aussi avoir une propriété qui ne correspond pas directement à un champ, mais qui soit le résultat d'une expression SQL. Dans ce cas, il faut désactiver l'insertion et la mise à jour.
<property name="identite" datatype="string" selectpattern="CONCAT(nom, ' ',prenom)" insertpattern="" updatepattern="" />
Attention, en ce qui concerne l'expression de selectPattern
:
- l'expression doit utiliser des champs d'une même table. Si le dao est basé sur plusieurs tables (ex : A et B, voir section suivante), il n'est pas possible que l'expression utilise à la fois des champs de la table A et de la table B
- si l'expression utilise des champs d'une table B qui ne soit pas la table
principale, la propriété doit être attribué à cette table B, et non à la
table principale. Ce qui veut dire que la propriété doit avoir un attribut
table
ayant pour valeur le nom/alias de cette table B.
Correspondance avec plusieurs tables ¶
On peut déclarer une table principale, mais aussi des tables annexes qui seraient liées à la table principale par des jointures. Il est utile, lorsque l'on veut récupérer un enregistrement, de récupérer en même temps des informations de tables annexes. Par exemple, si on veut récupérer un produit de la table "products", et en même temps le libellé de sa catégorie qui se trouve dans une table "category", on déclarera aussi la table "category". À noter que vous ne pourrez modifier que les données issues de la table principale quand vous voudrez mettre à jour un enregistrement.
Pour déclarer de telles tables étrangères, qui en toute logique sont liées à la table principale par des clés étrangères, il faut utiliser :
<foreigntable>
pour indiquer une table étrangère liée par une jointure normale.<optionalforeigntable>
pour indiquer une table étrangère liée par une jointure externe.
Exemple :
<primarytable name="p" realname="products" primarykey="id_product" />
<foreigntable name="cat" realname="categories" primarykey="id_cat" onforeignkey="id_cat" />
<optionalforeigntable name="man" realname="manufacturers" primarykey="id" onforeignkey="id_manufacturer" />
Comme pour la balise <primarytable>
, il y a les attributs name
,
realname
et primarykey
. Il y a par contre un attribut
supplémentaire, onforeignkey
, qui indique le nom du champ dans la table
primaire, qui est la clé étrangère sur la table en question. Ainsi, avec
l'exemple ci-dessus, jDao générera pour les requêtes de type SELECT
les
clauses FROM
et WHERE
suivantes :
FROM products as p left join manufacturers as man on (p.id_manufacturer = man.id), categories as cat
WHERE cat.id_cat = p.id_cat
Indiquer des tables annexes n'a de sens que si vous voulez avoir une ou
plusieurs propriétés correspondantes à leurs champs. Vous ajouterez donc autant
de balise <property>
que vous voudrez. La seule différence est qu'il faut
ajouter un attribut table
qui indique l'alias de la table dans laquelle se
trouve le champ.
<property
name="libelle_categorie"
fieldname="label"
table="cat"
/>
Dans la propriété $libelle_categorie
du record, se trouvera la valeur du
champ label
de la table categories
("cat" étant l'alias de cette table,
comme il a été défini plus haut dans la balise foreigntable
).
Utiliser une classe PHP pour les enregistrements ¶
Vous voudriez peut-être ajouter des méthodes et propriétés dans
l'objet record
généré par jDao. Cela n'est possible qu'en fournissant
vous-même une classe qui servira de classe de base pour l'objet généré.
- *Attention** : cette classe n'est pas destinée à faire des requêtes sql sur la table du DAO. Si vous voulez faire cela, voyez plutôt les "méthodes XML/PHP" de la factory. Les méthodes fournies par votre classe seront plutôt des méthodes qui calculent par exemple des valeurs de champs, qui vérifient des règles de gestion etc...
La classe doit être stockée dans le répertoire daos/
et doit suivre un nommage spécifique.
Cependant, depuis Jelix 1.8.1, vous pouvez aussi utiliser n'importe quelle classe auto-chargeable.
La classe est dans daos/
¶
Votre classe aura le nom que vous voudrez, avec le suffixe DaoRecord
, et
stockée dans le répertoire daos/
d'un module, sous le
nom <nomclasse>.daorecord.php
.
Vous indiquerez alors le sélecteur de cette classe <nommodule>~<nomclasse>
dans un attribut extends
de la balise <record>
.
Notez que votre classe doit étendre la classe jDaoRecordBase
.
Exemple, dans le fichier dao :
<record extends="news~news">
...
</record>
Les enregistrements seront donc de la classe newsDaoRecord
située dans le
fichier daos/news.daorecord.php
du module news
.
class newsDaoRecord extends jDaoRecordBase {
function methodeDeCalcul() {
$this->champs = $this->champs2 * 1.196;
}
function calculateTotal() {
return $this->amount + $this->vat;
}
}
Ainsi vous pourrez appeler cette méthode sur n'importe quel enregistrement:
$maFactory = jDao::get("mymodule~news");
$liste = $maFactory->findAll();
foreach ($liste as $row) {
$total = $row->calculateTotal();
//...
}
La classe est autochargeable ¶
Si votre classe a un namespace et est auto-chargeable, vous pouvez son
nom complet dans l'attribut extends
de la balise <record>
.
Votre classe doit étendre la classe jDaoRecordBase
.
Exemple, dans le fichier dao :
<record extends="\MyCompany\MyDomain\News">
...
</record>
Les enregistrements seront donc de la classe \MyCompany\MyDomain\News
située dans un fichier News.php
quelque part dans votre module ou ailleurs.
Un exemple de son code source :
class \MyCompany\MyDomain\News extends \jDaoRecordBase {
function methodeDeCalcul() {
$this->champs = $this->champs2 * 1.196;
}
function calculateTotal() {
return $this->amount + $this->vat;
}
}
Ainsi vous pourrez appeler cette méthode sur n'importe quel enregistrement:
$maFactory = jDao::get("mymodule~news");
$liste = $maFactory->findAll();
foreach ($liste as $row) {
$total = $row->calculateTotal();
//...
}
Importation de dao ¶
Dans vos projets, vous aurez parfois besoin de vous calquer sur un dao déjà
existant afin de créer votre propre dao. Afin de vous éviter de retranscrire
tout le contenu du dao initial avec des propriétés et méthodes qui resteront
inchangés, vous avez la possibilité de l'importer via l'attribut import
dans la balise dao
.
Fonctionnement ¶
- le dao à importer est lu en premier : votre Dao possède ainsi toutes ses propriétés, définitions de tables et méthodes.
- les tables, propriétés et methodes de votre dao sont ensuite lues. Celles qui ont le même nom que celles qui viennent d'être importées, écrasent ces dernières. Elles écrasent les tables, propriétés et méthodes du même nom qui viennent d'être importées.
- les classes PHP du DAO résultant sont alors générées.
Notez que l'import n'est pas un héritage au sens objet PHP.
Exemple :
Un bon exemple avec jauthdb : si vous avez besoin de définir votre propre dao, il est noté dans la documentation qu' "il doit avoir au moins les mêmes propriétés et méthodes que le dao jelixusers".
Voici donc par exemple votre dao "mymodule~mycustomuser" :
<dao import="jauthdb~jelixuser">
<datasources>
<primarytable name="user" realname="myusertable" primarykey="login" />
</datasources>
<record>
<property name="nationality" fieldname="nationality" datatype="string" maxlength="100" required="true">
</record>
<factory>
<method name="findByNationality" type="select">
<parameter name="nationality" />
<conditions>
<eq property="nationality" expr="$nationality" />
</conditions>
</method>
</factory>
</dao>
Ici, vous aurez donc défini simplement un dao personnalisé pour jauth , tout en
respectant les critères de base requis (propriétées login
et
password
, methodes getByLoginPassword
, getByLogin
, etc...)
grace à l'import de jauthdb~jelixuser
.