June 27, 2009

Coming Soon...

I have rather a poor track record of posting, but I hope to rectify that over the coming weeks and months.

I've been so busy that I haven't found the time to report on the things I've been working on and discovering. However, that does mean that I've been building up a bunch of ideas for articles that should be appearing here soon.

Here's what you can expect to see some time in the near(ish) future.
  • Re-usable class loader class for namespaced PHP 5.3 code. I recently wrote a really simple class for loading classes in projects using namespaces. It's intended to be used primarily for auto-loading, but you can also use it to manually load classes. It follows the same conventions as those proposed by the newly formed PHP Standards Group.
  • An improved, more test-friendly Singleton class for PHP 5.3. The code presented in my post last year was really intended only as a demonstration of one of the great new features of PHP 5.3, not a definitive implementation of Singleton. I've learned a lot since then, and having re-implemented the class for a product I've been working on, I can focus more on the nature of Singleton and the issues associated with its use. I want to put that one to bed because Singleton, I find, is one of the least useful of all patterns.
  • The pleasure and pain of Doctrine Collections. I've been working with large data-sets and this has highlighted some quite serious issues with Collections. Not only that, but the behaviour of some of the methods is, I think, really rather odd - behaviour that caught me out when I first started working with them. I will identify the advantages and pitfalls of using Collections, and some tips on how to deal with the problems. I may also discuss my experiences of processing large amounts of data using Doctrine.
  • Unit testing tips. I've been dabbling with unit-testing for only 18 months, but I've learned a few things along the way. There's loads of information out there, but it's quite difficult to get your head around testing when you first start out. There's no denying the usefulness of testing, the reasons for doing it, but it's hard to get into the right mind-set. It's difficult to know how and what to test. It's one of those things you really need to understand because otherwise you can end up writing crappy, worthless tests just like you can the code you're trying to test. I still write crappy tests - I think - but my brain is starting to align itself to the testing way. I've been trying to keep a note of the things I've found helpful and I'll try to get them up on the site.

May 02, 2009

Converting The Time Zone of a Date

This is as much a reminder to myself as it is a helping hand for anyone else working with international dates in PHP.

Converting from one time-zone to another is very easily - and cleanly - accomplished using the DateTime and DateTimeZone classes. The key, the DateTime::setTimezone() method, has been available since PHP 5.2.

The following example shows how to convert a UTC date, in a string, to EST (Eastern Standard Time). The date can be supplied in any format supported by strtotime() - which doesn't, it seems, include forward-specified dates containing slashes (e.g. "23/03/2009").

1 <?php
2
3 $oDateTime
= new DateTime('2-5-2009 10:12:00', new DateTimeZone('UTC'));
4
print_r($oDateTime);
5
6
$oDateTime->setTimezone(new DateTimeZone('EST'));
7
print_r($oDateTime);


Here's the output:

01 DateTime Object
02 (
03 [date] => 2009-05-02 10:12:00
04 [timezone_type] => 3
05 [timezone] => UTC
06 )
07 DateTime Object
08 (
09 [date] => 2009-05-02 06:12:00
10 [timezone_type] => 3
11 [timezone] => America/New_York
12 )
13


You've then got a couple of options for retrieving the converted date, as the next snippet shows.

01 <?php
02
03
// ...
04
05 // Return the date in the internal, MySQL-like, format
06
07
print $oDateTime->date; // => 2009-05-02 06:12:00
08
09 // Return the date in an alternate format. Use the date() formatting options.
10
11
print $oDateTime->format('jS F, Y'); // => 2nd May, 2009


Oh, and while we're on the subject of dates, don't forget that strtotime() always outputs a UTC timestamp.

April 26, 2009

A Custom Doctrine Migration Class With Default Table Options and Other Loveliness

Migrations are one of Doctrine's big draws, but it didn't take long - two tables, in fact - before I wanted more from the experience.

First, I wanted to be able to create tables using a set of default table options, so I didn't have to explicitly specify them every time; and, second, I wanted to be able to create column definitions using the data types I know from MySQL (e.g. "SMALLINT", an integer stored in two bytes). (As a bonus, the solution to the second problem allows us to deal with another minor annoyance: the wordiness of it all.)

Both improvements were implemented in an application-specific subclass of Doctrine_Migration_Base. All I need do now, to take advantage of the new functionality, is create migration classes extending the new class. (We can do this, by the way, because when you migrate your database, Doctrine_Migration looks for all classes, in your migrations directory, extending Doctrine_Migration_Base.)

Here's a very simple example showing how you can set default table options, and how you create column definitions using 'aliases', or "option groups" as I've called them internally, using this extended migration class.

01 <?php
02
03
// Load and initialise Doctrine
04
05 // Load application-specific migration class
06
07
MyDoctrineMigrationBase::setDefaultTableOptions(array(
08
'type' => 'INNODB',
09
'charset' => 'utf8',
10
'collate' => 'utf8_unicode_ci'
11
));
12
13
// ...
14
15
class CreateTest extends MyDoctrineMigrationBase {
16
17 public function
up () {
18
$this->createTable('test', array(
19
'id' => self::columnDef('smallint', 'unsigned', 'notnull'),
20 ));
21 }
22
23 public function
down () {
24
$this->dropTable('test');
25 }
26 }


Here's the class I created for the application I'm working on. I've defined my default table options and column option-groups in the class for now. The code is reusable, so I'll shortly be renaming the class and adding it to my component library.

001 <?php
002
003
/**
004 * @author Dan Bettles <danbettles@yahoo.co.uk>
005 */
006
abstract class EmrsDoctrineMigrationBase extends Doctrine_Migration_Base {
007
008
/**
009 * @var array
010 */
011
protected static $aDefaultTableOption = array(
012
'type' => 'INNODB',
013
'charset' => 'utf8',
014
'collate' => 'utf8_unicode_ci',
015 );
016
017
/**
018 * @var array
019 */
020
protected static $aColumnOptionGroup = array(
021
'smallint' => array('type' => 'integer', 'length' => 2),
022
'unsigned' => array('unsigned' => true),
023
'notnull' => array('notnull' => true),
024 );
025
026
/**
027 * @param array $p_aOption
028 */
029
public static function setDefaultTableOptions (array $p_aOption) {
030
self::$aDefaultTableOption = $p_aOption;
031 }
032
033
/**
034 * @return array
035 */
036
public static function defaultTableOptions () {
037 return
self::$aDefaultTableOption;
038 }
039
040
/**
041 * @param array $p_aDefaultOption
042 * @param array $p_aOption
043 * @return array
044 */
045
public static function mergeOptions (array $p_aDefaultOption, array $p_aOption) {
046 return
array_merge($p_aDefaultOption, $p_aOption);
047 }
048
049
/**
050 * Adds a create-table change, as per Doctrine_Migration_Base, but applies default table options, defined in
051 * $aDefaultTableOption, first
052 *
053 * @param string $tableName Name of the table
054 * @param array [$fields] Array of fields for table
055 * @param array [$options] Array of options for the table
056 */
057
public function createTable ($tableName, array $fields = array(), array $options = array()) {
058 return
parent::createTable($tableName, $fields, self::mergeOptions(self::$aDefaultTableOption, $options));
059 }
060
061
/**
062 * @param array $p_aColumnOptionGroup
063 */
064
public static function setColumnOptionGroups (array $p_aColumnOptionGroup) {
065
self::$aColumnOptionGroup = $p_aColumnOptionGroup;
066 }
067
068
/**
069 * @return array
070 */
071
public static function columnOptionGroups () {
072 return
self::$aColumnOptionGroup;
073 }
074
075
/**
076 * @param string,... $p_columnOptionGroupName
077 * @return array
078 * @throws BadMethodCallException
079 * @throws InvalidArgumentException
080 */
081
public static function columnDef () {
082
$aOption = array();
083
084 if (
func_num_args() < 1) {
085 throw new
BadMethodCallException("Column option-group not specified");
086 }
087
088 foreach (
func_get_args() as $optionGroupName) {
089 if (
is_array($optionGroupName)) {
090
$aOption = self::mergeOptions($aOption, $optionGroupName);
091 continue;
092 }
093
094 if (! isset(
self::$aColumnOptionGroup[$optionGroupName])) {
095 throw new
InvalidArgumentException("The column option-group \"{$optionGroupName}\" does not exist");
096 }
097
098
$aOption = self::mergeOptions($aOption, self::$aColumnOptionGroup[$optionGroupName]);
099 }
100
101 return
$aOption;
102 }
103 }


And here are the PHPUnit tests I've written so far.

