Mandatory error reporting: It's Gang of Four.GoF (Group of Four)
Nice idea, Herr Dr. Will contribute some later too.
| SitePoint Sponsor |





Mandatory error reporting: It's Gang of Four.GoF (Group of Four)
Nice idea, Herr Dr. Will contribute some later too.





Opps, yer your right it's Gang of Four... Did I post that?? ... I can't belive that, how embarrassing
And I and other would indeed welcome your contributions, so please do.





Having seen you around here for long and always valueing your contributions I too could not believe you posted that.
Back on topic now, though.![]()
Perhaps the next great patterns book will come from the Gaggle of Four![]()
Jason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.





Nice one, Jason.
I cannot believe I haven't read GoF yet..only PoEAA. But just got an email from Amazon that GoF has been shipped.Should be here soonish.
![]()





*sigh*


It would be good if a sub-forum could be requested on this forum, where we could have each PHP Pattern in their own threads. That way discussions could continue within the forum and everything would be well organised and not all muddled up: and it would be easy to link to if questions came up during forum conversations.
Just an idea.





@Ryan: Top notch idea.![]()
Here is a Dynamic Proxy implementation for PHP5 (could also be the basis for a Decorator, since the two patterns are structually identical):
PHP Code:class GenericProxy {
protected $subject;
public function __construct($subject) {
$this->subject = $subject;
}
public function __call($method, $args) {
return call_user_func_array(
array($this->subject, $method),
$args);
}
}
Edit:
Had a request for usage
Say you have a class:
which you want to Proxy/Decorate. Most of the methods you want to pass through, but someMethodTwo() you want to do something with. You could then do:PHP Code:class SomeClass {
public function __construct() {}
public function someMethodOne() {}
public function someMethodTwo() {}
//...
public function someMethodTwenty() {}
}
Then you can do:PHP Code:class SomeClassProxy extends GenericProxy {
public function someMethodTwo($foo) {
return strtoupper($this->subject->someMethodTwo($foo));
}
}
and the call will automatically be proxied to the SomeClass instantance stored in SomeClassProxy::$subject.PHP Code:$foo = new SomeClassProxy(new SomeClass);
$foo->someMethodTwenty();
A more typical example for a Proxy class might be to have some kind of an access control object as part of the Proxy construction. You could then write a Model class without worrying about whether a user is authorized to do any of the actions the Model provides. Whenever you use the Model, you could then use it through a Proxy object which would automatically check if the user is authorized before providing access to the proxied Model class.
HTH
Last edited by sweatje; Apr 16, 2005 at 12:24.
Jason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.





Pattern: Active Record
If the classes of your domain model and the structures of your sql tables match closely (which they often do), then you can take that to your advantage. An Active Record is an object - a part of the domain model - that is responsible for itself whether it is inserted into the db, deleted or updated. So unlike you have a central DataMapper approach for inserting/updating and deleting data, you can save some code, when you do it with the Active Record.
An ActiveRecord is typicially responsible for the following:
* Construct an instance of the Active REcord class from a SQL result set row
* Construct a new instance for later insertion into the table
* Either static finder methods or centralized Finder classes are used for fetching SQL result set..the Active Record is mostly used only for modifying data. Whatever way you take, the fetching mechanisms should return ActiveRecord objects.
* Update the Database and insert into it the data from the Active Record (every attribute becomes one column in the SQL row)
* Get and set the fields
I think this pattern is pretty straight-forward. I can write some example code, though, if requested.



Write some sample code please.
Ian Gordon
CSS / XHTML / PHP Programmer
http://www.iangordon.us





