View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFDEV:Zend Proposal Zone Template}

{zone-data:component-name}
Zend_Db_Table relationships
{zone-data}

{zone-data:proposer-list}
[Bill Karwin|mailto:bill.k@zend.com]
{zone-data}

{zone-data:revision}
1.1 - 17 January 2007: initial proposal.
1.2 - 22 February 2007: write section on cascading UPDATE and DELETE.
{zone-data}

{zone-data:overview}
This is a proposal to extend Zend_Db_Table and related classes Zend_Db_Table_Rowset and Zend_Db_Table_Row, to model relationships between relational database tables. The goal is to provide a simple and convenient API for developers to query data from related tables, given an instance of a row from one table.

This solution models relationships between tables in the RDBMS, and provides methods to retrieve rows based on "belongs_to", "has_many", and "has_one" relationships between tables.

The "belongs_to", "has_many" and "has_one" terms are based on Ruby on Rails parlance.
{zone-data}

{zone-data:references}
* [Ruby on Rails associations|http://www.rubyonrails.org/api/classes/ActiveRecord/Associations/ClassMethods.html]
* more references to be provided
{zone-data}

{zone-data:requirements}
* This component *will* model relationships between tables.
* This component *will not* require use of configuration files.
* Given a Zend_Db_Table_Row object instance for a master table, the object *will* have a method that returns a Zend_Db_Table_Rowset for matching rows in each dependent table. This models the one-to-many or "has_many" relationship.
* Given a Zend_Db_Table_Row object for a dependent table, the object *will* have a method that returns a Zend_Db_Table_Row for the matching row in its master table. This models the many-to-one or "belongs_to" relationship.
* Given a Zend_Db_Table_Row object for a table that is part of a many-to-many relationship with another table, the Row object *will* have a method that returns a Zend_Db_Table_Rowset object for the matching rows in the other table. This uses the intersection table that is necessary to implement the many-to-many model.
{zone-data}

{zone-data:dependencies}
* Zend_Db
* Zend_Exception
{zone-data}

{zone-data:operation}

h3. Storing information about references

Classes derived from Zend_Db_Table must store information about its declarative referential integrity (DRI) to other tables. Each foreign key must be stored in the class corresponding to the database table that contains the foreign key.

It is important that the information about DRI relationships is declared in only one class. If both the class containing the foreign key and the class containing the referenced primary key included the information, then they could become out of sync.

The reference information is stored in a protected static array. The array is an associative array, mapping strings which identify each DRI constraint to specific information about the constraint. This information includes the column(s) in the dependent table, the name of the master table, and the matching column(s) in the master table. For example:

{code}
class LineItems extends Zend_Db_Table
{

protected $_referenceMap = array(
'Order' => array(
'columns' => array('order_id'),
'refTable' => 'Orders',
'refColumns' => array('order_id')
),
'Referer' => array(
'columns' => array('referer_order_id'),
'refTable' => 'Orders',
'refColumns' => array('order_id')
),
'Product' => array(
'columns' => array('product_id'),
'refTable' => 'Products',
'refColumns' => array('product_id'),
)
);

...
}
{code}

The above example is for a class LineItems, which contains three foreign keys: two foreign keys reference to the Orders table, and one references the Products table.

The Zend_Db_Table class has a public method to return information about a reference. {{getReference($tableClassname, $ruleKey = null)}}, returns the element of the {{_referenceMap}} that refers to the specified table.

In cases where there is more than one foreign key to a given table, the first element of the array that matches the specified table is returned. The optional second argument specifies the key in the {{_referenceMap}}, so the user can query secondary foreign keys to the same table.

h3. Querying based on relationships

h4. Belongs_to relationships

The "belongs_to" or many-to-one relationship is the case where a dependent table contains a foreign key referencing the primary key of a master table. It is said that the dependent table "belongs to" the master table, as this relationship is commonly used to model a multi-valued dependent attribute of a master table.

A dependent table may have multiple foreign keys that reference different master tables. So retrieving rowsets from master tables must include some identification of the master table.

A dependent table may have multiple foreign keys that reference potentially different values in the primary key of the same master table. So retrieving rowsets from the master table must include some identification of the columns in the dependent table used for the foreign key reference.

The Zend_Db_Table_Row class has a method {{findParentRow()}}, which returns a Zend_Db_Table_Row object for the row in the master table with the matching primary key.

The Zend_Db_Table_Row class already contains a protected reference to its source Zend_Db_Table object. Using this, it can query the information about the correct reference, based on the information in the {{_referenceMap}}. {{findParentRow()}} can instantiate the referenced table class, and create a correct {{$where}} argument to query the rows matching the values in the foreign key of the current Zend_Db_Table_Row instance.

If no such relationship exists between the two tables, then the method throws an exception.

h4. Has_many relationships

The "has_many" or one-to-many relationship is the reverse of "belongs_to". A master table has a primary key and it "has many" rows in a dependent table, which reference the master table by using a foreign key.

A master table may have multiple dependent tables. So retrieving dependent data must include some identification of the dependent table.

A dependent table may have multiple foreign keys referencing potentially different values in the primary key of the master table. So retrieving dependent data from such a table must include some identification of the columns in that table that form the foreign key.

The Zend_Db_Table_Row class has a method {{findDependentRowset()}}, which returns a Zend_Db_Table_Rowset object for the row in the dependent table with the matching foreign key.

The {{findDependentRowset()}} method can instantiate the referenced table class, and query its {{_referenceMap}}. Then it can query that table with an appropriate {{$where}} clause.

If no such relationship exists between the two tables, then the method throws an exception.

h4. Many-to-many relationships

A many-to-many relationship is really a relationship between three tables. Two main tables, and one table in the middle. The middle table contains foreign keys referencing the primary keys of each respective main table.

A given table may belong to multiple many-to-many relationships with different intersection tables. So retrieving matching data from the other side of the relationship must include some identification of the intersection table, and of which columns in that intersection table are used for referencing each of the main tables.

The Zend_Db_Table_Row class has a method {{findManyToManyRowset()}}, which returns a Zend_Db_Table_Rowset object for the row in the dependent table with the matching primary key. The intersection table must be specified. The {{findManyToManyRowset()}} method instantiates the intersection table, queries the {{_referenceMap}} and retrieves a rowset from the intersection table. From this, it finds values to use in a {{$where}} clause to query the other table in the many-to-many relationship. If Zend_Db_Table supports joins or query by SQL (to allow us to use subqueries or joins), we can make this operation more efficient.

h4. Has_one relationships

The "has_one" relationship is curious. This occurs when a foreign key in the dependent table is also its primary key. Therefore there is at most one row in the dependent table referencing the master table.

Neither of the use cases below are part of any logical relational model; the first is an object-oriented model, and the second is used for physical optimization.

h5. Inheritence use case

Has-one relationships are sometimes used to model object-oriented inheritance.

If table B extends table A in an object-oriented fashion, then table B should have the attributes of table A, plus additional attributes. Store the attributes from the "superclass" in table A and only the extended attributes in table B. Thus every row in table B corresponds to exactly one row in table A, and the combination of these two rows forms one object instance of class B.

This design approximates some OO characteristics, in that alterations to the schema of table A are implicitly inherited by the table B model.

h5. Column-partitioning use case

Has_one relationships can also be used to physically partition columns of a table.

For instance, if a table T contains a few compact columns (like integers and dates) that are queried frequently, and the same table also contains some bulky columns (like varchar and blob) that are queried infrequently, there is some performance advantage to separating these two sets of columns into different tables, with a one-to-one relationship between them.

The advantage is based on some RDBMS implementations in which I/O efficiency or caching efficiency is benefited by smaller row size. A single disk read can read multiple rows if the rows are small. A fixed cache size can store more rows (perhaps even the entire table) if the rows are small.

h5. Solution for has_one relationships

- TO BE WRITTEN

h3. Cascading write operations

Model cascading operations, like those provided by declarative referential integrity clauses {{ON UPDATE}} and {{ON DELETE}}.

h4. Cascading DELETE

If a row in a parent table is deleted, all dependent rows in the database should be deleted. This requires that the parent table's class have some declaration of which tables contain referencing foreign keys to the parent.

{code}
class Orders extends Zend_Db_Table
{
protected $_dependentTables = array(
'LineItems'
);
}

class LineItems extends Zend_Db_Table
{
protected $_referenceMap = array(
'Order' => array(
'columns' => array('order_id'),
'refTable' => 'Orders',
'refColumns' => array('order_id'),
'onDelete' => self::CASCADE,
'onUpdate' => self::CASCADE
),
);
}
{code}

When the {{delete()}} method of a {{Zend_Db_Table_Row}} object is called, it checks its table class and gets a list of each table listed in the protected array {{$_dependentTables}}. Then it calls a method in that class to invoke

Objects of type {{Zend_Db_Table_Row}} may exist in the application, containing data previously fetched from the dependent table, data which are now deleted as a result of the cascading delete operation. These objects are _not_ synchronized with the database; they represent phantom data that does not exist anymore in the database, and they have no automatic indication of this state.

The PHP application must delete rows in dependent tables first, before it deletes a row in the parent table.

If the RDBMS schema also contains DRI for an {{ON DELETE CASCADE}} rule, there is no conflict. Because the dependent rows are deleted explicitly before the row in the parent table is deleted, the cascading delete implemented in the server simply deletes zero rows.

Note that currently, Zend_Db_Table_Row has no {{delete()}} method.

h4. Cascading UPDATE

If the primary key of a row in a parent table is changed, all dependent rows that refer to this primary key value should be uupdated to match. Like the cascading {{DELETE}}, this also requires that the parent table's class have a declaration of dependent tables. This is done in exactly the same way as shown in the case of Cascading {{DELETE}} above.

When the {{save()}} method of a {{Zend_Db_Table_Row}} object is called, and the primary key column(s) are among the columns that have been modified, then a similar process is done to that in the {{DELETE}} case. The table(s) listed in {{$_dependentTables}} are consulted, and through the {{$_referenceMap}}, dependent rows are updated.

To preserve referential integrity, the update of the primary key of the parent table and the update of foreign keys of dependent tables must be done atomically; referential integrity constraints in the RDBMS server should be applied in a deferred way, or else the RDBMS will prohibit the update of dependent records. If the RDBMS does not support deferred constraint enforcement, then the schema must not be designed to enforce the RI at all, or else non-atomic cascading {{UPDATE}} performed by Zend_Db_Table will cause a violation in the RI enforement.

If the RDBMS schema is designed to enforce the RI using contraints, then cascading {{UPDATE}} must also be performed by the RDBMS. The developer should declare {{'onUpdate' => self::CASCADE}} in the {{$_referenceMap}} of dependent tables *only* if the RDBMS schema does not enforce RI for these relationships.

Fortunately, cascading {{UPDATE}} is a fairly uncommon operation. There is no reason to change the primary key value of a pseudokey (such an an auto-generated integer {{id}} column), so this typically is used only with natural keys.

Note also that currently in Zend_Db_Table_Row, changing the values of a primary key is not permitted; it throws an exception. This will have to change.

Also note that currently Zend_Db_Table_Row does not track the original data values, but only the data values that may have been modified by the application. So if we permit the application to change the primary key values, we must preserve the original values internally, so that we can use them to to update the correct rows in dependent tables.

h4. Cascading INSERT

There is no such thing as a cascading {{INSERT}}. If I create a new record, there is no way the DRI knows what, if any, rows to create in dependent tables. New rows in dependent tables must be created explicitly, after the row to which they refer is created.

h4. Entity Lifecycle

Some ORM technology implements the concept of entity lifecycle. That is, objects in application memory track changes to attributes, and therefore know when they are "dirty" and need to be posted to the database. They also track any dependent objects that have been fetched from the database and exist as objects in the application space, and therefore know to keep such objects in sync with their state in the database.

The feature of entity lifecycle management is beyond the scope of Zend Framework 1.0 and will not be addressed with this proposal. It might be addressed in future versions of Zend Framework.

h3. Auto-discovery of relationships

{note}
Auto-discovery of metadata may be added as a future enhancement of Zend_Db_Table. In the short term, only explicit declaration of DRI information is supported.
{note}

If the user's class derived from Zend_Db_Table does not include an explicit declaration of the relationships in the {{_referenceMap}} array, then the class can query the database adapter for metadata information, and may cache this information.

This information should be based on standard system views conforming to the {{INFORMATION_SCHEMA}} where these views are available. Of the databases currently supported, here is the status of support for {{INFORMATION_SCHEMA}}:

||RDBMS ||Foreign Keys ||{{INFORMATION_SCHEMA}} ||
| PostgreSQL |(/) |(!) use pg_catalog views |
| Oracle |(/) |(!) use nonstandard views |
| IBM DB2 |(/) |(!) use SYSCAT views |
| MS SQL Server |(/) |(/) since circa 2000 |
| MySQL |(/) except INNODB|(/) since 5.0 |
| SQLite |(x) |(x) |

{zone-data}

{zone-data:milestones}
Milestone 1: \[DONE\] Publish proposal.
Milestone 2: Revise proposal, approve for Incubator development.
Milestone 3: Commit working prototype to Incubator.
Milestone 4: Commit working unit tests for one RDBMS back-end.
Milestone 5: Write end-user documentation.
Milestone 6: Release prototype in Zend Framework 0.8 Preview Release.
Milestone 7: Revise implementation, tests, and documentation based on feedback.
Milestone 8: Merge changes from Incubator to Core in Zend Framework 0.9 Beta Release.
{zone-data}

{zone-data:class-list}
* Zend_Db_Table
* Zend_Db_Table_Row
* Zend_Db_Table_Rowset
{zone-data}

{zone-data:use-cases}


The use cases below are based on a schema for an order-processing database. Here is pseudocode for the SQL schema:

{code}
CREATE TABLE Customers (
customer_id PRIMARY KEY
)

CREATE TABLE Orders (
order_id PRIMARY KEY,
customer_id FOREIGN KEY REFERENCES Customers
)

CREATE TABLE Products (
product_id PRIMARY KEY
)

CREATE TABLE LineItems (
order_id FOREIGN KEY REFERENCES Orders,
product_id FOREIGN KEY REFERENCES Products
referer_order_id FOREIGN KEY REFERENCES Orders,
PRIMARY KEY (order_id, product_id)
)

CREATE TABLE Deliveries (
order_id,
product_id,
date DATE,
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id, product_id)
REFERENCES LineItems (order_id, product_id)
)
{code}