001 <?php
002
003
require_once('PHPUnit/Framework.php');
004
005 require_once(
__DIR__ . '/../../../include/loadAll.php');
006
007 class
EmrsDoctrineMigrationBaseTest extends PHPUnit_Framework_TestCase {
008
009 public function
testIsAbstract () {
010
$oReflectionClass = new ReflectionClass('EmrsDoctrineMigrationBase');
011
$this->assertTrue($oReflectionClass->isAbstract());
012 }
013
014
/**
015 * @dataProvider tableOptions
016 */
017
public function testDefaulttableoptionsReturnsOptionsSetWithSetdefaulttableoptions ($p_aOption) {
018
EmrsDoctrineMigrationBase::setDefaultTableOptions($p_aOption);
019
$this->assertEquals($p_aOption, EmrsDoctrineMigrationBase::defaultTableOptions());
020 }
021
022 public function
tableOptions () {
023 return array(
024 array(array(
'charset' => 'utf8')),
025 array(array()),
026 );
027 }
028
029
/**
030 * @dataProvider mergedOptions
031 */
032
public function testMergeoptionsMergesUserOptionsIntoDefaultOptions ($p_defaults, $p_user, $p_expected) {
033
$this->assertEquals($p_expected, EmrsDoctrineMigrationBase::mergeOptions($p_defaults, $p_user));
034 }
035
036 public function
mergedOptions () {
037 return array(
038 array(
039 array(
'type' => 'INNODB', 'charset' => 'utf8', 'collate' => 'utf8_unicode_ci'),
040 array(),
041 array(
'type' => 'INNODB', 'charset' => 'utf8', 'collate' => 'utf8_unicode_ci'),
042 ),
043 array(
044 array(
'type' => 'INNODB', 'charset' => 'utf8', 'collate' => 'utf8_unicode_ci'),
045 array(
'charset' => 'latin1', 'collate' => 'latin1_general_ci'),
046 array(
'type' => 'INNODB', 'charset' => 'latin1', 'collate' => 'latin1_general_ci'),
047 ),
048 );
049 }
050
051 public function
testCreatetableCreatesATableUsingTheDefaultTableOptions () {
052
$this->markTestIncomplete();
053 }
054
055
/**
056 * @dataProvider columnOptionGroups
057 */
058
public function testColumnoptiongroupsReturnsTheColumnOptionGroupsSetWithSetcolumnoptiongroups ($p_aColumnOptionGroup) {
059
EmrsDoctrineMigrationBase::setColumnOptionGroups($p_aColumnOptionGroup);
060
$this->assertEquals($p_aColumnOptionGroup, EmrsDoctrineMigrationBase::columnOptionGroups());
061 }
062
063 public function
columnOptionGroups () {
064 return array(
065 array(array(
'smallint' => array('type' => 'integer', 'length' => 2))),
066 );
067 }
068
069
/**
070 * @dataProvider columnDefinitions
071 */
072
public function testColumndefReturnsAColumnDefinitionArrayCreatedFromTheSpecifiedOptionGroups ($p_groupNames, $p_args, $p_expected) {
073
EmrsDoctrineMigrationBase::setColumnOptionGroups($p_groupNames);
074
$this->assertEquals($p_expected, call_user_func_array('EmrsDoctrineMigrationBase::columnDef', $p_args));
075 }
076
077 public function
columnDefinitions () {
078 return array(
079 array(
080 array(
'smallint' => array('type' => 'integer', 'length' => 2)),
081 array(
'smallint'),
082 array(
'type' => 'integer', 'length' => 2),
083 ),
084 array(
085 array(
086
'smallint' => array('type' => 'integer', 'length' => 2),
087
'unsigned' => array('unsigned' => true),
088
'notnull' => array('notnull' => true)
089 ),
090 array(
'smallint', 'unsigned', 'notnull'),
091 array(
'type' => 'integer', 'length' => 2, 'unsigned' => true, 'notnull' => true),
092 ),
093 );
094 }
095
096 public function
testColumndefThrowsAnExceptionIfOneOfTheSpecifiedOptionGroupsDoesNotExist () {
097
$this->setExpectedException('InvalidArgumentException');
098
EmrsDoctrineMigrationBase::setColumnOptionGroups(array());
099
EmrsDoctrineMigrationBase::columnDef('foo');
100 }
101
102 public function
testColumndefThrowsAnExceptionIfCalledWithoutArguments () {
103
$this->setExpectedException('BadMethodCallException');
104
EmrsDoctrineMigrationBase::columnDef();
105 }
106
107
/**
108 * @dataProvider mixedColumnDefinitions
109 */
110
public function testColumndefWillAcceptAnArrayOfOptionsInPlaceOfAnOptionGroupName ($p_groupNames, $p_args, $p_expected) {
111
EmrsDoctrineMigrationBase::setColumnOptionGroups($p_groupNames);
112
$this->assertEquals($p_expected, call_user_func_array('EmrsDoctrineMigrationBase::columnDef', $p_args));
113 }
114
115 public function
mixedColumnDefinitions () {
116 return array(
117 array(
118 array(
'smallint' => array('type' => 'integer', 'length' => 2)),
119 array(
'smallint', array('unsigned' => true, 'notnull' => true)),
120 array(
'type' => 'integer', 'length' => 2, 'unsigned' => true, 'notnull' => true),
121 ),
122 );
123 }
124 }


