Raccourcis : Contenu - rubriques - sous rubriques
EN FR

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êtes
  • realname (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'attribut name. Dans ce cas name 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.