DAO personnalisées


1Pré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\Dao standard.


2Mono-table

2.1Création

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

class ArticleDao extends \Temma\Dao\Dao {
    /**
     * Retourne la liste des articles écrits depuis moins d'un jour.
     * @return  array  Liste de tableaux associatifs.
     */
    public function getLastArticles() {
        // 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
        $articles = $this->search($criteria);

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

Vous pouvez voir que cet objet étend l'objet \Temma\Dao\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.


2.2Configuration

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\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 = [
        '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.


2.3Utilisation

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 Article extends \Temma\Web\Controller {
    /** Indique que le framework doit créer automatiquement
     * une instance de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

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

3Critè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\Dao : get(), search(), update(), et ainsi de suite.

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

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

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

    /** Action qui affiche les 5 plus vieux articles
     *  qui ne sont pas vides. */
    public function showOlders() {
        $crit = $this->_dao->criteria()->different('content', '');
        $data = $this->_dao->search($crit, sort: 'date', offset: null, limit: 5);
        $this['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\Dao, mais en écrivant des critères de recherche plus explicites car plus proches de la logique métier.


3.1Création de critères

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

class ArticleDaoCriteria extends \Temma\Dao\Criteria {
    /** 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);

        // 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 combinés par un "OU" logique
        // (et non pas un "ET" logique), 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


3.2Configuration 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\Dao {
    // configuration du critère
    protected $_criteriaObject = 'ArticleDaoCriteria';
}

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

class Article extends \Temma\Web\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->search(
            $this->_dao->criteria()->mostRecent()
        );
        $this['articles'] = $data;
    }

    /** Action qui affiche les articles récents qui parlent
     *  de PHP ou de Linux. */
    public function execGoodNews() {
        $data = $this->_dao->search(
            $this->_dao->criteria()
            ->mostRecent()
            ->aboutRealStuff()
        );
        $this['articles'] = $data;
    }
}
  • Ligne 4 : On configure la DAO pour qu'elle utilise l'objet DAO personnalisé ArticleDao.
  • Ligne 8 : On utilise la méthode search() habituelle, mais avec le nouveau critère mostRecent(), pour récupérer les nouveaux articles.
  • Lignes 17 à 20 : 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.


3.3Configuration 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 Article extends \Temma\Web\Controller {
    /** Indique que le framework doit utiliser l'objet
     *  de critères ArticleDaoCriteria. */
    protected $_temmaAutoDao = [
        '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).


4Multi-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\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 Article extends \Temma\Web\Controller {
    /** Indique que le framework doit créer automatiquement
     *  une instance de ArticleDao. */
    protected $_temmaAutoDao = 'ArticleDao';

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

    /**
     * Action qui recherche les articles d'un utilisateur.
     * @param  string  $email  Adresse email de l'utilisateur.
     */
    public function search($email) {
        $data = $this->_dao->getArticlesFromEmail($email);
        $this['articles'] = $data;
    }
}

5Charger 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 Article extends \Temma\Web\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['articles'] = $data;
        }
    }
}

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

class Article extends \Temma\Web\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([
            'cache'  => false,
            'base'   => 'cms',
            'table'  => 'content',
        ]);
    }

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