Active Record
-------------
PHP Code://...
$person = new PersonObj;
$person -> id = 1;
$person -> firstname = 'Dr';
$person -> lastname = 'Livingston';
$person -> insert();
//...
class PersonObj {
private $db;
public $id;
public $firstname;
public $lastname;
public function __construct() {
try {
$this -> db = MySqlDatabase::getInstance();
} catch( SqlException $e ) {
die( $e -> getMessage() );
}
}
public function insert() {
$stmt = $this -> db -> createStatment( 'insert into persons set id = ?, firstname = ?, lastname = ?' );
$stmt -> setInteger( 1, $this -> id );
$stmt -> setParameter( 2, $this -> firstname );
$stmt -> setParameter( 3, $this -> lastname );
$rslt = $stmt -> execute();
//... validation etc
}
public function update() { }
public function delete() { }
}
Here is a unit test case for an ActiveRecord representing bookmarks. You should be able to see usage from the test, and build you own implementation if you wanted
It uses ADOdb as a database access layer, and DB::conn() is a static function to return an ADOdb connection.
PHP Code:define('BOOKMARK_TABLE_DDL', <<<EOS
CREATE TABLE `bookmark` (
`id` INT NOT NULL AUTO_INCREMENT ,
`url` VARCHAR( 255 ) NOT NULL ,
`name` VARCHAR( 255 ) NOT NULL ,
`description` MEDIUMTEXT,
`tag` VARCHAR( 50 ) ,
`created` DATETIME NOT NULL ,
`updated` DATETIME NOT NULL ,
PRIMARY KEY ( `id` )
)
EOS
);
Mock::generate('ADOConnection');
class ActiveRecordTestCase extends UnitTestCase {
protected $conn;
function __construct($name='') {
$this->UnitTestCase($name);
$this->conn = DB::conn();
}
// no test interference
function setup() {
$this->conn->execute('drop table bookmark');
$this->conn->execute(BOOKMARK_TABLE_DDL);
}
function testSetupLeavesTableEmptyWithCorrectStructure() {
$rs = $this->conn->execute('select * from bookmark');
$this->assertIsA($rs, 'ADORecordSet');
$this->assertEqual(0,$rs->recordCount());
foreach(array(
'id',
'url',
'name',
'description',
'tag',
'created',
'updated') as $i => $name) {
$this->assertEqual($name, $rs->fetchField($i)->name);
}
}
function testNew() {
$link = new Bookmark;
$link->url = 'http://simpletest.org/';
$link->name = 'SimpleTest';
$link->description = 'SimpleTest project homepage';
$link->tag = 'testing';
$link->save();
$this->assertEqual(1, $link->getId());
// fetch the table as an array of hashes
$rs = $this->conn->getAll('select * from bookmark');
$this->assertEqual(1, count($rs), 'returned 1 row');
foreach(array('url', 'name', 'description', 'tag') as $key) {
$this->assertEqual($link->$key, $rs[0][$key]);
}
}
function testAdd() {
$this->add('http://php.net', 'PHP', 'PHP Language Homepage', 'php');
$this->add('http://phparch.com', 'php|architect', 'php|arch site', 'php');
$rs = $this->conn->execute('select * from bookmark');
$this->assertEqual(2,$rs->recordCount());
$this->assertEqual(2,$this->conn->Insert_ID());
}
function testDbFailure() {
$conn = new MockADOConnection($this);
$conn->expectOnce('execute', array('*','*'));
$conn->setReturnValue('execute',false);
$conn->expectOnce('errorMsg');
$conn->setReturnValue('errorMsg', 'The database has exploded!!!!');
$link = new Bookmark(1,$conn);
$this->assertErrorPattern('/exploded/i');
$conn->tally();
}
function testAddReturnsBookmark() {
$link = $this->add(
'http://blog.casey-sweat.us/',
'My Blog',
'Where I write about stuff',
'php');
$this->assertIsA($link, 'Bookmark');
$this->assertWantedPattern('/sweat/i', $link->url);
$this->assertEqual('My Blog', $link->name);
$this->assertWantedPattern('/write/i', $link->description);
$this->assertTrue((time()-strtotime($link->created))<2);
$this->assertEqual($link->created, $link->updated);
}
function testGetId() {
$this->add('http://php.net', 'PHP',
'PHP Language Homepage', 'php');
// second bookmark, id=2
$link = $this->add('http://phparch.com',
'php|architect', 'php|arch site', 'php');
$this->assertEqual(2, $link->getId());
//how would you check it by hand? do it in the test!
$alt_test = $this->conn->getOne(
"select id from bookmark where url = 'http://phparch.com'");
$this->assertEqual(2, $alt_test);
//alternatively
$this->assertEqual($link->getId(), $alt_test);
}
function testCreateById() {
$link = $this->add(
'http://blog.casey-sweat.us/',
'My Blog',
'Where I write about stuff',
'php');
$this->assertEqual(1, $link->getId());
$link2 = new Bookmark(1);
$this->assertIsA($link2, 'Bookmark');
$this->assertEqual($link, $link2);
}
function testFindByUrl() {
$this->add('http://blog.casey-sweat.us/', 'My Blog',
'Where I write about stuff', 'php');
$this->add('http://php.net', 'PHP',
'PHP Language Homepage', 'php');
$this->add('http://phparch.com', 'php|architect',
'php|arch site', 'php');
$result = Bookmark::findByUrl('php');
$this->assertIsA($result, 'array');
$this->assertEqual(2, count($result));
$this->assertEqual(2, $result[0]->getId());
$this->assertEqual('php|architect', $result[1]->name);
}
function testSave() {
$link = $this->add(
'http://blog.casey-sweat.us/',
'My Blog',
'Where I write about stuff',
'php');
sleep(2);
$link->description = 'Where I write about PHP, Linux and other stuff';
$link->save();
$link2 = Bookmark::findById($link->getId());
$this->assertEqual($link->getId(), $link2->getId());
$this->assertNotEqual($link2->created, $link2->updated);
$this->assertTrue(strtotime($link2->updated)>strtotime($link2->created));
$this->assertEqual($link->updated, $link2->updated);
}
function add($url, $name, $description, $tag) {
$link = new Bookmark;
$link->url = $url;
$link->name = $name;
$link->description = $description;
$link->tag = $tag;
$link->save();
return $link;
}
}
Any more patterns?



