DAO génériques


1Présentation

Comme vous avez pu le voir rapidement dans l'introduction, Temma est capable de créer automatiquement des DAO. Le cas le plus simple est lorsque vous créez un contrôleur qui doit accéder à une seule table de la bases de données.
Il suffit alors que ce contrôleur possède un attribut protégé nommé $_temmaAutoDao, positionné à la valeur booléenne true.

Si on reprend l'exemple donné en introduction :

class Article extends \Temma\Web\Controller {
    // Indique que la DAO doit être créée automatiquement
    protected $_temmaAutoDao = true;
}

En procédant de la sorte, Temma va créer un objet de type \Temma\Dao\Dao, qui sera automatiquement configuré pour faciliter la manipulation des données stockées dans une table dont le nom est identique à celui du contrôleur (article).
Cet objet sera disponible via l'attribut $this->_dao.


2Configuration avancée

2.1Principe général

Par défaut, les DAO présupposent plusieurs choses :

  • La connexion à la base de données est configurée avec une source de données nommée db.
  • Si le cache est accessible (configuré avec une source de données nommée cache), il est utilisé pour accélérer l'accès aux données.
  • La table est dans la base de données sur laquelle la connexion est ouverte.
  • Le nom de la table correspond au nom du contrôleur.
  • Le champ contenant la clé primaire se nomme id.
  • Tous les champs de la table doivent être récupérés quand on y accède.
  • Quand on récupère les champs de la table, on récupère les noms tels qu'ils sont en base.

Pour pouvoir paramétrer le fonctionnement de la DAO (désactiver le cache, spécifier un nom de base ou de table différent, renommer les champs, ...), il est conseillé d'écrire un objet DAO personnalisé, qui pourra contenir les informations spécifiques dont vous avez besoin.


2.2Configuration spécifique

Toutefois, si vous êtes dans le cas où un contrôleur doit pouvoir manipuler des données en configurant finement le comportement de la DAO, mais que vous ne souhaitiez pas créer d'objet DAO personnalisé, il est possible de fournir les paramètres directement dans le contrôleur, en remplissant un tableau associatif.
Notez bien que cette technique reste limitée et ne doit surtout pas être employée dans le cas où la même table est accédée par plusieurs contrôleurs différents.

Voici un exemple d'utilisation avec paramétrage spécifique :

class Article extends \Temma\Web\Controller {
    protected $_temmaAutoDao = [
        'cache'  => false,
        'base'   => 'cms',
        'table'  => 'content',
        'id'     => 'cid',
        'fields' => [
            'cid'     => 'contentId',
                         'title',
                         'login',
            'content' => 'text',
            'date'    => 'creationDate',
                         'status',
        ],
    ];
}
  • Ligne 2 : Définition du tableau de configuration de la DAO.
  • Ligne 3 : Désactivation du cache.
  • Ligne 4 : Définition du nom de la base de données contenant la table.
  • Ligne 5 : Définition du nom de la table.
  • Ligne 6 : Définition du nom du champ contenant la clé primaire.
  • Lignes 7 à 14 : Définition d'un tableau associatif contenant la liste des champs à récupérer dans la base de données. S'il faut renommer les champs, il suffit de déclarer une paire associative dont la clé est le nom du champ dans la table, et la valeur associée est le nom tel qu'il doit être renommé.

Les paramètres sont indépendants, vous n'êtes pas obligés de tous les redéfinir.


3Opérations basiques

Les objets \Temma\Dao proposent 6 méthodes basiques :

  • count() : Compte le nombre d'éléments dans une table.
  • get() : Retourne toutes les informations sur un élément de la table.
  • search() : Recherche des entrée.
  • create() : Ajoute un nouvel élément dans la table.
  • remove() : Efface un ou plusieurs éléments.
  • update() : Met-à-jour un ou plusieurs enregistrements.

Ces méthodes sont expliquées en détail plus bas, mais nous allons d'abord voir comment sont construits les critères de recherche et les critères de tri, qui peuvent être utilisés dans certaines de ces méthodes.


4Critères de recherche

Les DAO proposent un mécanisme permettant de composer facilement des filtres de recherche. Pour cela, il faut créer un critère, qui pourra être passé en paramètre de certaines méthodes.

Le type de critère le plus simple est un tableau associatif dont les clés correspondent à des champs de la table, et les valeurs correspondent à celles qui seront recherchées dans les lignes sélectionnées.

Sinon, vous pouvez créer un critère grâce à la méthode criteria(), qui crée un objet de type \Temma\Dao\Criteria.

