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. |
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