Here is an Entity Relationship diagram for the above schema:
!orders_erd.png|thumbnail!

{composition-setup}

{deck:id=use-cases1}

{card:label=UC 1 : Has_many}

In this case, the master table (Orders) is referenced by the foreign key of one dependent table (LineItems).

For example, the application has a Zend_Db_Table_Row object for the master table, and queries for matching rows in the dependent table.

{code}
$ordersTable = new Orders();
$ordersRowset = $ordersTable->find(123);
$order = $ordersRowset[0];
$lineitemsRowset = $order->findDependentRowset('LineItems');
{code}

A magic {{__call()}} method can also be used to fetch the dependent rows:

{code}
...
$lineitemsRowset = $order->findLineItems();
{code}

No inflectors are used. The string used in both the non-magic and the magic find methods must match the classname for the dependent table exactly.

{card}

{card:label=UC 2 : Has_many, multiple tables}

In this case, the master table (XXX) is referenced by the foreign keys of multiple dependent tables (XXX).

For example, the application has a Zend_Db_Table_Row object for the master table, and queries for matching rows in each of the dependent tables.

TO BE WRITTEN.

{card}

{card:label=UC 3 : Has_many, multiple keys}

In this case, the master table (Orders) is referenced by more than one foreign key in one dependent table (LineItems).

