Creating a simple abstract model to reduce boilerplate code

Tags:

In a usual scenario involving models in PHP, you will have some repeating things: You will usually need getters and setters for the model’s properties, you will need to be able to construct the model from an SQL query or such, and you’ll also need to write SQL queries for the models.

While none of this is very complex, it’s something that you need to repeat all the time, and it’s not really something that actually does anything complex. So why not automate at least some of it?

Getters and setters

In this post we’ll look at creating a simple base model class that can be easily used to reduce the amount of boring code we need to write. The first thing is going to be getters and setters for the properties in a model.

If you use an IDE like Zend Studio or NetBeans, you will have the option of generating the code for this. The code will still exist, though, and you may have to tweak it if you change a name of a variable or such.

We can also use PHP’s magic __call method to automate this completely!

The key idea is that we shouldn’t need to write unnecessary code for this. I think the most optimal solution is using an array of keys and values: Each key is the name of the property, and each value is the default value of the corresponding property.

As such, we will need to define them in the class:

class UserModel extends AbstractModel {
  protected $_magicProperties = array(
    'id' => '',
    'firstName' => '',
    'lastName' => '',
    'password' => ''
  );
}

So there. We’re calling this array _magicProperties because we can also write some getters and setters ourselves – this array will only be a convenience for all the properties that won’t need more complex logic and can simply have very basic auto-generated setters.

Now, let’s look at the AbstractModel class:

abstract class AbstractModel {
  public function __call($method, $parameters) {
    //for this to be a setSomething or getSomething, the name has to have 
    //at least 4 chars as in, setX or getX
    if(strlen($method) < 4) 
      throw new Exception('Method does not exist');
 
    //take first 3 chars to determine if this is a get or set
    $prefix = substr($method, 0, 3);
 
    //take last chars and convert first char to lower to get required property
    $suffix = substr($method, 3);
    $suffix[0] = strtolower($suffix[0]);
 
    if($prefix == 'get') {
      if($this->_hasProperty($suffix) && count($parameters) == 0) 
        return $this->_magicProperties[$suffix];
      else
        throw new Exception('Getter does not exist');
    }
 
    if($prefix == 'set') {
      if($this->_hasProperty($suffix) && count($parameters) == 1)
        $this->_magicProperties[$suffix] = $parameters[0];
      else
        throw new Exception('Setter does not exist');
    }
  }
 
  private function _hasProperty($name) {
    return array_key_exists($name, $this->_magicProperties);
  }
}

So by just adding the __call and _hasProperty methods, we’ve just created code which will allow our models to have getters and setters for properties by just declaring a protected array _magicProperties, and by doing that we’ve greatly reduced the amount of code we need to write.

Using the class is easy, let’s see how:

$u = new UserModel();
 
//these are all provided by the __call method:
$u->setFirstName('Cole');
$u->setLastName('Train');
 
echo 'Name: ' . $u->getFirstName() . ' ' . $u->getLastName();

We could also implement one getter to the UserModel class to get the full name easily:

public function getFullName() {
  return $this->getFirstName() . ' ' . $this->getLastName();
}

We could also write a setter for the full name, but it might be a bit pointless.

Filling models from arrays

Another usual task is filling model properties from arrays. This is common for example when dealing with result rows from an SQL query.

We can add a method for this to the AbstractModel class:

public function fromArray(array $array) {
  foreach($array as $key => $value) {
    //We need to convert first char of key to upper to get the correct 
    //format required in the setter method name
    $property = $key;
    $property[0] = strtoupper($key);
 
    $mtd = 'set' . $property;
    $this->$mtd($value);
  }
}

We may also need the ability to turn an instance of the class into an array:

public function toArray() {
  return $this->_magicProperties;
}

In this case we simply need to return the _magicProperties array, as it already contains the properties of the model as a key-value array. You may also wish to override this method, in case your implemented model provides some more properties that don’t exist in the _magicProperties array.

Generating some SQL queries

By now we’ve reduced the amount of code we need to write to deal with typical model cases. There’s one more example we’ll look at: generating SQL queries for the models.

This is a very common task, and it can be automated easily, assuming your database structure is similar to the naming of your model properties.

SQL queries are code that shouldn’t necessarily live inside the model. It’s good practice to have a separate class which deals with database actions related to a specific model, but designing such is probably worth another blogpost so we’ll just look at automating the query generation part for now.

Generating SQL from the models is quite simple. We will just employ some code to convert the property names into column names, and column names back into property names. In essence, we are converting camel-case into underscore separated and converting underscore separated into camel-case. This is because database column naming usually uses underscores, unlike property naming.

The following code can be used to convert underscore separated into camel-case:

public function underscoreToCamelCase($row) {
  $convertedRow = array();
  //foo_bar -> fooBar
  foreach($row as $key => $value) {
    $parts = explode('_', $key);
    for($i = 1, $partCount = count($parts); $i < $partCount; $i++) {
      $parts[$i][0] = strtoupper($parts[$i][0]);
    }
 
    $fixedKey = implode('', $parts);
    $convertedRow[$fixedKey] = $value;
  }
 
  return $convertedRow;
}

And the following to convert camel-case into underscore separated:

public function camelCaseToUnderscore($row) {
  $convertedRow = array();
  foreach($row as $key => $value) {
    $newKey = strtolower(preg_replace('/([a-z])([A-Z])/','$1_$2',$key));
    $convertedRow[$newKey] = $value;
  }
 
  return $convertedRow;
}

So now, when you need to write an SQL query for a model, you can simply utilize these functions to save you from writing all the possible properties in the model.

You may also wish to look at Zend Framework’s Zend_Filter_Inflector classes. They are ready to use filters for converting strings to/from various formats, including the cases shown above.

Conclusion

By utilizing PHP’s “magic” features we can easily reduce the amount of code we need to write. I think this is something that sets PHP apart from some other languages without this kind of features.

What do you think of the approach shown in this post? Personally I think it’s quite useful, but it may be a bit confusing for people not familiar with the concept, as it looks like the classes have methods that actually don’t exist at all… (which is kind of true)

If you want ideas on how to use models like this with a database, have a look at my post about the Data Access Object pattern in PHP, which shows a good way to do this. You may also be interested in reading how to use models as criteria objects in database queries.