Documentation : DAO personnalisées

Présentation

Comme vous avez pu le voir en introduction et dans la documentation des DAO génériques, Temma fournit un mécanisme qui permet de manipuler très facilement les données d'une table, en passant par des méthodes génériques.

Toutefois, dans le cadre d'un «vrai» projet, vous aurez envie d'aller plus loin. Vous pouvez choisir deux directions, qui peuvent se compléter :

  • Au lieu d'utiliser les méthodes get(), search(), update(), etc., vous pouvez vouloir utiliser vos propres méthodes, qui seront plus simples à utiliser.
  • Vous aurez peut-être besoin de manipuler des données qui sont stockées dans plusieurs tables, et donc de faire des jointures.

Dans un cas comme dans l'autre, le première étape va être de créer votre propre objet DAO, qui va dériver de l'objet \Temma\Dao standard.

Mono-table

Création

Voici un exemple d'objet DAO personnalisé, qui propose simplement une méthode supplémentaire :

class ArticleDao extends \Temma\Dao {
    /**
     * Retourne la liste des articles écrits depuis moins de 24 heures.
     * @return  array  Liste de tableaux associatifs.
     */
    public function getLastArticles() {
        // écrit la date et l'heure correspondant à 24 heures plus tôt
        $yesterday = date('c', time() - 86400);

        // définit le critère de recherche, en utiilisant cette date
        $criteria = $this->criteria()->greaterThan('date', $yesterday);

        // effectue la recherche, en utilisant ce critère de recherche
        $articles = $this->search($criteria);

        // retourne le résultat de cette recherche
        return ($articles);
    }
}
						

Vous pouvez voir que cet objet étend l'objet \Temma\Dao d'une manière très "légère", en n'ajoutant qu'une seule méthode, et que celle-ci il utilise les critères de recherche proposés par Temma.

Pour être utilisable, cet objet devra être enregistré dans un fichier nommé ArticleDao.php, placé dans le répertoire lib/ du projet.
> Pour en savoir plus sur le chargement des objets, voir la documentation de l'autoloader

Configuration

Si vous avez besoin de spécifier un ou plusieurs paramètres de création de la DAO, vous pouvez ajouter les attributs suivants :

  • _disableCache : Mettez-le à true pour désactiver l'utilisation du cache par la DAO.
  • _tableName : Nom de la table que la DAO va utiliser.
  • _dbName : Nom de la base de données qui contient la table.
  • _idField : Nom du champ qui contient la clé primaire.
  • _fields : 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é.
  • _criteriaObject : voir plus bas

Tous ces attributs sont optionnels, et doivent être déclarés avec une visibilité protected.

Exemple d'objet DAO utilisant ces paramètres :

class ArticleDao extends \Temma\Dao {
    // désactivation du cache
    protected $_disableCache = true;
    // configuration du nom de la base de données
    protected $_dbName = 'cms';
    // configuration du nom de la table à utiliser
    protected $_tableName = 'content';
    // configuration du nom du champ contenant la clé primaire
    protected $_idField = 'cid';
    // liste des champs à récupérer, certains étant renommés
    protected $_fields = array(
        'cid'     => 'contentId',
                     'title',
                     'login',
        'content' => 'text',
        'date'    => 'creationDate',
                     'status'
    );

    /* le reste du code */
}
						

