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 :

<?php
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 :

<?php
$client 
$facture->findParentRow('Model_Clients');
echo 
$client->nom;
?>

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

<?php
$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 :

<?php
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 :

<?php
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 :
<?php
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 associated with the e-mail address you provide, it will be used to display your avatar.
CAPTCHA
La vérification ne tient pas compte des minuscules ou des majuscules.
Image CAPTCHA
Enter the characters shown in the image.