2008年12月9日星期二

Re: [fw-mvc] Another Model Design Thread

Pauls approach is what I would consider good practice when it comes to
models, you are breaking OO inheritance by extending your models from
Zend_Db_Table. Models should have a has-a not is-a relationship with
models.

There are really three options when it comes down to your Model / Domain:

1) Extend models from Zend_Db_Table (The Ruby way)
This is good for quick prototypes and small applications with low complexity.
It is hard to test without the database

2) Use a has-a relationship
This is probably the most common ZF way currently and is good for
medium to large apps with moderate complexity.
It is fairly easy to test without the database but needs additional
tools to achieve this.

3) Use a pure OO Domain Model
This is where you have a totally clean domain layer, this means that
your models do not know about any database/etc code. To achieve this
you probably would be looking at implementing the Repository and Unit
of Work patterns plus others.
This is good for enterprise apps with high complexity.
Testing is made very easy as the system will have distinct layers that
are loosely coupled.

Modeling the Domain is the best part of any application build :)


2008/12/9 Paul Court <paul@pmcnetworks.co.uk>:
>>
>>>
>>> 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
>
>

--
----------------------------------------------------------------------
[MuTe]
----------------------------------------------------------------------

没有评论: