Introduction
Most PHP developers utilizing MVC frameworks would argue that the best approach for models are classes with a varied number of getters and setters. After a long time working with PHP and especially MVC frameworks and data mining/retrieval, I am of a completely different opinion and I will discuss why.
Heavy Models
First let’s start with the traditional approach which I will refer to as a “heavy model”. Consider the following:
class Person {
private $id = 0;
private $first_name = null;
private $last_name = null;
public function setId($id){
$this->id = $id;
}
public function getId(){
return $this->id;
}
public function setFirstName($first_name){
$this->first_name = $first_name;
}
public function getFirstName(){
return $this->first_name;
}
public function setLastName($last_name){
$this->last_name = $last_name;
}
public function getLastName(){
return $this->last_name;
}
}
Admittedly, this is a very rudimentary data model, however it does follow the standard approach for most MVC frameworks with regard to model layout and corresponding getters and setters for privatized properties. Its usage might then follow something along the lines of:
$p = new Person();
$p->setId(1);
$p->setFirstName('Bob');
$p->setLastName('Dobalina');
Usability wise, this above approach is very clean and makes perfect sense from an object oriented model approach even if it is rather remedial. However, having been living in PHP for quite some time now I have two problems with this approach. The first comes from data retrieval, or to be more precise, retrieving data from a database and populating models for use. The other is flexibility/code maintenance. For example, let’s say we’re using PDO to connect to a MySQL database:
$conn = /* get a database connection */;
$sql = 'SELECT * FROM person';
$stmt = $conn->prepare($sql);
$stmt->execute();
$people = array();
while($result = $stmt->fetch(PDO::FETCH_ASSOC)){
$p = new Person();
$p->setId($result['id']);
$p->setFirstName($result['first_name']);
$p->setLastName($result['last_name']);
$people[] = $p;
}
So if we examine the above we see an array of Person objects being populated from a database result set. Now ignoring the semantics of the above, this is a pretty common way to retrieve and populate models. Sure there are different ways, methods of the model, etc. But in essence they are all pretty much performing this kind of loop somewhere. What if I told you there was a more efficient way to do the above, that executed faster, was more flexible and required less code?
Light Models
Now consider this example, a modification to the above model which I will refer to as “light model”.
class Person {
public $id = 0;
public $first_name = null;
public $last_name = null;
}
Now I know a lot of developers who see this are currently cringing, just stay with me for a minute. The above acts more like a structure than the traditional model, but it has quite a few advantages. Let me demonstrate with the following data mining code:
$conn = /* get a database connection */;
$sql = 'SELECT * FROM person';
$stmt = $conn->prepare($sql);
$stmt->execute();
$people = array();
while($result = $stmt->fetch(PDO::FETCH_ASSOC)){
$p = new Person();
foreach($result as $field_name=>$field_value)
$p->{$field_name} = $field_value;
$people[] = $p;
}
If you’re unfamiliar with the foreach notation within the while loop, all it is doing is dynamically using the result set name and value to populate the model’s respective matching property. Here’s why I find the light model a much better practice especially when combined with the above while and foreach mining pattern. Firstly, the light model will populate faster with a smaller execution time being that no functions are being invoked. Each function call of the heavy model takes an additional hit performance wise, this can be validated quite easily using time stamps and loops. Secondly, the second mining example allows us to paint our models with all the values being mined out of the database directly, which means going forward if the table changes, only the properties of the model change, the data mining pattern will still work with no code changing. If the database changed on the previous heavy model example, both the model and all mining procedures would have to be updated with the new result field names and respective set methods or at the very least, updated in some model method.
Business Objects
Finally to come full circle, what about the CRUD methods which are usually attached to the models such as save()
or get()
, etc? Instead of creating instance methods of models which have the overhead of object instantiation, how about static methods of like objects which would be termed “business objects”. For example:
class PersonBO{
public static function save($person){
/* do save here */
}
public static function get($person){
/* do get here */
}
}
This example performs the same functionality that is usually attached to the model however, it makes use of static methods and executes faster with less overhead than its heavy model counterpart. This adds an additional layer of abstraction between the model and its functional business object counterpart and lends itself to clean and easy code maintainability.
Conclusion
In summary, the light models used in conjunction with the data mining pattern demonstrated above reduce quite a bit of retrieval code and add to the codebase’s overall flexibility. This pattern is currently in use within several very large enterprise codebases and have been nothing but a pleasure to work with.
Special thanks to Robert McFrazier for his assistance in developing this pattern.
1 Comment
Peter · March 2, 2015 at 12:05 am
A wonderful discussion about how patterns change if they evolve to become more generic. Thanks a lot for this.
I beliefe there cannot be a judgement about “right” or “wrong” about a piece of code, it totally depends on what happens next to the code. So we end up in some kind of “scenario thinking”.
In your “scenario”, the class Person should become more generic as an efficient transportation object between the database and the controller. In an other “scenario”, the class Person could be required to be more intelligent. E.g. if you want to calculate an insurance rate for a person, you may want to hide the calculation behind a getter method of person, so this has nothing to do with database field name matching. In a third scenario, the implicit name matching between database fields and code may become an obstacle, think of adopting the code to a given database. It might then be wise to get the field names from an XML file.
Your discussion is really inspiring to me 🙂