For example, a LineItems row may belong to an order, but another foreign key in LineItems references an Orders row that is the "referer". Say in this order processing system, a line item can be given a discounted price by being referred by some past order. So each LineItems row references another Orders row other than the one to which the lineitem belongs.

The class for the LineItems table (or any dependent table) must declare the relationships between these tables for each reference, listing the column(s) in each table.

{code}
class LineItems extends Zend_Db_Table
{

protected $_referenceMap = array(
'Order' => array(
'columns' => array('order_id'),
'refTable' => 'Order',
'refColumns' => array('order_id')
),
'Referer' => array(
'columns' => array('referer_order_id'),
'refTable' => 'Order',
'refColumns' => array('order_id')
)
);

...
}
{code}

The columns entries must be arrays, because keys can be multi-column. It should be implicit that {{refColumns}} is always comprised of the primary key of the referent table, but by specifying the columns, this allows us to establish referential integrity to columns that aren't declared primary keys.

For example, the application has a Zend_Db_Table_Row object for the master table, and queries for matching rows in the dependent table for each of the keys.

{code}
$ordersTable = new Orders();
$ordersRowset = $ordersTable->find(123);
$order = $ordersRowset[0];
$lineitemsRowset = $order->findDependentRowset('LineItems', 'Referer');
{code}

