PHP Doctrine introduction for dummies

It was long overdue but finally I’ve taken a look at Doctrine. And I’m blown away, bye bye Zend DB.

I’ve been whining about Zend Controller before, how it forces me to do things I don’t want to do. Therefore it’s my intention to simply write my own routing and I’m basically finished with the basics already but let’s focus on that in the next article because this will surely turn into a series :) .

It’s time to try and convey how awesome I think Doctrine is, let’s start with the models that I’ve put in a subfolder with the same name, in Member.php:

class Member extends Doctrine_Record{
  public function setTableDefinition(){
    $this->hasColumn('username', 	'string',    30, array('minlength' => 6, 'regexp' => '/^\w+$/', 'unique' => true));
    $this->hasColumn('password', 	'string',    30, array('minlength' => 6));
    $this->hasColumn('email', 	      'string', 	30, array('notblank' => true, 'email' => true));
    $this->hasColumn('city', 		'integer', 	20);
  }
  
  public function setUp(){
    $this->actAs('Timestampable');
    $this->hasOne('City', 			 array('local' => 'city', 	'foreign' => 'id'));
    $this->hasMany('Message as From', array('local' => 'id', 	  'foreign' => 'from_id'));
    $this->hasMany('Message as To',    array('local' => 'id', 	   'foreign' => 'to_id'));
    $this->hasMany('Member as Senders', array('local' 	=> 'from_id', 
    											'foreign' 	=> 'to_id',
    											'refClass' 	=> 'Message'));
  	$this->hasMany('Member as Recipients', array('local' 	=> 'to_id', 
    											'foreign' 	=> 'from_id',
    											'refClass' 	=> 'Message'));
  }
}

So setTableDefinition() is filled with repeat calls to hasColumn() which basically defines our table. Notice the absence of ‘id’, it will be created automatically later when we use these definitions to actually create the tables in MySQL, this behavior mirrors Datamapper’s.

The actAs(’Timestampable’) call will automatically add two columns that will keep track of when the object was created and last updated respectively.

The hasOne will setup a one directional relation, in this case a city whose model we’ll cover in a minute. HasMany() will setup all kinds of relationships which somehow involves more than one object on one side of the relation. In this case we have two one-to-many and many-to-many relationships. From the member to several messages, either sent or received and from the member to other members he might have sent and/or received messages from/to. Note the aliases (X as Y).

In Message.php:

class Message extends Doctrine_Record{
  public function setTableDefinition(){
    $this->hasColumn('subject', 'string', 	100);
    $this->hasColumn('body', 	'string', 	5000);
    $this->hasColumn('from_id', 'integer', 	20);
    $this->hasColumn('to_id',   'integer', 	20);
  }
  
  public function setUp(){
    $this->actAs('Timestampable');
    $this->hasOne('Member as From', array('local' => 'from_id', 'foreign' => 'id'));
    $this->hasOne('Member as To', array('local' => 'to_id',   'foreign' => 'id'));
  }
}

Nothing new except for the string column definition with a length of 5000, I noticed that that line will automatically create a column of type text.

City.php:

class City extends Doctrine_Record{
  public function setTableDefinition(){
    $this->hasColumn('name', 'string', 100);
  }
}

Below is a chopped up script from top to bottom that will test the functionality of the above models:

require_once('../lib/doctrine/lib/Doctrine.php');
spl_autoload_register(array('Doctrine','autoload'));
Doctrine_Manager::getInstance()->setAttribute('model_loading', 'conservative');
Doctrine::loadModels('models');
$conn = Doctrine_Manager::connection('mysql://root:@localhost/doctrine_test');

Basic bootstrap code, refreshingly little. We have to include the Doctrine.php script of course. Set the stuff to autoload, initiate conservative loading (i.e. load stuff only when needed), so loadModels(’models’) will only parse the models directory, not load them. Finally we create the connection object. Note that in this case I’ve already created the database doctrine_test in phpMyAdmin even though it is possible to create databases with Doctrine too.

Doctrine::createTablesFromArray(array('Member', 'City', 'Message'));

