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:
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:
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:
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:
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.
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:
- a DAO (data access object) class
- 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);
}
}
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 bc.id id,
bc.name name,
bc.website website,
bc.email 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);
}
* retrieves all comments for a given blog
*
* @param int $blogId
* @return Collection
*/
public function get($blogId)
{
$query = '/* ' . __METHOD__ . ' */' .
"SELECT bc.id id,
bc.name name,
bc.website website,
bc.email 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;
}
}
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.
32 comments
are you sure you want to delete this comment?
are you sure you want to delete this comment?
are you sure you want to delete this comment?
are you sure you want to delete this comment?
8[ preteen nymphet models
62092 naked nymphet
474 cute little nymphets
=( naked nymphet forum
%]]] bbs nymphets
>:[[[ nymphet top
%P virgin nymphets
485270 young pthc bbs
hfrkz cp pthc
>:DDD
are you sure you want to delete this comment?
8-( pthc topsites
884 nymphets pics
30447 chstcnt pthc cp pay sites
8805 preteen nymphet models
ngd naked nymphet
vsrout top nymphet sites
22662 virgin nymphets
8-DD young pthc bbs
9084 extreme nymphets
230962
are you sure you want to delete this comment?
lwg pthc hussyfan
yftk pthc topsites
immx naked nymphet
:[ preteen loli nymphets
torl nymphets family
04707 young pthc bbs
65878 cp pthc
:]] extreme nymphets
462650 sandra imageboard bbs loli pthc
57430
are you sure you want to delete this comment?
>:DD cp pics pthc
21606 pthc cp videos toplist
ncscky pthc cp pthc bbs
qtta pthc forum img
=-PP alina 14 pthc model
0012 loli toplist pthc
jnwzib hussyfan pthc gallery
xctmf best p2p for pthc
>:)) pthc loli cp zshare
>:-]
are you sure you want to delete this comment?
ouqr cp pics pthc
>:-))) pthc boy
>:-)) pthc cp pthc bbs
:D young pthc
9029 pthc bbs forum
goul pthc girls
:))) cp pthc bbs
8069 pthc hussyfan r@ygold
875 kazz310 pthc models
gedmi
are you sure you want to delete this comment?
wva ovens bbs
>:OO gaijin bbs
pxmeh bbs cgi loli imageboard
:-] elweb bbs gateway
yvd loli bbs
%-DD max bbs touzokudan 12449
hiup nn girl bbs
yrs zeps bbs
8-] pedoland bbs pthc
ifxdh
are you sure you want to delete this comment?
owrcj sandra img bbs
lnycn angel bbs innocent
:[[[ smuggler bbs
14162 dark little bbs
76891 lola bbs
zmco loli bbs
72815 max bbs touzokudan 12449
65346 zeps bbs
62925 young bbs
udwshh
are you sure you want to delete this comment?
owrcj sandra img bbs
lnycn angel bbs innocent
:[[[ smuggler bbs
14162 dark little bbs
76891 lola bbs
zmco loli bbs
72815 max bbs touzokudan 12449
65346 zeps bbs
62925 young bbs
udwshh
are you sure you want to delete this comment?
lxty lola bbs magazine
yil ovens bbs
>:-P sandra model bbs
:-( max adult bbs
>:-DDD elweb bbs gateway
=-DDD lola links bbs
vuq max bbs touzokudan 12449
ysczf nn girl bbs
925 klass bbs
torjo
are you sure you want to delete this comment?
lxty lola bbs magazine
yil ovens bbs
>:-P sandra model bbs
:-( max adult bbs
>:-DDD elweb bbs gateway
=-DDD lola links bbs
vuq max bbs touzokudan 12449
ysczf nn girl bbs
925 klass bbs
torjo
are you sure you want to delete this comment?
hwoxo boy bbs
aksc girl child bbs
4795 sandra bbs
39566 loli bbs board
nreh young love bbs
%-D loli hc bbs
25224 freedom bbs ls
:[ bbs list young
lvtujv yoda bbs
141
are you sure you want to delete this comment?
8]] zeps guide bbs board
knyz girl child bbs
877 loli bbs board
bcfqp young love bbs
1158 freedom bbs ls
3649 yoda bbs
170 best lol bbs
qdwgc max4teen bbs
qebk pthc bbs
pci
are you sure you want to delete this comment?
9361 young bbs post
dgtwfi max touzokudan bbs
128 mixman bbs
:-))) vlad model bbs
677 ptsc bbs links
122 loli bbs board
243995 best lol bbs
exxv fozya bbs
739 pthc bbs
>:[[[
are you sure you want to delete this comment?
%]] felixx boy bbs
yawra zeps bbs guide
sogp pre bbs
094 pedo
qupbg peachy bbs
%))) ranchi bbs gateway
%( max bbs touzokudan livedoor
lzmw ls magazine bbs
%)) top list pedo sites
:((
are you sure you want to delete this comment?
947 ls models bbs
mzdvc pedo loli
8(( bbs lola
823131 felixx boy bbs
>:[ zeps bbs guide
ofb svens place bbs
350 peachy bbs
=))) young bbs links
haq top list pedo sites
059458
are you sure you want to delete this comment?
nxbcut pedo love
=-]]] pedo aficionados
888 zeps bbs guide
ajqdh teeny bbs
yybg peachy bbs
=DDD ranchi bbs gateway
>:-O young bbs links
gzxeng top list pedo sites
tbuwe great bbs
8-)))
are you sure you want to delete this comment?
nxbcut pedo love
=-]]] pedo aficionados
888 zeps bbs guide
ajqdh teeny bbs
yybg peachy bbs
=DDD ranchi bbs gateway
>:-O young bbs links
gzxeng top list pedo sites
tbuwe great bbs
8-)))
are you sure you want to delete this comment?
1195 pedo art
320 pedo cp
cdcr pedo portal
8[ pedo models
tzkik child pedo pics
%-] pedo files
zjl little girl pedo
:-] pedo boy
%-O pedo top
=)))
are you sure you want to delete this comment?
kruzlz little models top sites
%-))) young little models
80785 young nn models
=-[[[ nn child models
%-))) teen models
=-[[[ young model galleries
835245 bottomless models
8PP lia model
86607 sandra model jpg
lscry
are you sure you want to delete this comment?
28151 nn junior models
8OO nn child models
58168 sandra model nn
tlyw teenage models
474 young model galleries
rvorjl bottomless models
2627 female models
544804 lia model
60805 pre teen models
40802
are you sure you want to delete this comment?
are you sure you want to delete this comment?
bjmv cp portal
>:-PP cp fans club 9 best cp paysites
013 young cp
9825 dreamzone cp
%-] cp paysite
80052 free cp gallery
>:OOO cp galleries
8-[ cp bbs
>:DD pedoclub illegal cp
283619
are you sure you want to delete this comment?
%] nn legal models
mgnht illegal cp links
19213 nn young girls
acm cp topsite
8219 russian cp bbs
cyewb nn girls
9916 nn young models galleries
07630 nn model
>:((( cp money maker
=))
are you sure you want to delete this comment?
zvxu nn cuties
%O nn model galleries
=) nn girl models
hhb nn model board
rxhn 14 yo models nn
=D nn tgp
8OO young beautiful models nn
492 young nn galleries
314998 pre teenage nn models
8(((
are you sure you want to delete this comment?
aciqjm nn models photos
:-[[[ nn model galleries
349358 nn girl models
7751 young little nn models
%-))) nn girl index
%((( nn model board
dkgp 14 yo models nn
djm nn child models galleries
:((( nn teen models
dkb
are you sure you want to delete this comment?
:-DDD preteen models nn
801 nn models photos
rld nn model board
=OO very very young nn model galleries
534 nn young pre lola models
6975 pre teenage nn models
ojc nn teen models
vyggnq young beautiful models nn
=PPP nn girl links
lfamb
are you sure you want to delete this comment?
wrzxf child modeling nn video
:PP young nn model forum
8( young nn models pics
8((( teen nn models
=( young nn model videos
%)) cute nn models
987 nn teenmodel club
xovjik nn girl guestbook
guzra ls nn models
sftxac
are you sure you want to delete this comment?
>:-))) little pre nn
018548 nn teen model
124930 child modeling nn video
1356 nn young models
xueow young nn model videos
=-D nn preteen pics
498918 cute nn models
=-]] nn models video
ssyzo nn girl guestbook
fedn
are you sure you want to delete this comment?