| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- EN-Revision: 15103 -->
- <!-- 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 <xref linkend="zend.db.table.defining" />. Also
- see <xref linkend="zend.db.adapter.example-database" /> for a description of the
- example database for which the following example code is designed.
- </para>
- <para>
- Below are the PHP 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 UPDATE and DELETE operations, declare the
- <methodname>$_dependentTables</methodname> 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 SQL
- table.
- </para>
- <note>
- <para>
- Skip declaration of <methodname>$_dependentTables</methodname> if you use referential integrity
- constraints in the RDBMS server to implement cascading operations. See
- <xref linkend="zend.db.table.relationships.cascading" /> for more information.
- </para>
- </note>
- <para>
- Declare the <methodname>$_referenceMap</methodname> 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 <methodname>$_referenceMap</methodname> 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 PHP method
- name, as you will see later.
- </para>
- <para>
- In the example PHP code above, the rule keys in the Bugs table class are:
- <methodname>'Reporter'</methodname>, <methodname>'Engineer'</methodname>, <methodname>'Verifier'</methodname>, and
- <methodname>'Product'</methodname>.
- </para>
- <para>
- The value of each rule entry in the <methodname>$_referenceMap</methodname> 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 name(s) 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 SQL 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 <methodname>bugs</methodname> table
- to the <methodname>products</methodname> table, but three references from the
- <methodname>bugs</methodname> table to the <methodname>accounts</methodname> table. Put each reference
- in a separate entry in the <methodname>$_referenceMap</methodname> array.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>refColumns</emphasis> => A string or an array of
- strings naming the primary key column name(s) 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
- <methodname>'columns'</methodname> entry must match the order of columns in the
- <methodname>'refColumns'</methodname> entry.
- </para>
- <para>
- It is optional to specify this element. If you don't specify the
- <methodname>refColumns</methodname>, the column(s) reported as the primary key columns of
- the parent table are used by default.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>onDelete</emphasis> => The rule for an action to
- execute if a row is deleted in the parent table. See
- <xref linkend="zend.db.table.relationships.cascading" /> 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
- <xref linkend="zend.db.table.relationships.cascading" /> 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 <methodname>$table</methodname> that refer to the row identified by the
- <methodname>$row</methodname> object.
- </para>
- <para>
- The first argument <methodname>$table</methodname> 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 <methodname>Accounts</methodname>, and
- finding the <methodname>Bugs</methodname> 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 <methodname>$rule</methodname> is optional. It is a string that names the rule
- key in the <methodname>$_referenceMap</methodname> 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
- <methodname>'Reporter'</methodname>.
- </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 <methodname>Accounts</methodname>, and
- finding the <methodname>Bugs</methodname> assigned to be fixed by the user of that account. The
- rule key string that corresponds to this reference relationship in this example is
- <methodname>'Engineer'</methodname>.
- </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>
- <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 <methodname>Accounts</methodname>,
- and finding the <methodname>Bugs</methodname> 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>
- 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>
- <methodname>$row->find<TableClass>()</methodname>
- </para>
- </listitem>
- <listitem>
- <para>
- <methodname>$row->find<TableClass>By<Rule>()</methodname>
- </para>
- </listitem>
- </itemizedlist>
- <para>
- In the patterns above, <methodname><TableClass></methodname> and <methodname><Rule></methodname> 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 <methodname>$table</methodname> 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 <methodname>Bugs</methodname> (for
- example one of those bugs with status 'NEW'), and finding the row in the
- <methodname>Accounts</methodname> 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 <methodname>$rule</methodname> is optional. It is a string that names the rule
- key in the <methodname>$_referenceMap</methodname> 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 <methodname>'Reporter'</methodname>.
- </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 <methodname>Bugs</methodname>, 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
- <methodname>'Engineer'</methodname>.
- </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>
- <methodname>$row->findParent<TableClass>([Zend_Db_Table_Select $select])</methodname>
- </para>
- </listitem>
- <listitem>
- <para>
- <methodname>$row->findParent<TableClass>By<Rule>([Zend_Db_Table_Select
- $select])</methodname>
- </para>
- </listitem>
- </itemizedlist>
- <para>
- In the patterns above, <methodname><TableClass></methodname> and <methodname><Rule></methodname>
- 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
- <methodname>$table</methodname>, satisfying the many-to-many relationship. The current Row object
- <methodname>$row</methodname> 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 <methodname>$table</methodname> 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 <methodname>$intersectionTable</methodname> can be a string that specifies the
- intersection table between the two tables in the 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 from the origin table
- <methodname>Bugs</methodname>, and finding rows from the destination table
- <methodname>Products</methodname>, 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 <methodname>$rule1</methodname> and <methodname>$rule2</methodname> are
- optional. These are strings that name the rule keys in the <methodname>$_referenceMap</methodname>
- array of the intersection table.
- </para>
- <para>
- The <methodname>$rule1</methodname> key names the rule for the relationship from the intersection
- table to the origin table. In this example, this is the relationship from
- <methodname>BugsProducts</methodname> to <methodname>Bugs</methodname>.
- </para>
- <para>
- The <methodname>$rule2</methodname> key names the rule for the relationship from the intersection
- table to the destination table. In this example, this is the relationship from
- <methodname>Bugs</methodname> to <methodname>Products</methodname>.
- </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 <methodname>$_referenceMap</methodname> 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, <methodname>$rule1</methodname> is
- <methodname>'Reporter'</methodname> and <methodname>$rule2</methodname> is <methodname>'Product'</methodname>.
- </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 from the origin table
- <methodname>Bugs</methodname>, and finding rows from the destination table
- <methodname>Products</methodname>, 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:
- <methodname>findManyToManyRowset('<TableClass>', '<IntersectionTableClass>',
- '<Rule1>', '<Rule2>')</methodname> if you invoke a method matching any of the
- following patterns:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <methodname>$row->find<TableClass>Via<IntersectionTableClass>
- ([Zend_Db_Table_Select $select])</methodname>
- </para>
- </listitem>
- <listitem>
- <para>
- <methodname>$row->find<TableClass>Via<IntersectionTableClass>By<Rule1>
- ([Zend_Db_Table_Select $select])</methodname>
- </para>
- </listitem>
- <listitem>
- <para>
- <methodname>$row->find<TableClass>Via<IntersectionTableClass>By<Rule1>And<Rule2>
- ([Zend_Db_Table_Select $select])</methodname>
- </para>
- </listitem>
- </itemizedlist>
- <para>
- In the patterns above, <methodname><TableClass></methodname> and
- <methodname><IntersectionTableClass></methodname> are strings that correspond to the class
- names of the destination table and the intersection table, respectively.
- <methodname><Rule1></methodname> and <methodname><Rule2></methodname> 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 RDBMS brands that do not support
- declarative referential integrity (DRI).
- </para>
- <para>
- For example, if you use MySQL's MyISAM storage engine, or SQLite, these solutions
- do not support DRI. You may find it helpful to declare the cascading operations
- with <classname>Zend_Db_Table</classname>.
- </para>
- <para>
- If your RDBMS implements DRI and the <methodname>ON DELETE</methodname> and
- <methodname>ON UPDATE</methodname> clauses, you should declare these clauses in your database
- schema, instead of using the cascading feature in <classname>Zend_Db_Table</classname>. Declaring
- cascading DRI rules in the RDBMS is better for database performance, consistency,
- and integrity.
- </para>
- <para>
- Most importantly, do not declare cascading operations both in the RDBMS 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 <methodname>UPDATE</methodname> or a <methodname>DELETE</methodname> 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 <methodname>Products</methodname> table, which is
- configured to automatically delete dependent rows in the <methodname>Bugs</methodname> 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 <methodname>UPDATE</methodname> 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
- <methodname>$_referenceMap</methodname>. Set the associative array keys <methodname>'onDelete'</methodname> and
- <methodname>'onUpdate'</methodname> to the string 'cascade' (or the constant
- <methodname>self::CASCADE</methodname>). 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 <methodname>Bugs</methodname> table are automatically deleted
- if the row in the <methodname>Products</methodname> table to which they refer is deleted. The
- <methodname>'onDelete'</methodname> element of the reference map entry is set to
- <methodname>self::CASCADE</methodname>.
- </para>
- <para>
- No cascading update is done in the example below if the primary key value in the
- parent class is changed. The <methodname>'onUpdate'</methodname> element of the reference map
- entry is <methodname>self::RESTRICT</methodname>. You can get the same result by omitting
- the <methodname>'onUpdate'</methodname> 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 <methodname>UPDATE</methodname> executed by a <classname>Zend_Db_Table</classname> class
- conflicts with the constraint, and results in a referential integrity violation.
- You can use cascading <methodname>UPDATE</methodname> in <classname>Zend_Db_Table</classname>
- <emphasis>only</emphasis> if your database does not enforce that referential
- integrity constraint.
- </para>
- <para>
- Cascading <methodname>DELETE</methodname> 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 <methodname>UPDATE</methodname> and <methodname>DELETE</methodname>, 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 RDBMS 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 <methodname>INSERT</methodname>.</emphasis>
- </para>
- <para>
- There is no support for a cascading <methodname>INSERT</methodname>. You must insert a row to a
- parent table in one operation, and insert row(s) to a dependent table in a separate
- operation.
- </para>
- </sect3>
- </sect2>
- </sect1>
- <!--
- vim:se ts=4 sw=4 et:
- -->
|