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.
<?php
if ($facture->client_id) echo $facture->client->nom;
?>
Add new comment