Dunno if you noticed, but this thread is almost two years old
Anyway, I've found http://www.phparch.com/shop_product.php?itemid=96 very good. If you want free examples, then maybe else can give you a link...





> Any more patterns?
No, this thread has fallen by the way side by the looks of things; At the time it was a good idea, as fewer people really knew enough about patterns, plus a couple of years ago there was little documentation in regards to PHP and patterns.
That isn't the case today, where there are more plentiful resources; Did you have any particular patterns in mind that you wanted to know more about?
Thank, I have a questions regarding Active Record & Table Data Gateway, So this is what I understand: the Active Record pattern deals with a single record in a table and the Table Data Gateway deals with multiple records. How can I use them together effectively? For example, A User class abstracts the user table and provides some domain logic. If I want to retrieve multiple records at a time, how can I get a multiple records of User object so that I can make use of the domain logic.
Is this how it works?
PHP Code:class User {
public function __construct($user_id) {
}
}
class UserGateway() {
public function findbylastname($lastname) {
//sql to get all the user id
while(looping the sql result) {
$User[] = new User(id);
}
return $User;
}
}
You can have a domain object which essentially equates to a database row with both patterns, the key distinction is where the actual logic which accesses the database itself resides. In the ActiveRecord, this logic is contained in the row level object itself, whereas the TableDataGateway has the database logic and the can return collections of (or individual) domain objects, which still can have business logic, but would need to ask the TableDataGateway to persist any changes to themselves in the database.
Jason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.





> but would need to ask the TableDataGateway to persist any changes to themselves in the
> database.
So, with the Active Record you would do this,
Whereas, with Jason's TableDataGateway (from my understanding of course) you would do this instead,PHP Code:// ...
$record -> save();
Where the Gateway in question would have the knowledge to know how to save the record for example. Is this what you had in mind yourself?PHP Code:// ...
$gateway -> save( $record );
Personally I would one or the other but not both at the same time, as I believe therefore if you used the Gateway option, the Active Record (from my viewpoint) would be bloated, having to carry around additional responsibilities, which would be taken care of anyways by the Gateway.
But that's just my thoughts; Your milage may vary![]()
Dr. L is correct. One additional possibility is to have the model retain knowledge of it's gateway:
the you can have the record level object have the API of the ActiveRecord, without the additional responsibilities of knowing how to interact with the database.PHP Code:class Model {
protected $gateway;
function __construct($gateway) {
$this->gateway = $gateway;
//...
}
function save() {
$this->gateway->save($this);
}
//...
}
Jason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.



we gonna wiki this or what?![]()
My-Bic - Easiest AJAX/PHP Framework Around
Now Debug PHP scripts with Firebug!
Thanks, Jason & Dr. L
Dr. L, take my User class as example again, if this class defines some business logic like: is_vip_user(), get_estimated_income(), wouldn't you want to reuse this code in the active record instead of redefining them in the gateway.
With http://www.phppatterns.com/ getting a bit long in the tooth, http://www.patternsforphp.com/wiki/Main_Page was another attempt to start up a pattern wiki. Looks like perhaps it may have stalled out last fall.
Jason Sweat ZCE - jsweat_php@yahoo.com
Book: PHP Patterns
Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
Detestable (adjective): software that isn't testable.



yea , none have been updated in quite a while.
My-Bic - Easiest AJAX/PHP Framework Around
Now Debug PHP scripts with Firebug!





> wouldn't you want to reuse this code in the active record instead of redefining them in the
> gateway.
Yes. The logic in this case is in regards to that one row of data that is likely to be unique; The emphasis is with the data therefore and belongs with that row of data. From my point of view, this would be the case where you have that uniqueness so any behaviour you have acts on that row of data only.
If there is common, shared values then it belongs elsewhere...
Bookmarks