Zend_Db_Table-Relationships.xml 38 KB

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