Posts Tagged ‘initialisation’

Object initialisation with PHP5

Wednesday, December 12th, 2007

Frequently we’ll write PHP classes for objects which need to be both listed on a page, and have their own detail page. I’ve seen lots of methods used, each with their own pros and cons. In this article I talk about my personal choice and the factors that influenced that choice. This article refers to PHP5.

An overview of the problem

I’m sure you’ve all done this before, you have your “user-list.php” page which lists all the users in the system then you have your “user-detail.php” page which allows the editing of a specific user object. Now, your User object may have a constructor defined like so:

  1. <?php
  2. class User {
  3.     function __construct($id = 0) {
  4.          /* connect to db */
  5.         $results = $db->query(
  6.             SELECT `id`, `name`, `email`, `live`
  7.             FROM `users`
  8.             WHERE `id` = ‘ . $id
  9.         );
  10.  
  11.         /*
  12.          * Put table data in to class vars for
  13.          * use in getter/setter functions.
  14.          */
  15.     }
  16. }
  17. ?>

So your $id is the record id in the “users” table, probably passed in as a GET variable from the listing page. So where are the issues here? Well what happens when you want to list all of the records on the listing page? You’ll might do something like the following (yes, I’ve done this before):

  1. <?php
  2. /* connect to db */
  3.  
  4. $results = $db->query(
  5.     SELECT `id`
  6.     FROM `users`
  7. );
  8.  
  9. while ($row = $results->fetch_row())
  10. {
  11.     $user = new User($row[0]);
  12.  
  13.     /* echo out the details for this user here */
  14. }
  15. ?>

So you select all the id values for the users in your system, pass them in to your User object which then does a lookup for the record in the “users” table relating to the id passed in - and if you’re displaying 50 users on the page that’s 50 SELECT statements per hit (assuming you’re not caching the page or data). You may be thinking “why bother creating an object when all you need is the data?” and fair point, if that’s the case then you wouldn’t need to instantiate a User object, but for the sake of this example lets assume that the User object contains some business logic unknown to the coder implementing this particular page and therefore a User object must be instantiated rather than outputting the raw table data directly.

So the problem we face is efficiently using the data we’ve already queried from the database in a manageable and consistent way?

Option 1: User constructor can accept an integer or an array

We could allow the user to pass in an integer record ID or an array of name/value pairs which map to User member variables. It might look something like this:

  1. <?php
  2. class User {
  3.     function __construct($idOrProperties = 0) {
  4.         if (is_array($idOrProperties)) {
  5.             /*
  6.              * Extract the data from the array and
  7.              * use it to populate class members.
  8.              */
  9.         }
  10.         else {
  11.             /*
  12.              * Record ID was passed in so query the
  13.              * database and get the required data.
  14.              */
  15.         }
  16.     }
  17. }
  18. ?>

That’d work quite nicely, however there’s two things that concerns me about this code. The first is the extensibility. If all of a sudden we’re to start accepting data from say a SimpleXML object the the constructor starts to grow:

  1. <?php
  2. class User {
  3.     function __construct($arg = 0) {
  4.         if (is_array($arg)) {
  5.             /*
  6.              * Extract the data from the array and
  7.              * use it to populate class members.
  8.              */
  9.         }
  10.         elseif ($arg instanceof SimpleXMLElement){
  11.             /*
  12.              * SimpleXMLElement was passed in so
  13.              * extract the data from that.
  14.              */
  15.         }
  16.         else {
  17.             /*
  18.              * Record ID was passed in so query the
  19.              * database and get the required data.
  20.              */
  21.         }
  22.     }
  23. }
  24. ?>

Ok so the input variable name could be a little prettier but you get my point. Each of those conditional blocks is extracting data from quite different sources with the same end result - several basic pieces of data relating to the user. Can you can imagine the phpdoc comment you’re going to need to write to explain this function?! One further crucial issue is the case where you want to instatiate with two different SimpleXMLObjects which contain different data formats which aren’t related?

Option 2: Static functions to create the User object

Using this method, the coder/implementer never constructs the User object directly, in fact they can’t because the constructor is made private. Instead of a constructor we use static methods, each method accepting one single format of data, however they all have the same end result, they construct and return a User object (static functions can instantiate an object of the class they belong too even if the constructor is private). The constructor is now altered to accept accept an array of name/value pairs and nothing else, this keeps the constructor definition clean and simple and free of the details of extracting from various data sources. Because the constructor is private and the static functions are apart of the User class we can rely on those static functions to pass in safe, reliable data.