A magic {{__call()}} method can also be used to fetch the dependent rows:

{code}
...
$lineitemsRowset = $order->findLineItemsByReferer();
{code}

The word 'Referer' does not indicate any column name; it indicates the key in the {{_referenceMap}} array, which is up to the developer who implements this LineItems class, and it is not bound to match any name in the database schema. No inflectors are used. The string used for the array key must be used exactly in both the non-magic and the magic find method.

The find method must look up the {{_referenceMap}} in the dependent table and use the column(s) named to construct a {{$where}} clause, which it uses when querying the LineItems class.

{card}

{deck}

{deck:id=use-cases2}

{card:label=UC 4 : Belongs_to}

In this case, the dependent table (Orders) references one master table (Customers).

For example, the application has a Zend_Db_Table_Row object for the dependent table, and queries for matching rows in the master table.

{code}
$ordersTable = new Orders();
$ordersRowset = $ordersTable->find(123);
$order = $ordersRowset[0];
$customerRow = $order->findParentRow('Customers');
{code}

There can only be one parent row, so this find method returns a Zend_Db_Table_Row object instead of a Zend_Db_Table_Rowset.

A magic {{__call()}} method can also be used to fetch the parent row:

{code}
...
$customerRow = $order->findParentCustomers();
{code}

No inflectors are used. The string used in both the non-magic and the magic find methods must match the classname of the master table exactly. Because it is customary for table names and therefore their corresponding class to be spelled in the plural form, the result appears odd, since there can be only one parent row. But this is a small inconvenience we accept, because eliminating use of inflectors makes so many other things simpler.

