Specification Design Pattern in PHP - Explained!

Specification Design Pattern in PHP - Explained! Thumbnail image

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:

  1. class User
  2. {
  3.     public $is_admin = false;
  5.     public function __construct($is_admin=false)
  6.     {
  7.         $this->is_admin=$is_admin;
  8.     }
  9. }

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)

  1. if (is_admin($user)) { echo "[show delete button here]"; }

Let's write a specification class to check if $user is an admin user:

  1. class UserIsAdmin
  2. {
  3.     public function checkUserIsAdmin(User $user)
  4.     {
  5.         if ($user->is_admin) {
  6.             return true;
  7.         }
  8.         return false;
  9.     }
  10. }

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:

  1. $user_is_admin_spec = new UserIsAdmin;
  2. if ($user_is_admin_spec->checkUserIsAdmin($user)) {
  3.     echo "[show delete button here]";
  4. }

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:

  1.       Schema::create('staff_members', function (Blueprint $table) {
  2.             $table->string("last_name")->nullable();
  3.             $table->boolean("is_manager")->default(false);
  4.             $table->timestamps();
  5.         });

The StaffMember model:
  1. class StaffMember extends \Illuminate\Database\Eloquent\Model
  2. {
  3.     public $fillable = [
  4.         "last_name",
  5.         "is_manager",
  6.         ];
  7. }

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)

  1. class StaffRepository
  2. {
  3.     public function all()
  4.     {
  5.         return StaffMember::all();
  6.     }
  8.     public function matchingSpec(StaffSpecification $spec)
  9.     {  
  10.         return $spec->asQuery(StaffMember::query())->get();
  11.     }
  12. }

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
  1. interface StaffSpecification
  2. {
  3.     public function staffMatches(StaffMember $staffMember);
  5.     public function asQuery($query);
  6. }

And two specifications - one for managers, and the opposite for staff members who are not managers.

Both of these specs are very simple.

  1. class IsManagerSpecification implements StaffSpecification
  2. {
  3.     public function staffMatches(StaffMember $staffMember)
  4.     {
  5.         if ($staffMember->is_manager) {
  6.             return true;
  7.         }
  8.         return false;
  9.     }
  11.     public function asQuery($query)
  12.     {
  13.         return $query->where("is_manager", true);
  14.     }
  15. }
  17. class IsNormalStaffSpecification implements StaffSpecification
  18. {
  19.     public function staffMatches(StaffMember $staffMember)
  20.     {
  21.         if ($staffMember->is_manager) {
  22.             return false;
  23.         }
  24.         return true;
  25.     }
  27.     public function asQuery($query)
  28.     {
  29.         return $query->where("is_manager", false);
  30.     }
  32. }

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.

  1. $normal_staff_1 = StaffMember::create(['last_name'=>"Normal1"]);
  2. $normal_staff_2 = StaffMember::create(['last_name'=>"Normal2"]);
  3. $normal_staff_3 = StaffMember::create(['last_name'=>"Normal3"]);
  4. $normal_staff_4 = StaffMember::create(['last_name'=>"Normal4"]);
  5. $manager_1 = StaffMember::create(['last_name'=>"Manager1", 'is_manager'=>true]);
  6. $manager_2 = StaffMember::create(['last_name'=>"Manager2", 'is_manager'=>true]);

Now if we do the following:

  1. 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:
  1. $spec = new IsManagerSpecification();
  2. $repo = new StaffRepository();
  4. $all_managers = $repo->matchingSpec($spec);
  6. 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
  1. $spec = new IsManagerSpecification();
  3. $staff = StaffMember::first(); // get the first StaffMember row from database
  4. $is_manager = $spec->staffMatches($staff); // does $staff match the specification?
  5. 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.

Subscribe to my spam free newsletter for other Laravel and Vue updates like this

I never spam, and only email when I have a good in-depth post published on my site (mostly about Laravel and Vue). You can also follow me on social media to get updates.

webdevetc profile pic

I've been working as a software developer for many years (with the last few years specialising in Laravel and Vue). I mostly write about PHP and JavaScript/TypeScript on this site. Contact me here. Need to hire a contract software developer in London, UK (or freelance)? Contact me and check my availability.

Leave a Comment