Dans cet exemple on peut voir que le cache est désactivé, que c'est la table content de la base cms qui sera utilisée, dont le champ contenant la clé primaire s'appelle cid. On peut voir que les requêtes get() et search() récupèreront 6 colonnes pour chaque ligne de la table (la table a peut-être d'autres colonnes, mais elles ne seront pas lues), et que trois de ces colonnes seront renommées.

Utilisation

Pour utiliser la DAO que nous venons de créer, il faut spécifier son nom dans le contrôleur, en utilisant l'attribut _temmaAutoDao déjà vu auparavant :

class ArticleController extends \Temma\Controller {
    /** Indique que le framework doit créer automatiquement une instance de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Action qui affiche les derniers articles. */
    public function execShowLast() {
        $data = $this->_dao->getLastArticles();
        $this->set('articles', $data);
    }
}
						
Critères évolués

Dans l'exemple que nous avons vu précédemment, il faut bien comprendre qu'il est toujours possible d'utiliser les méthodes standard de l'objet \Temma\Dao : get(), search(), update(), et ainsi de suite.

Par exemple, on pourrait écrire le contrôleur ainsi :

class ArticleController extends \Temma\Controller {
    /** Indique que le framework doit créer automatiquement une instance de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Action qui affiche les derniers articles. */
    public function execShowLast() {
        $data = $this->_dao->getLastArticles();
        $this->set('articles', $data);
    }

    /** Action qui affiche les 5 plus vieux articles qui ne sont pas vides. */
    public function execShowOlders() {
        $criteria = $this->_dao->criteria()->different('content', '');
        $data = $this->_dao->search($criteria, 'date', null, 5);
        $this->set('articles', $data);
    }
}
						

Vous pouvez voir que la première action utilise la nouvelle méthode getLastArticles(), alors que la deuxième action utilise la méthode search() habituelle.

La création de méthodes spécifiques (telle la méthode getLastArticles() dans notre exemple) peut être assez fastidieuse. À la place, on peut préférer une autre manière de faire, qui est d'étendre l'objet de gestion des critères. En procédant ainsi, on peut continuer à utiliser les méthodes standard (search(), update()) de l'objet \Temma\Dao, mais en écrivant des critères de recherche plus explicites car plus proches de la logique métier.

Création de critères

Créons un objet ArticleDaoCriteria, qui hérite de l'objet \Temma\DaoCriteria, et ajoutons-y des critères spécifiques :

class ArticleDaoCriteria extends \Temma\DaoCriteria {
    /** Critère qui prend les articles ayant été écrits il y a moins de 24 heures. */
    public function mostRecent() {
        // création d'un critère simple
        $yesterday = date('c', time() - 86400);
        $this->greaterThan('date', $yesterday);

        // IMPORTANT : toujours retourner l'instance de l'objet courant
        return ($this);
    }

    /** Critère qui prend les articles parlant de PHP ou de Linux. */
    public function aboutRealStuff() {
        // en cas de critères multiples, il faut créer un sous-objet de critères,
        // en spécifiant le type de combinaison entre les critères
        $subCrit = $this->subCriteria('or')
                   ->like('title', '%PHP%')
                   ->like('title', '%Linux%');

        // on ajoute ensuite ce critère
        $this->_addCriteria($subCrit);

        return ($this);
    }
}
						

Pour être utilisable, cet objet devra être enregistré dans un fichier nommé ArticleDaoCriteria.php, placé dans le répertoire lib/ du projet.
> Pour en savoir plus sur le chargement des objets, voir la documentation de l'autoloader

Configuration et utilisation des critères

Pour utiliser le nouvel objet de critères, nous devons spécifier son nom à l'objet DAO personnalisé :

class ArticleDao extends \Temma\Dao {
    // configuration du critère
    protected $_criteriaObject = 'ArticleDaoCriteria';
}
						

Maintenant que ce critère a été créé, nous pouvons l'utiliser dans notre contrôleur :

01 class ArticleController extends \Temma\Controller {
02     /** Indique que le framework doit créer automatiquement une instance de ArticleDao. */
03     protected $_temmaAutoDao = 'ArticleDao';
04 
05     /** Action qui affiche les derniers articles. */
06     public function execShowLast() {
07         $data = $this->_dao->search($this->_dao->criteria()->mostRecent());
08         $this->set('articles', $data);
09     }
10 
11     /** Action qui affiche les articles récents qui parlent de PHP ou de Linux. */
12     public function execGoodNews() {
13         $data = $this->_dao->search($this->_dao->criteria()
14                                     ->mostRecent()
15                                     ->aboutRealStuff()
16         );
17         $this->set('articles', $data);
18     }
19 }
						

Ligne 3 : On configure la DAO pour qu'elle utilise l'objet DAO personnalisé ArticleDao.
Ligne 7 : On utilise la méthode search() habituelle, mais avec le nouveau critère mostRecent(), pour récupérer les nouveaux articles.
Lignes 13 à 15 : On utilise encore la méthode search(), avec les nouveaux critères mostRecent() et aboutRealStuff().

L'avantage de cette manière de faire est évident. Quand on utilise le critère aboutRealStuff(), on n'a pas à se soucier des détails de son implémentation.

Ligne 4 : On configure la DAO pour qu'elle utilise l'objet de critère ArticleDaoCriteria.
Ligne 9 : On utilise la méthode search() habituelle, mais avec le nouveau critère mostRecent(), pour récupérer les nouveaux articles.
Lignes 15 à 17 : On utilise encore la méthode search(), avec les nouveaux critères mostRecent() et aboutRealStuff().

Configuration sans objet DAO personnalisé

Dans le cas où vous n'avez pas créé d'objet DAO personnalisé, vous pouvez configurer directement l'objet de critères au niveau du contrôleur.

class ArticleController extends \Temma\Controller {
    /** Indique que le framework doit utiliser l'objet de critères ArticleDaoCriteria. */
    protected $_temmaAutoDao = array(
        'criteria' => 'ArticleDaoCriteria'
    );

    /** reste du code... */
}
						

L'utilisation de l'attribut _temmaAutoDao permet d'ajouter d'autres paramètres spécifiques (cf. documentation des DAO génériques).

Multi-tables

Dans le cas où vous avez besoin de faire des jointures, on atteint les limites de la simplification autorisée par les DAO. Nous vous conseillons alors d'écrire vos propres requêtes SQL, vous aurez alors accès à une liberté totale.

Dans ce cas, il est aussi conseillé de désactiver l'utilisation du cache, pour ne pas risquer d'avoir de grosses incohérences de données.

Voici un exemple simple de DAO qui fait des requêtes sur la table article avec une jointure sur la table user :

class ArticleDao extends \Temma\Dao {
    // désactivation du cache
    protected $_disableCache = true;

    /**
     * Retourne la liste des articles, avec les informations sur leurs auteurs,
     * les plus récents en premier.
     * @return  array  Liste de tableaux associatifs.
     */
    public function getArticles() {
        $sql = 'SELECT *
                FROM article
                    INNER JOIN user ON (article.user_id = user.id)
                ORDER BY article.date DESC';
        $articles = $this->_db->queryAll($sql);
        return ($articles);
    }

    /**
     * Retourne les articles qui appartiennent à un utilisateur, à partir de
     * son adresse email.
     * @param  string  $email  Adresse email de l'auteur.
     * @return array   Liste de tableaux associatifs.
     */
    public function getArticlesFromEmail($email) {
        $sql = "SELECT *
                FROM article
                    INNER JOIN user ON (article.user_id = user.id)
                WHERE user.email = '" . $this->_db->quote($email) . "'
                ORDER BY article.date DESC";
        $articles = $this->_db->queryAll($sql);
        return ($articles);
    }
}
						

Le contrôleur est alors bien simple :

class ArticleController extends \Temma\Controller {
    /** Indique que le framework doit créer automatiquement une instance de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

    /** Action qui affiche tous les articles. */
    public function execShowAll() {
        $data = $this->_dao->getArticles();
        $this->set('articles', $data);
    }

    /**
     * Action qui recherche les articles d'un utilisateur.
     * @param  string  $email  Adresse email de l'utilisateur.
     */
    public function execSearch($email) {
        $data = $this->_dao->getArticlesFromEmail($email);
        $this->set('articles', $data);
    }
}
						
Charger plusieurs DAO

Il arrive souvent qu'un contrôleur ait besoin d'utiliser plusieurs objets DAO durant ses traitements. La méthode loadDao() est faite pour cela ; elle attend un paramètre de configuration, qui peut être de deux types :

  • Une chaîne de caractères contenant le nom de l'objet DAO personnalisé à charger.
  • Un tableau associatif contenant tous les paramètres, comme vu à la page précédente.

Voici un exemple de création de DAO multiple, utilisant des DAO personnalisées :

class ArticleController extends \Temma\Controller {
    /** Indique que le framework doit créer automatiquement une instance de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';
    /** Attribut qui contiendra une instance de \MyApp\UserDao. */
    private $_userDao = null;

    /** Initialisation du contrôleur. */
    public function init() {
        $this->_userDao = $this->loadDao('\MyApp\UserDao');
    }

    /**
     * Action qui recherche les articles d'un utilisateur.
     * @param  string  $email  Adresse email de l'utilisateur.
     */
    public function execSearch($email) {
        // vérification de l'adresse email
        if ($this->_userDao->checkEmail($email) {
            // récupération des données
            $data = $this->_dao->getArticlesFromEmail($email);
            $this->set('articles', $data);
        }
    }
}
						

Voici un second exemple, utilisant un tableau associatif de paramétrage :

class ArticleController extends \Temma\Controller {
    /** Indique que le framework doit créer automatiquement une instance de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';
    /** Attribut qui contiendra une instance de \MyApp\UserDao. */
    private $_userDao = null;

    /** Initialisation du contrôleur. */
    public function init() {
        $this->_userDao = $this->loadDao(array(
            'cache'  => false,
            'base'   => 'cms',
            'table'  => 'content'
        ));
    }

    /** ... suite du code ... */
}