{card}

{card:label=UC 5 : Belongs_to, multiple masters}

In this case, the dependent table (LineItems) references two master tables (Orders and Products).

For example, the application has a Zend_Db_Table_Row object for the dependent table, and queries for matching rows in each of the master tables.

The class for the LineItems table (or any dependent table) must declare the relationships between these tables for each reference, listing the column(s) in each table.

{code}
class LineItems extends Zend_Db_Table
{

protected $_referenceMap = array(
'Order' => array(
'columns' => array('order_id'),
'refTable' => 'Order',
'refColumns' => array('order_id')
),
'Referer' => array(
'columns' => array('referer_order_id'),
'refTable' => 'Order',
'refColumns' => array('order_id')
),
'Product' => array(
'columns' => array('product_id'),
'refTable' => 'Products',
'refColumns' => array('product_id'),
)
);

...
}
{code}

{code}
$lineitemsTable = new LineItems();
$lineitemsRowset = $lineitemsTable->find(array(123, 'Abc'));
$lineitem = $lineitemRowset[0];
$orderRow = $lineitem->findParentRow('Orders', 'Order');
$productRow = $lineitem->findParentRow('Products', 'Product');
{code}


A magic {{__call()}} method can also be used to fetch the parent row in each case:

{code}
...
$orderRow = $lineitem->findParentOrders();
$productRow = $lineitem->findParentProducts();
{code}