Hope you find this useful.

October 23, 2008

Debugging Made Easier

Fact: you can make your life a whole lot easier, when debugging, simply by not making assumptions - by guessing, essentially - about what might be at fault. Concentrate on what you can see and what you know to be true - or what you can find out by directly querying the system. (Really, this applies to any kind of problem solving, in any walk of life.)

I often forget, and that's why I'm making a point to bring it up now. As a means of drumming it into my own thick head. Only last week, in a moment of weakness, I fell into the trap of assuming I knew the cause of a problem I was having. The curious thing is that when I discovered that my initial assumption was wrong, I went and replaced it with another, equally invalid one. And so my fate was sealed. I might just as well have packed it in then and there, but instead, I spent Lord-knows how long flitting aimlessly from one source-file to another. I was blinded by my assumptions: I couldn't see the wood for the trees. Worst of all, they were the wrong trees in the wrong forest.

I wonder, actually, how I came to find the source of the problem on that occasion. I'm guessing it was the result of a kind of brute-force attack on my subconscious: I must have fully memorized the entire section of code after the 80th reading, pieced it together somehow, and then - thank God - had an epiphany. I clearly remember, afterwards, thinking that I'd looked at the block of code that would have quickly led me to the fault, but because I thought I already knew what the cause was, I didn't take the time to really look at the lines in front of me. My mind was closed-off and wandering elsewhere. You don't want to know the awful feeling you get after wasting hours tracking down a bug that insignificant.

Even if you find yourself in the middle of such a predicament, there's still hope. Take a break. Give yourself 30 minutes, say, and if, at the end of that, you still haven't had any success, get a change of scenery. Better still, seek help from a colleague. There have been countless times when I've been in the middle of explaining a problem to my buddy, and I've spotted the cause. By explaining a problem, by breaking it down for another person to understand, you're forced to confront the facts. Sometimes that's all it takes. And if they can't help, at least you'll be in the right mindset.

And don't go thinking that it's safe to take a stab at the answer because a problem looks simple. Remember that one problem can often turn out to be the work of a whole gang of pesky little bugs. If you go into a fight like that without an open mind, you're gonna be in for a world of pain.

October 04, 2008

Implementing a Singleton Base Class in PHP 5.3

Introduction

To my mind, PHP 5.3 is the most exciting thing to happen to PHP since PHP 5 itself was released. There's plenty to say about the new features and the opportunities that are now available to us, but in this article I want to get down to some good ol' fashioned coding. I'll save the eulogising for another time.

At the heart of the code I'll shortly be introducing, is a new, seemingly innocuous function, get_called_class. On its own, it really doesn't do much. This simple little function, however, plugs a sizeable hole in PHP's object-oriented feature-set and makes it possible to write some really elegant code.

Just one of the things it allows us to do is build a complete, all singing, all dancing Singleton base-class that we can simply extend to create any number of fully functioning singletons.

Roughing-Out the Class

We can start by creating an abstract class – there won't ever be a need to get an instance of the Singleton class – with stubs for the main methods.

01 <?php
02
03
abstract class Singleton {
04
05 protected function
__construct() {
06 }
07
08 final public static function
getInstance() {
09 }
10
11 final private function
__clone() {
12 }
13 }


The first thing we've done is create a protected constructor: although we don't want our users creating instances directly, we do want subclasses of Singleton to be able to implement their own initialization code – so we can't make it private.

