views:

247

answers:

2

Using Zend Framework, I've created a Model to insert a record into a database. My question is, after $this->insert($data) how can I switch the active table so that I can insert a record into another table?

Here's my code so far:

class Model_DbTable_Foo extends Zend_Db_Table_Abstract
{
  protected $_name = 'foo';

  public function addFoo($params)
  {
    $data = array(
      'foo' => $params['foo'],
    );
    $this->insert($data);
    $foo_id = $this->getAdapter()->lastInsertId();

    $data2 = array(
      'bar' => $params['bar']
    );
    // I need to change the Db Table name here.
    $this->insert($data2);
    $bar_id = $this->getAdapter()->lastInsertId();
  }
}
+1  A: 

I think you should have another Model_DbTable, Model_DbTable_Bar and call it either

internally:

class Model_DbTable_Foo ... {
  public function addFooAndBar() {
    ...
    $bar = new Model_DbTable_Bar();
    $bar->insert($data2);
  }
}

or externally:

class ModelX ... {
  public function addFoos() {
    $foo = new Model_DbTable_Foo();
    $foo->insert($data);
    $bar = new Model_DbTable_Bar();
    $bar->insert($data2);
  }
}
chelmertz
Unique classes for unique tables makes sense and feels cleaner. Thanks!
jwhat
+1  A: 

Zend_Db_Table is a Table Data Gateway. It

acts as a Gateway to a database table. One instance handles all the rows in the table.

This means, you have one class per table. Your Model_DbTable_Foo represents the Foo table in your database and only this table. It should not do inserts on other tables. That's what you would use another table class for. The cleanest option would be to add another layer on top of your TDGs that knows how to handle inserts to multiple tables, e.g.

class Model_Gateway_FooBar
{
    protected $_tables;

    public function __construct(Zend_Db_Table_Abstract $foo, 
                                Zend_Db_Table_Abstract $bar)
    {
        $this->_tables['foo'] = $foo;
        $this->_tables['bar'] = $bar;
    }

    public function addFoo($data)
    {
        $this->_tables['foo']->insert($data['foo']);
        // yaddayaddayadda
        $this->_tables['bar']->insert($data['bar']);
    }
}

However, it's your app and you can decide not to bother and simply create a new instance of the other class in the Foo class and do the insert from there, e.g.

$otherTable = new Model_DbTable_Bar;
$otherTable->insert($data);

Another option would be to put the logic into the controller, but I cannot recommend it, because this is not the responsibility of a controller and generally controllers should be kept thin and models should be fat.

On a sidenote, when you're doing multiple inserts, you might want to use transactions to make both inserts work as expected, e.g.

$this->_tables['foo']->getAdapter()->beginTransaction();

and then commit() or rollback() depending on the query outcome.

Also note that as of ZF1.9, you can also create instances of Zend_Db_Table without having to define a concrete subclass first, e.g.

$fooTable = new Zend_Db_Table('foo');

See the chapter on Zend_Db_Table in the ZF Reference Guide.

Gordon
Excellent advice! I forgot all about transactions, I definitely want to use them. Thanks!
jwhat
Gordon, can you please explain why I'd want to type cast $foo and $bar in the constructor as Zend_Db_Table_Abstract instead of my custom Model_DbTable_Foo and Model_DbTable_Bar?
jwhat
Just a note for anyone else interested in MySQL transactions: I had to change my DB type from MyISAM to InnoDB for transactional support.
jwhat
@jwhat that's not a type*cast* but a type*hint*. See http://php.net/manual/en/language.oop5.typehinting.php - you could use your custom model classes here, but that would couple your concrete model classes to the gateway. When using the abstract class for the hint, you make sure the instances passed are (just) table instances. They are not necessarily your custom classes, but the point in this case is, to make sure the instances have the methods of Zend_Db_Table_Abstract. It's also more flexible in case you'd need to exchange the custom class with something else.
Gordon
@jwhat In a nutshell: It is always better to depend on abstract types (interfaces, abstract base classes) rather than concrete types. This allows you to change the implementation of a method without impacting any of its callers.
Gordon