Cool Object Building With PHP
  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 OrientedPHP 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:

  1 <?php 2 class Dao 3 { 4 /** 5 * @var Db 6 */ 7 protected $_db; 8 9 /** 10 * @var Builder 11
  */ 12 protected $_builder; 13 14 /** 15 * constructor 16 * 17 * sets the database class this dao
  should use 18 * 19 * @return void 20 */ 21 function __construct() 22 { 23 $this->_db = new
  Db(); 24 } 25 26 /** 27 * determines what builder class to use 28 * 29 * @param string $builder
  30 * @return Builder 31 */ 32 protected function _getBuilder($builderName) 33 { 34 // if we have
  already instantiated a builder class 35 if (!is_null($this->_builder)) { 36 return $this->_builder;
  37 } 38 39 // if we have not passed in a specific builder to use 40 if (is_null($builderName)) { 41
  $builderName = str_replace('_Dao', '_Builder', get_class($this)); 42 } 43 44 // make sure the
  builder exists 45 if (!class_exists($builderName)) { 46 throw new Exception($builderName . ' does
  not exist!'); 47 } 48 49 // instantiate the builder 50 $this->_builder = new $builderName; 51 52
  return $this->_builder; 53 } 54 55 /** 56 * calls the builder to build a collection of the stuff
returned from the 57 * dao! 58 * 59 * @param mixed $records 60 * @param string $builder
name of class to use for building 61 * @return Collection 62 */ 63 protected function
_buildCollection($records, $builder = null) 64 { 65 // if the database doesn't have any records 66
if ($records === false) { 67 return false; 68 } 69 70 $collection = new Collection(); 71 foreach
($records as $record) { 72 $collection->append($this->_buildOne($record, $builder)); 73 } 74
return $collection; 75 } 76 77 /** 78 * calls the builder and builds a single object 79 * 80 *
@param mixed 81 * @param string $builder 82 * @return Object 83 */ 84 protected function
_buildOne($record, $builder = null) 85 { 86 // if the database doesn't have any record 87 if
($record === false) { 88 return false; 89 } 90 91 $builder = $this->_getBuilder($builder); 92
return $builder->assemble($record); 93 } 94 } 95

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:

39 /** 40 * retrieves all comments for a given blog 41 * 42 * @param int $blogId 43 * @return
Collection 44 */ 45 public function get($blogId) 46 { 47 $query = '/* ' . __METHOD__ . ' */' . 48
"SELECT bc.id id, 49 bc.name name, 50 bc.website website, 51 bc.email email, 52 bc.body body,
53 bc.posted_on posted_on 54 FROM blog_comment bc 55 WHERE bc.blog_id = :blog_id"; 56 57
$sth = $this->_db->prepare($query); 58 $sth->bindValue(':blog_id', $blogId); 59 $sth-
>execute(); 60 $records = $sth->fetchAllAssoc(); 61 62 return $this->_buildCollection($records);
63 }

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:

1 <?php 2 class Blog_Comment_Builder 3 { 4 /** 5 * creates a comment object from database
record 6 * 7 * @param array 8 * @return Blog_Comment 9 */ 10 public function assemble(array
$record) 11 { 12 $comment = new Blog_Comment(); 13 $comment->name = $record['name'];
14 $comment->website = $record['website']; 15 $comment->email = $record['email']; 16
$comment->body = $record['body']; 17 $comment->postedOn = new
Date($record['posted_on']); 18 return $comment; 19 } 20 } 21

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.

Cool Object Building With PHP

  • 1.
    Cool Object BuildingWith PHP 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 OrientedPHP 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: 1 <?php 2 class Dao 3 { 4 /** 5 * @var Db 6 */ 7 protected $_db; 8 9 /** 10 * @var Builder 11 */ 12 protected $_builder; 13 14 /** 15 * constructor 16 * 17 * sets the database class this dao should use 18 * 19 * @return void 20 */ 21 function __construct() 22 { 23 $this->_db = new Db(); 24 } 25 26 /** 27 * determines what builder class to use 28 * 29 * @param string $builder 30 * @return Builder 31 */ 32 protected function _getBuilder($builderName) 33 { 34 // if we have already instantiated a builder class 35 if (!is_null($this->_builder)) { 36 return $this->_builder; 37 } 38 39 // if we have not passed in a specific builder to use 40 if (is_null($builderName)) { 41 $builderName = str_replace('_Dao', '_Builder', get_class($this)); 42 } 43 44 // make sure the builder exists 45 if (!class_exists($builderName)) { 46 throw new Exception($builderName . ' does not exist!'); 47 } 48 49 // instantiate the builder 50 $this->_builder = new $builderName; 51 52 return $this->_builder; 53 } 54 55 /** 56 * calls the builder to build a collection of the stuff
  • 2.
    returned from the57 * dao! 58 * 59 * @param mixed $records 60 * @param string $builder name of class to use for building 61 * @return Collection 62 */ 63 protected function _buildCollection($records, $builder = null) 64 { 65 // if the database doesn't have any records 66 if ($records === false) { 67 return false; 68 } 69 70 $collection = new Collection(); 71 foreach ($records as $record) { 72 $collection->append($this->_buildOne($record, $builder)); 73 } 74 return $collection; 75 } 76 77 /** 78 * calls the builder and builds a single object 79 * 80 * @param mixed 81 * @param string $builder 82 * @return Object 83 */ 84 protected function _buildOne($record, $builder = null) 85 { 86 // if the database doesn't have any record 87 if ($record === false) { 88 return false; 89 } 90 91 $builder = $this->_getBuilder($builder); 92 return $builder->assemble($record); 93 } 94 } 95 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: 39 /** 40 * retrieves all comments for a given blog 41 * 42 * @param int $blogId 43 * @return Collection 44 */ 45 public function get($blogId) 46 { 47 $query = '/* ' . __METHOD__ . ' */' . 48 "SELECT bc.id id, 49 bc.name name, 50 bc.website website, 51 bc.email email, 52 bc.body body, 53 bc.posted_on posted_on 54 FROM blog_comment bc 55 WHERE bc.blog_id = :blog_id"; 56 57 $sth = $this->_db->prepare($query); 58 $sth->bindValue(':blog_id', $blogId); 59 $sth- >execute(); 60 $records = $sth->fetchAllAssoc(); 61 62 return $this->_buildCollection($records); 63 } 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: 1 <?php 2 class Blog_Comment_Builder 3 { 4 /** 5 * creates a comment object from database record 6 * 7 * @param array 8 * @return Blog_Comment 9 */ 10 public function assemble(array $record) 11 { 12 $comment = new Blog_Comment(); 13 $comment->name = $record['name']; 14 $comment->website = $record['website']; 15 $comment->email = $record['email']; 16 $comment->body = $record['body']; 17 $comment->postedOn = new Date($record['posted_on']); 18 return $comment; 19 } 20 } 21 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
  • 3.
    each one hasa 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.