Tuesday, December 30. 2008Model InfrastructureIn the last two entries in this series on models, I covered using forms as input filters and integrating ACLs into models. In this entry, I tackle some potential infrastructure for your models. The Model is a complex subject. However, it is often boiled down to either a single model class or a full object relational mapping (ORM). I personally have never been much of a fan of ORMs as they tie models to the underlying database structure; I don't always use a database, nor do I want to rely on an ORM solution too heavily on the off-chance that I later need to refactor to use services or another type of persistence store. On the other hand, the model as a single class is typically too simplistic. I am, however, a fan of the Domain Model. To quote wikipedia, [The] domain model can be thought of as a conceptual model of a system which describes the various entities involved in that system and their relationships. When you think in these terms, you start breaking your system into discrete pieces that you need to manipulate, as well as consider how each piece relates to the others. This type of exercise also helps you stop thinking of your model in terms of database tables; instead, your database becomes the container in which data is persisted from one use of your model to the next. Your model instead is an object that can do things with either incoming or stored data -- or even completely autonomously.
As an example, when starting with Zend Framework, it's tempting to use
Additionally, if in the future you wish to refactor your application to utilize memcached or a web service, you now not only need to completely rewrite your models, but also all consumer code, because the return values from your model have changed. So, if you're not going to use an ORM or a Table Data Gateway directly, how should you architect your model infrastructure? What are you modelling?The principal question to ask is, "What am I modelling?" Let's look at a rather standard website issue: user management. Typically, you'll get a requirement such as, "Users should be able to register for an account on the site. Once registered, they should be able to login with the credentials they provided. Administrators should be able to ban accounts or grant users higher levels of privileges." That's assuming you actually get good requirement documents, of course. Most developers will immediately setup a database with a few fields that represent a user -- full name, username, email, password, etc -- create a form for registration and another for login, write a routine to validate each, create a page to list users for the administration screen... you know the drill. But what are you modelling? The answer is: users. So, now it's time to define what a user is, and what a user can do. We have to decide what constitutes a new user, and what constitutes an authenticated user. We have an additional modelling consideration that's often overlooked: user roles. There's also the matter of what a group of users might look like (since the administrator needs to be able to list users), and how we might want to work with groups. Let's start with narrowing down the definition of a user:
Notice the fifth piece of metadata? It mentions a "role"? That's something to do with our ACLs -- which means that ACLs are part of our user domain. I'll touch on this later.
If you look at the remaining points carefully, you'll note that there's talk
of validation, authentication, and user and session persistence. Validation
rules are part of our model -- and we'll use Now, let's turn to defining lists of users:
These criteria indicate that a list of users should be an object.
This list will likely implement the SPL class We started this article by discussing the Domain Model, and defined it as a system, its entities, and the relations between those entities. We've now identified our domain: user management. The various entities include users, lists of users, ACL roles, a user persistence layer (database), and session persistence layer (web server sessions). Now that we know what we're modelling, let's look at some of the objects in our model. Gateway to the DomainWe've identified "user management" as the purpose of our model. This will include retrieving and saving individual users, as well as selecting groups of users. It's clear that we'll need an object to represent a user, as well as another to represent a selection or group of users. But what may not be entirely clear is that we should likely have an object that is used to create new user objects, create selections of users, and basically coordinate several of the related objects -- the root ACL and data access, in particular. This object will be what I'll term our domain gateway. It will be used to fetch other objects in our model, and will inject various dependencies into them when doing so, such as the data access and ACLs. The various dependencies may themselves be injected into the gateway -- or it can lazy-load them. The API of this gateway might look something like the following. // Instantiate the gateway $userGateway = new Spindle_Model_UserGateway(); // configure the gateway: $userGateway->setAcl(new Spindle_Acl_Spindle()) ->setDbAdapter(Zend_Registry::get('db')); // Alternately, do it all at instantiation: $userGateway = new Spindle_Model_UserGateway(array( 'acl' => new Spindle_Acl_Spindle(), 'dbAdapter' => Zend_Registry::get('db'), )); // Grab a single user $user = $userGateway->retrieve('matthew'); // Grab many users $users = $userGateway->sort('email', 'ASC') ->criteria(array('banned' => true)) ->fetch(array('offset' => 20, 'limit' => 20)); // Better yet, add some transaction script methods with preset criteria: $users = $userGateway->fetchBannedUsers(array( 'offset' => 20, 'limit' => 20, 'sort' = array('email', 'ASC'), )); // Create a new user: $user = $userGateway->createUser(array( 'username' => 'matthew', 'fullname' => "Matthew Weier O'Phinney", 'password' => 'secret', 'email' => 'matthew@local', )); The basic idea is to provide a scaffold for lazyloading necessary objects, methods for specifying options (such as sort order, criteria, limits, etc), and transaction methods for retrieving individual users and groups of users. Of Value Objects and Record SetsTo other objects we've identified in our model are users and user lists. How should we define these? The traditional answer is as value or data transfer objects and record sets. The Value Object is a standard design pattern used to aggregate all metadata that defines a single value. The Record Set is an aggregation of Value Objects. Value ObjectsMartin Fowler makes a differentiation between value objects and data transfer objects in his book "Patterns of Enterprise Application Architecture" (PoEAA). In it, he associates value objects with language variable types (i.e., Value Objects act as custom variable types), while defining data transfer objects as aggregating related values for the purpose of serialization and data transfer between objects. In Java, however, value objects are arbitrary objects used to store a specific set of attributes -- very similar to the data transfer object. For the purposes of this discussion, I'll use the term "value object," as it will be familiar to those with a Java background, and to indicate that we are aggregating a unique value that is the sum of a number of attributes. Basically, all of this verbiage describes something incredibly simple in implementation: an object with a specific set of attributes or properties. If you've been doing any OOP programming in PHP, this is the most natural and fundamental thing you can do. class Spindle_Model_User { protected $_data = array( 'username' => null, 'email' => null, 'fullname' => '', 'role' => 'guest', ); public function __construct($data) { $this->populate($data); if (!isset($this->username)) { throw new Exception('Initial data must contain an id'); } } public function populate($data) { if (is_object($data)) { $data = (array) $data; } if (!is_array($data)) { throw new Exception('Initial data must be an array or object'); } foreach ($data as $key => $value) { $this->$key = $value; } return $this; } public function __set($name, $value) { if (!array_key_exists($name, $this->_data)) { throw new Exception('Invalid property "' . $name . '"'); } $this->_data[$name] = $value; } public function __get($name) { if (array_key_exists($name, $this->_data)) { return $this->_data[$name]; } return null; } public function __isset($name) { return isset($this->_data[$name]); } public function __unset($name) { if (isset($this->$name)) { $this->_data[$name] = null; } } } The above example is fairly simplistic, but it gets the idea across: the object defines a limited range of valid values, and enforces that only these values may be set -- as well as which values are required. You could certainly add accessor and mutator methods to enforce consistent access to member data, but the above will certainly suffice for many use cases. (I'll look at data integrity momentarily.)
One addition you might make to the class definition is to add some
conversions from different types of objects. For instance, if you know that
you'll be using class Spindle_Model_User { /* ... */ public function populate($data) { if ($data instanceof Zend_Db_Table_Row_Abstract) { $data = $data->toArray(); } elseif (is_object($data)) { $data = (array) $data; } if (!is_array($data)) { throw new Exception('Initial data must be an array or object'); } foreach ($data as $key => $value) { $this->$key = $value; } return $this; } /* ... */ } This will help keep your model code clean, as you can potentially take the results of data storage operations and push them directly into your value object -- resulting in less re-working of code.
Now, what about data integrity? This is where class Spindle_Model_User { /* ... */ public function __set($name, $value) { if (!array_key_exists($name, $this->_data)) { throw new Exception('Invalid property "' . $name . '"'); } $inputFilter = $this->getForm(); if ($element = $inputFilter->getElement($name)) { if (!$element->isValid($value)) { throw new Exception(sprintf( 'Invalid value provided for "%s": %s', $name, implode(', ', $element->getMessages()) ); } } $this->_data[$name] = $value; } /* ... */ protected $_form; public function getForm() { if (null === $this->_form) { $this->_form = new Spindle_Form_User(); } return $this->_form; } /* ... */ }
One note: if your model contains metadata that will never be represented as
part of a form, you shoould look into using Now that we have input filtering out of the way, how shall we address saving a user? Recall in our discussion of the domain gateway that one of its responsibilities is injecting other dependencies into our objects. I find it's often easier to inject the gateway into objects, and then pull what I need from it. Let's look at how that might work for saving the user. class Spindle_Model_User { /* ... */ protected $_gateway; public function __construct($data, $gateway) { $this->setGateway($gateway); /* ... */ } public function setGateway(Spindle_Model_UserGateway $gateway) { $this->_gateway = $gateway; return $this; } public function getGateway() { return $this->_gateway; } public function save() { $gateway = $this->getGateway(); $dbTable = $gateway->getDbTable('user'); if ($row = $dbTable->find($this->username)) { foreach ($this->_data as $key => $value) { $row->$key = $value; } $row->save(); } else { $dbTable->insert($this->_data); } } /* ... */ }
Note that the constructor now has a second argument -- the gateway. This
ensures that the user always has a gateway instance, which further ensures
that operations like the one listed -- retrieving the
Another requirement we identified was that a user be able to authenticate
itself. This can be done trivially by implementing
class Spindle_Model_User implements Zend_Auth_Adapter_Interface { /* ... */ public function authenticate() { $gateway = $this->getGateway(); $table = $manager->getDbTable('user'); $select = $table->select(); $select->where('username = ?', $this->username) ->where('password = ?', $this->password) ->where('date_banned IS NULL'); $user = $table->fetchRow($select); if (null === $user) { // failed $result = new Zend_Auth_Result( Zend_Auth_Result::FAILURE_UNCATEGORIZED, null ); } else { // passed $this->populate($user); unset($this->password); $result = new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $this); } return $result; } /* ... */ } To authenticate a user, you would create a new user object with the username and password, and then attempt to authenticate it: $auth = Zend_Auth::getInstance(); $user = $gateway->createUser(array( 'username' => $username, 'password' => $password, )); if ($auth->authenticate($user)) { // AUTHENTICATED! } This also has the effect of populating the user from the persistence store, as well as storing the identity in the session. I covered ACL roles previously, so I won't go into that here. However, you should now be getting a pretty clear understanding of how this object works, and how it coordinates with the user gateway. It should also illustrate that this aspect of our model is much, much more than simply data access: we're coordinating authentication, input filtering, and ACLs -- and providing a fairly simple API for manipulating the user itself. Record SetsA Record Set is similarly easy to create. Typically, you will merely want the object to be iterable and countable. Like the user object, we'll require a gateway instance in the constructor. <?php class Spindle_Model_Users implements Iterator,Countable { protected $_count; protected $_gateway; protected $_resultSet; public function __construct($results, $gateway) { $this->setGateway($gateway); $this->_resultSet = $results; } public function setGateway(Spindle_Model_UserGateway $gateway) { $this->_gateway = $gateway; return $this; } public function getGateway() { return $this->_gateway; } public function count() { if (null === $this->_count) { $this->_count = count($this->_resultSet); } return $this->_count; } public function current() { if ($this->_resultSet instanceof Iterator) { $key = $this->_resultSet->key(); } else { $key = key($this->_resultSet); } $result = $this->_resultSet[$key]; if (!$result instanceof Spindle_Model_User) { $gateway = $this->getGateway(); $result = $gateway->createUser($result); $this->_resultSet[$key] = $result; } return $result; } public function key() { return key($this-_resultSet); } public function next() { return next($this->_resultSet); } public function rewind() { return reset($this->_resultSet); } public function valid() { return (bool) $this->current(); } } The logic here is incredibly simple. The main benefit from using a Record Set over an array is that it allows you to ensure the types of each item in the set, as well as allow your consuming code to perform type hinting on the Record Set class. Using Value Objects and Record Sets in Your Gateway
Within your gateway class, it is then your responsibility to ensure that
instances of your new classes are returned. As an example, let's look at
some easy class Spindle_Model_UserGateway { /* ... */ public function fetch($id) { $dbTable = $this->getDbTable(); $select = $dbTable->select(); $select->where('id = ?', $id); $result = $dbTable->fetchRow($select); if (null !== $result) { $result = $this->createUser($result); } return $result; } public function fetchAll() { $result = $this->getDbTable()->fetchAll(); return new Spindle_Model_Users($result, $this); } /* ... */ }
You'll notice the downside immediately: you have to introduce new objects,
and that means re-casting of data. But let's look at it from a consumer
viewpoint: the consuming code is looking for return types of
But what's the point of the gateway, really? Couldn't both the value object and result set object simply inherit from a common base? Certainly they could. However, one common use case I have for gateways is providing pre-defined methods encapsulating common selection criteria. For instance, let's say you wanted to retrieve all banned users, and that this will be a common task. Define a method for it: class Spindle_Model_UserGateway { /* ... */ public function fetchBannedUsers() { $dbTable = $this->getDbTable(); $select = $dbTable->select()->where('date_banned IS NOT NULL'); $result = $dbTable->fetchAll($select); return new Spindle_Model_Users($result, $this); } /* ... */ } This is admittedly a trivial example, but it clearly illustrates the benefits: we now have an API method that tells us, in plain English, what operation we are performing, and provides a repeatable way to do it. The user consuming the model needs not know anything about how it works under the hood, only that they can expect to get a list of banned users when they call it. Another key benefit to creating a gateway is for those times when we need to replace our data access layer with something else. Let's refactor our code to use a service instead: class Spindle_Model_UserGateway { /* ... */ public function fetch($id) { $result = $this->getService()->fetchUser($id); return $this->createUser($result); } public function fetchAll() { $result = $this->getService()->fetchAll(); return new Spindle_Model_Users($result, $this); } /* ... */ } From a consumer standpoint, nothing has changed; they are still calling the same methods, and receiving the same responses. This is absolutely key in creating maintainable, future proof code. SummaryThe solutions presented here are by no means canonical. You may find that your own models do not need a gateway class, or that you never work with lists of objects. Hopefully, however, I've illustrated that a model should cleanly provide a separation of concerns and consist of discrete objects -- whether they are directly related to your model, or related to aspects of how your model does stuff, like validation and data persistence. You should strive to make your models as simple as possible, while still meeting each of your requirements. The end result should be a re-usable, testable suite of functionality, and careful architecture of your solution should make it robust and easy to refactor in the future. Updates:
Comments
Display comments as
(Linear | Threaded)
Nice post Matthew!
For those of you interested, there's a good book on Domain Driven Design: http://www.amazon.com/exec/obidos/ASIN/0321125215/domainlanguag-20 There's also a short, free version available at: http://www.infoq.com/minibooks/domain-driven-design-quickly Highly recommended reads! I've flipped through a copy at the bookstore before, but don't yet have it on my shelf. Thanks to the link to the "Quickly" download, though -- I'll give that a read.
How exactly would you deal with a table's auto-incrementing numeric ID? In case you want to select a user based on his ID, you'll need it somewhere in Spindle_Model_User. A possible problem would be that this is closely linked to the actual database table. Should you just keep the ID in the DB and try to select rows based on other unique values, like a username?
Identifiers are not unique to databases -- web services often use them, JSON-RPC uses identifiers for transactions with the server (to match requests to responses), git creates identifiers for each commit, etc. While they *are* part of the persistence mechanism, I don't think that means you should not reference them in your models.
If you *can* identify other unique fields, however, that's never a bad practice. There would be also nice to mention model flow problem. Simple model is good for simple things only and can be avoid. In enterprise you often refine your model data as a whole and moving back to previous state is necessary. Then you realy need some more complex model flow mechanism to wrap your simple model. My current project for example handles a tree hierarchy of different model flows (stack model flow, bidirectional model flow, simple model flow, incremental model flow).
Model is just the begining and is only one state of model flow state machine Is the decision to use the domain gateway driven primarily by wanting to have both a User object & a result set object or does it have additional benefits that go beyond some static find methods on the User class?
That's the primary motivator. You can certainly combine the value and result set objects into a single object, but my feeling is that this is confusing -- a User is not multiple Users, and vice versa. (It's one of my problems with ActiveRecord as well.)
The other rationale is that there may be other related objects later down the line. Consider a BugTracker gateway -- it might need objects representing a bug, a comment, an attachment etc. Having a gateway allows you to manage dependencies for each of these. However, it's all a matter of preference -- there are many ways to design models. This article was mainly intended to show how your model should grow out of discrete requirements, and may encompass multiple objects. so the gateway method: getDbTable, is just a method that returns an user class that extends Zend db table?
Yes. It could also be renamed to 'getDataProvider()', but that masks what is being done.
Isn't your userGateway actually a data mapper? Or doesn't it fit the definition.... How do you see the data mapper pattern in the context of a domain?
Is it basically one step closer to ORM, which also ties you to the database? Or should I abstract it a bit and also allow for mapping data from web services, handle validation, etc. Like you did in your gateway? Excellent post (again), learning a bit more each day! The Gateway is actually one of the POEAA patterns: http://martinfowler.com/eaaCatalog/gateway.html The way I use it, it's basically handling dependencies for the other objects -- injecting the data access object, ACL object, etc. into the subcomponents.
The value objects actually act as data mappers in my examples here -- they have their own save() methods, which translate their properties to database calls. It's definitely *not* classic ORM, though. Very good post indeed!!!
I have one question though: how this diffentiate from the ActiveRecord pattern? Thank you. There are several differences. First, classic ActiveRecord doesn't use instances to access individual items, but instead uses static access (this varies from implementation to implementation). Second, ActiveRecord typically doesn't rely on a separate data access layer that needs to be injected, but instead contains this access internally (it may come from a separate component, but instantiation and handling of that component is entirely internal).
The domain model typically involves a certain amount of dependency injection - i.e., the class may need certain artifacts, such as the database connection, ACLs, etc. This makes use a bit more complicated -- you need to provide arguments at instantiation -- but also makes it more flexible. Finally, the way the examples here are defined, they could actually *consume* ActiveRecords to handle data persistence; it's actually common in domain models to use TDG or AR for that purpose. I just wanted to point you to Magento's Model implementation. It's very similar to your solution described above.
Model => Value Object plus Gateway methods ResultSet => Collection of Value Objects DbTable/Services => Resources Just have a look at it =) (Urgently have to blog about it...) Not surprising; I'm using some well-established patterns here. The build up is to an entry on why ZF doesn't, and likely won't, have any formal model support... but I'm getting ahead of myself.
So I actually tried out this code today, for fun, and while it took some mangling to get it to work, one part that is generating an error is when trying the authenticate method:
Message: You cannot serialize or unserialize PDO instances My code follows yours pretty well, I'm not sure exactly where or why its trying to serialize PDO, (i have a debug backtrace that is slightly helpful), I'm assuming that it might be coming from these lines: $this->populate($user); unset($this->password); $result = new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $this); where $this has the gateway property which has the dbAdapter property. Is this where it might be trying to serialize it? Yeah -- you need to add some __sleep() and __wake() code for this to work correctly. I should probably post about that, too...
well, this might not be the optimal solution but it works, I just changed $this to $user->toArray() (the select statement I modified etc.). Now just a simple array is in the session instead of the object, which I think is fine, I guess the object would be nicer, I'm just not sure where to implement the sleep and wake.
I assume in the user class then and not the gateway class. I guess the sleep would have to unload the gateway and the wake would have to reinitialize it? Sorry I'm sure this is out of the scope of what you have been intending, but your tutorials of late have really coincided with what I'm currently re-writing, so I guess I'm taking your examples a little too literally Yeah, it's actually fairly simple. In your __sleep() method, only export serializable properties -- the $_data and $_allowed arrays -- and then have getGateway() capable of lazy-loading the gateway object. (I have it working locally that way, or similarly, that is.)
so I tried to get the __sleep functionality working on just the User class, (just return array('_data') ) but whats crazy is that now zend session is throwing an exception after logging in... man I've been stepping through with the debugger so now Im in no mans land woohoo!
I know this is just a simple example, but for anyone wanting to try out some of the code, I ran into a problem when trying to iterate through the Spindle_Model_Users result set.
Fixed it by adding a check that $result is true under the current() method: if ($result && !$result instanceof FormField) { Great article, very interesting. One question: is the Model_User class used in the Spindle_Model_UserGateway as well? And how? Something like:
class Spindle_Model_UserGateway { public function createUser($user) { return new Spindle_Model_User($user, $this); } } Yes, basically something like that.
Matthew - Excellent article!
One question -- I run into an issue with the Users object when passing it a Db_Table_Rowset in my UserGateway. Admittedly, I am not that familiar with the Iterator interface, but what seems to happen is that the Users::current() method calls current() on the resultSet being passed in (in my case a Db_Table_Rowset). In this use case, Rowset::current() does not seem to return the Db_Table_Row as I would expect, but instead it appears to iterate over the properties of the RowSet. Now, Zend_Db_Table_Rowset implements a SeekableIterator, so I would assume that Rowset::current() would return a Db_Table_Row. I know Zend_Db isn't your baby, but I assume you may have an idea of what I'm doing wrong. Any suggestions? It's probably worth mentioning that I'm following this article and your pastebin/bugapp code on github, so I'm attempting to use the abstract Result and ResultSet objects to keep things clean. The code for the __unset method doesn't seem right to me: First, unset() never returns a value, and placing it after a return statement is a syntax error. Second, if you unset the value, you cannot set the property again because array_key_exists() will return false. Maybe you should just set the value in data to null.
Good points. I'll update. However, these are just minor implementation details -- don't take the code in here verbatim and expect it to work, as it's primarily for illustration purposes.
Hi Matthew,
very good tutorial. Thank you. And now my question: Is this right? if (!$result instanceof User) { $key = key($this->_resultSet); $result = $gateway->createUser($result); $this->_resultSet[$key] = $result; } Or must it be a check for an instance of Spindle_Model_User? gah! You're right. I've updated the code now. (I wrote the entry, then ported ideas into my Spindle application for testing things out, and then edited my draft. Missed that one.)
Hi Matthew, also "$this->_resultSet[$key] = $result;" won't have any effect if $this->_resultSet is instance of Zend_Db_Table_Rowset and User will be instantiated twice in every iteration as current() method is also called in valid() method.
Do you plan any integration with Paginator component? Actually, that notation ($this->_resultSet[$key] = $result) *will* work, as Zend_Db_Table_Rowset_Abstract implements ArrayAccess.
What *won't* work is key($this->_resultSet), and it should likely check to see if the resultset implements Iterator, and call it's key() method if so. I'm updating the post to reflect that. As for valid/current instantiating the object twice, did you determine this during profiling? The first call to current() *should* set the value in the resultSet, so that the next call would get the instance. If it is double-instantiating, the way around it is to grab the key instead of current, and use the key to retrieve the value from the resultSet. Hey, Zend_Db_Table_Rowset do implement ArrayAccess, but offsetSet() (and offsetUnset()) method do nothing, please see the code (http://framework.zend.com/code/browse/~raw,r=12770/Zend_Framework/standard/trunk/library/Zend/Db/Table/Rowset/Abstract.php).
"key($this->_resultSet)" should be ok, Rowset implements SeekableIterator.
#15.1.1.1.1
Martin
on
2009-01-05 11:37
(Reply)
Interesting -- I see offsetExists() and offsetGet() are filled out, but not the ones for writing. That would require an internal registry... ugh. I'll play some more with it.
Thanks for the heads-up. Excellent article, Matthew. I'm reading it through for the second time now. Your recent series on proper modeling techniques is just what the PHP community needs right now.
Thanks!! Hey Matthew,
I like your blog entries a lot but this time I've to ask multiple questions and some of them may sound like criticism 1) Let's start with the populate method where you check the type of the parameter - didn't we implement the gateway pattern to not rely on a specific DAL implementation? 2) I don't like to pass a certain gateway instance to my model. In my understanding of models a model can have access to multiple gateways, therefore a factory for creating and retrieving gateway instances inside the model would be much more applicable, wouldn't it? 3) Implementing the authentication method inside the user model looks kind of strange to me - in an oop approach we should try to represent the real world; i.e. if you'd like to have a credit for your new bmw m5 you'll go to your bank's account manager and beg for a credit (well as a qualified zend employee you probably don't have to beg him, but it sounds more dramatic 4) Your save() method retrieves the ZDT instance from the gateway and calls a find() and save() method on it. Why don't you proxy the find() and save() call through your gateway? This way you don't have to touch your models once you decide to exchange your DAL 5) I think the following calls should reside in the key() method of your result set instead of the current() method. current() should simply call $this->key() or key($this) if ($this->_resultSet instanceof Iterator) { $key = $this->_resultSet->key(); } else { $key = key($this->_resultSet); } Maybe I got your post totaly wrong but I think my points are valid. Best regards, Daniel The other thing thats interesting, which I just figured out right now as Im testing these ideas, is that in the User model, the save function tries to do this:
if ($row = $dbTable->find($this->id)) { foreach ($this->_data as $key => $value) { $row->$key = $value; } $row->save(); } else { // insert Well, you can't really do a $row= $dbTable, as it doesnt return false, it always returns a Rowset object, regardless of # of rows, or 0 rows.. So the save method will just always be trying to do an 'update' and never get to the insert part. Ugh. I should have pasted code verbatim from the working models I have. That should either be a fetchRow() with a select statement, or be "$rowset = $dbTable->find(...)" followed by checks on the count of $rowset, and pulling $rowset->current().
yeah the other gotcha, as I'm trying to figure out zend db, is that if say you try to do this (within that save method):
$select = $dbTable->select()->where('id=?', $this->id); if ($row = $dbTable->fetchRow($select)) { ... it will throw this exception: SQLSTATE[HY093]: Invalid parameter number: no parameters were bound Well, $this->id happens to be null, cause its a new user so I don't have a key for it yet, but I can't even query the db as a 'check'. Guess I'll figure out how zend db works sooner or later. ah turns out I was bitten by an old PDO bug, now Im having fun with the Users::current method..
Hi,
I think there is something wrong in the "authenticate" function. First you call the getGaetway-Function and pass it to the $gateway Variable but then you call $manager->getDbTable('user'). I think it should be $gateway->getDbTable('user'). I hope this could help. See a previous note: I have working code, but the examples here were not updated completely prior to posting (despite my best efforts when editing). I'll update.
Again, though, these examples are incomplete, and I wouldn't expect anyone to be able to use them directly. Sorry, didn´t read all the comments
The examples how they are now are very good and i´m really excited to read the updated ones then =) Hi Matt,
another great article, my question though is how do you make reusable applications with the Zend Framework? Would you use Zend_form and Zend_config to create an ini or xml file that is utilized by the bootstrap? Also in the manual is their a list of what should be read first? I would think zend controller would be one of the first chapters to read ( besides the quickstart ) . I also think that a move to make the manual like the php manual would be helpful. Great article.
public function authenticate() { $gateway = $this->getGateway(); $table = $manager->getDbTable('user'); Shouldn't $manager be $gateway? Yep -- I'll be making updates soon, to incorporate all the feedback I've received.
Great article and interesting pattern, i will try to get into because of it.
Would be great to have a kind of ZIP file with the skeletons and a graph to show the relationships :p Well, many of the ideas are already coded and posted in my pastebin demo app, in the bugapp branch -- and thus already available.
Hi Matthew,
thank you very much for this tutorial! One question, though: Wouldn't it be nicer if you removed the table stuff from your model? I think the gateway should handle everything concerning materialization and loading, not the model itself. Thus it would be easier to replace the "SQL-Gateway" by a web service. This seems to be ugly: $gateway = $this->getGateway(); $dbTable = $gateway->getDbTable('user'); .... Would be nicer: $gateway = $this->getGateway(); $gateway->save($this); Regards maurice. I am confused by your blog post.
You said it was tempting to use Zend_Db_Table / Row as models, so instead you just wrap those classes inside your 'model', which still couples your model with your storage method (in this case Zend_Db_Table). At the moment, your Spindle_Model_User depends on your UserGateway returning a Zend_Db_Table, which is just as bad as your Spindle_Model_User extending Zend_Db_Table_Row or Model_UserGateway being child class of Zend_Db_Table in the first place? I thought the idea of this pattern was to use the gateway as an disposable interface for your chosen storage method (DB, Filesystem, remote storage service etc), leaving the business logic in the Model_User free from storage concerns. e.g. in Model_User 'updateName' or similar n.b. please take as pseudo code $this->name = 'foo'; $this->getGateway()->save($this); -not- $row = $this->getGateway()->fetchStorage()->find($this->id); $row->username = 'foo'; $row->save(); I thought the gateway should be the class that takes all the dependencies on the chin, rather than passing the blow onto the Model_User - otherwise I don't see the advantage, since I still have to rewrite my all of my models when I change storage interface. Something I think #24 was also trying to point out. Perhaps I've missed the point of this article? Don't blame him, it's been a long time and he is doing much better last time. Pay attention to his last data mapper experiments and object modelling presentations, he's much closer to the truth
Though, for those who are still confused as Mathew, probably, was at the time of this article being created, and for those who are still reading this post trying to find a true way, I would suggest Mathew to give some links on more later presentations and articles about the Model somewhere on the page. Your gateway looks to me like a service layer object whose methods are modeled after use cases. The service layer object is injected with the dependencies that are required to fetch/persist domain model objects in accordance with the requisite business logic; it then composes those injected objects to perform fetching/persistence. Examples of injected dependencies might be a database connection (or persistence manager), a web service proxy, ACL rules, et al. A controller then uses the service layer object (or a facade that calls to one or more of them) in order to complete an application transaction.
One thing I don't like about placing data-store specific information inside of a domain model object is that it ties it to a specific application and thus makes it non-reusable. If I ever want to use model object Foo in another project, I have to rework it so that it fits with my new domain model. Keeping things as generic as possible facilitates reusability. Regarding ORMs: they don't necessarily couple models to a particular database structure. Done right, you can have complete decoupling, since the service layer and the O/R mapper work in concert to populate the model without intruding upon it whatsoever. You service layer gets injected with the O/R mapper and then calls said mapper to fetch your object graph. The mapper only populates your model's properties (fields or mutators) and does not impose any dependencies on said model. One last and minor thing: if I needed to retrieve more than one User, I wouldn't create a separate class encapsulating them. Instead, I would use some kind of generic list or set implementation as a container and pass that back to the caller instead of returning a Users object. Either way, this was an excellent post. I'll be forwarding this along to a co-worker soon. Interesting Post. Could you provide an example on how to utilize the Gateway inside an MVC application? I am curious how you would tie this to the controller. Should the controller pass any request params to the gateway? Should the Gateway know about the View and maybe even set data to it?
This post is all about the M in MVC -- models. You instantiate the gateway, pass input to it (request parameters), and then do something with the result, typically passing it to your view.
As a quick example, you might instantiate the model, fetch a list of users, and pass them to the view: $gateway = new Spindle_User_Gateway(); $this->view->users = $gateway->fetchAll(); In your view, you then iterate over each user: foreach ($this->users as $user): // do something with the user object... endforeach; Thanks Matthew. Does it make sense to do ...
$gateway = new Spindle_User_Gateway(); $gateway->setRequest($this->getRequest()); $gateway->setView($this->view); $gateway->fetchBannedUsers(); ... and let the Gateway decide what data it needs from the Request Object and what it gives back to the View. This would make for extremely thin controllers and fat Gateways with very simple interfaces. While it's appealling to do that, you're breaking a few rules of encapsulation here. The request environment should be managed by the controller, and it should pass input to the model. Similarly, mixing view logic into the model layer is a no-no, as you're now coupling display logic with the model. It's better for the view to fetch items out of the model as part of its display logic. This approach still achieves the goal of thin controllers and fat models, but ensures an appropriate separation of responsibilities.
Is there anyone who has worked through this that would like to share their code as an example?
After reading through this a few times, I have some remaining questions that I think probably can be answered by looking at working code. (FWIW, the pastebin/bugapp UserManager, BugTracker gateways in git as of Dec. 31 haven't fully been refactored yet. Matthew has been swamped) Thanks, --Rob T. Here's a cheat Sheet type summary that I did for myself.
Descriptions: Model_User - Value Object, Simple implementation of "an object with a specific set of attributes or properties" Model_Users - Record Set Object, set of Model_User Objects that can be iterated over. Model_UserGateway - "Gateway to the domain ... used to fetch other objects in our model, and will inject various dependencies into them when doing so, such as the data access and ACLs. The various dependencies may themselves be injected into the gateway -- or it can lazy-load them." Can return a number of different types such Model_User or Model_Users (single object, vs. set of Model_User objects that can be iterated over). Interfaces: Model_User * $_data (hold both values and provides valid attribute names) * $_gateway, $_form * __construct($data required) (calls populate, gateway not required?) * populate() (fills in $_data from object using toArray or array copy) * __set(), __get(), __isset(), __unset() customizable OO interface to internal data. * save() * getGateway(), setGateway() * getForm() * authenticate() Model_Users * $_count, $_gateway, $_resultSet; * __construct($results & $gateway required) * getGateway(), setGateway() * count(), current(), key(), next(), rewind(), valid() (to implement Iterator & Countable interface) Model_UserGateway * createUser(array of user attributes/values) * retrieve() (not defined, one example fetching username without spec'ing attribute name) * fetch($id), fetchAll(), fetchBannedUser() * sort() Hello,
very good articel! But it really needs to get updated! Please. Well, i try to understand all the things mentioned.. In the last passage you mentioned that when replacing the data abstract layer only refactoring of the gateway is needed. But I don't get it.. You use Zend_Db methods in the Object-Value classes as well... so i think this must be refactored too! That seems strange, because this whole pattern should avoid much refactoring!?!? maybe i'm wrong, but i need explanation. Matthew, I'm writing a post about ZF and Model-Driven Design. Quick question, considering that Spindle_Model_User is a Value Object, shouldn't it be immutable? To void being modified you can remove the __set() method and make populate() protected.
I didn't read all the comments so hopefully this hasn't been mentioned yet.
In the example below this text: *** If we think of it as an input filter, we can use it for data integrity: *** Shouldn't you get the value from the inputFilter something like $this->_data[$name] = $element->getValue(); If you are using filters like StrToLower, that filter would not have been applied to the value, which I think should happen in this case. - Ambis Out of interest how do you then integrate your Zend_Form into save() if you pass in a complete set of data from the post array? The way you have it save doesn't do any validation at all?
Thanks for this article, it helped me a lot. But I have one question: Isn't better to make the Gateway class a singleton and then easily getInstance() wherever it is necessary?
Imagine a collection of objects of class User, where there is a method getAddress() which internally calls AddressGateway's method retrieveAddressByUser(User $user). When I iterated throught that collection of Users and wanted to retrieve each User's Address, it would instantiate AddressGateway many many times... And I thing this kind of coupling is very common... Thanks! Singletons are almost always a bad solution, as they provide implicit (instead of explicit) coupling, and lead to difficult to test architectures (the only really good cases I can find for them currently are for autoloaders and session access). It's better to inject your dependencies into an object, either via the constructor or setter methods, and with good planning, this can be accomplished fairly trivially.
I've been reading blog posts on this topic a lot recently - yours, Rob Allens and more recently Leendert's (HolyGoat), which focuses more on the idea and theory, rather than code http://www.angryobjects.com/2009/03/30/writing-robust-php-backends-with-zend-framework/
What I've found is that the idea makes perfect sense and I really think it's the best approach, but then when I see ideas like this starting to go in the direction of injecting the persistence layer into the domain model, it seems as though the entire point of separating them to begin with was pointless. Why not just stick your domain logic inside Zend_Db_Table_Row_Abstract? Sure, the consumer of the model will technically have access to the persistence layer's methods - but muddying your domain model by making it aware of the persistence layer seems equally wrong. Surely it's possible to come up with a solution where by there's even less coupling? I like the service layer approach that Leendert proposes, but even this seems to have some shortfalls for me (in the way data is accessed). Hopefully I can find a way that I'm happy with soon enough, but just thought I'd share my opinion. I'm nearly always on irc (in #zftalk and ##php on freenode) if anybody wants to give me an education - I am always ready to learn new things! Best Regards Hi Matthew, couple of concerns I have with the implemenation that I was hoping you could work through the though process on...
1. having a $_data array that contains the model attributes. Wouldn't it make more sense to make them actual attributes for documentation purposes to put phpdocs on those? What are you trying to gain with the array? 2. Should a model really have a "save" method or even have any idea a gateway exists on it? Your article mentions the Gateway is the "Gateway to the domain" but by having the gateway in the model itself what your saying is the model is the gateway to the gateway of the domain. I would think it would be cleaner to have the model standalone with business logic and all operations on that model are done through the gateway. 3. You have an authenticate method on the model itself. Shouldn't that also go in the gateway since the auth is entirely dependent on the gateway and the persistence layer underneath? Your saying authenticate -> grab the gateway, then do some actual persistence specific items. Wouldn't it be better to have $user = $userMapper->authenticate($user); if($user->isAuthenticated) { do stuff } to keep the model clean and more reusable? anyway just some issues I was hoping you could address thanks Jim -- thank you for your comments. I've re-thought a lot of this since I wrote it originally, but haven't had chance to blog it yet. Probably the most representative state of where I'm experimenting is my DPC09 ZF workshop: http://www.slideshare.net/weierophinney/zend-framework-workshop-dpc09
In this, I show using Domain Models with a DataMapper that maps the model to a data source. On top of it all is a service layer which does the actual work of manipulating the models. save() is about persisting the entity. While it doesn't belong directly with the entity itself, it's typically convenient to define it at the entity level -- this is in fact what most ORM solutions do at this time, and I see no really useful reason to diverge from it (I see an OOP purity reason, but not a pragmatic reason). I've also been looking at utilizing Doctrine in order to define my model entities, and then building service layers on top of these. It's a nice concept, and allows me to separate the more application-level tasks such as data validation/filtering and ACL checking from the domain models themselves -- and simultaneously create my basic application API, which can then be re-used for both the MVC application and web services. Matt
I've seen you mention Doctrine several times in relation to Models and Service Layers and am anxiously awaiting a follow up as I've recently begun using the ORM. When using Doctrine from your service layer, do you simply hydrate as an array and populate your models using the returned data? How do you handle related entities (not much point using an ORM without dealing with relationships)? How about the other nifty features of Doctrine such as being able to return related entities (or even just a count of such) using the one query? so I've thought about this a little more and did some digging around Eric Evans works...
I created a little diagram here: http://litfuel.net/plush/images/UserDomainSample.jpg Where the concept is you have a User who belongs to a Company. So we have two models. User and Company. A user holds a Company instance. Each one of those has a mapper that will contain all the SQL required to persist their data. The real business logic sits in the service layer that would allow for X number of complex business rules using the same object entities. The service layer would be in charge of instantiating the appropriate models or mappers and then carrying out the business logic. The bonus with the service layer is you can show stakeholders a concrete example of the domain scenario they were describing to you instead of having everything buried inside an entity itself. The User model stays clean and just represents a User. The biz logic can be grown and tested around it. Here are a couple of code examples using the above... using the auth service... http://pastebin.com/f228acba0 creating a new user: http://pastebin.com/f52ec244 Is that along the lines of what you're toying around with ? Here's a sample directory structure for the above: --models/ ---------User/ --------------User.php --------------UserMapper.php --------------UserAuthService.php --------------UserPayrollService.php ---------Company/ -----------------Company.php -----------------CompanyMapper.php This is precisely what I've been doing. My directory structure is a little flatter:
|-- models/ | |-- User.php | |-- UserTable.php // I'm using Doctrine lately, which implements mapper | |-- Company.php | `-- CompanyTable.php |-- services/ | |-- UserAuth.php // App_Service_UserAuth | `-- UserPayroll.php // App_Service_UserPayroll As you say -- this pattern then lets you keep your models clean, and presents the API directly via the service classes, which do the nitty gritty of things like ACLs, validation, etc. A side benefit is you have something that you can typically very easily expose via a facade as a web service. I have a few questions about testability. Do you unit test your code? If yes..
1. How do you test your table classes? Do you use database fixtures like in PHPUnit Database extension getDataSet() ? 2. Let's imagine you have to pay salaries to all the employees in the company. How would you do using the latest model you described? How would you test it? Thank you for sharing your insights. Yes, I do unit test my code. As for testing my table classes, it depends on what I'm doing. If all I'm doing is extending Zend_Db_Table_Abstract, and I'm not adding any extra functionality other than defining the table name and potentially the primary key, then I don't test the table class at all; ZF contains unit tests for that already.
If I am adding functionality, I typically write a fixture that utilizes an in-memory sqlite database. This fixture will create the database and populate it with a dataset so that I have a known state at the beginning of my test case. As for an example of testing, that's really a topic big enough for its own blog post. Hey Jim,
Quick question around the naming convention you've run into with your directory structure. Does that mean you have something like: new Default_Model_User_User(); when creating a new User model? Wondering how you get around this ugly prefix or if you just deal with it? Well, it'd be "Default_Model_User" (not "_User_User"). But yes, I "just deal with it." The conventions ensure that I know where to look for the model class files -- particularly when I have multiple modules in play (which would mean multiple model directories)
I wonder if the new namespace feature in PHP will be able to alleviate this. Because_Having_Really_Long_Class_Names_Is_A_Real_Pain_In_The_Butt_Especially_When_You're_Calling_Static_Methods. I wish it were more like Java which has the notion of a package. In Java, there isn't a _convention_ regarding packages and the directories in which package member's reside; rather, it's strictly enforced. A convention is a convenience that you use in lieu of a mandatory rule; the downside is that people can ignore conventions, whereas you cannot ignore a rule or "law." It's much like Python which enforces a particular source code style (indentation). The result is consistency, universal readability, and uniform expectations.
Namespaces will make this much easier, particularly when you're in the given namespace, or have aliased the namespace within the file you're currently working in.
As for strict enforcement, the nature of dynamically typed languages is such that strict enforcement simply makes no sense. That said, there are some efforts to make this easier (the interop group, aka "php standards", for instance, is providing some standards around namespace naming); since namespaces are new, however, you'll need to wait for the standards to develop. Hey,
Nice article. Wondering if this is still relevant after 2 years in 1.9.6 Kind regards. Absolutely it is. (And, actually, the article is only 13 months old
Zend Framework still does not dictate how your models should look, which means it is up to the developer to determine the best practices; posts like these are intended to help put forth some ideas on what those might be. Matthew after reading this article and also dealing with the new way to do the things using data mapper (currently quickstart guide) i ask :
The class "Spindle_Model_UserGateway" would become (Spindle_Model_UserMapper) right? or it becomes a service (Spindle_Service_User) ? For example, the bellow method should be in the DataMapper and not in a service layer right? Or not? Or it depends?...lol public function fetchBannedUsers() { $dbTable = $this->getDbTable(); $select = $dbTable->select()->where('date_banned IS NOT NULL'); $result = $dbTable->fetchAll($select); return new Spindle_Model_Users($result, $this); } Thanks for your effort on ZF Yes -- I'd recommend using a data mapper, and yes, put methods in your mapper for retrieving individual or aggregate items.
Your Service Layer would then consume one or more mappers and domain entities (such as your user, team, etc.). Add Comment
|
CalendarQuicksearchLinks
ArchivesCategoriesSyndicate This BlogShow tagged entries |






I have been following the discussion of models recently, in particular the blog posts by Rob Allen and Matthew Weier O’Phinney. I’m not going to go into detail about my model implementation, for that I suggest you have a read of the aforeme...
Tracked: Jan 18, 16:45
In den letzten Wochen sind in diversen englischsprachigen Blogs sehr interessante und teilweise recht ausführliche Beiträge erschienen, die verschiedene Themen zum Zend Framework abdecken. Ich möchte an dieser Stelle einmal auf diese Beiträge hinwe...
Tracked: Jan 20, 12:23
Vor einigen Wochen las ich auf der Homepage von Matthew Weier O'Phinney einen interessanten Artikel über das Domain-Model (Model Infrastructure). Dieses Model ist ein konzeptueller Ansatz, um die Verbindung zwischen den Systemen zu beschreiben. Einfach au
Tracked: Feb 01, 12:46