This is how our new code might look:

  1. <?php
  2. class User {
  3.     private function __construct($properties = null) {
  4.         /*
  5.          * Extract the data from the array and
  6.          * use it to populate class members.
  7.          */
  8.     }
  9.  
  10.     public static function fromId($id) {
  11.         /*
  12.          * Lookup the record in the user table
  13.          * and popuplate populate the $data array
  14.          * with the appropriate data.
  15.          */
  16.  
  17.         return new User($data);
  18.     }
  19.  
  20.     public static function fromArray(array $properties) {
  21.         /*
  22.          * Extract the data from the array and
  23.          * use it to populate $data array. It may
  24.          * not need any manipulation at all infact.
  25.          */
  26.  
  27.         return new User($data);
  28.     }
  29.  
  30.     public static function fromXML(SimpleXMLElement $xml) {
  31.         /*
  32.          * Extract the data from the XML object
  33.          * and use it to populate $data array.
  34.          */
  35.  
  36.         return new User($data);
  37.     }
  38. }
  39. ?>

So we’ve taken care of the problem of all of our different data sources, we now have another problem however, how do we construct a new object without any data? My first thought would be to create another static function, lets call if “fromNew”:

  1. <?php
  2. class User {
  3.     private function __construct($properties = null) {
  4.         /*
  5.          * Extract the data from the array and
  6.          * use it to populate class members.
  7.          */
  8.     }
  9.  
  10.     /*
  11.      * All the other functions here.
  12.      */
  13.  
  14.     public static function fromNew() {
  15.         /*
  16.          * Create $data array but set the
  17.          * values to blank/zero etc as required
  18.          */
  19.  
  20.         return new User($data);
  21.     }
  22. }
  23. ?>

So using User::fromNew() will create a new instnance of the User object with all it’s variables initialised to their defaults. I personally don’t like the idea of a “fromNew()” function to create a new instance of the class, afterall that’s what the “new” keyword is for. So how can we use the “new” keyword to create a User class rather than all these static function?

Option 3: Static functions to return array to pass to constructor

The third optional is to use static functions which return an array which is then passed in to the constructor. The advantage with this method is that we can now instantiate a new User object using the “new” keyword. The disadvantage is that the syntax starts looking a little messy and bloated, it also relies on the user understanding that an object must be instantiated from a static method. Here’s how it might look:

  1. <?php
  2. class User {
  3.     function __construct($properties = null) {
  4.         /*
  5.          * Extract the data from the array and
  6.          * use it to populate class members.
  7.          */
  8.     }
  9.  
  10.     public static function fromId($id) {
  11.         /*
  12.          * Lookup the record in the user table
  13.          * and popuplate populate the $data array
  14.          * with the appropriate data.
  15.          */
  16.  
  17.         return $data;
  18.     }
  19.  
  20.     public static function fromArray(array $properties) {
  21.         /*
  22.          * Extract the data from the array and
  23.          * use it to populate $data array. It may
  24.          * not need any manipulation at all infact.
  25.          */
  26.  
  27.         return $data;
  28.     }
  29.  
  30.     public static function fromXML(SimpleXMLElement $xml) {
  31.         /*
  32.          * Extract the data from the XML object
  33.          * and use it to populate $data array.
  34.          */
  35.  
  36.         return $data;
  37.     }
  38. }
  39. ?>

And then to use it you might do the following:

  1. <?php
  2. include(‘class.User.php’);
  3.  
  4. /*
  5.  * Create a new user from an ID
  6.  */
  7. $userA = new User(User::fromId(5));
  8.  
  9. /*
  10.  * Create a new user from an XML object
  11.  */
  12. $userB = new User(User::fromXML($simpleXMLElement));
  13. ?>

A little over complicated don’t you think? Is there a cleaner and better way?

Option 4: Static function called from within the constructor

We’re almost going full circle with this one. It’s very similar to Option 1 with the exception that the the static functions are called from the constructor depending on the type of data sent in, i.e.:

  1. <?php
  2. class User {
  3.     function __construct($arg = null) {
  4.         if (is_array($arg){
  5.             $data = self::fromArray($arg);
  6.         }
  7.         elseif ($arg instanceof SimpleXMLElement) {
  8.             $data = self::fromXML($arg);
  9.         }
  10.         else {
  11.             $data = self::fromId($arg);
  12.         }
  13.  
  14.         /*
  15.          * Use the values from $data array to
  16.          * populate the object members.
  17.          */
  18.     }
  19.  
  20.     public static function fromId($id) {
  21.         /*
  22.          * Lookup the record in the user table
  23.          * and popuplate populate the $data array
  24.          * with the appropriate data.
  25.          */
  26.  
  27.         return $data;
  28.     }
  29.  
  30.     public static function fromArray(array $properties) {
  31.         /*
  32.          * Extract the data from the array and
  33.          * use it to populate $data array. It may
  34.          * not need any manipulation at all infact.
  35.          */
  36.  
  37.         return $data;
  38.     }
  39.  
  40.     public static function fromXML(SimpleXMLElement $xml) {
  41.         /*
  42.          * Extract the data from the XML object
  43.          * and use it to populate $data array.
  44.          */
  45.  
  46.         return $data;
  47.     }
  48. }
  49. ?>

Unfortunately this sports all the same problems as Option 1, all we’re doing is tidying things up a little by using static functions rather than embedding all the logic in the constructor.

Option 5: Tuned for integer and array to constructor

With this option we determine the type of the constructor argument which must be either an integer which represents the record ID, or it’s an array of User data. I think it’s a reasonable trade-off between complexity of implementation and ease of extensibility

This option has the following features:

  1. Allows instantiation with a record ID
  2. Allows instantiation with an array
  3. Is extensible by using static functions

Here’s the implementation:

  1. <?php
  2. class User {
  3.     function __construct($arg = null) {
  4.         if (is_integer($arg)){
  5.             $data = self::fromId($arg);
  6.         }
  7.         else {
  8.             $data = $arg;
  9.         }
  10.  
  11.         /*
  12.          * Use the values from $data array to
  13.          * populate the object members.
  14.          */
  15.     }
  16.  
  17.     public static function fromId($id) {
  18.         /*
  19.          * Lookup the record in the user table
  20.          * and popuplate populate the $data array
  21.          * with the appropriate data.
  22.          */
  23.  
  24.         return $data;
  25.     }
  26.  
  27.     public static function fromXML(SimpleXMLElement $xml) {
  28.         /*
  29.          * Extract the data from the XML object
  30.          * and use it to populate $data array.
  31.          */
  32.  
  33.         return $data;
  34.     }
  35. }
  36. ?>

An implementation might look something like this:

  1. <?php
  2. include(‘class.User.php’);
  3.  
  4. /*
  5.  * Create a new user from an ID
  6.  */
  7. $userA = new User(5);
  8.  
  9. /*
  10.  * Create an array of new User objects
  11.  * from a SELECT on the entire users table
  12.  */
  13.  
  14. $results = $db->query(
  15.     SELECT `id`, `name`, `email`, `live`
  16.     FROM `users`
  17. );
  18.  
  19. while ($row = $results->fetch_assoc())
  20. {
  21.     $user = new User($row);
  22.  
  23.     /* do stuff to the user */
  24. }
  25.  
  26. /*
  27.  * Create a new user from an XML object
  28.  */
  29. $userB = new User(User::fromXML($simpleXMLElement));
  30. ?>

It’s very important to define and document the name/value pairs and their types for the array that’s passed in to the User constructor. That way another coder can easily add in further static functions to retrieve data from other sources and pass return them according to your well defined array definition.

There’s one major flaw with this method - it breaks inheritance!

If we were to extend User with a class called AdminUser it might look something like the following, bear in mind I’m focusing purely on the fromXML() function in this example:

  1. <?php
  2. class AdminUser extends User {
  3.     function __construct($arg = null) {
  4.  
  5.         /*
  6.          * Construct the parent object
  7.          */
  8.         parent::__construct($arg);
  9.     }
  10.  
  11.     public static function fromXML($xml) {
  12.         /*
  13.          * Extract the data from the XML object
  14.          * and use it to populate $data array.
  15.          */
  16.  
  17.         /*
  18.          * Now call the same static function
  19.          * on the parent class (User)
  20.          */
  21.         $userData = User::fromXML($xml);
  22.  
  23.         /*
  24.          * Merge the base class and sub
  25.          * class data arrays.
  26.          */
  27.         $data = array_merge($userData, $data);
  28.  
  29.         return $data;
  30.     }
  31. }
  32.  
  33. /*
  34.  * Now instantiate an AdminUser with a
  35.  * SimpleXMLElement object.
  36.  */
  37. $a = new AdminUser(AdminUser::fromXML($simpleXMLElement));
  38. ?>

The problem with this is that it increases the coupling between base and sub-class, this is potentially dangerous. For example, there is nothing to stop User::fromXML() returning an array with an item which has the same name as one returned from AdminUser::fromXML(). If this did happen then if it’s coded as per my example then the duplicate name item from $userData would be overwritten by the duplicate name item in $data, such is the functionality of array_merge(). This would no doubt cause all sorts of problems. Next we look at a possible solution to these issues.

Option 6: Adding a type parameter to the constructor

The final option, my method of choice, determines what to do based on a type parameter passed in to the constructor. This type parameter may be defined as a static class constant, it’s also optional and defaults to a mode which still determines what to do based on the data type passed in. If we stick with the AdminUser class, it now looks like this:

  1. <?php
  2. class AdminUser extends User {
  3.     const DETERMINE = 0;
  4.  
  5.     function __construct($value = null, $type = self::DETERMINE) {
  6.  
  7.         if ($type == self::DETERMINE) {
  8.             /*
  9.              * Determine if $value is an int or an
  10.              * array. If it’s an int call AdminUser::fromId
  11.              * otherwise it must be an array in the
  12.              * required format.
  13.              */
  14.             if (is_integer($value)) {
  15.                 $data = self::fromId($value);
  16.             }
  17.             else {
  18.                 $data = $value;
  19.             }
  20.         }
  21.         elseif ($type == self::XML) {
  22.             $data = self::fromXML($value);
  23.         }
  24.  
  25.         /*
  26.          * Construct the parent object
  27.          */
  28.         parent::__construct($value, $type);
  29.     }
  30.  
  31.     public static function fromXML($xml) {
  32.         /*
  33.          * Extract the data from the XML object
  34.          * and use it to populate $data array.
  35.          */
  36.  
  37.         return $data;
  38.     }
  39. }
  40. ?>

As you can see the value and type parameters are passed straight through to the parent constructor. Yes, there’s going to be duplication of code between the base class and sub-class in terms of the conditional statements to determine which static function to use to extract the data from the $value variable but I don’t personally consider that too much of an issue given that we now have a solution which:

  1. Supports inheritance
  2. Allows initialisation with “new” keyword and no args to constructor
  3. Allows initialisation with a record ID
  4. Allows initialisation with an array from a SELECT query
  5. Is very exstensible

Here’s a snippet of how the User class might be used:

  1. <?php
  2. /*
  3.  * New blank user class.
  4.  */
  5. $user = new User();
  6.  
  7. /*
  8.  * Initialise from a record ID, the
  9.  * the most likely form of construction.
  10.  */
  11. $user = new User(4);
  12.  
  13. /*
  14.  * Initialise from an array generated
  15.  * from a sql SELECT statement.
  16.  */
  17. $results = $db->query(
  18.     SELECT `id`, `name`, `email`, `live`
  19.     FROM `users`
  20. );
  21.  
  22. while ($row = $results->fetch_assoc())
  23. {
  24.     $user = new User($row);
  25.  
  26.     /* echo out the details for this user here */
  27. }
  28.  
  29. /*
  30.  * Initialise with a SimpleXMLElement object.
  31.  */
  32. $user = new User($xmlObject, User::XML);
  33. ?>

In reality this is again very similar to Option 1. The difference is that we’ve put the data extraction functions in to separate static methods meaning more maintainable and readable code, we’ve also added in the type parameter too to make it more extensible. The disadvantage of this method is perhaps that the interface is a little confusing to the user as it could accep any type of data depending on the type parameter, however I’m sure with clear and thorough documentation this won’t be an issue.

I’d really like to hear your thoughts on any of the above, especially how you tackle the scenario I outlined at the start.

Note that none of the code above is tested so I expect I’ll get a whole host of posts pointing out my syntax errors ;)

James.