It is optional to specify the rule name either in the {{findParentRow()}} method or in the matching magic method. Sensible defaults are used. In the case that there is one reference declared to the Products table, there is no ambiguity. In the case that there are two references declared tot he Orders table, the {{_referenceMap}} array is searched in the order it was declared, and the first entry matching the referent table is used.

{card}

{card:label=UC 6 : Belongs_to, multiple keys}

In this case, the dependent table (LineItems) contains two foreign keys, referencing potentially different rows in one master table (Orders).

For example, the application has a Zend_Db_Table_Row object for the dependent table, and queries for matching rows in the master table for each of the keys.

{code}
$lineitemsTable = new LineItems();
$lineitemsRowset = $lineitemsTable->find(array(123, 'Abc'));
$lineitem = $lineitemRowset[0];
$orderRow = $lineitem->findParentRow('Orders', 'Order');
$refererOrderRow = $lineitem->findParentRow('Orders', 'Referer');
{code}


A magic {{__call()}} method can also be used to fetch the parent row in each case:

{code}
...
$orderRow = $lineitem->findParentOrdersByOrder();
$refererOrderRow = $lineitem->findParentOrdersByReferer();
{code}

{card}

{deck}

{deck:id=use-cases3}

{card:label=UC 7 : Many-to-many}

In this case, there is a many-to-many relationship between two tables (Orders and Products). The LineItems table functions as an intersection table.

For example, the application has a Zend_Db_Table_Row object for the Orders table, and queries for matching rows in the Products table.

{code}
$ordersTable = new Orders();
$ordersRowset = $ordersTable->find(123);
$order = $ordersRowset[0];
$productRowset = $order->findManyToManyRowset('Products', 'LineItems');
{code}

A magic {{__call()}} method can also be used to fetch the matching rows:

{code}
...
$productRowset = $order->findProductsViaLineItems();
{code}

The intersection table's class is named in the second parameter of findManyToManyRowset(), or following the string 'Via' in the magic method. The {{_referenceMap}} array in this class is consulted to find the declarations of the relationships between tables.

{card}

{card:label=UC 8 : Many-to-many, multiple keys}

In this case, the intersection table may have more than one reference to one or both of the main tables in the many-to-many relationship.

Optional arguments to the findManyToManyRowset() method name the rules in the {{_referenceMap}} declaration. The first of these optional arguments names the rule that references the primary table (the one invoking the method). The last argument names the rule that references the other table in the relationship.

{code}
$ordersTable = new Orders();
$ordersRowset = $ordersTable->find(123);
$order = $ordersRowset[0];
$productRowset = $order->findManyToManyRowset('Products', 'LineItems', 'Order', 'Product');
{code}

A magic {{__call()}} method can also be used to fetch the matching rows:

{code}
...
$productRowset = $order->findProductsViaLineItemsByOrderAndProduct();
{code}

{card}

{card:label=UC 9 : Has_one}

In this case, the master (LineItems) and dependent (Deliveries) tables have a one-to-one relationship.

For example, the application has a Zend_Db_Table_Row object for the master table, and queries for matching rows in the dependent table.

- TO BE WRITTEN

{card}

{deck}

{zone-data}

{zone-data:skeletons}

{composition-setup}

{deck:id=skeletons1}

{card:label=Zend_Db_Table}

{code}