Ensuite, vous pouvez combiner des appels aux méthodes suivantes :

  • equal() : un champ vaut une certaine valeur (possibilité de donner une liste de valeurs possibles)
  • different() : un champ ne vaut pas une certaine valeur (possibilité de donner une liste de valeurs possibles)
  • like() : un champ textuel vérifie une expression de recherche
  • notLike() : un champ textuel ne vérifie pas une expression de recherche
  • is() : un champ booléen est positionné sur "vrai"
  • isNot() : un champ boolén est positionné sur "faux"
  • lessThan() : la valeur d'un champ numérique est inférieure à une certaine valeur
  • greaterThan() : la valeur d'un champ numérique est supérieure à une certaine valeur
  • lessOrEqualTo() : la valeur d'un champ numérique est inférieure ou égale à une certaine valeur
  • greaterOrEqualTo() : la valeur d'un champ numérique est supérieure ou égale à une certaine valeur

Il existe des alias, pour simplifier l'écriture :

  • eq() : équivalent à equal()
  • ne() : équivalent à different()
  • lt() : équivalent à lessThan()
  • gt() : équivalent à greaterThan()
  • le() : équivalent à lessOrEqualTo()
  • ge() : équivalent à greaterOrEqualTo()

Par défaut, les critères sont combinés en utilisant des opérateurs booléens "ET", ce qui conduit à la création d'un système de filtrage des données : seules les données qui vérifient toutes les conditions sont récupérées. Il est possible de combiner tous les critères suivant l'opérateur "OU" en passant la chaîne "or" en paramètre de la méthode criteria().
Il est aussi possible de combiner les critères avec des opérateurs booléens grâce aux méthodes and() et or(), qui prennent chacune un nouvel objet de critère en paramètre.


4.1Exemple de tableau associatif

// efface les entrées dont la catégorie est "article"
// et la visibilité est cachée
$criteria = [
    'category'   => 'article',
    'visibility' => 'hidden',
];
$this->_dao->remove($criteria);

4.2Exemple equal() et is()

// recherche les entrées dont l'email vaut "tom@tom.com"
// ET dont le booléen "free" est à vrai
$critera = $this->_dao->criteria()
           ->equal('email', 'tom@tom.com')
           ->is('free');
$users = $this->_dao->search($criteria);
  • Ligne 3 : Création de l'objet de critère.
  • Ligne 4 : Ajout d'un critère d'égalité. Le champ email doit avoir la valeur tom@tom.com.
  • Ligne 5 : Ajout d'un critère d'égalité sur un booléen. Le champ free doit être positionné à "vrai".
  • Ligne 6 : L'objet de critère est utilisé avec la méthode search() de la DAO pour récupérer les éléments correspondant au critère.

4.3Exemple greaterThan() et lessThan()

// recherche les entrées dont l'âge est supérieur à 12
// ET inférieur à 20
$criteria = $this->_dao->criteria()
            ->greaterThan('age', 12)
            ->lessThan('age', 20);
  • Ligne 3 : Création de l'objet de critère.
  • Ligne 4 : Ajout d'un critère de comparaison. Le champ age doit être strictement supérieur à 12.
  • Ligne 5 : Ajout d'un critère de comparaison. Le champ age doit être strictement inférieur à 20.

4.4Exemple or(), like() et different()

// recherches les entrées dont l'email vient de Gmail
// OU dont le nom n'est pas celui d'un créateur de Google
$criteria = $this->_dao->criteria('or')
            ->like('email', '%@gmail.com')
            ->different('name', ['Sergey', 'Larry']);
  • Ligne 3 : Création de l'objet de critère, en spécifiant que les critères seront associés par des opérateurs "OU" (et non pas "ET" comme par défaut).
  • Ligne 4 : Ajout d'un critère de comparaison. Le champ email doit se terminer par la chaîne "@gmail.com".
  • Ligne 5 : Ajout d'un critère de comparaison. Le champ name ne doit pas contenir la valeur "Sergey" ni "Larry".

4.5Exemple logique booléenne

// recherche les entrées dont :
// l'email vaut "john@john.com" ou "bob@bob.com",
// ET dont l'âge est inférieur ou égal à 12
// OU strictement supérieur à 24
$criteria = $this->_dao->criteria()
            ->equal('email', ['john@john.com', 'bob@bob.com'])
            ->and_(
                   $this->_dao->criteria('or')
                   ->lessOrEqualTo('age', 12)
                   ->greaterThan('age', 24)
            );
  • Ligne 5 : Création de l'objet de critère.
  • Ligne 6 : Ajout d'un critère d'égalité, en fournissant une liste de valeurs.
  • Ligne 7 : Ajout d'un opérateur booléen "ET", qui contient un sous-ensemble de critères.
  • Ligne 8 : Création du sous-ensemble de critères. On spécifie que les différents critères de cet ensemble seront liés par des opérateurs "OU".
  • Ligne 9 : Ajout d'un critère de comparaison. Le champ age doit contenir une valeur inférieure ou égale à 12.
  • Ligne 10 : Ajout d'un critère de comparaison. Le champ age doit être strictement supérieur à 24.

