Auto-Jointure
Dans mon article « Ajouter un champ calculé dans une table. » Je parlais d’une méthode pour ajouter des champs dans les objets row issue d’une table qui ne les contient pas base.
La version 1.5 de ZF a introduit une nouvelle classe qui facilite ce genre de manipulation.
L’Auto Jointure simple
Cette petite nouveauté introduit de façon claire les select qui sous-tend la classe Zend_Db_Table. Pour voir comment cette introduction change la donne je me suis posé le problème de l’auto jointure. J’ai une table principale et une où plusieurs table de références.
Le but de l’autojointure et de remonter avec les éléments de la table les valeurs de références. Par exemple les éléments de ma table principale sont typés, il contient un id sur la table de référence des types. Je veux lorsque je lis un enregistrement pouvoir remonter le label de sont type sans avoir systématiquement à faire une deuxième requête.
Les données complémentaires étant des données de référence leur valeur ne change pas (pas lorsqu’on manipule un élément de la table principale) je ne poserais donc pas ici le problème de l’écriture en base.
Zend_Db_Table_Select
Voyons comment fonction les classes de Zend_Db lorsque nous lisons dans la table. L’appel des méthodes fetch, fetchCols, fetchRow, fetchAll, et find font tous appels à _fetch. Dans les versions antérieures à la 1.5 la méthode _fetch recevais les paramètres de la requête. Where et autres order ou limit. La méthode _fetch fabriquait donc un select avec $this->db->select() et y appliquait les clauses adéquates.
Avec la version 1.5 les fonctions suscitées vont-elles-même créer l’objet select et lui ajouter les clauses nécessaires. Pour éviter que chaque fonction réinvente la roue elles ne vont pas créer un simple select mais un Zend_Db_Table_Select dont la base est simplement
Select * From tablename. Pour cela une nouvelle méthode à été ajouté à Zend_Db_Table : select il est donc possible de demander à la table un select préconfiguré pour le manipuler à sa sauce. Nous allons donc nous arranger pour que ce select ne soit pas un simple Zend_Db_Table_Select mais pour qu’il ajoute automatiquement une ou plusieurs jointures à la table.
Fast_Db_Table
Une classe table acceptant l’autojointure. Notre but est de définir une classe comme Zend_Db_Table_Abstract que nous pourrons dériver pour la mapper sur les éléments de la base. On va donc introduire un membre qui listera les jointures à effectuer dans les requêtes. Et tant que nous y sommes nous allons ajouter des restrictions automatique (clauses where ajouté systématiquement)
-
/**
-
* Definition de base d’une table Fast
-
* elle étend la classe Zend_Table et lui adjoint un classe spécifique pour les enregistrement
-
* ainsi que les méthode courantes d’accès au données
-
*
-
* @see Zend_Db_Table
-
* @see Fast_Exception_Db
-
* @see Fast_Db_Row
-
* @author Jean-Yves Terrien
-
*/
-
Class Fast_Db_Table extends Zend_Db_Table_Abstract {
-
-
const FAST_RESTRICT = ‘fast_restrict’;
-
const FAST_AUTOJOIN = ‘fast_autojoin’;
-
/**
-
* Classname for select , Zend_Db_Table_Select,…
-
*
-
* @var string
-
*/
-
protected $_selectClass = ‘Fast_Db_Table_Select’;
-
/**
-
* Restriction for query
-
*
-
* @var string
-
*/
-
protected $_restrict = null;
-
/**
-
* Auto Joined table for query
-
*
-
* @var string
-
*/
-
protected $_autojoin = NULL;
-
/**
-
* Returns an instance of a Zend_Db_Table_Select object.
-
*
-
* @return Zend_Db_Table_Select
-
*/
-
public function select()
-
{
-
if (‘Zend_Db_Table_Select’ == $this->_selectClass) {
-
$select = parent::select();
-
} else {
-
Zend_Loader::loadClass($this->_selectClass);
-
$select = new $this->_selectClass($this);
-
}
-
return $select;
-
}
-
/**
-
* Returns table information.
-
*
-
* @return array
-
*/
-
public function info()
-
{
-
$info = parent::info();
-
$info[self::FAST_RESTRICT] = $this->_restrict;
-
$info[self::FAST_AUTOJOIN] = $this->_autojoin;
-
return $info;
-
}
-
}
Voilà la base de notre classe on va lui indiquer la classe Select à utiliser (il faudra qu’elle dérive de Zend_Db_Table_Select) cela permettra de la surcharger. On redéfinit la méthode Select() pour tenir compte de notre classe Select et on redéfini la méthode info pour que la classes Select connaisse les jointures à faire.
La définition de la requête étant faite dans la classe Select c’est tout pour la classe Table.
Fast_Db_Table_Select
Un select pour table à auto jointure.
Il est un peu étonnant de voir que la classe Zend_Db_Table_Select ne crée pas la partie from de la table par défaut. Elle ne le fait qu’au moment de la transformation en chaine. Du coup si on tente de faire le join avant on obtient une exception. Il faut donc respecter cette mécanique pour nous insérer au mieux dans ZF
-
class Fast_Db_Table_Select extends Zend_Db_Table_Select
-
{
-
private $_autojoined = false;
-
private $_useRestrict = true;
-
/**
-
* Performs a validation on the select query before passing back to the parent class.
-
* Ensures that only columns from the primary Zend_Db_Table are returned in the result.
-
*
-
* @return string This object as a SELECT string.
-
*/
-
public function __toString()
-
{
-
if (!$this->_autojoined) {
-
$this->_autojoined = true;
-
-
$fields = $this->getPart(Zend_Db_Table_Select::COLUMNS);
-
$primary = $this->_info[Zend_Db_Table_Abstract::NAME];
-
$schema = $this->_info[Zend_Db_Table_Abstract::SCHEMA];
-
-
// If no fields are specified we assume all fields from primary table
-
$this->from($primary, ‘*’, $schema);
-
$fields = $this->getPart(Zend_Db_Table_Select::COLUMNS);
-
}
-
-
if ($this->_useRestrict) {
-
$restricts[] = $this->_info[Fast_Db_Table::FAST_RESTRICT];
-
$restricts = $this->_info[Fast_Db_Table::FAST_RESTRICT];
-
} else {
-
}
-
foreach ($restricts as $restrict) {
-
$this->where($restrict);
-
}
-
}
-
$this->setIntegrityCheck(false);
-
foreach ($this->_info[Fast_Db_Table::FAST_AUTOJOIN] as $join) {
-
$this->join($join[‘table’], $join[‘on’], $join[‘fields’]);
-
}
-
}
-
}
-
}
-
return parent::__toString();
-
}
-
/**
-
@function setRestrict()
-
@param boolean $restrict Use restriction for this select
-
@return Fast_Db_Table_Select Description
-
*/
-
function setRestrict($restrict) {
-
$this->_useRestrict = $restrict;
-
return $this;
-
} // end function setRestrict
-
}
Le premier membre private $_autojoined indique que la jointure à déjà été faite. Vu que nous l’ajoutons lors de la transformation en chaine il ne faudrait pas que le soit plusieurs fois. Un simple echo sur l’objet appelle __toString()
Le second private $_useRestrict = true indique que nous devons ou pas utiliser les clauses restrictives
La méthode setRestrict permet de changer ce mode.
La méthode __toString est semblable à celle de sa classe parente elle ne fait que parcourir le tableau d’auto jointure pour ajouter les clauses join.
Une utilisation
La classe user. Nous allons remonter le champ label du profil en même temps que l’utilisateur.
-
Class Adm_Model_User_Table extends Fast_Db_Table {
-
/**
-
* The table name.
-
*
-
* @var array
-
*/
-
protected $_name = ‘user’;
-
-
/**
-
* Classname for row
-
*
-
* @var string
-
*/
-
protected $_rowClass = ‘Adm_Model_User_Row’;
-
/**
-
* Auto Joined table for query
-
*
-
* @var string
-
*/
-
‘on’ => ‘profile.prf_id = user.prf_id’,
-
)
-
);
-
/**
-
* Restriction for query
-
*
-
* @var string
-
*/
-
}
Lorsque nous utiliserons cette classe la requête générée par défaut sera
-
Select
-
user.*,
-
profile.prf_label
-
FROM user
-
WHERE profile.prf_valid = 1
Conclusion
Zend_Db_Table_Select apporte une ouverture nouvelle pour adapter au mieux à ses besoins le mapping objet de ZF. Les classes ci-dessus ne sont que des premières versions. Elles sont grandement améliorables. Par exemple pour gérer les jointures plus complexes comme left rigth mais aussi avec des clauses de recoupement etc.