views:

198

answers:

2

I've recently started using Zend Framework (1.8.4), to provide admin tools for viewing the orders of a shopping cart site.

What I'd like to do is to efficiently create multiple model (Zend_Db_Table_Row_Abstract) objects from a single database result row.

The relationship is simple: an Order has one Customer (foreign key order_custid=customer.cust_id); a Customer has many Orders.

Loading the orders is easy enough. Using the method documented here:

http://stackoverflow.com/questions/638622/modeling-objects-with-multiple-table-relationships-in-zend-framework

...I could then grab the customer for each.


    foreach ($orderList as $o)
    {
        cust = $o->findParentRow('Customers');
        print_r ($cust); // works as expected.
    }

But when you're loading a long list of orders - say, 40 or more, a pageful - this is painfully slow.

Next I tried a JOIN:


    $custTable = new Customers();
    $orderTable = new Orders();
    $orderQuery = $orderTable->select()
        ->setIntegrityCheck(false) // allows joins
        ->from($orderTable)
        ->join('customers', 'cust_id=order_custid')
        ->where("order_status=?", 1); //incoming orders only.
    $orders = $orderTable->fetchAll($orderQuery);

This gives me an array of order objects. print_r($orders) shows that each of them contains the column list I expect, in a protected member, with raw field names order_* and cust_*.

But how to create a Customer object from the cust_* fields that I find in each of those Order objects?


foreach ($orders as $o) {
    $cols = $o->toArray();
    print_r ($cols); // looks good, has cust_* fields...

    $cust = new Customer(array( 'table' => 'Customer', 'data' => $cols ) );
    // fails - $cust->id, $cust->firstname, etc are empty

    $cust->setFromArray($cols);
    // complains about unknown 'order_' fields.

}

Is there any good way to create an Order and a Customer object simultaneously from the joined rows? Or must I run the query without the table gateway, get a raw result set, and copy each of the fields one-by-one into newly created objects?

+1  A: 

I use this method to assign database row fields to objects. I use setter methods, but this could probably be also done with only properties on object.

public function setOptions(array $options){
    $methods = get_class_methods($this);
    foreach ($options as $key => $value) {
        $method = 'set' . ucfirst($key);
        if (in_array($method, $methods)) {
            $this->$method($value);
        }
   }
   return $this;
}
gregor
+1  A: 

Zend_Db doesn't provide convenience methods to do this.

Hypothetically, it'd be nifty to use a Facade pattern for rows that derive from multiple tables. The facade class would keep track of which columns belong to each respective table. When you set an individual field or a whole bunch of fields with the setFromArray() method, the facade would know how to map fields to the Row objects for each table, and apply UPDATE statements to the table(s) affected.

Alternatively, you could work around the problem of unknown fields by subclassing Zend_Db_Table_Row_Abstract, changing the __set() behavior to silently ignore unknown columns instead of throwing an exception.

You can't have an OO interface to do everything SQL can do. There must be some line in the sand where you decide a reasonable set of common cases have been covered, and anything more complex should be done with SQL.

Bill Karwin
Hibernate (Java) does it elegantly; if you do: query.addEntity(Order.class) query.addEntity(Customer.class); ...then what you get back is a List, each row of which is an array with those two object types in the specified order.
Matt Hucke
That's cool. But for what it's worth, Hibernate consists of >490,000 lines of Java code. Zend_Db is about 2,700 lines of PHP code. Because of the runtime nature of the PHP platform, it's more sensitive to code bloat, and so it's more important to make the libraries lean, implementing only on the most commonly used features.
Bill Karwin
Good point! Yes, I can see the need to keep a framework size within reason.
Matt Hucke