Blog // Exirel.me

Multi-Db avec Zend

Par Florian Strzelecki - 20:20 - 07.10.2011

Tags : loldev, Framework, Web, Zend Framework, Technique, Documentation, Programmation, SQL, PHP

Depuis que je travaille avec le Zend Framework (et ce n'est définitivement pas par passion ni envie), je ne passe pas une semaine sans avoir besoin d'aller voir dans le code source du framework pour comprendre ce qu'il fait, pourquoi, comment, et en quel honneur.

Et généralement, je me marre - enfin pas vraiment, mais faites comme si.

Cette semaine pour le #loldev du vendredi, c'est la documentation qui m'a donné l'info qui me manquait pour résoudre un problème qui n'est pas trivial à l'origine, mais qui devrait l'être avec un framework web digne de ce nom : comment gérer une application qui doit se connecter à différentes bases de données ?

En voilà une question intéressante... voici ma réponse.

Pages and pages of source code.

Image : Pages and pages of source code. - Neil Crosby (http://www.flickr.com/photos/thevoicewithin/) - Creative-Common By-NC-SA

Le contexte et le problème

Le contexte est une application qui présente des listes de données. Il y a deux sources différentes pour cela : une base V1, et une base V2, suite à une migration interrompue. Cela entraîne d'ailleurs tout un tas de questions et de problèmes, mais ils ne concernent pas ce billet, qui se concentre uniquement sur le fait d'avoir deux bases de données.

Sur certaines pages de l'application, une seule connexion est nécessaire, et tout va bien. Sur d'autres, il faut alternativement utiliser l'une ou l'autre des connexions, et que, bien entendu, cela fonctionne sans problème ni perte de connexion (ou taper dans une base alors qu'on devrait taper sur une autre).

À cela s'ajoute le découpage en couche : les contrôleurs-vue d'un côté, la couche de données de l'autre, avec ses managers responsables de l'accès aux données. On considère que ce n'est pas aux contrôleurs de savoir comment configurer l'accès aux données, notamment parce que la librairie en charge de cette partie est partagée entre plusieurs applications (qui, eux, n'utilisent pas forcément l'ensemble du Zend Framework, comme les contrôleurs) (question de performances d'ailleurs... ça pourrait être un sujet à part entière).

Bref, nous avons une couche de code qui doit connaître sa configuration toute seule dans son coin, et cette configuration ne doit pas empiéter sur les autres.

Configuration

La configuration de plusieurs connexions à des bases de données différentes est la partie la plus "simple" : il suffit d'utiliser la ressource Multi-Db. Elle fonctionne globalement comme la ressource Db de Zend Framework, et là dessus, rien de particulier.

Il y a juste un détail... comment configurer l'utilisation d'une connexion ou d'une autre en fonction du manager ? Pour rappel, les managers ne peuvent pas dépendre des ressources, puisque c'est une librairie à part, qui ne se trouve liée ni au bootstrap, ni au contrôleur.

Il faut donc trouver une solution à ce problème. Une solution simple et qui ne demande pas de ré-écrire un gestionnaire de connexion, évidement.

Un cas idéal...

Le cas idéal, ce serait que Zend Framework propose un paramètre dans Zend_Db_Table_Abstract, qui permette de configurer nom seulement le nom de la table, la clé primaire, les classes de Rowset et de Row, mais aussi, le nom de la connexion à la base !

En fouillant un peu, j'ai trouvé qu'il était possible de configurer... le nom de la base. C'est bien, si vos connexions sont toutes vers le même serveur de données... ce qui n'est pas mon cas - et il n'y a pas de raison particulièrement pour que ce soit le cas, ce serait même plutôt l'inverse (ça pourrait être le cas notamment d'un système Master/Slave par exemple).

Autre possibilité : que la Ressource MultiDb propose un mécanisme pour obtenir les connexions (dans le registre par exemple, ou via une classe singleton ?). Sauf que non, là non plus il n'y a rien, et comme il est hors de question que les managers dépendent du Bootstrap ou des Contrôleurs, c'est mort de ce côté là...

Alors je suis parti au début sur l'idée de créer une classe singleton qui va connaître les connexions aux bases via une configuration par une ressource particulière. Rien que là, je sens venir le truc un peu compliqué... alors je suis allé voir comment est-ce que les Zend_Db_Table_Abstract faisait pour choisir leur connexion...

La solution finale

Et là, surprise ! Il existe un vieux mécanisme qui va se révéler être la solution que je cherche. Il s'agit du bout de code suivant :

protected static function _setupAdapter($db)
{
    if ($db === null) {
        return null;
    }
    if (is_string($db)) {
        require_once 'Zend/Registry.php';
        $db = Zend_Registry::get($db);
    }
    if (!$db instanceof Zend_Db_Adapter_Abstract) {
        require_once 'Zend/Db/Table/Exception.php';
        throw new Zend_Db_Table_Exception('Argument must be of type Zend_Db_Adapter_Abstract, or a Registry key where a Zend_Db_Adapter_Abstract object is stored');
    }
    return $db;
}

Et plus précisément, le cas où $db est une chaîne de caractère : Zend_Db_Table_Abstract va chercher dans le registre la connexion à la base de données qui le concerne ! D'ailleurs, j'ai finalement trouvé le bout de documentation qui en parle. Il ne me reste plus qu'à :

Sauf que je tombe sur un os, puisque la ressource Multi-Db ne met rien en registre. Ah ah. Ah ah ah. Comment devenir blaser en deux minutes. D'autant plus qu'il n'y a rien dans la documentation qui explique comment gérer cela simplement (j'en viens parfois à me demander comment font les autres dev, et pourquoi ce problème n'a jamais été résolu par le framework).

Bref, me revoilà à écrire une ressource (pour pouvoir la partager entre plusieurs applications, sinon je serais passé par le bootstrap !) pour obtenir la liste des connexions configurées avec multi-db, et les ajouter au Registre.

Et voilà. C'est tout.

Conclusion

Alors, ce billet est un peu long, et ce n'est pas un #loldev pur, au sens que c'est seulement un truc à trouver et à faire soi-même. Mais justement : j'estime qu'un framework de cette taille et avec le contexte qu'il apporte avec lui, se doit de proposer une solution plus simple pour gérer ça.

La première chose à faire, ce serait que multi-db mette de lui-même ça dans le registre. Ou alors, qu'il y ait quelque chose dans Zend Framework qui permette facilement et sans dépendre d'autres composants que Zend_Db, de configurer cela.

Parce que devoir chercher dans le code et la documentation (ce qui est juste pénible vu ce que ça représente), pour résoudre un problème trivial en 2011, c'est juste une perte de temps inutile.

loldev