Implementing the Repository Pattern with CakePHP

Published on Jun 3, 2013 by Jamie Munro

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:

$user = $this->User->find('first', array('conditions' => array('id' => $id)));

Compared to a repository example:

$user = $this->UserRepository->GetById($id);

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Ö


I must confess, I have been doing a lot of C# programming recently and some heavy unit testing.† With this I am going to bring along the interfacing techniques that I have been using with it.

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:

<?php
App::uses('AppModel', 'Model');
class UserModel extends AppModel {
    public $name = 'User';
}

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:

<?php

interface IRepo {
    public function GetById($id);
    public function GetAll();
}

interface IUserRepo {
    public function GetByEmail($email);
}

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:

<?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');
    }

}

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:

<?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)));
    }

}

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:

<?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);
    }

}

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/

Tags: CakePHP | PHP | repository

Related Posts

blog comments powered by Disqus