| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- Reviewed: no -->
- <sect1 id="zend.db.table.relationships">
- <title>Zend_Db_Table Relationships</title>
- <sect2 id="zend.db.table.relationships.introduction">
- <title>Introduction</title>
- <para>
- Tables have relationships to each other in a relational database. An entity in one
- table can be linked to one or more entities in another table by using referential
- integrity constraints defined in the database schema.
- </para>
- <para>
- The <classname>Zend_Db_Table_Row</classname> class has methods for querying related rows
- in other tables.
- </para>
- </sect2>
- <sect2 id="zend.db.table.relationships.defining">
- <title>Defining Relationships</title>
- <para>
- Define classes for each of your tables, extending the abstract class
- <classname>Zend_Db_Table_Abstract</classname>, as described in
- <link linkend="zend.db.table.defining">this chapter</link>. Also see
- <link linkend="zend.db.adapter.example-database">this chapter</link> for a description
- of the example database for which the following example code is designed.
- </para>
- <para>
- Below are the <acronym>PHP</acronym> class definitions for these tables:
- </para>
- <programlisting language="php"><![CDATA[
- class Accounts extends Zend_Db_Table_Abstract
- {
- protected $_name = 'accounts';
- protected $_dependentTables = array('Bugs');
- }
- class Products extends Zend_Db_Table_Abstract
- {
- protected $_name = 'products';
- protected $_dependentTables = array('BugsProducts');
- }
- class Bugs extends Zend_Db_Table_Abstract
- {
- protected $_name = 'bugs';
- protected $_dependentTables = array('BugsProducts');
- protected $_referenceMap = array(
- 'Reporter' => array(
- 'columns' => 'reported_by',
- 'refTableClass' => 'Accounts',
- 'refColumns' => 'account_name'
- ),
- 'Engineer' => array(
- 'columns' => 'assigned_to',
- 'refTableClass' => 'Accounts',
- 'refColumns' => 'account_name'
- ),
- 'Verifier' => array(
- 'columns' => array('verified_by'),
- 'refTableClass' => 'Accounts',
- 'refColumns' => array('account_name')
- )
- );
- }
- class BugsProducts extends Zend_Db_Table_Abstract
- {
- protected $_name = 'bugs_products';
- protected $_referenceMap = array(
- 'Bug' => array(
- 'columns' => array('bug_id'),
- 'refTableClass' => 'Bugs',
- 'refColumns' => array('bug_id')
- ),
- 'Product' => array(
- 'columns' => array('product_id'),
- 'refTableClass' => 'Products',
- 'refColumns' => array('product_id')
- )
- );
- }
- ]]></programlisting>
- <para>
- If you use <classname>Zend_Db_Table</classname> to emulate cascading
- <constant>UPDATE</constant> and <constant>DELETE</constant>
- operations, declare the <varname>$_dependentTables</varname> array in the class for the
- parent table. List the class name for each dependent table. Use the class name, not the
- physical name of the <acronym>SQL</acronym> table.
- </para>
- <note>
- <para>
- Skip declaration of <varname>$_dependentTables</varname> if you use referential
- integrity constraints in the <acronym>RDBMS</acronym> server to implement cascading
- operations. See <link linkend="zend.db.table.relationships.cascading">this
- chapter</link> for more information.
- </para>
- </note>
- <para>
- Declare the <varname>$_referenceMap</varname> array in the class for each dependent
- table. This is an associative array of reference "rules". A reference rule identifies
- which table is the parent table in the relationship, and also lists which columns in the
- dependent table reference which columns in the parent table.
- </para>
- <para>
- The rule key is a string used as an index to the <varname>$_referenceMap</varname>
- array. This rule key is used to identify each reference relationship. Choose a
- descriptive name for this rule key. It's best to use a string that can be part of a
- <acronym>PHP</acronym> method name, as you will see later.
- </para>
- <para>
- In the example <acronym>PHP</acronym> code above, the rule keys in the Bugs table class
- are: <command>'Reporter'</command>, <command>'Engineer'</command>,
- <command>'Verifier'</command>, and <command>'Product'</command>.
- </para>
- <para>
- The value of each rule entry in the <varname>$_referenceMap</varname> array is also an
- associative array. The elements of this rule entry are described below:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <emphasis>columns</emphasis> => A string or an array of strings
- naming the foreign key column names in the dependent table.
- </para>
- <para>
- It's common for this to be a single column, but some tables have multi-column
- keys.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>refTableClass</emphasis> => The class name of the parent table. Use
- the class name, not the physical name of the <acronym>SQL</acronym> table.
- </para>
- <para>
- It's common for a dependent table to have only one reference to its parent
- table, but some tables have multiple references to the same parent table. In
- the example database, there is one reference from the <command>bugs</command>
- table to the <command>products</command> table, but three references from the
- <command>bugs</command> table to the <command>accounts</command> table. Put each
- reference in a separate entry in the <varname>$_referenceMap</varname> array.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>refColumns</emphasis> => A string or an array of
- strings naming the primary key column names in the parent table.
- </para>
- <para>
- It's common for this to be a single column, but some tables have multi-column
- keys. If the reference uses a multi-column key, the order of columns in the
- <command>'columns'</command> entry must match the order of columns in the
- <command>'refColumns'</command> entry.
- </para>
- <note>
- <para>
- It is recommended that the <property>refColumns</property> element is always declared as
- cascading operations will not work unless you do so.
- </para>
- </note>
- </listitem>
- <listitem>
- <para>
- <emphasis>onDelete</emphasis> => The rule for an action to
- execute if a row is deleted in the parent table. See
- <link linkend="zend.db.table.relationships.cascading">this chapter</link> for
- more information.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>onUpdate</emphasis> => The rule for an action to
- execute if values in primary key columns are updated in the parent table. See
- <link linkend="zend.db.table.relationships.cascading">this chapter</link> for
- more information.
- </para>
- </listitem>
- </itemizedlist>
- </sect2>
- <sect2 id="zend.db.table.relationships.fetching.dependent">
- <title>Fetching a Dependent Rowset</title>
- <para>
- If you have a Row object as the result of a query on a parent table, you can fetch rows
- from dependent tables that reference the current row. Use the method:
- </para>
- <programlisting language="php"><![CDATA[
- $row->findDependentRowset($table, [$rule]);
- ]]></programlisting>
- <para>
- This method returns a <classname>Zend_Db_Table_Rowset_Abstract</classname> object,
- containing a set of rows from the dependent table <varname>$table</varname> that refer
- to the row identified by the <varname>$row</varname> object.
- </para>
- <para>
- The first argument <varname>$table</varname> can be a string that specifies the
- dependent table by its class name. You can also specify the dependent table by using an
- object of that table class.
- </para>
- <example id="zend.db.table.relationships.fetching.dependent.example">
- <title>Fetching a Dependent Rowset</title>
- <para>
- This example shows getting a Row object from the table <command>Accounts</command>,
- and finding the <command>Bugs</command> reported by that account.
- </para>
- <programlisting language="php"><![CDATA[
- $accountsTable = new Accounts();
- $accountsRowset = $accountsTable->find(1234);
- $user1234 = $accountsRowset->current();
- $bugsReportedByUser = $user1234->findDependentRowset('Bugs');
- ]]></programlisting>
- </example>
- <para>
- The second argument <varname>$rule</varname> is optional. It is a string that names the
- rule key in the <varname>$_referenceMap</varname> array of the dependent table class. If
- you don't specify a rule, the first rule in the array that references the parent table
- is used. If you need to use a rule other than the first, you need to specify the key.
- </para>
- <para>
- In the example code above, the rule key is not specified, so the rule used by default
- is the first one that matches the parent table. This is the rule
- <command>'Reporter'</command>.
- </para>
- <example id="zend.db.table.relationships.fetching.dependent.example-by">
- <title>Fetching a Dependent Rowset By a Specific Rule</title>
- <para>
- This example shows getting a Row object from the table <command>Accounts</command>,
- and finding the <command>Bugs</command> assigned to be fixed by the user of that
- account. The rule key string that corresponds to this reference relationship in this
- example is <command>'Engineer'</command>.
- </para>
- <programlisting language="php"><![CDATA[
- $accountsTable = new Accounts();
- $accountsRowset = $accountsTable->find(1234);
- $user1234 = $accountsRowset->current();
- $bugsAssignedToUser = $user1234->findDependentRowset('Bugs', 'Engineer');
- ]]></programlisting>
- </example>
- <para>
- You can also add criteria, ordering and limits to your relationships using the parent
- row's select object.
- </para>
- <example id="zend.db.table.relationships.fetching.dependent.example-by-select">
- <title>Fetching a Dependent Rowset using a Zend_Db_Table_Select</title>
- <para>
- This example shows getting a Row object from the table
- <command>Accounts</command>, and finding the <command>Bugs</command> assigned to
- be fixed by the user of that account, limited only to 3 rows and ordered by
- name.
- </para>
- <programlisting language="php"><![CDATA[
- $accountsTable = new Accounts();
- $accountsRowset = $accountsTable->find(1234);
- $user1234 = $accountsRowset->current();
- $select = $accountsTable->select()->order('name ASC')
- ->limit(3);
- $bugsAssignedToUser = $user1234->findDependentRowset('Bugs',
- 'Engineer',
- $select);
- ]]></programlisting>
- </example>
- <para>
- Alternatively, you can query rows from a dependent table using a special mechanism
- called a "magic method". <classname>Zend_Db_Table_Row_Abstract</classname> invokes the
- method: <methodname>findDependentRowset('<TableClass>',
- '<Rule>')</methodname> if you invoke a method on the Row object matching
- either of the following patterns:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <command>$row->find<TableClass>()</command>
- </para>
- </listitem>
- <listitem>
- <para>
- <command>$row->find<TableClass>By<Rule>()</command>
- </para>
- </listitem>
- </itemizedlist>
- <para>
- In the patterns above, <command><TableClass></command> and
- <command><Rule></command> are strings that correspond to the class name of the
- dependent table, and the dependent table's rule key that references the parent table.
- </para>
- <note>
- <para>
- Some application frameworks, such as Ruby on Rails, use a mechanism called
- "inflection" to allow the spelling of identifiers to change depending on usage. For
- simplicity, <classname>Zend_Db_Table_Row</classname> does not provide any inflection
- mechanism. The table identity and the rule key named in the method call must match
- the spelling of the class and rule key exactly.
- </para>
- </note>
- <example id="zend.db.table.relationships.fetching.dependent.example-magic">
- <title>Fetching Dependent Rowsets using the Magic Method</title>
- <para>
- This example shows finding dependent Rowsets equivalent to those in the previous
- examples. In this case, the application uses the magic method invocation instead of
- specifying the table and rule as strings.
- </para>
- <programlisting language="php"><![CDATA[
- $accountsTable = new Accounts();
- $accountsRowset = $accountsTable->find(1234);
- $user1234 = $accountsRowset->current();
- // Use the default reference rule
- $bugsReportedBy = $user1234->findBugs();
- // Specify the reference rule
- $bugsAssignedTo = $user1234->findBugsByEngineer();
- ]]></programlisting>
- </example>
- </sect2>
- <sect2 id="zend.db.table.relationships.fetching.parent">
- <title>Fetching a Parent Row</title>
- <para>
- If you have a Row object as the result of a query on a dependent table, you can fetch
- the row in the parent to which the dependent row refers. Use the method:
- </para>
- <programlisting language="php"><![CDATA[
- $row->findParentRow($table, [$rule]);
- ]]></programlisting>
- <para>
- There always should be exactly one row in the parent table referenced by a dependent
- row, therefore this method returns a Row object, not a Rowset object.
- </para>
- <para>
- The first argument <varname>$table</varname> can be a string that specifies the parent
- table by its class name. You can also specify the parent table by using an object of
- that table class.
- </para>
- <example id="zend.db.table.relationships.fetching.parent.example">
- <title>Fetching the Parent Row</title>
- <para>
- This example shows getting a Row object from the table <command>Bugs</command> (for
- example one of those bugs with status 'NEW'), and finding the row in the
- <command>Accounts</command> table for the user who reported the bug.
- </para>
- <programlisting language="php"><![CDATA[
- $bugsTable = new Bugs();
- $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?' => 'NEW'));
- $bug1 = $bugsRowset->current();
- $reporter = $bug1->findParentRow('Accounts');
- ]]></programlisting>
- </example>
- <para>
- The second argument <varname>$rule</varname> is optional. It is a string that names the
- rule key in the <varname>$_referenceMap</varname> array of the dependent table class. If
- you don't specify a rule, the first rule in the array that references the parent table
- is used. If you need to use a rule other than the first, you need to specify the key.
- </para>
- <para>
- In the example above, the rule key is not specified, so the rule used by default is the
- first one that matches the parent table. This is the rule <command>'Reporter'</command>.
- </para>
- <example id="zend.db.table.relationships.fetching.parent.example-by">
- <title>Fetching a Parent Row By a Specific Rule</title>
- <para>
- This example shows getting a Row object from the table <command>Bugs</command>, and
- finding the account for the engineer assigned to fix that bug. The rule key string
- that corresponds to this reference relationship in this example is
- <command>'Engineer'</command>.
- </para>
- <programlisting language="php"><![CDATA[
- $bugsTable = new Bugs();
- $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?', 'NEW'));
- $bug1 = $bugsRowset->current();
- $engineer = $bug1->findParentRow('Accounts', 'Engineer');
- ]]></programlisting>
- </example>
- <para>
- Alternatively, you can query rows from a parent table using a "magic method".
- <classname>Zend_Db_Table_Row_Abstract</classname> invokes the method:
- <methodname>findParentRow('<TableClass>', '<Rule>')</methodname> if you
- invoke a method on the Row object matching either of the following patterns:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <command>$row->findParent<TableClass>([Zend_Db_Table_Select
- $select])</command>
- </para>
- </listitem>
- <listitem>
- <para>
- <command>$row->findParent<TableClass>By<Rule>([Zend_Db_Table_Select
- $select])</command>
- </para>
- </listitem>
- </itemizedlist>
- <para>
- In the patterns above, <command><TableClass></command> and
- <command><Rule></command> are strings that correspond to the class name of the
- parent table, and the dependent table's rule key that references the parent table.
- </para>
- <note>
- <para>
- The table identity and the rule key named in the method call must match the
- spelling of the class and rule key exactly.
- </para>
- </note>
- <example id="zend.db.table.relationships.fetching.parent.example-magic">
- <title>Fetching the Parent Row using the Magic Method</title>
- <para>
- This example shows finding parent Rows equivalent to those in the previous
- examples. In this case, the application uses the magic method invocation instead of
- specifying the table and rule as strings.
- </para>
- <programlisting language="php"><![CDATA[
- $bugsTable = new Bugs();
- $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?', 'NEW'));
- $bug1 = $bugsRowset->current();
- // Use the default reference rule
- $reporter = $bug1->findParentAccounts();
- // Specify the reference rule
- $engineer = $bug1->findParentAccountsByEngineer();
- ]]></programlisting>
- </example>
- </sect2>
- <sect2 id="zend.db.table.relationships.fetching.many-to-many">
- <title>Fetching a Rowset via a Many-to-many Relationship</title>
- <para>
- If you have a Row object as the result of a query on one table in a many-to-many
- relationship (for purposes of the example, call this the "origin" table), you can
- fetch corresponding rows in the other table (call this the "destination" table) via an
- intersection table. Use the method:
- </para>
- <programlisting language="php"><![CDATA[
- $row->findManyToManyRowset($table,
- $intersectionTable,
- [$rule1,
- [$rule2,
- [Zend_Db_Table_Select $select]
- ]
- ]);
- ]]></programlisting>
- <para>
- This method returns a <classname>Zend_Db_Table_Rowset_Abstract</classname> containing
- rows from the table <varname>$table</varname>, satisfying the many-to-many relationship.
- The current Row object <varname>$row</varname> from the origin table is used to find
- rows in the intersection table, and that is joined to the destination table.
- </para>
- <para>
- The first argument <varname>$table</varname> can be a string that specifies the
- destination table in the many-to-many relationship by its class name. You can also
- specify the destination table by using an object of that table class.
- </para>
- <para>
- The second argument <varname>$intersectionTable</varname> can be a string that specifies
- the intersection table between the two tables in the many-to-many relationship by
- its class name. You can also specify the intersection table by using an object of that
- table class.
- </para>
- <example id="zend.db.table.relationships.fetching.many-to-many.example">
- <title>Fetching a Rowset with the Many-to-many Method</title>
- <para>
- This example shows getting a Row object from the origin table
- <command>Bugs</command>, and finding rows from the destination table
- <command>Products</command>, representing products related to that bug.
- </para>
- <programlisting language="php"><![CDATA[
- $bugsTable = new Bugs();
- $bugsRowset = $bugsTable->find(1234);
- $bug1234 = $bugsRowset->current();
- $productsRowset = $bug1234->findManyToManyRowset('Products',
- 'BugsProducts');
- ]]></programlisting>
- </example>
- <para>
- The third and fourth arguments <varname>$rule1</varname> and <varname>$rule2</varname>
- are optional. These are strings that name the rule keys in the
- <varname>$_referenceMap</varname> array of the intersection table.
- </para>
- <para>
- The <varname>$rule1</varname> key names the rule for the relationship from the
- intersection table to the origin table. In this example, this is the relationship from
- <command>BugsProducts</command> to <command>Bugs</command>.
- </para>
- <para>
- The <varname>$rule2</varname> key names the rule for the relationship from the
- intersection table to the destination table. In this example, this is the relationship
- from <command>Bugs</command> to <command>Products</command>.
- </para>
- <para>
- Similarly to the methods for finding parent and dependent rows, if you don't specify a
- rule, the method uses the first rule in the <varname>$_referenceMap</varname> array that
- matches the tables in the relationship. If you need to use a rule other than the first,
- you need to specify the key.
- </para>
- <para>
- In the example code above, the rule key is not specified, so the rules used by default
- are the first ones that match. In this case, <varname>$rule1</varname> is
- <command>'Reporter'</command> and <varname>$rule2</varname> is
- <command>'Product'</command>.
- </para>
- <example id="zend.db.table.relationships.fetching.many-to-many.example-by">
- <title>Fetching a Rowset with the Many-to-many Method By a Specific Rule</title>
- <para>
- This example shows geting a Row object from the origin table
- <command>Bugs</command>, and finding rows from the destination table
- <command>Products</command>, representing products related to that bug.
- </para>
- <programlisting language="php"><![CDATA[
- $bugsTable = new Bugs();
- $bugsRowset = $bugsTable->find(1234);
- $bug1234 = $bugsRowset->current();
- $productsRowset = $bug1234->findManyToManyRowset('Products',
- 'BugsProducts',
- 'Bug');
- ]]></programlisting>
- </example>
- <para>
- Alternatively, you can query rows from the destination table in a many-to-many
- relationship using a "magic method." <classname>Zend_Db_Table_Row_Abstract</classname>
- invokes the method: <command>findManyToManyRowset('<TableClass>',
- '<IntersectionTableClass>', '<Rule1>', '<Rule2>')</command> if you
- invoke a method matching any of the following patterns:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <command>$row->find<TableClass>Via<IntersectionTableClass>
- ([Zend_Db_Table_Select $select])</command>
- </para>
- </listitem>
- <listitem>
- <para>
- <command>$row->find<TableClass>Via<IntersectionTableClass>By<Rule1>
- ([Zend_Db_Table_Select $select])</command>
- </para>
- </listitem>
- <listitem>
- <para>
- <command>$row->find<TableClass>Via<IntersectionTableClass>By<Rule1>And<Rule2>
- ([Zend_Db_Table_Select $select])</command>
- </para>
- </listitem>
- </itemizedlist>
- <para>
- In the patterns above, <command><TableClass></command> and
- <command><IntersectionTableClass></command> are strings that correspond to the
- class names of the destination table and the intersection table, respectively.
- <command><Rule1></command> and <command><Rule2></command> are strings that
- correspond to the rule keys in the intersection table that reference the origin table
- and the destination table, respectively.
- </para>
- <note>
- <para>
- The table identities and the rule keys named in the method call must match the
- spelling of the class and rule key exactly.
- </para>
- </note>
- <example id="zend.db.table.relationships.fetching.many-to-many.example-magic">
- <title>Fetching Rowsets using the Magic Many-to-many Method</title>
- <para>
- This example shows finding rows in the destination table of a many-to-many
- relationship representing products related to a given bug.
- </para>
- <programlisting language="php"><![CDATA[
- $bugsTable = new Bugs();
- $bugsRowset = $bugsTable->find(1234);
- $bug1234 = $bugsRowset->current();
- // Use the default reference rule
- $products = $bug1234->findProductsViaBugsProducts();
- // Specify the reference rule
- $products = $bug1234->findProductsViaBugsProductsByBug();
- ]]></programlisting>
- </example>
- </sect2>
- <sect2 id="zend.db.table.relationships.cascading">
- <title>Cascading Write Operations</title>
- <note>
- <title>Declare DRI in the database:</title>
- <para>
- Declaring cascading operations in <classname>Zend_Db_Table</classname> is intended
- <emphasis>only</emphasis> for <acronym>RDBMS</acronym> brands that do not support
- declarative referential integrity (<acronym>DRI</acronym>).
- </para>
- <para>
- For example, if you use MySQL's or MariaDB's MyISAM storage engine, or SQLite, these solutions
- do not support <acronym>DRI</acronym>. You may find it helpful to declare the
- cascading operations with <classname>Zend_Db_Table</classname>.
- </para>
- <para>
- If your <acronym>RDBMS</acronym> implements <acronym>DRI</acronym> and the
- ON <constant>DELETE</constant> and ON <constant>UPDATE</constant> clauses, you
- should declare these clauses in your database schema, instead of using the cascading
- feature in <classname>Zend_Db_Table</classname>. Declaring cascading
- <acronym>DRI</acronym> rules in the <acronym>RDBMS</acronym> is better for database
- performance, consistency, and integrity.
- </para>
- <para>
- Most importantly, do not declare cascading operations both in the
- <acronym>RDBMS</acronym> and in your <classname>Zend_Db_Table</classname> class.
- </para>
- </note>
- <para>
- You can declare cascading operations to execute against a dependent table when you
- apply an <constant>UPDATE</constant> or a <constant>DELETE</constant> to a row in a
- parent table.
- </para>
- <example id="zend.db.table.relationships.cascading.example-delete">
- <title>Example of a Cascading Delete</title>
- <para>
- This example shows deleting a row in the <command>Products</command> table, which is
- configured to automatically delete dependent rows in the <command>Bugs</command>
- table.
- </para>
- <programlisting language="php"><![CDATA[
- $productsTable = new Products();
- $productsRowset = $productsTable->find(1234);
- $product1234 = $productsRowset->current();
- $product1234->delete();
- // Automatically cascades to Bugs table
- // and deletes dependent rows.
- ]]></programlisting>
- </example>
- <para>
- Similarly, if you use <constant>UPDATE</constant> to change the value of a primary key
- in a parent table, you may want the value in foreign keys of dependent tables to be
- updated automatically to match the new value, so that such references are kept up to
- date.
- </para>
- <para>
- It's usually not necessary to update the value of a primary key that was generated by a
- sequence or other mechanism. But if you use a <emphasis>natural key</emphasis> that may
- change value occasionally, it is more likely that you need to apply cascading updates
- to dependent tables.
- </para>
- <para>
- To declare a cascading relationship in the <classname>Zend_Db_Table</classname>, edit
- the rules in the <varname>$_referenceMap</varname>. Set the associative array keys
- <command>'onDelete'</command> and <command>'onUpdate'</command> to one of these options:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- Cascade: This option configures a single-level cascade (parent table plus all
- directly-dependent tables). To enable this option set the appropriate key in
- <varname>$_referenceMap</varname> to string 'cascade' or use the constant
- <constant>self::CASCADE</constant>.
- </para>
- </listitem>
- <listitem>
- <para>
- Recursive Cascade: This option configures a full recursive cascade starting
- with the parent table. To enable this option set the appropriate key in
- <varname>$_referenceMap</varname> to string 'cascadeRecurse' or use the constant
- <constant>self::CASCADE_RECURSE</constant>.
- </para>
- </listitem>
- </itemizedlist>
- <para>
- Before a row is deleted from the parent table, or its primary key values updated, any
- rows in the dependent table that refer to the parent's row are deleted or updated first.
- </para>
- <example id="zend.db.table.relationships.cascading.example-declaration">
- <title>Example Declaration of Cascading Operations</title>
- <para>
- In the example below, rows in the <command>Bugs</command> table are automatically
- deleted if the row in the <command>Products</command> table to which they refer is
- deleted. The <command>'onDelete'</command> element of the reference map entry is set
- to <constant>self::CASCADE</constant>.
- </para>
- <para>
- No cascading update is done in the example below if the primary key value in the
- parent class is changed. The <command>'onUpdate'</command> element of the reference
- map entry is <constant>self::RESTRICT</constant>. You can get the same result by
- omitting the <command>'onUpdate'</command> entry.
- </para>
- <programlisting language="php"><![CDATA[
- class BugsProducts extends Zend_Db_Table_Abstract
- {
- ...
- protected $_referenceMap = array(
- 'Product' => array(
- 'columns' => array('product_id'),
- 'refTableClass' => 'Products',
- 'refColumns' => array('product_id'),
- 'onDelete' => self::CASCADE,
- 'onUpdate' => self::RESTRICT
- ),
- ...
- );
- }
- ]]></programlisting>
- </example>
- <sect3 id="zend.db.table.relationships.cascading.notes">
- <title>Notes Regarding Cascading Operations</title>
- <para>
- <emphasis>Cascading operations invoked by <classname>Zend_Db_Table</classname> are
- not atomic.</emphasis>
- </para>
- <para>
- This means that if your database implements and enforces referential integrity
- constraints, a cascading <constant>UPDATE</constant> executed by a
- <classname>Zend_Db_Table</classname> class conflicts with the constraint, and
- results in a referential integrity violation. You can use cascading
- <constant>UPDATE</constant> in <classname>Zend_Db_Table</classname>
- <emphasis>only</emphasis> if your database does not enforce that referential
- integrity constraint.
- </para>
- <para>
- Cascading <constant>DELETE</constant> suffers less from the problem of referential
- integrity violations. You can delete dependent rows as a non-atomic action before
- deleting the parent row that they reference.
- </para>
- <para>
- However, for both <constant>UPDATE</constant> and <constant>DELETE</constant>,
- changing the database in a non-atomic way also creates the risk that another
- database user can see the data in an inconsistent state. For example, if you delete
- a row and all its dependent rows, there is a small chance that another database
- client program can query the database after you have deleted the dependent rows, but
- before you delete the parent row. That client program may see the parent row with no
- dependent rows, and assume this is the intended state of the data. There is no way
- for that client to know that its query read the database in the middle of a change.
- </para>
- <para>
- The issue of non-atomic change can be mitigated by using transactions to isolate
- your change. But some <acronym>RDBMS</acronym> brands don't support transactions, or
- allow clients to read "dirty" changes that have not been committed yet.
- </para>
- <para>
- <emphasis>Cascading operations in <classname>Zend_Db_Table</classname> are invoked
- only by <classname>Zend_Db_Table</classname>.</emphasis>
- </para>
- <para>
- Cascading deletes and updates defined in your <classname>Zend_Db_Table</classname>
- classes are applied if you execute the <methodname>save()</methodname> or
- <methodname>delete()</methodname> methods on the Row class. However, if you update
- or delete data using another interface, such as a query tool or another application,
- the cascading operations are not applied. Even when using
- <methodname>update()</methodname> and <methodname>delete()</methodname> methods
- in the <classname>Zend_Db_Adapter</classname> class, cascading operations defined in
- your <classname>Zend_Db_Table</classname> classes are not executed.
- </para>
- <para>
- <emphasis>No Cascading <constant>INSERT</constant>.</emphasis>
- </para>
- <para>
- There is no support for a cascading <constant>INSERT</constant>. You must insert a
- row to a parent table in one operation, and insert rows to a dependent table in a
- separate operation.
- </para>
- </sect3>
- </sect2>
- </sect1>
- <!--
- vim:se ts=4 sw=4 et:
- -->
|