I’ve been doing a bit of development in PHP frameworks lately, and the way that they handle models in their Model/View/Controller setup has be bothered. The major issue is that the frameworks aren’t using PHP5, and so they can’t take advantage of “true” objects for data retrieval and operation.
One fancy thing we’ve done with Habari is use PHP5’s built-in PDO classes. Apart from giving us access to many database types (MySQL, Postgres, MSSQL, SQLite, etc.) simply by changing the connection string, it allows us to tell the engine to store retreived record information not just in a generic class, but in a class that we define.
For example, all records that are returned by PDO default to being an instance of a QueryRecord class. The QueryRecord class contains additional methods that are useful for working with rows of data. The properties of the object are the row’s fields. Setting the properties of the object changes the field values for that row. Calling ->update() on the object stores the new values back under that record’s primary key. But it gets more interesting when you create custom data classes for specific purposes.
If we retrieve a post from the database, we can have the created object be an instance of the Post class instead of the QueryRecord class. The Post is a descendant of the QueryRecord class, but it contains additional methods that allow actions specific to a Post. For example, posts in Habari have users (authors), info (post metadata), and comments associated to them. Accessing the ->comments property of a Post object will retrieve and cache all of the comments for that specific post. Accessing the ->info property of a Post object will cache the metadata for that post and allow access to it as an array.
Assuming you store AIM ids for your authors and you obtained a Post object, you could use $post->author->info[‘aim’] to obtain the AIM id of the author of that post. This is all done with the magic of PDO creating specific class instances for rows and classes exposing properties of their objects. There is no need to create a complicated chain of functions like you would in PHP4 of get_user_meta(get_post_author($post_id), ‘aim’).
Additionally, Habari’s model implementation can store multiple rows of data in aggregating objects. Instead of returning a simple array of QueryRecord descendants, Habari creates ArrayObject instances to store things like Post and Comment objects. The ArrayObject instance provides additional features beyond what an array can provide by itself.
In addition to behaving exactly like an array, ArrayObject instances can contain methods that can do many useful things with the object they contain. For example, if an “array” of spam comments was stored in $comments, you could call something like $comments->delete() to delete them all at once, rather than looping through them yourself. One neat thing that the Comments ArrayObject does specifically in Habari is let you easily filter the Comment objects it contains without making additional queries. So if you just want the spam comments, use $comments->spam. Because the ->spam property returns another Comments ArrayObject, you can chain the commands together, like $comments->spam->delete();
Put everything together, and you can easily purge the spam comments from a specific post: $post->comments->spam->delete();
Even though these features are purposed specifically to Habari features, there is a reasonable enough amount of overlap to merit a couple of base classes, such as QueryRecord, that handle some of the generic operations like inserting new records and updating existing ones. This is where the failure exists in the frameworks I’ve observed.
The Model base class that is provided by CodeIgniter, for instance, isn’t very functional at all. It doesn’t behave like QueryRecord, and there is no additional ArrayObject-like class that aggregates the rows that are returned from a query. Everything is in a simple array and standard class objects. While the Model classes in CodeIgniter provide the opportunity to affect the data by adding your functions there, there is really no codified relationship between your class and the data it represents. You could easily (although foolishly) code all of your database interaction into a single Model descendant. They’re so useless as to make it tempting to avoid them altogether.
One thing that I’ve thought a lot about is the database interoperability. CodeIgniter abstracts query language into PHP so that you can build your queries as a series of PHP commands that emulate SQL. In this way, the system can detect in advance the engine that you intend to use and then build SQL that is appropriate for the database from the commands that you call. It’s pretty spiffy, though not entirely comprehensible in use.
I can’t tell if it’s difficult to use because I havn’t spent a lot of time trying to understand it, but I can say that it’s nowhere near as easy as writing out a query in the raw. It would have to be only a couple of steps removed from the ease of SQL to merit use in a project that wasn’t so dependent on engine interoperability, and much easier to bother with it if database independence isn’t necessary for a project.
Many of the features I’ve mentioned are impossible without PHP5. Implementing these features provides instant API access, since all of the public functions of those classes give you direct access to the data without performing SQL queries around the code. Using descendant objects gives you instantly documenting clarity between objects - You know that any descendant of QueryRecord is going to have update() and insert() methods and for the most part behave the same. This makes it easier for developers to memorize the common aspects and focus on the specialized aspects of each class. If a glitch exists in a parent class, fixing it once fixes it across all descendant classes, rather than updating duplicated code across all data-accessing functions. Data caching can be standardized at the parent class level, offering instance caching capabilities to any descendant class, rather than coding in caching specific to each data accessing function. The list goes on…
The benefits of object oriented design are immediate to developers and are learned very quickly. Sure, anything you can do with OOP, you can do with traditional top-down coding. But the advantage to clean OOP code is significant.