Zend_Db_Table-Relationships.xml 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!-- EN-Revision: 13846 -->
  3. <!-- Reviewed: no -->
  4. <sect1 id="zend.db.table.relationships">
  5. <title>Relations Zend_Db_Table</title>
  6. <sect2 id="zend.db.table.relationships.introduction">
  7. <title>Introduction</title>
  8. <para>Les tables possèdent des relations entre elles, dans une base de données relationnelle. Une entité d'une
  9. table peut être liée à une autre entité d'une autre table, via un procédé appelé contrainte d'intégrité
  10. référentielle</para>
  11. <para>La classe <classname>Zend_Db_Table_Row</classname> possède des méthodes pour récupérer des enregistrement dans
  12. d'autres tables, liées à celle en cours.</para>
  13. </sect2>
  14. <sect2 id="zend.db.table.relationships.defining">
  15. <title>Définir ses relations</title>
  16. <para>Chaque table doit avoir sa classe étendant <classname>Zend_Db_Table_Abstract</classname>, comme décrit dans <xref
  17. linkend="zend.db.table.defining" />. Voyez aussi <xref linkend="zend.db.adapter.example-database" /> pour une
  18. description de la base de donnée qui servira d'exemple pour la suite de ce chapitre.</para>
  19. <para>Voici les classes correspondantes à ces tables :</para>
  20. <programlisting role="php"><![CDATA[
  21. class Accounts extends Zend_Db_Table_Abstract
  22. {
  23. protected $_name = 'accounts';
  24. protected $_dependentTables = array('Bugs');
  25. }
  26. class Products extends Zend_Db_Table_Abstract
  27. {
  28. protected $_name = 'products';
  29. protected $_dependentTables = array('BugsProducts');
  30. }
  31. class Bugs extends Zend_Db_Table_Abstract
  32. {
  33. protected $_name = 'bugs';
  34. protected $_dependentTables = array('BugsProducts');
  35. protected $_referenceMap = array(
  36. 'Reporter' => array(
  37. 'columns' => 'reported_by',
  38. 'refTableClass' => 'Accounts',
  39. 'refColumns' => 'account_name'
  40. ),
  41. 'Engineer' => array(
  42. 'columns' => 'assigned_to',
  43. 'refTableClass' => 'Accounts',
  44. 'refColumns' => 'account_name'
  45. ),
  46. 'Verifier' => array(
  47. 'columns' => array('verified_by'),
  48. 'refTableClass' => 'Accounts',
  49. 'refColumns' => array('account_name')
  50. )
  51. );
  52. }
  53. class BugsProducts extends Zend_Db_Table_Abstract
  54. {
  55. protected $_name = 'bugs_products';
  56. protected $_referenceMap = array(
  57. 'Bug' => array(
  58. 'columns' => array('bug_id'),
  59. 'refTableClass' => 'Bugs',
  60. 'refColumns' => array('bug_id')
  61. ),
  62. 'Product' => array(
  63. 'columns' => array('product_id'),
  64. 'refTableClass' => 'Products',
  65. 'refColumns' => array('product_id')
  66. )
  67. );
  68. }
  69. ]]></programlisting>
  70. <para>Si vous utilisez <classname>Zend_Db_Table</classname> pour émuler les cascades <code>UPDATE</code> et
  71. <code>DELETE</code>, alors déclarez <code>$_dependentTables</code> en tant que tableau dans la classe des tables
  72. parentes. Listez ainsi le nom de chaque table dépendante. Utilisez bien le nom des classes, et non les noms
  73. physiques des tables.</para>
  74. <note>
  75. <para>Si votre SGBD implémente le mécanisme des cascades, alors vous n'avez pas besoin de déclarer
  76. <code>$_dependentTables</code>. Voyez <xref linkend="zend.db.table.relationships.cascading" /> pour plus
  77. d'informations.</para>
  78. </note>
  79. <para>Déclarez un tableau <code>$_referenceMap</code> dans les classes de chaque table dépendante (qui "reçoit
  80. une clé"). C'est un tableau associatif, dit de "rôles". Un rôle définit quelle table est parente dans la
  81. relation, et quelle est sa colonne de parenté.</para>
  82. <para>Le rôle est utilisé comme index du tableau <code>$_referenceMap</code>. Il est utilisé pour définir la
  83. relation, et pourra faire partie du nom de certaines méthodes, comme nous le verrons plus tard. Choisissez ainsi
  84. un nom de rôle de manière intelligente.</para>
  85. <para>Dans l'exemple du dessus, les rôles dans la classe Bugs sont : <code>"Reporter"</code>,
  86. <code>"Engineer"</code>, <code>"Verifier"</code> et <code>"Product"</code>.</para>
  87. <para>La valeur de chaque rôle dans le tableau <code>$_referenceMap</code> est aussi un tableau associatif. Les
  88. éléments de chaque rôle sont décrits ci-après.</para>
  89. <itemizedlist>
  90. <listitem>
  91. <para><emphasis role="strong">columns</emphasis> =&gt; une chaîne de caractères ou un tableau de chaînes
  92. désignant le(s) nom(s) des clés étrangères dans la table dépendante (la table actuelle donc).</para>
  93. <para>Il est courant qu'il s'agisse d'une seule colonne, mais on peut rencontrer le cas de clés
  94. composées de multiples colonnes.</para>
  95. </listitem>
  96. <listitem>
  97. <para><emphasis role="strong">refTableClass</emphasis> =&gt; désigne la classe de la table parente, liée
  98. à cette colonne. Utilisez le nom de la classe et non le nom de la table physique.</para>
  99. <para>Il est courant qu'une table dépendante n'ait qu'une seule référence d'une même table parente.
  100. Cependant certaines tables peuvent avoir plusieurs références vers une même table parente. Dans notre
  101. base de données d'exemple, c'est le cas avec la table <code>bugs</code>. Elle possède soit une et une
  102. seule colonne référençant la table parente <code>products</code>, mais elle possède trois références
  103. (donc trois colonnes) vers la table parente <code>accounts</code>. Chaque référence doit être
  104. matérialisée par un rôle unique dans le tableau <code>$_referenceMap</code>.</para>
  105. </listitem>
  106. <listitem>
  107. <para><emphasis role="strong">refColumns</emphasis> =&gt; c'est une chaîne de caractères ou un tableau
  108. de chaînes nommant la(es) colonne(s) (clé primaire) de la table parente.</para>
  109. <para>Si vous devez utiliser de multiples colonnes parentes pour une seule clé, alors veillez à bien les
  110. entrer dans <code>'columns'</code> dans le même ordre que dans <code>'refColumns'</code>.</para>
  111. <para>Il est optionnel de spécifier la <code>refColumns</code>. La clé primaire est utilisée par défaut
  112. comme colonne parente dans une relation.</para>
  113. </listitem>
  114. <listitem>
  115. <para><emphasis role="strong">onDelete</emphasis> =&gt; le nom de l'action à exécuter si un
  116. enregistrement est supprimé de la table parente. Voyez <xref
  117. linkend="zend.db.table.relationships.cascading" /> pour plus d'informations.</para>
  118. </listitem>
  119. <listitem>
  120. <para><emphasis role="strong">onUpdate</emphasis> =&gt; le nom de l'action à exécuter si un
  121. enregistrement est mis à jour dans la table parente. Voyez<xref
  122. linkend="zend.db.table.relationships.cascading" /> pour plus d'informations.</para>
  123. </listitem>
  124. </itemizedlist>
  125. </sect2>
  126. <sect2 id="zend.db.table.relationships.fetching.dependent">
  127. <title>Récupérer des enregistrements dépendants (enfants)</title>
  128. <para>Si vous possédez un enregistrement actif (<code>Row</code>), il est possible de récupérer ses enfants
  129. dépendants, si les dépendances ont été déclarées suivant la procédure ci-dessus. Utilisez la méthode :</para>
  130. <programlisting role="php"><![CDATA[
  131. $row->findDependentRowset($table, [$rule]);
  132. ]]></programlisting>
  133. <para>Cette méthode retourne un objet instance de <classname>Zend_Db_Table_Rowset_Abstract</classname>, qui contient tous
  134. les enregistrements (<code>Row</code>) de la table dépendante <code>$table</code> faisant référence à
  135. l'enregistrement actif actuel <code>$row</code>.</para>
  136. <para>Le paramètre <code>$table</code> désigne la table dépendante à utiliser. Ceci peut être une chaîne de
  137. caractères aussi bien qu'un objet de la classe de cette table.</para>
  138. <example id="zend.db.table.relationships.fetching.dependent.example">
  139. <title>Récupérer des enregistrements dépendants</title>
  140. <para>Cet exemple montre comment obtenir un enregistrement actif (objet <code>Row</code>) de la table
  141. <code>Accounts</code>, et comment en récupérer les enfants dépendants de la table <code>Bugs</code>. (les
  142. bugs reportés par ce compte)</para>
  143. <programlisting role="php"><![CDATA[
  144. $accountsTable = new Accounts();
  145. $accountsRowset = $accountsTable->find(1234);
  146. $user1234 = $accountsRowset->current();
  147. $bugsReportedByUser = $user1234->findDependentRowset('Bugs');
  148. ]]></programlisting>
  149. </example>
  150. <para>Le second paramètre <code>$rule</code> est optionnel. Il s'agit du nom du rôle à utiliser depuis le
  151. tableau <code>$_referenceMap</code> de la classe de la table dépendante. Si vous ne le spécifiez pas, le premier
  152. rôle sera utilisé. Il n'y a dans la majorité des cas qu'un seul rôle.</para>
  153. <para>Dans l'exemple ci dessus, nous ne fournissons pas de nom de rôle, le premier est donc pris en
  154. considération, et il s'agit de <code>"Reporter"</code>.</para>
  155. <example id="zend.db.table.relationships.fetching.dependent.example-by">
  156. <title>Récupérer des enregistrements dépendants avec un rôle spécifique</title>
  157. <para>Dans cet exemple nous montrons comment obtenir un enregistrement (<code>Row</code>) depuis la table
  158. <code>Accounts</code>, et comment trouver les <code>Bugs</code> assignés à ce compte (<code>Account</code>).
  159. Nous devrons alors nommer le rôle <code>"Engineer"</code>.</para>
  160. <programlisting role="php"><![CDATA[
  161. $accountsTable = new Accounts();
  162. $accountsRowset = $accountsTable->find(1234);
  163. $user1234 = $accountsRowset->current();
  164. $bugsAssignedToUser = $user1234->findDependentRowset('Bugs',
  165. 'Engineer');
  166. ]]></programlisting>
  167. </example>
  168. <para>Vous pouvez rajouter des critères à vos relations, comme l'ordre ou la limite, ceci en utilisant l'objet
  169. <code>select</code> de l'enregistrement parent.</para>
  170. <para><example id="zend.db.table.relationships.fetching.dependent.example-by-select">
  171. <title>Récupérer des enregistrements dépendants en utilisant un objet
  172. <classname>Zend_Db_Table_Select</classname></title>
  173. <para>Dans cet exemple nous montrons comment obtenir un enregistrement (<code>Row</code>) depuis la
  174. table <code>Accounts</code>, et comment trouver les <code>Bugs</code> assignés à ce compte
  175. (<code>Account</code>), mais limités seulement à trois enregistrements, et ordonnés par nom. Nous
  176. devrons nommer le rôle <code>"Engineer"</code>.</para>
  177. <programlisting role="php"><![CDATA[
  178. $accountsTable = new Accounts();
  179. $accountsRowset = $accountsTable->find(1234);
  180. $user1234 = $accountsRowset->current();
  181. $select = $accountsTable->select()->order('name ASC')
  182. ->limit(3);
  183. $bugsAssignedToUser = $user1234->findDependentRowset('Bugs',
  184. 'Engineer',
  185. $select);
  186. ]]></programlisting>
  187. </example>Vous pouvez récupérer les enregistrements dépendants d'une autre manière. En utilisant les
  188. "méthodes magiques". En effet, <classname>Zend_Db_Table_Row_Abstract</classname> va utiliser la méthode
  189. <code>findDependentRowset('&lt;TableClass&gt;', '&lt;Rule&gt;')</code> si vous appelez sur l'enregistrement une
  190. méthode correspondante à un de ces motifs :</para>
  191. <itemizedlist>
  192. <listitem>
  193. <para><code>$row-&gt;find&lt;TableClass&gt;()</code></para>
  194. </listitem>
  195. <listitem>
  196. <para><code>$row-&gt;find&lt;TableClass&gt;By&lt;Rule&gt;()</code></para>
  197. </listitem>
  198. </itemizedlist>
  199. <para>Dans les motifs ci-dessus, <code>&lt;TableClass&gt;</code> et <code>&lt;Rule&gt;</code> désignent
  200. respectivement le nom de la table dépendante et le rôle à utiliser.</para>
  201. <note>
  202. <para>Certains frameworks tels que Rails pour Ruby, utilise un mécanisme dit d'inflexion, qui permet de
  203. transformer les noms des identifiants (nom de table, de rôle...) d'une certaine manière bien spécifique dans
  204. les méthodes appelées. Cela n'est pas le cas de Zend Framework : vous devez, dans vos méthodes magiques,
  205. utiliser l'orthographe exacte des noms des rôles et classes, tels que vous les définissez.</para>
  206. </note>
  207. <example id="zend.db.table.relationships.fetching.dependent.example-magic">
  208. <title>Récupérer des enregistrements dépendants en utilisant les méthodes magiques</title>
  209. <para>Cet exemple a le même effet que le précédent. Il utilise simplement les méthodes magiques pour
  210. récupérer les enregistrements dépendants.</para>
  211. <programlisting role="php"><![CDATA[
  212. $accountsTable = new Accounts();
  213. $accountsRowset = $accountsTable->find(1234);
  214. $user1234 = $accountsRowset->current();
  215. // Utilise le rôle par défaut (le premier de la liste)
  216. $bugsReportedBy = $user1234->findBugs();
  217. // Utilise un rôle spécifique
  218. $bugsAssignedTo = $user1234->findBugsByEngineer();
  219. ]]></programlisting>
  220. </example>
  221. </sect2>
  222. <sect2 id="zend.db.table.relationships.fetching.parent">
  223. <title>Récupérer l'enregistrement parent</title>
  224. <para>Si vous possédez un enregistrement (<code>Row</code>) dont la table possède une table parente, il est
  225. possible alors de récupérer l'enregistrement parent. Utilisez pour cela la méthode :</para>
  226. <programlisting role="php"><![CDATA[
  227. $row->findParentRow($table, [$rule]);
  228. ]]></programlisting>
  229. <para>La logique veut qu'il ne puisse y avoir qu'un et un seul parent par enregistrement. Ainsi, cette méthode
  230. retourne un objet <code>Row</code> et non un objet <code>Rowset</code></para>
  231. <para>Le premier paramètre <code>$table</code> désigne la table parente. Ceci peut être une chaîne de
  232. caractères, ou un objet instance de la classe de la table parente.</para>
  233. <example id="zend.db.table.relationships.fetching.parent.example">
  234. <title>Récupérer l'enregistrement parent</title>
  235. <para>Cet exemple illustre la récupération d'un enregistrement <code>Bugs</code> ( disons par exemple ceux
  236. avec le statut "NEW"), et l'obtention de l'enregistrement parent correspondant à <code>Accounts</code> (la
  237. personne ayant reporté le bug)</para>
  238. <programlisting role="php"><![CDATA[
  239. $bugsTable = new Bugs();
  240. $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?' => 'NEW'));
  241. $bug1 = $bugsRowset->current();
  242. $reporter = $bug1->findParentRow('Accounts');
  243. ]]></programlisting>
  244. </example>
  245. <para>Le second paramètre <code>$rule</code> est optionnel. Il s'agit du nom du rôle à utiliser depuis le
  246. tableau <code>$_referenceMap</code> de la classe de la table dépendante. Si vous ne le spécifiez pas, le premier
  247. rôle sera utilisé. Il n'y a dans la majorité des cas qu'un seul rôle.</para>
  248. <para>Dans l'exemple ci dessus, nous ne fournissons pas de nom de rôle, le premier est donc pris en
  249. considération, et il s'agit de <code>"Reporter"</code>.</para>
  250. <example id="zend.db.table.relationships.fetching.parent.example-by">
  251. <title>Récupérer un enregistrement parent avec un rôle spécifique</title>
  252. <para>Cet exemple va démontrer comment, à partir d'un enregistrement de <code>Bugs</code>, récupérer la
  253. personne en étant assignée. Il va falloir utiliser le rôle <code>"Engineer"</code>.</para>
  254. <programlisting role="php"><![CDATA[
  255. $bugsTable = new Bugs();
  256. $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?', 'NEW'));
  257. $bug1 = $bugsRowset->current();
  258. $engineer = $bug1->findParentRow('Accounts', 'Engineer');
  259. ]]></programlisting>
  260. </example>
  261. <para>Vous pouvez récupérer l'enregistrement parent d'une autre manière. En utilisant les "méthodes magiques".
  262. En effet, Zend_Db_Table_Row_Abstract va utiliser la méthode<code>findParentRow('&lt;TableClass&gt;',
  263. '&lt;Rule&gt;')</code> si vous appelez sur l'enregistrement une méthode correspondante à un de ces motifs
  264. :</para>
  265. <itemizedlist>
  266. <listitem>
  267. <para><code>$row-&gt;findParent&lt;TableClass&gt;([Zend_Db_Table_Select $select])</code></para>
  268. </listitem>
  269. <listitem>
  270. <para><code>$row-&gt;findParent&lt;TableClass&gt;By&lt;Rule&gt;([Zend_Db_Table_Select
  271. $select])</code></para>
  272. </listitem>
  273. </itemizedlist>
  274. <para>Dans les motifs ci-dessus, <code>&lt;TableClass&gt;</code> et <code>&lt;Rule&gt;</code> représentent
  275. respectivement le nom de la classe de la table parente, et le rôle à utiliser éventuellement.</para>
  276. <note>
  277. <para>Les noms de la table et du rôle doivent être orthographiés de la même manière qu'ils ne le sont lors
  278. de leur définition dans la table.</para>
  279. </note>
  280. <example id="zend.db.table.relationships.fetching.parent.example-magic">
  281. <title>Récupérer un enregistrement parent en utilisant les méthodes magiques</title>
  282. <para>Cet exemple a le même effet que le précédent. Il utilise simplement les méthodes magiques pour
  283. récupérer l'enregistrement parent.</para>
  284. <programlisting role="php"><![CDATA[
  285. $bugsTable = new Bugs();
  286. $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?', 'NEW'));
  287. $bug1 = $bugsRowset->current();
  288. // Utilise le rôle par défaut ( le premier déclaré)
  289. $reporter = $bug1->findParentAccounts();
  290. // Utilise un rôle spécifique
  291. $engineer = $bug1->findParentAccountsByEngineer();
  292. ]]></programlisting>
  293. </example>
  294. </sect2>
  295. <sect2 id="zend.db.table.relationships.fetching.many-to-many">
  296. <title>Récupérer des enregistrements dans une relation N-N (plusieurs-à-plusieurs ou "many-to-many")</title>
  297. <para>Si vous possédez un enregistrement sur une table (appelons la "table d'origine") ayant une relation
  298. plusieurs à plusieurs vers une autre table (appelons la "table de destination"), vous pouvez alors accéder aux
  299. enregistrements de la table de destination, via une table dite "d'intersection". Utilisez la méthode :</para>
  300. <programlisting role="php"><![CDATA[
  301. $row->findManyToManyRowset($table,
  302. $intersectionTable,
  303. [$rule1,
  304. [$rule2,
  305. [Zend_Db_Table_Select $select]]]);
  306. ]]></programlisting>
  307. <para>Cette méthode retourne un objet instance de <classname>Zend_Db_Table_Rowset_Abstract</classname> qui contient les
  308. enregistrements de la table <code>$table</code> qui correspondent à la relation plusieurs à plusieurs.
  309. L'enregistrement courant de la table courante, <code>$row</code>, est utilisé comme point de départ pour
  310. effectuer une jointure vers la table de destination, via la table d'intersection.</para>
  311. <para>Le premier paramètre <code>$table</code> peut être soit une chaîne soit un objet instance de la classe de
  312. la table de destination dans la relation plusieurs à plusieurs.</para>
  313. <para>Le second paramètre <code>$intersectionTable</code> peut être soit une chaîne soit un objet instance de la
  314. classe de la table d'intersection dans la relation plusieurs à plusieurs.</para>
  315. <example id="zend.db.table.relationships.fetching.many-to-many.example">
  316. <title>Récupérer des enregistrements dans une relation plusieurs-à-plusieurs</title>
  317. <para>Cet exemple montre comment posséder un enregistrement de la table d'origine <code>Bugs</code>, et
  318. comment en récupérer les enregistrements de <code>Products</code>, qui représentent les produits qui font
  319. référence à ce bug.</para>
  320. <programlisting role="php"><![CDATA[
  321. $bugsTable = new Bugs();
  322. $bugsRowset = $bugsTable->find(1234);
  323. $bug1234 = $bugsRowset->current();
  324. $productsRowset = $bug1234->findManyToManyRowset('Products',
  325. 'BugsProducts');
  326. ]]></programlisting>
  327. </example>
  328. <para>Les troisième et quatrième paramètres, <code>$rule1</code> et <code>$rule2</code>, sont optionnels. Ce
  329. sont des chaînes de caractères qui désignent les rôles à utiliser dans le tableau <code>$_referenceMap</code> de
  330. la table d'intersection.</para>
  331. <para><code>$rule1</code> nomme le rôle dans la relation entre la table d'origine et la table d'intersection.
  332. Dans notre exemple, il s'agit donc de la relation de <code>Bugs</code> à <code>BugsProducts</code>.</para>
  333. <para><code>$rule2</code>nomme le rôle dans la relation entre la table d'origine et la table d'intersection.
  334. Dans notre exemple, il s'agit donc de la relation de <code>BugsProducts</code> à <code>Products</code>.</para>
  335. <para>Si vous ne spécifiez pas de rôles, alors le premier rôle trouvé pour la table, dans le tableau
  336. <code>$_referenceMap</code>, sera utilisé. Dans la grande majorité des cas, il n'y a qu'un rôle.</para>
  337. <para>Dans l'exemple ci-dessus, les rôles ne sont pas spécifiés. Ainsi <code>$rule1</code> prend la valeur
  338. <code>"Reporter"</code> et <code>$rule2</code> prend la valeur <code>"Product"</code>.</para>
  339. <example id="zend.db.table.relationships.fetching.many-to-many.example-by">
  340. <title>Récupérer des enregistrements dans une relation plusieurs-à-plusieurs avec un rôle spécifique</title>
  341. <para>Cet exemple montre comment à partir d'un enregistrement de <code>Bugs</code>, récupérer les
  342. enregistrements de <code>Products</code>, représentant les produits comportant ce bug.</para>
  343. <programlisting role="php"><![CDATA[
  344. $bugsTable = new Bugs();
  345. $bugsRowset = $bugsTable->find(1234);
  346. $bug1234 = $bugsRowset->current();
  347. $productsRowset = $bug1234->findManyToManyRowset('Products',
  348. 'BugsProducts',
  349. 'Bug');
  350. ]]></programlisting>
  351. </example>
  352. <para>Vous pouvez récupérer l'enregistrement de destination d'une autre manière. En utilisant les "méthodes
  353. magiques". En effet, <classname>Zend_Db_Table_Row_Abstract</classname> va utiliser la méthode
  354. <code>findManyToManyRowset('&lt;TableClass&gt;', '&lt;IntersectionTableClass&gt;', '&lt;Rule1&gt;',
  355. '&lt;Rule2&gt;')</code> si vous appelez sur l'enregistrement une méthode correspondante à un de ces motifs
  356. :</para>
  357. <itemizedlist>
  358. <listitem>
  359. <para><code>$row-&gt;find&lt;TableClass&gt;Via&lt;IntersectionTableClass&gt;([Zend_Db_Table_Select
  360. $select])</code></para>
  361. </listitem>
  362. <listitem>
  363. <para><code>$row-&gt;find&lt;TableClass&gt;Via&lt;IntersectionTableClass&gt;By&lt;Rule1&gt;([Zend_Db_Table_Select
  364. $select])</code></para>
  365. </listitem>
  366. <listitem>
  367. <para><code>$row-&gt;find&lt;TableClass&gt;Via&lt;IntersectionTableClass&gt;By&lt;Rule1&gt;And&lt;Rule2&gt;([Zend_Db_Table_Select
  368. $select])</code></para>
  369. </listitem>
  370. </itemizedlist>
  371. <para>Dans les motifs ci dessus, <code>&lt;TableClass&gt;</code> et <code>&lt;IntersectionTableClass&gt;</code>
  372. sont des chaînes de caractères correspondantes aux noms des classes des tables de destination et d'intersection
  373. (respectivement). <code>&lt;Rule1&gt;</code> et <code>&lt;Rule2&gt;</code> sont respectivement des chaînes
  374. désignant les rôles dans la table d'intersection pour la table de référence, et de destination.</para>
  375. <note>
  376. <para>Les noms de la table et des rôles doivent être orthographiés de manière exacte, tel qu'ils le sont
  377. lors de leurs définitions respectives.</para>
  378. </note>
  379. <example id="zend.db.table.relationships.fetching.many-to-many.example-magic">
  380. <title>Récupérer des enregistrements dans une relation plusieurs-à-plusieurs avec les méthodes
  381. magiques</title>
  382. <para>Cet exemple illustre la récupération d'enregistrements dans une table de destination, bugs, depuis un
  383. produit, en passant par une table d'intersection, le tout, via des méthodes magiques.</para>
  384. <programlisting role="php"><![CDATA[
  385. $bugsTable = new Bugs();
  386. $bugsRowset = $bugsTable->find(1234);
  387. $bug1234 = $bugsRowset->current();
  388. // Utilisation des rôles par défaut
  389. $products = $bug1234->findProductsViaBugsProducts();
  390. // Utilisation d'un rôle spécifique
  391. $products = $bug1234->findProductsViaBugsProductsByBug();
  392. ]]></programlisting>
  393. </example>
  394. </sect2>
  395. <sect2 id="zend.db.table.relationships.cascading">
  396. <title>Opérations d'écritures en cascade</title>
  397. <note>
  398. <title>Déclarer l'intégrité référentielle</title>
  399. <para>Déclarer les opérations de cascades dûes à l'intégrité référentielle dans <classname>Zend_Db_Table</classname>
  400. directement, ne doit se faire <emphasis role="strong">seulement</emphasis> si votre SGBD ne supporte pas
  401. nativement ce genre d'opérations.</para>
  402. <para>C'est le cas par exemple de MySQL utilisant le stockage de tables MyISAM, ou encore SQLite. Ces
  403. solutions là ne supportent pas l'intégrité référentielle. Il peut alors être intéressant d'utiliser
  404. <classname>Zend_Db_Table</classname> pour émuler un tel comportement</para>
  405. <para>Si votre SGBD en revanche supporte les clauses <code>ON DELETE</code> et <code>ON UPDATE</code>, alors
  406. vous devriez les déclarer directement dans le SGBD plutôt que de vous fier à l'émulation proposée par
  407. <classname>Zend_Db_Table</classname>. Déclarer son intégrité référentielle dans son SGBD directement est tout à fait
  408. recommandé pour les performances, l'intégrité (l'atomicité des opérations), et la logique de base de
  409. données.</para>
  410. <para>Il est très important de ne pas déclarer ses règles d'intégrité référentielle à la fois dans son SGBD
  411. et dans les classes <classname>Zend_Db_Table</classname>.</para>
  412. </note>
  413. <para>Vous pouvez déclarer des opérations de cascade sur un <code>UPDATE</code> ou un <code>DELETE</code>, à
  414. appliquer sur les enregistrements dépendants à la table en cours.</para>
  415. <example id="zend.db.table.relationships.cascading.example-delete">
  416. <title>Exemple de DELETE cascade</title>
  417. <para>Cet exemple montre l'effacement d'un enregistrement de <code>Products</code>, qui va propager
  418. l'effacement des enregistrements dépendants dans la table <code>Bugs</code>.</para>
  419. <programlisting role="php"><![CDATA[
  420. $productsTable = new Products();
  421. $productsRowset = $productsTable->find(1234);
  422. $product1234 = $productsRowset->current();
  423. $product1234->delete();
  424. // Cascades automatiques vers le table Bugs
  425. // et suppression des enregistrements dépendants.
  426. ]]></programlisting>
  427. </example>
  428. <para>De la même manière, si vous utilisez un <code>UPDATE</code> pour changer la valeur de la clé primaire
  429. d'une table parente, vous pourriez nécessiter que les clés étrangères des tables dépendantes soient mises à
  430. jour.</para>
  431. <para>En général s'il s'agit d'une séquence, il n'est pas nécessaire de mettre à jour les enregistrements
  432. dépendants. En revanche concernant les clé dites <emphasis>naturelles </emphasis>, il peut s'avérer nécessaire
  433. de propager un changement de valeur.</para>
  434. <para>Afin de déclarer une relation de cascades dans <classname>Zend_Db_Table</classname>, éditer les rôles dans
  435. <code>$_referenceMap</code>. Ajoutez les clés <code>'onDelete'</code> et <code>'onUpdate'</code> et donnez leur
  436. la valeur 'cascade' (ou la constante <code>self::CASCADE</code>). Avant qu'un enregistrement ne soit modifié(sa
  437. clé primaire) / supprimé, tous les enregistrements dans les tables dépendantes seront modifiés /
  438. supprimés.</para>
  439. <example id="zend.db.table.relationships.cascading.example-declaration">
  440. <title>Exemple de déclaration des opérations de cascade</title>
  441. <para>Dans l'exemple ci-après, les enregistrements de <code>Bugs</code> sont automatiquement supprimés si
  442. l'enregistrement dans la table <code>Products</code> auquel ils font référence est supprimé. L'élément
  443. <code>"onDelete"</code> de la <code>$_referenceMap</code> est mis à <code>self::CASCADE</code>.</para>
  444. <para>Pas de mise à jour en cascade en revanche pour cette table, si la clé primaire de la table parente est
  445. changée. En effet, l'élément <code>"onUpdate"</code> est mis à <code>self::RESTRICT</code>. Vous auriez
  446. aussi pu tout simplement ne pas spécifier <code>"onUpdate"</code> .</para>
  447. <programlisting role="php"><![CDATA[
  448. class BugsProducts extends Zend_Db_Table_Abstract
  449. {
  450. ...
  451. protected $_referenceMap = array(
  452. 'Product' => array(
  453. 'columns' => array('product_id'),
  454. 'refTableClass' => 'Products',
  455. 'refColumns' => array('product_id'),
  456. 'onDelete' => self::CASCADE,
  457. 'onUpdate' => self::RESTRICT
  458. ),
  459. ...
  460. );
  461. }
  462. ]]></programlisting>
  463. </example>
  464. <sect3 id="zend.db.table.relationships.cascading.notes">
  465. <title>Notes concernant les opérations de cascade</title>
  466. <para><emphasis role="strong">Les opérations de cascades déclenchées par Zend_Db_Table ne sont pas
  467. atomiques.</emphasis></para>
  468. <para>Ceci signifie que si votre SGBD possède un moyen de gérer les cascades, comme l'intégrité
  469. référentielle (et les clés étrangères), alors vous ne devriez pas utiliser les cascades INSERT via
  470. <classname>Zend_Db_Table</classname>, car elles vont entrer en conflit avec le système d'intégrité référentielle du
  471. SGBD qui lui, est atomique.</para>
  472. <para>Le problème est plus mitigé concernant <code>DELETE</code>. Vous pouvez détruire de manière non
  473. atomique un enregistrement dépendant, avant de détruire son parent.</para>
  474. <para>Cependant, les deux opérations <code>UPDATE</code> et <code>DELETE</code> utilisées de manière non
  475. atomique(que), c'est à dire avec le mécanisme de <classname>Zend_Db_Table</classname>, peuvent laisser la base de
  476. données dans un état non désiré, ou état intermédiaire. Supposez que vous supprimiez tous les
  477. enregistrements dépendants, pour finir par leur parent unique. A un moment donnée, la base de donnée sera
  478. dans un état tel que le parent sera sans enfants, mais toujours bel et bien présent. Si un autre client se
  479. connecte exactement à ce moment là, il va pouvoir requêter éventuellement le parent, en croyant que celui-ci
  480. n'a plus d'enfant, ce qui normalement n'est pas le cas. Il est alors totalement impossible pour ce client là
  481. de se rendre compte qu'il a effectuer une lecture au beau milieu d'une plus vaste opération
  482. d'effacement.</para>
  483. <para>Les problèmes de changements non-atomique peuvent être anéantis en utilisant les transactions
  484. isolantes, c'est d'ailleurs un de leur rôle clé. Cependant certains SGBDs ne supportent pas encore les
  485. transactions, et autorisent leurs clients à lire des changements incomplets pas validés en totalité.</para>
  486. <para><emphasis role="strong">Les opérations de cascades de Zend_Db_Table ne sont utilisées que par
  487. Zend_Db_Table.</emphasis></para>
  488. <para>Les cascades pour <code>DELETE</code> et <code>UPDATE</code> définies dans vos classes
  489. <classname>Zend_Db_Table</classname> ne sont utilisées que lors du recours aux méthodes <code>save()</code> ou
  490. <code>delete()</code> sur les enregistrements <code>Row</code>. Si vous utilisez une autre interface pour
  491. vos <code>UPDATE</code> ou <code>DELETE</code>, comme par exemple un outil de requêtes, ou une autre
  492. application, les opérations de cascades ne sont bien sûr pas appliquées. C'est même le cas si vous utilisez
  493. les méthodes <code>update()</code> et <code>delete()</code> dans la classe
  494. <classname>Zend_Db_Adapter</classname>.</para>
  495. <para><emphasis role="strong">Pas d'<code>INSERT</code> en cascade</emphasis></para>
  496. <para>Le support pour les cascades d'<code>INSERT</code> n'est pas assuré. Vous devez explicitement insérer
  497. les enregistrements dépendants à un enregistrement parent.</para>
  498. </sect3>
  499. </sect2>
  500. </sect1>