Relations Zend_Db_Table

Logo Zend Framework

Zend_Db_Table permet de définir des relations entre les tables. Si par exemple on a une table factures et une table clients :

CREATE TABLE `clients` (
  `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `nom` char(32) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
CREATE TABLE `factures` (
  `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `numero` char(20) NOT NULL,
  `client_id` smallint(5) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `client_id` (`client_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
ALTER TABLE `factures`
  ADD CONSTRAINT `client_ibfk` FOREIGN KEY (`client_id`) 
  REFERENCES `clients` (`id`) ON DELETE CASCADE;

La table factures est liée à la table client par la clé étrangère client_id. C'est une relation un-vers-N (un client peut être associé à plusieurs factures).

Avec Zend_Db_Table, on peut définir cette relation dans le modèle à l'aide du tableau _referenceMap :

class Model_Factures extends Zend_Db_Table_Abstract
{    
    protected $_referenceMap = array(
        'client' => array(
            'columns' => 'client_id',
            'refTableClass' => 'Model_Clients'  
        )
    );
}

columns correspond à la clé étrangère (donc à une ou plusieurs colonnes du modèle courant). refTableClass correspond à la classe dérivée de Zend_Db_Table_Abstract qui représente la table clients.

Une fois cette structure mise en place, on peut retrouver le client lié à une facture à l'aide de la méthode findParentRow de Zend_Db_Table_Row_Abstract :

$client = $facture->findParentRow('Model_Clients');
echo $client->nom;

On peut utiliser également une méthode "magique" :

$client = $facture->findParentModel_Clients();
echo $client->nom;

Tout ça n'est pas très joli. Ne serait ce pas mieux si on pouvait faire tout simplement :

echo $facture->client->nom;

Le pire, c'est que ce n'est pas vraiment pas très compliqué à mettre en place. Il suffit de créer une classe dérivée de Zend_Db_Table_Row_Abstract et de surcharger sa méthode magique __get :

class Wiip_Db_Table_Row_Abstract extends Zend_Db_Table_Row_Abstract 
{
    public function __get($columnName)
    {
        if (!isset($this->_data[$columnName])) {
            $referenceMap = $this->_table->info('referenceMap');
            if (isset($referenceMap[$columnName])) {
               $this->_data[$columnName] = $this->findParentRow(
                   $referenceMap[$columnName]['refTableClass']
               );
               return $this->_data[$columnName];
            }
        }
        return parent::__get($columnName);
    }
}

Mise à jour du 18/07 : dans ma première version, j'avais choisi de stocker l'objet parent dans une propriété publique pour éviter les appels à la méthode __set, mais cela ne fonctionne pas car l'affectation entraine l'appel de la méthode magique __set. Cette dernière échoue car le nom de la règle ne correspond pas à une colonne de la table.

Mise à jour du 22/07 : finalement, ça ne marche toujours pas.

Cette méthode est similaire à celle que je vous avais présenté dans un précédent article pour transformer les colonnes de type date en objet Zend_Date.

On récupère la table des références, puis on regarde si il y a une règle qui correspond au nom de la colonne demandée. Si c'est le cas, on récupère l'enregistrement avec findParentRow et on le stocke dans le tableau _data. De cette façon, si la colonne est demandée une deuxième fois, la méthode __get ne sera plus appelée.

Attention cependant, il ne faut pas utiliser cette technique dans une boucle, car il y aurait une ou plusieurs requêtes SQL exécutées à chaque itération. Dans ce cas, il vaut mieux utiliser une jointure SQL pour récupérer les colonnes des enregistrements parents (et on peut également utiliser les relations Zend_Db_Table pour faciliter l'opération). Je vous expliquerai ça dans un prochain article.

Si la relation est optionnelle, on va alors autoriser les valeurs NULL dans la colonne client_id. Dans ce cas, il faut tester cette colonne avant d'essayer d'accéder à l'objet. Par exemple :
if ($facture->client_id) echo $facture->client->nom;

Poster un nouveau commentaire

Le contenu de ce champ ne sera pas montré publiquement. If you have a Gravatar account, used to display your avatar.
  • Les adresses de pages web et de messagerie électronique sont transformées en liens automatiquement.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. The supported tag styles are: <foo>, [foo].

Plus d'informations sur les options de formatage

CAPTCHA
La vérification ne tient pas compte des minuscules ou des majuscules.
Image CAPTCHA
Enter the characters shown in the image.