$conn->beginTransaction();
$mbrs = array(
	array('member1', 'password1', 'member1@members.com', 'Berlin'),
	array('member2', 'password2', 'member2@members.com', 'Stuttgart'),
	array('member3', 'password3', 'member3@members.com', 'Hamburg')
);
foreach($mbrs as $m){
	$mbr = new Member();
	$mbr->username 		= $m[0];
	$mbr->password 		= $m[1];
	$mbr->email 		= $m[2];
	$mbr->City->name 	= $m[3];
	$mbr->save();
}
$conn->commit();

We setup the tables with the help of our models and create three members. That’s actually not completely true, note the $mbr->City->name line. Since the member hasOne City the line will automagically create a city too if one with the given name doesn’t already exist, great!

$tbl_mbr 	= Doctrine::getTable('Member');
$mbr1 		= $tbl_mbr->find(1);
$mbr2 		= $tbl_mbr->find(2);

$msg = new Message();
$msg->subject 	= 'from mbr1 to mbr2';
$msg->body 		= 'Hello mbr2 this is mbr1';
$msg->To 		= $mbr2;
$msg->From 		= $mbr1;
$msg->save();

$msg = new Message();
$msg->fromArray(array(
	'subject' 	=> 'from mbr2 to mbr1',
	'body'		=> 'Hello mbr1 this is mbr2',
	'from_id'	=> $mbr2->id,
	'to_id'		=> $mbr1->id
));
$msg->save();

$msg = new Message();
$msg->fromArray(array(
	'subject' 	=> 'from mbr1 to mbr3',
	'body'		=> 'Hello mbr3 this is mbr1',
	'from_id'	=> $mbr1->id,
	'to_id'		=> 3
));
$msg->save();
$conn->commit();

A demonstration of two different ways of doing things. We begin by creating two member objects for the members with id 1 and 2 with the help of a Doctrine::getTable(’Member’) object. Since the message has a relation with two members in the form of To and From we can simply assign the objects directly and Doctrine will do the rest. The other way is to use an array with keys and values in a more old fashioned way that might be preferable in some situations. Finally we commit all our changes.

You can finish off with the following tests, they will all output the expected data:

$mbr = Doctrine::getTable('Member')->find(1);
print_r($mbr->Senders->toArray());
print_r($mbr->Recipients->toArray());
print_r($mbr->From->toArray());
print_r($mbr->To->toArray());

We get all people who has sent messages to the member in question through the Senders relation. Same goes for Recipients but now we get all people that member1 has sent messages to instead. And the two last From and To relations will get all messages that member1 has sent and received.

I’m not really sure how big impact all this magic has on performance, but it has to be pretty darn big for me to give up Doctrine and I hope the above has made you as excited as I am.

  • Digg
  • del.icio.us
  • blogmarks
  • Reddit
  • Simpy
  • StumbleUpon
  • Technorati
  • description
  • Ma.gnolia
  • Slashdot
  • Sphinn
  • Spurl

Related Posts

Sponsored links

excavator spare parts
Tracking
GPS Vehicle Tracking

Tags: , , ,

Posts linking to this article:

links for 2008-08-30
guia de referencias do linux. guia de referências do linux. (tags: linux). data filtering using php's filter functions - part one - devolio. (tags: php tips). bonita: open source workflow / bpm solution - xpdl open source workflow ...

introduction à php doctrine
doctrine est un orm pour php : object relational mapper. il établit une correspondance entre les lignes d'une base de données et les objets créés dans un script php. doctrine assure l'abstraction de la base de données, en fournissant ...

Weekly Roundup - August 18th 2008
This is my weekly roundup for the week of August 11th to 17th 2008, where I look back at the posts I made over the past week as well as useful and interesting articles on other websites and blogs that I might have read. ...

ProDevTips.com: Doctrine for dummies
Henrik waves goodbye to the Zend_Db component of the Zend Framework in this new post to the ProDevTips blog - his new favorite is Doctrine. It was long overdue but finally I've taken a look at Doctrine. And I'm blown away, ...

Subscribe with Google Reader