class Zend_Db_Table
{
/**
* Hash map of DRI rules, declaring which
* tables this one references.
*
* @var array
*/
protected static $_referenceMap = array();

/**
* Simple array of class names of tables that
* reference this one.
*
* @var array
*/
protected static $_dependentTables = array();

/**
* @return array - One element in the _referenceMap array.
* Matching $tableClassname and optionally $ruleKey.
*/
public function getReference($tableClassname, $ruleKey = null)
{
// return an entry from the _referenceMap array
}

/**
* Called by parent table's class during delete() method.
*
* @param string $parentTableClassname
* @param array $primaryKey
*/
public function cascadeDelete($parentTableClassname, $primaryKey)
{
// make an appropriate $where clause
return $this->delete($where);
}

/**
* Called by parent table's class during save() method.
*
* @param string $parentTableClassname
* @param array $oldPrimaryKey
* @param array $newPrimaryKey
*/
public function cascadeUpdate($parentTableClassname, $oldPrimaryKey, $newPrimaryKey)
{
// make an appropriate $where clause
return $this->update($where, $newPrimaryKeyValues);
}
}
{code}

{card}

{card:label=Zend_Db_Table_Rowset}

{code}
class Zend_Db_Table_Rowset
{
// No changes needed in this class.
}
{code}

{card}

{card:label=Zend_Db_Table_Row}

{code}
class Zend_Db_Table_Row
{

/**
* This is set to a copy of $_data when the data is fetched from
* a database, specified as a new tuple in the constructor, or
* when dirty data is posted to the database with save().
* @var array
*/
protected $_cleanData = array();

/**
* @return Zend_Db_Table_Rowset - Query $dependentTableClass
* for the rows matching the primary key in the current row.
*/
public function findDependentRowset($dependentTableClass, $refRule = null)
{
}

/**
* @return Zend_Db_Table_Row - Query $parentTableClass for the
* single row matching the foreign key in the current row.
*/
public function findParentRow($parentTableClass, $refRule = null)
{
}

/**
* @return Zend_Db_Table_Rowset - Query $matchingTableClass
* and $intersectionTableClass for the rows matching the primary
* key in the current row.
*/
public function findManyToManyRowset(
$matchingTableClass, $intersectionTableClass,
$primaryRefRule = null, $matchingRefRule = null)
{
}

/**
* Turn magic function calls into non-magic function calls
* to the above methods.
*
* @return Zend_Db_Table_Row or Zend_Db_Table_Rowset
*/
protected function __call($method, $args)
{
// If recognize methods for Has_many cases:
// find<Classname>()
// find<Classname>By<Rule>()
if (...) {
return $this->findDependentRowset(<Classname>, <Rule>);
}

// Recognize methods for Belongs_to cases:
// findParent<Classname>()
// findParent<Classname>By<Rule>()
if (...) {
return $this->findParentRow(<Classname>, <Rule>);
}

// If recognize methods for Many-to-many cases:
// find<Classname1>Via<Classname2>()
// find<Classname1>Via<Classname2>By<Rule>()
// find<Classname1>Via<Classname2>By<Rule1>And<Rule2>()
if (...) {
return $this->findManyToManyRowset(
<Classname1>, <Classname2>,
<Rule1>, <Rule2>);
}
}

public function delete()
{
foreach ($this->_dependentTables as $tableClassname) {
$t = new $tableClassname();
$t->cascadeDelete($this->get_class(), $this->_data[..primary key column(s)...]);
}
// make $where clause based on current row's primary key
$this->_table->delete($where);
}

public function save()
{
...
// Skip insert() case; there is no cascading INSERT
...

// Within the update() case...
foreach ($this->_dependentTables as $tableClassname) {
$t = new $tableClassname();
$t->cascadeUpdate($this->get_class(),
$this->_cleanData[...primary key column(s)...],
$this->_data[...primary key column(s)...]);
}
// Proceed with existing update() logic
}

}
{code}

{card}

{deck}

{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>