Specification Design Pattern in PHP Table of contents
The Specification Design Pattern isn't too commonly used, in my opinion. But if you have some complicated business rules that you need to implement then it can be the perfect solution to keeping things organised.
Very basic example of the specification design pattern
to begin with, I'll show a really simplified example to get the basic idea of the specification pattern across
Let's pretend you have a User type, and one (or some) of these users are admin users.
Your app needs a way to decide if a user is an admin user.
Your user model:
<?php
class User
{
public $is_admin = false;
public function __construct($is_admin=false)
{
$this->is_admin=$is_admin;
}
}
This is a very simple model. The $is_admin
property is either true or false.
Now let's say that you are showing a product on your website. If the current logged in user (which is $user) is an admin, then you should show the delete button. Something like this:
So the code in our view file could look something like this... (semi pseudo code at the moment)
<?php
if (is_admin($user)) { echo "[show delete button here]"; }
Let's write a specification class to check if $user is an admin user:
<?php
class UserIsAdmin
{
public function checkUserIsAdmin(User $user)
{
if ($user->is_admin) {
return true;
}
return false;
}
}
This is a stupidly basic example. In the real world for something like this it would normally be better to just add a is_admin()
method to User
. But this is a simplified example to help with this explanation...
Now, the code in our view file could be:
<?php
$user_is_admin_spec = new UserIsAdmin;
if ($user_is_admin_spec->checkUserIsAdmin($user)) {
echo "[show delete button here]";
}
Again, this is simplified for the purposes of explaining the concept of the design pattern. It isn't really a good idea to be instantiating classes like that in your view files... And again, for this simple example it would be better to add is_admin()
directly on the model class (User).
This is basically what the specification pattern is about.
Using the specification design pattern with things such as databases
Let's expand this example to something a tiny bit likely to be used in the real world.
In the next example we will have a StaffMember class. Some StaffMember objects will be managers. Again, we will use a simple boolean value to check if a StaffMember is a manager.
This time we will use Eloquent (from Laravel), just to help with database operations. (This should be easy to follow even if you aren't familiar with Eloquent).
Setting things up - the staff_members database table
The simple database migration file looks like this:
<?php
Schema::create('staff_members', function (Blueprint $table) {
$table->string("last_name")->nullable();
$table->boolean("is_manager")->default(false);
$table->timestamps();
});
The StaffMember model:
<?php
class StaffMember extends \Illuminate\Database\Eloquent\Model
{
public $fillable = [
"last_name",
"is_manager",
];
}
A StaffMember repository
For this example we will use a repository. If you don't know what that is, please see my page here explaining what the repository design pattern is.
We want two functions here - one to get all staff members, and one to return any that match a specification (which we will define in a sec)
<?php
class StaffRepository
{
public function all()
{
return StaffMember::all();
}
public function matchingSpec(StaffSpecification $spec)
{
return $spec->asQuery(StaffMember::query())->get();
}
}
StaffSpecification interface
We have a simple interface for the specification. There are two methods:
-
staffMatches(StaffMember $staffMember)
which returns true if $staffMember matches the specification. - and
asQuery()
which will help create the database query to match only those that fit the specification
<?php
interface StaffSpecification
{
public function staffMatches(StaffMember $staffMember);
public function asQuery($query);
}
And two specifications - one for managers, and the opposite for staff members who are not managers.
Both of these specs are very simple.
<?php
class IsManagerSpecification implements StaffSpecification
{
public function staffMatches(StaffMember $staffMember)
{
if ($staffMember->is_manager) {
return true;
}
return false;
}
public function asQuery($query)
{
return $query->where("is_manager", true);
}
}
class IsNormalStaffSpecification implements StaffSpecification
{
public function staffMatches(StaffMember $staffMember)
{
if ($staffMember->is_manager) {
return false;
}
return true;
}
public function asQuery($query)
{
return $query->where("is_manager", false);
}
}
Time to create some StaffMembers
This code will create 4 normal staff members and two managers.
All will be saved to the database so we can use it in the rest of this explanation of the specification pattern in php.
<?php
$normal_staff_1 = StaffMember::create(['last_name'=>"Normal1"]);
$normal_staff_2 = StaffMember::create(['last_name'=>"Normal2"]);
$normal_staff_3 = StaffMember::create(['last_name'=>"Normal3"]);
$normal_staff_4 = StaffMember::create(['last_name'=>"Normal4"]);
$manager_1 = StaffMember::create(['last_name'=>"Manager1", 'is_manager'=>true]);
$manager_2 = StaffMember::create(['last_name'=>"Manager2", 'is_manager'=>true]);
Now if we do the following:
<?php
var_dump((new StaffRepository())->all());
You will see the 6 staff, from the database.
Back to the specification design pattern...
How to get all managers from the database, using our IsManagerSpecification
specification:
<?php
$spec = new IsManagerSpecification();
$repo = new StaffRepository();
$all_managers = $repo->matchingSpec($spec);
var_dump($all_managers);
The above code will output the two managers.
How to check if a user is a manager, using the specification class
<?php
$spec = new IsManagerSpecification();
$staff = StaffMember::first(); // get the first StaffMember row from database
$is_manager = $spec->staffMatches($staff); // does $staff match the specification?
var_dump($is_manager);
In this case, it will return false, as the first row in the database is a normal user.
When to use the specification design pattern
The two main examples above were very simple. In the real world. you might have some complicated logic when it comes to selecting certain rows from the database. It can often be fine to put the code inside the model (for deciding is a row matches the rules you have) or model repo (for selecting rows from the database which match your rules). However, by using the specification design pattern you can keep things organised once you end up with many different rules and specs.
Comments →Specification Design Pattern in PHP