5Critères de tri

La méthode search() est susceptible de retourner plusieurs éléments, que l'on peut vouloir trier dans un certain ordre.

Il est possible de trier de 3 manières différentes :

  1. En fournissant le nom d'un champ, ce qui effectuera un tri ascendant sur les valeurs de ce champ. Si le nom du champ commence par un tiret, un tri descendant sera effectué.
  2. En transmettant un tableau contenant les noms des champs sur lesquels le tri doit être effectué. Par défaut, les tris sont ascendants (de la plus petite valeur à la plus grande), mais il est possible de spécifier un tri descendant mettant un tiret devant le nom du champ. Il est aussi possible d'utiliser un tableau associatif dont la clé est le nom du champ et la valeur est la chaîne desc.
  3. En fournissant la valeur booléenne false, pour obtenir un tri aléatoire.
// tri suivant la date de naissance, de manière ascendante
$sort = 'birthday';
// tri suivant la date de naissance, de manière descendante
$sort = '-birthday';

// tri suivant la date de naissance (ascendant)
// et le nombre de points (descendant)
$sort = ['birthday', '-points'];

// équivalent au précédent
$sort = [
    'birthday',
    'points' => 'desc'
];

// équivalent au précédent
$sort = [
    'birthday' => 'asc',
    'points'   => 'desc'
];

// tri aléatoire
$sort = false;

6Méthodes

6.1count()

count(null|array|\Temma\Dao\Criteria $criteria=null) : int

Si cette méthode est appelée sans paramètre, elle retourne le nombre total d'éléments dans la table.

Si elle est appelée avec un tableau ou un objet de critère en paramètre, elle retourne le nombre d'éléments qui correspondent au(x) critère(s).

Exemple d'utilisation :

// récupère le nombre total d'éléments dans la table
$nbr = $this->_dao->count();

// récupère le nombre d'utilisateurs qui s'appellent "Bob" (tableau)
$nbr = $this->_dao->count(['name' => 'Bob']);

// récupère le nombre d'utilisateurs qui s'appellent "Bob" (objet)
$nbr = $this->_dao->count(
    $this->_dao->criteria()->equal('name', 'Bob')
);

6.2get()

get(int|string|array|\Temma\Dao\Criteria $id) : array

Cette méthode retourne toutes les informations sur un enregistrement de la table, dont l'identifiant de clé primaire est passé en paramètre. Les données sont retournées dans un tableau associatif, qui liste des tuples dont la clé est le nom du champ.

Un critère de recherche peut être fourni en paramètre à la place d'une clé primaire. Si ce critère récupère plusieurs enregistrements, seul le premier sera retourné.

Si une liste de champs a été fournie à la création de la DAO, seuls les champs en question sont retournés. Si cette liste prévoyait de renommer les champs, les clés du tableau associatif sont modifiées en conséquence.

Exemple d'utilisation :

// récupère les informations sur l'article ayant l'identifiant 12
$data = $this->_dao->get(12);

// affichage du titre
print($data['title']);

search(null|array|\Temma\Dao\Criteria $criteria=null, null|false|string|array $sort=null,
       ?int $offset=null, ?int $limit=null) : array

Cette méthode sert à récupérer les données de plusieurs lignes de la table. Elle retourne une liste dont chaque élément est un tableau associatif (dont le contenu est identique à ce que retourne la méthode get(), cf. ci-dessus).
Le premier paramètre est un critère de recherche, tel qu'expliqué plus haut dans cette page. S'il n'est pas fourni, ou passé à null, la méthode prendra toutes les lignes de la table.
Le deuxième paramètre contient les options de tri, telles qu'expliquées plus haut dans cette page.
Le troisième paramètre peut contenir le numéro du premier élément à retourner (en commençant à zéro).
Le quatrième paramètre peut contenir le nombre d'éléments à retourner.

Exemple d'utilisation :

// recherche les 5 articles les plus récents,
// parmi tous ceux écrits depuis le 1er janvier 2000
$articles = $this->_dao->search(
    $this->_dao->criteria()->greaterThan('date', '2011-01-01'),
    '-date',
    null,
    5
);

// affichage des titres de tous les articles récupérés
foreach ($articles as $article)
    print($article['title']);

// recherche 3 articles au hasard
$articles = $this->_dao->search(null, false, null, 3);

6.4create()

create(array $data, mixed $safeData=null) : int

