I must admit, my recent articles are becoming a bit obsessed around the repository pattern. What can I say, I like it, it’s useful, and it’s not restrictive based on a language or a framework.
I’ve long professed how I dislike convoluted controllers. CakePHP’s find method almost immediately causes this when used inside a controller. More importantly, the code inside the find method is extremely unreadable. This is almost more important than a large controller function!
This is where the repository pattern comes in. At its most basic example (which some will consider overkill – you know who you are), I still think the repository pattern is clearer.
Here is an example using the regular find approach:
[code] $user = $this->User->find('first', array('conditions' => array('id' => $id))); [/code]
Compared to a repository example:
[code] $user = $this->UserRepository->GetById($id); [/code]
The code is almost identically; however, in the second example, it’s clear that if I were to “read” the code I am retrieving a user by id opposed to I’m finding the first user with the conditions of id being equal to the variable $id.
So if you are sold, let’s continue with a full suite example…
Before you start following along, be sure you have a working CakePHP application up-and-running.
The full source code is available on GitHub: https://github.com/endyourif/CakePHPRepoTest/
At the root of our repository we need a model. Here is a basic Model/UserModel.php:
[code] <?php App::uses('AppModel', 'Model'); class UserModel extends AppModel { public $name = 'User'; } [/code]
With the model out of the way, let’s start with the actual repository files. Begin by creating a Repository folder in the root of your App folder. I like to place my interfaces in the root folder and create a secondary folder called Impl inside the Repository folder that will contain my actual repository classes. This helps keeps my folders are bit smaller, especially if you have 10 or 15 repositories.
Inside the Repository folder I have created to interfaces: IRepo and IUserRepo:
[code] <?php interface IRepo { public function GetById($id); public function GetAll(); } interface IUserRepo { public function GetByEmail($email); } [/code]
It’s important to note that you do not necessarily need an Interface for each repository you create, it’s only really necessary if your repository has additional functions than the IRepo.
Next, I’ve created a BaseRepo inside the Impl folder that implements the IRepo interface:
[code] <?php App::uses('IRepo', 'Repository'); abstract class BaseRepo implements IRepo { protected $Model; public function __construct($model) { $this->Model = $model; } public function GetById($id) { return $this->Model->find('first', array('conditions' => array('id' => $id))); } public function GetAll() { return $this->Model->find('all'); } } [/code]
I’ve made this an abstract class as it should not be instantiated directly.
It’s important to notice that my GetById function performs the exact same find function that I mentioned in the introduction. This code has to exist somewhere and I would much prefer it inside this lower layer that I don’t need to visit often.
And finally, to complete the repository I created a UserRepo class inside of the Impl directory that implements the IUserRepo:
[code] <?php App::uses('BaseRepo', 'Repository/Impl'); App::uses('IUserRepo', 'Repository'); class UserRepo extends BaseRepo implements IUserRepo { public function GetByEmail($email) { return $this->Model->find('first', array('conditions' => array('email' => $email))); } } [/code]
The repository layer is now completed at its most basic level. Prior to using this code in production, it would be a good idea to add some type checking; otherwise, you might get some obvious SQL errors. E.g. the GetById function should perform some basic checking to ensure $id is in fact a number.
The final piece to the puzzle is to implement the repository in a controller. Here is a basic UsersController that I created inside of the Controller folder that executes the three repository functions:
[code] <?php App::uses('AppController' , 'Controller'); App::uses('UserRepo', 'Repository/Impl'); class UsersController extends AppController { var $UserRepository; public function __construct($request = null, $response = null) { parent::__construct($request, $response); $this->UserRepository = new UserRepo($this->User); } public function index() { $users = $this->UserRepository->GetAll(); debug($users); $this->render(false); } public function view($id) { $user = $this->UserRepository->GetById($id); debug($user); $this->render(false); } public function find($email) { $user = $this->UserRepository->GetByEmail($email); debug($user); $this->render(false); } } [/code]
I’ve overloaded the constructor and instantiated the UserRepository variable passing in the reference to $this->User.
If you wish to unit test this controller, you may wish to update the constructor to accept a third parameter that would default to a new instance of UserRepo.
Summary
I hope you enjoyed this simple example to get started with the repository layer using CakePHP. If you like it, be sure to share it with your friends and scoff at them for not using it before!
I’ve placed the full source code on GitHub: https://github.com/endyourif/CakePHPRepoTest/