2008年12月9日星期二

Re: [fw-mvc] Another Model Design Thread

>
>>
>> I can extend Zend_Db_Table but that seems to leave me with only
>> insert,
>> update, delete because the select statements are moved to
>> Zend_Db_Table_Select (according to the guide).
>>
>> So, what if I want to be able to load one model and perform both of
>> these
>> functions? Am I missing the idea?
>>
>
> I currently extend the Zend_Db_Table as you have mentioned and
> create public
> methods for anything that is outside the standard functionality
> offered by
> the parent class.
>
>
> public function getList()
> {
> $SQL = "SELECT `a`.`id`, `a`.`company`, `b`.`name` as
> `user_type`,
> CONCAT_WS(' ',`first_name`,`middle_name`,`last_name`)
> as
> `contact`
> FROM {$this->_name} `a`
> LEFT JOIN `user_types` `b` ON `a`.`user_type_id` =
> `b`.`id`";
> $this->_db->setFetchMode(Zend_Db::FETCH_OBJ); // FETCH_ASSOC
> for
> associated (I think this is the default anyway).
> return $this->_db->fetchAll($SQL);
> }
>
> // ... other methods
> }
>

I am not so keen on this method anymore. This is how I have done it up
until now, but what you are returning from getList() are instances of
Zend_Db_Table_Row_Abstract. Which means that other parts of the code
could theoretically do something like:-

$user_one->id = 0;
$user_one->save();

If we assume that "id" is an auto_increment primary key, what business
does any other part of the application have changing that id?

Instead what I am working on at the moment, is based around the idea
that my models will "use" Zend_Db(_Table/_Row) not extend them. This
means that the database access is locked away from the rest of the
program, so if you are working with a team of developers, there is
much less chance of someone making a mistake like the one above.

It also means that your models should match your application closer,
and expose less public code. This makes your application more
decoupled and should make it easier to maintain.

I came up with something like:-

class User {
protected _table;
protected _id;
protected _name;

public function getId(){ return $this->_id; }

public function getName(){ return $this->_name; }
public function setName( $value ){ $this->_name = $value; }

public function setTableHandler( Zend_Db_Table_Abstract $table )
{ $this->_table = $table; }

public function __construct( ){ }

public function save(){
// Use $this->_table to save the row.
}

public function load(){
// Use $this->_table to load the row.
}
}

At first, this may seem like you would be needlessly creating a lot of
get and set methods, but a good IDE will be able to make snippets for
you to reduce the typing, and if you are doing something like test
driven development, then is only a small increase in the grand scheme
of things.

Also, whose job is it to make sure your data values are in correct
ranges? For arguments sake, lets say we are modelling a car. We might
have something like:-

class Car {
protected $_num_gears;
protected $_current_gear;

public function __construct( $gears ){
$this->_num_gears = $gears;
$this->_gear = 0; // neutral (-1 could be reverse! )
}

public function ChangeUpGear(){
$this->_current_gear++;
}
public function ChangeDownGear(){
$this->_current_gear--;
}
public function SelectGear( $gear ){
$this->_current_gear = $gear;
}
}

Now it's pretty obvious where you would add extra code to make sure
that your gear selections are within the allowed ranges. For example,
if I tried to SelectGear(6) in a 5 gear car, I could throw an
exception. Then if any other parts of my code attempted to do it, I
would know right away - if I were using the models return from
Zend_Db_Table->fetchAll(), I would not!

This is by no means a full working example. I still have some issues
that I would like to resolve, like the best way to load multiple
items. At the moment I also have a public function loadFromArray()
which will populate the model from array values instead of loading
from the DB. This obviously opens up a hole similar to the one I
mentioned earlier in that you could do:-

$user->loadFromArray( array('id' => 0) );

I am thinking I am going to move towards putting the loadFromArray()
functionality so that it is protected and you have to pass the array
of data to the constructor. This would make it much harder to
accidentally update the wrong value in the model.


Paul

没有评论: