Amazon.com Widgets

Using Introspection in PHP

One of the things we love about Java is its ability to use introspection. Don’t know what methods or field an object has? Just ask it.

It turns out that PHP provides some nifty introspection features as well. We’ve found one to be particularly useful: the ability of a PHP object to tell us its member variables at runtime. Not sure what that means? Let’s look at an example.

Suppose you’re building a web-enabled content manager (who isn’t?). You want to provide a set of web forms that let your client enter and manage information that gets stored in a database. Further, let’s assume you want to manage several different types of objects that are relavent to your client’s busienss in the database - say, for example, Leads, Events, Stories, Links, Product Reviews, and Blog Entries (maybe you’ve got a demanding client).

In this example, we’re not going to worry about the web forms, nor are we going to worry about the database. I assume you know how to build web pages, and that you know how to read and write data with a database. Instead, we’re going to focus on the business objects that ferry data from the web form to the database, and from the database to the web form. One of the key tasks you’re going to find yourself performing again and again is populating these business objects with data, either from the web form, or from the results of a database lookup.

So, how can introspection help? First, let’s build our base class for our business objects. Let’s call this BusinessObject, and let’s give it the ability to populate itself from an arbitrary array.


  1. class BusinessObject
  2. {
  3. function populateFromArray($someArray)
  4. {
  5. $fieldList = get_class_vars($this);
  6. foreach ($fieldList as $key => $value)
  7. {
  8. $this->$key = $someArray[$key];
  9. }
  10. }
  11.  
  12. }

There it is. The call to get_class_vars($this) returns a list of all the member variables of the current object. We then loop through the list and assign the value of each element ($someArray[$key]) to the matching member variable ($this->$key). So, at this point, any subclass of BusinessObject will inherit the ability to populate itself from a PHP associative array. But what good does that do?

The answer lies in the fact that many things of interest to PHP developers arrive as arrays. Let’s add a couple more methods to our base class to illustrate:

  1. class BusinessObject
  2. {
  3. function populateFromArray($someArray)
  4. {
  5. $fieldList = get_class_vars($this);
  6. foreach ($fieldList as $key => $value)
  7. {
  8. $this->$key = $someArray[$key];
  9. }
  10. }
  11.  
  12. function populateFromRequest()
  13. {
  14. $this->populateFromArray( $_REQUEST );
  15. }
  16. function populateByPrimaryKey($id)
  17. {
  18. $tableName = get_class($this);
  19. $pkField = $this->getPrimaryKeyFieldName();
  20. // Assume the PKs are numeric
  21. $sql = “select * from $tableName where $pkField = $id”;
  22. $result = mysql_query($sql);
  23. $row = mysql_fetch_array($result);
  24. if (isset($row))
  25. {
  26. $this->populateFromArray( $row );
  27. }
  28. }
  29. }

Now we have an BusinessObject that provides all its subclasses with the ability to populate themselves from an HTTP request, as well as from a database when given the primary key. Some things to note here: for simplicity, we assume that we’ve essentially followed Fowler’s Active Record pattern. In a real system, you may want to put the database access calls into a finder object (as per Fowler’s Row Data Gateway pattern). This wouldn’t change the code much - your finder would call the BusinessObject.populateFromArray() method in your subclass with the row instead of having the subclass do it itself.

Also - note that the introspective call: $tableName = get_class($this) in the populateByPrimaryKey method implicitly assumes that the name of your database table is exactly the same as the name of your BusinessObject subclass - so records for the Article BusinessObject are stored in an Article table, etc.

Finally, we’ve also assumed that member variables in your BusinessObject classes have the same names as the database fields - as well as the html form elements that are going to be used to populate them. This may not be realistic for your system, but it does add a nice level of predictability to your code as you won’t have to guess what the field mapping from form to object to database will look like.

What are the caveats? First - you may notice that little call to getPrimaryKeyFieldName(). You have to specify a name for your primary key field. OK - that’s not so bad. What might be bad is if, while looping through $someArray in the populateByArray() method, you don’t want to overwrite some fields - even if they’re not in the array. In this implementation, you could erase your own primary key in your BusinessObject if it’s not present in the list of fields in a web request.


To solve these problems, you can make the following adjustments to BusinessObject:

  1. class BusinessObject
  2. {
  3.  
  4. function populateFromArray($someArray)
  5. {
  6. $fieldList = getFieldList();
  7. foreach ($fieldList as $key => $value)
  8. {
  9. $this->$key = $someArray[$key];
  10. }
  11. }
  12.  
  13. function getPrimaryKeyFieldName()
  14. {
  15. return $this->primaryKeyFieldName;
  16. }
  17.  
  18. function getFieldList()
  19. {
  20. $allFields = get_class_vars($this);
  21. $excludeFields = $this->excludeFields;
  22. return array_merge(array_diff( $allFields, $excludeFields));
  23. }
  24.  
  25.  
  26.  
  27. }

Now, what we’ve done is placed a requirement for you to define 2 things in your BusinessObject subclasses: a field containing the name of the primary key, and an array of fields to exclude from automatic population. This lets you get around the problems associated with erasing metadata or other private data that you might want to keep track of - or may want to populate or calculate in some other manner.

PHPDeveloper.org said,

June 27, 2006 @ 12:14 pm

Springboard Software Blog: Using Introspection in PHP

RSS feed for comments on this post · TrackBack URI


Leave a Comment