Cette méthode ajoute une nouvelle ligne dans la table.
Il faut donner en paramètre un tableau associatif, contenant des paires clé/valeur correspondant à chaque champ de la table.

La méthode retourne l'identifiant de clé primaire de l'élément nouvellement créé.

Exemple d'utilisation :

// création du nouvel article
$id = $this->_dao->create([
    'title'   => 'Titre de test',
    'login'   => 'Bob',
    'date'    => date('c'),
    'content' => 'Texte...',
]);

// affichage de l'identifiant du nouvel article
print($id);

La méthode peut prendre un second paramètre optionnel, qui sert à éviter les blocages causés par les insertions qui génèrent une duplication de clé.
Le paramètre peut prendre comme valeur :

  • null : (valeur par défaut) La requête génère une erreur en cas de duplication de clé.
  • true : Tous les champs listés dans le premier paramètre seront mis à jour, en utilisant les valeurs fournies.
  • Le nom d'un champ : Ce champ sera mis à jour. Si ce champ est listé dans le premier paramètre, la valeur associée sera utilisée ; sinon le champ gardera la valeur qu'il a en base de données.
  • Un tableau associatif contenant les champs qui seront mis à jour. Les clés sont les noms des champs, et les valeurs associées seront utilisées pour les mettre à jour.

6.5remove()

remove(null|int|string|array|\Temma\Dao\Criteria $criteria=null) : int

Cette méthode sert à effacer des enregistrements de la table.

Si elle est appelée sans paramètre, elle efface tous les éléments.
Si un identifiant numérique est passé en paramètre, il sera utilisé comme clé primaire de l'élément à effacer.
Si un critère de recherche est passé en paramètre, il est utilisé pour choisir les éléments qui seront effacés.

Cette méthode retourne le nombre de lignes qui ont été effacées.

Exemple d'utilisation :

// effacement de l'article ayant l'identifiant 12
$this->_dao->remove(12);

// effacement de tous les articles écrits par Bob (tableau)
$this->_dao->remove(['login' => 'Bob']);

// effacement de tous les articles écrits par Bob (objet)
$this->_dao->remove(
    $this->_dao->criteria()->equal('login', 'Bob')
);

6.6update()

update(null|int|string|array|\Temma\Dao\Criteria $criteria, array $data=[]) : int

Avec cette méthode, vous pouvez modifier les données enregistrée dans la table.

Si le premier paramètre est mis à null, tous les éléments de la table seront modifiés.
Si un identifiant numérique ou une chaîne de caractères est passé en paramètre, il sera utilisé comme clé primaire de l'élément qui sera modifié.
Si un critère de recherche est passé en paramètre, il sera utilisé pour sélectionner les éléments qui seront modifiés.

Le second paramètre doit contenir un tableau associatif, contenant des paires clé/valeur correspondant aux champs à mettre à jour (avec une déclaration identique à la méthode create()).

Cette méthode retourne le nombre de lignes qui ont été modifiées.

Exemple d'utilisation :

// renomme "Bob" en "Robert" dans tous ses articles (tableau)
$this->_dao->update(
    ['login' => 'Bob'],
    ['login' => 'Robert']
);

// renomme "Bob" en "Robert" dans tous ses articles (objet)
$this->_dao->update(
    $this->_dao->criteria()->equal('login', 'Bob'),
    ['login' => 'Robert']
);

7Gestion du cache

L'utilisation du cache accélère les lectures sur la base de données. Malheureusement, cela peut avoir un effet indésirable : Toutes les requêtes identiques retourneront des résultats identiques pendant la durée de mise en cache. Cela peut être problématique lorsque vous exécutez des requêtes qui s'écrivent à l'identique mais qui doivent retourner des résultats différents (par exemple des sélections dont les résultats sont triés de manière aléatoire, ou si vous devez ponctuellement prendre des données à jour).

Il est donc possible de désactiver momentanément l'utilisation du cache en utilisant la méthode disableCache(). La réactivation se fait en utilisant la méthode enableCache(). Ces deux méthodes retournent l'instance de leur DAO, sauf si un paramètre leur est fourni, qui sera alors retourné.

Exemple d'utilisation :

// désactivation du cache
$this->_dao->disableCache();
// recherche
$result = $this->_dao->search();
// réactivation du cache
$this->_dao->enableCache();

// résultat identique au précédent
$result = $this->_dao->disableCache()->search();
$this->_dao->enableCache();

// résultat identique aux précédents
// ordre d'exécution :
// 1. disableCache()
// 2. search() qui est écrit en paramètre à enableCache()
// 3. enableCache()
// au final, c'est le résultat du search() qui est récupéré,
// car il est retourné par enableCache()
$result = $this->_dao->disableCache()->enableCache($this->_dao->search());