Next, we've got a public, static method, “getInstance”, that'll serve-up the single instance of a subclass. (“getInstance”, or simply “instance”, appear to be the preferred names for the instance getter in implementations of the singleton pattern, at least in PHP.) The method must be at class-level (i.e. static) because we'll be needing privileged access to the class' internals now that we've hidden the constructor from the outside world. getInstance is additionally marked “final” to prevent subclasses re-implementing it. (If there was a need to re-implement getInstance, which is small and well focused, it may be a sign that the subclass isn't actually a Singleton after all, or it may be that we need to expand our definition of the singleton pattern.)

Finally, we've created an empty, private implementation of the magic “__clone” to prevent anyone cloning an instance – using the keyword “clone”. If it were possible to clone an instance, the class could no longer claim to be a singleton and the integrity of our code could be in jeopardy. __clone is marked “final” only for completeness: since it is already private, subclasses won't be able to re-implement it anyway.

Solution

Now, let's take a look inside getInstance, the real meat of the pattern.

01 <?php
02
03
abstract class Singleton {
04
05 protected function
__construct() {
06 }
07
08 final public static function
getInstance() {
09 static
$aoInstance = array();
10
11
$calledClassName = get_called_class();
12
13 if (! isset (
$aoInstance[$calledClassName])) {
14
$aoInstance[$calledClassName] = new $calledClassName();
15 }
16
17 return
$aoInstance[$calledClassName];
18 }
19
20 final private function
__clone() {
21 }
22 }


The first thing you'll notice is an array of instances. If you remember, our objective is to create a class that we can just extend to create a working singleton: we don't want to have to implement any singleton-related code further down the class hierarchy. Since we want to inherit all functionality in subclasses, the getInstance method must store many single instances: one for each subclass of Singleton. Once we get round to actually storing instances, we'll key each of them on the name of the class to which they belong.

Now, to create and store an instance. Using PHP's extremely useful variable variables we can instantiate a class by name. The problem is we must first get hold of the name of the class to instantiate. Let's say we're gearing-up to create a DatabaseConnection subclass – the “Hello World!” of singleton examples. Once written, we want to be able to call DatabaseConnection::getInstance() to fetch the instance. We get the name of the class we're calling, the name in front of the two colons - “DatabaseConnection” in the case of the previous example - using the new function, get_called_class.

Testing the Class

All that remains is to introduce some tests to prove that Singleton does actually do what it says on the tin. Here's a script, written for SimpleTest, that tests two key aspects of our class.

01 <?php
02
03
//@todo Load SimpleTest
04
05 //@todo Load Singleton
06
07
class TestSingleton01 extends Singleton {
08
09 protected function
__construct() {
10 }
11 }
12
13 class
TestSingleton02 extends Singleton {
14
15 protected function
__construct() {
16 }
17 }
18
19 class
TestSingleton03 extends TestSingleton01 {
20
21 protected function
__construct() {
22 }
23 }
24
25 class
SingletonUnitTestCase extends UnitTestCase {
26
27 public function
testGetInstanceAlwaysReturnsSingleInstanceOfSubclass() {
28
$this->assertTrue (
29
TestSingleton01::getInstance() instanceof TestSingleton01
30
);
31
32
$this->assertReference (
33
TestSingleton01::getInstance(),
34
TestSingleton01::getInstance()
35 );
36 }
37
38 public function
testGetInstanceReturnsCorrectInstance() {
39
$this->assertTrue (
40
TestSingleton01::getInstance() instanceof TestSingleton01
41
);
42
43
$this->assertReference (
44
TestSingleton01::getInstance(),
45
TestSingleton01::getInstance()
46 );
47
48
$this->assertTrue (
49
TestSingleton02::getInstance() instanceof TestSingleton02
50
);
51
52
$this->assertReference (
53
TestSingleton02::getInstance(),
54
TestSingleton02::getInstance()
55 );
56
57
$this->assertTrue (
58
TestSingleton03::getInstance() instanceof TestSingleton03
59
);
60
61
$this->assertReference (
62
TestSingleton03::getInstance(),
63
TestSingleton03::getInstance()
64 );
65 }
66 }


The first test checks if getInstance always returns the same instance when called repeatedly against a subclass. The second test is almost certainly over the top, but ensures that no matter what, Singleton will always return the correct instance.