From The Archives

Cool Object Building With PHP

created February 24, 2009 at 1:16 am

At work I write mostly PHP code and work among some very respected developers in the PHP community. One thing that bothers me about the PHP world is how most people think in terms of arrays instead of in terms of objects. This is understandable considering Object Oriented PHP didn't really come around until PHP 5 so using arrays is probably an old habit that is hard to get rid of, but that is no excuse. Arrays have their uses, but I think more often than not an object can be used instead.

One of the core problems most PHP developers run into is finding a way to map records in the database to PHP objects. This is understandable considering your database model is often very different from your object model, and at the moment PHP does not offer any great solutions that I know of. Basically my proposal involves two parts:

  1. a DAO (data access object) class
  2. a Builder class

What these classes do is very simple: The DAO contains the SQL to query the database and then calls a builder to take the resulting array and assemble an object (or a collection of objects) out of it which is then returned.

This implementation allows for no question about what is being returned and it forces you to use more objects in your code which is never a bad thing. Another plus is that you can cache the collections directly in memcache and therefore when you hit cache your PHP doesn't have to do any work at all with the data.

This is actually the method I am using on this site so I will demonstrate using real world examples, but before I do so I want to point out that much of the awesomeness in the example relies on a certain naming pattern for your classes. For example I have classes named Blog_Dao, Blog_Comment, Blog_Builder, etc.

The first code I am going to show you is the base DAO class I am using:

<?php class Dao { /** * @var Db */ protected $_db; /** * @var Builder */ protected $_builder; /** * constructor * * sets the database class this dao should use * * @return void */ function __construct() { $this->_db = new Db(); } /** * determines what builder class to use * * @param string $builder * @return Builder */ protected function _getBuilder($builderName) { // if we have already instantiated a builder class if (!is_null($this->_builder)) { return $this->_builder; } // if we have not passed in a specific builder to use if (is_null($builderName)) { $builderName = str_replace('_Dao', '_Builder', get_class($this)); } // make sure the builder exists if (!class_exists($builderName)) { throw new Exception($builderName . ' does not exist!'); } // instantiate the builder $this->_builder = new $builderName; return $this->_builder; } /** * calls the builder to build a collection of the stuff returned from the * dao! * * @param mixed $records * @param string $builder name of class to use for building * @return Collection */ protected function _buildCollection($records, $builder = null) { // if the database doesn't have any records if ($records === false) { return false; } $collection = new Collection(); foreach ($records as $record) { $collection->append($this->_buildOne($record, $builder)); } return $collection; } /** * calls the builder and builds a single object * * @param mixed * @param string $builder * @return Object */ protected function _buildOne($record, $builder = null) { // if the database doesn't have any record if ($record === false) { return false; } $builder = $this->_getBuilder($builder); return $builder->assemble($record); } }

The first thing you will probably notice is that $builder is optional! This means you only need to pass in a builder class if the builder is in some wacky place it shouldn't be. So the Blog_Comment_Dao class will default to the Blog_Comment_Builder which makes sense and it forces good use of objects cause it means everything you create a dao for is an object and that DAO should only return things related to that object. Another point of note is the Collection object. This is simply a child of ArrayObject to allow easier access to ArrayObject methods.

Anyway now it is time to see this in action! Here is actual code from this site to get all comments related to a given blog post taken from the Blog_Comment_Dao which extends the base Dao class:

/** * retrieves all comments for a given blog * * @param int $blogId * @return Collection */ public function get($blogId) { $query = '/* ' . __METHOD__ . ' */' . "SELECT id, name, website, email, bc.body body, bc.posted_on posted_on FROM blog_comment bc WHERE bc.blog_id = :blog_id"; $sth = $this->_db->prepare($query); $sth->bindValue(':blog_id', $blogId); $sth->execute(); $records = $sth->fetchAllAssoc(); return $this->_buildCollection($records); }

Look how pretty that is! Now let's take a look at the Blog_Comment_Builder class to see how simple that is as well:

<?php class Blog_Comment_Builder { /** * creates a comment object from database record * * @param array * @return Blog_Comment */ public function assemble(array $record) { $comment = new Blog_Comment(); $comment->name = $record['name']; $comment->website = $record['website']; $comment->email = $record['email']; $comment->body = $record['body']; $comment->postedOn = new Date($record['posted_on']); return $comment; } }

Something to note here is that these are not public properties. They are all protected, but each domain object extends a Domain_Object class that has magic __set() and __get() methods so you don't have to write setters and getters for every single property in every single class! For more on that check out this post by Matthew Purdon.

You can see that properties of objects can still be other objects such as the Date class here. In the case where you are showing a bunch of Users on your site and each one has a bunch of other classes associated it with it you probably wouldn't want to use the other class's builders because then you end up running queries in loops which is never a good thing. Instead you can handle that logic in the User_Builder cause at the end of the day even if there are other classes involved the Builder is just returning one object from one database record.

An obvious downfall here, of course, is that every object needs to have its own Builder class even though the builder seems like something that could happen automatically if there was better ORM support in PHP or any for that matter. This is something that PHP developers are used to, however, because no matter what framework you use, you still end up having to write tons of code to do even simple operations. Hopefully this will make your life easier.