Two weeks ago, I discussed Two Design Patterns That Will Make Your Applications Better. Since then, there has been high demand for a demonstration of the Repository Pattern in action. Today, we'll look at how the Repository Pattern fits into the Laravel Faq Package.

Simply put, Respositories are abstraction layers between some type of storage and your application or business logic. Think of it as going to shelves to grab some items in one motion. So let's look at the different ways we want to grab or interact with the data store or persistance layer:

  1. Grab all questions asked
  2. Create a bare question for form binding in our views
  3. Grab paginated questions
  4. Create and store a question
  5. Grab a question by its primary key
  6. Update a question with a primary key and some array data
  7. Delete a question by its primary key

If you are familiar with Laravel already, you're probably wondering "what's wrong with Eloquent?" The only thing that is a bit off would be updating with primary key and attributes.

However, I hid a few requirements. First, our questions use Eloquent Rankable and our collection grabs should all be sorted by rank. Also, the paginated questions should only answered questions. This lends reasoning to a Repository as it removes database specific logic from our controllers (and this makes Taylor "happy, happy, happy").

First, we will create an interface of our method names and their arguments. This interface will later be implemented by our Eloquent Repository. In src/Rtablada/LaravelFaq/Repositories/FaqRepository.php create the following:

<?php namespace Rtablada\LaravelFaq\Repositories;

interface FaqRepository
{
    public function all($columns = array('*'));

    public function newInstance(array $attributes = array());

    public function paginate($perPage = 15, $columns = array('*'));

    public function create(array $attributes);

    public function find($id, $columns = array('*'));

    public function updateWithIdAndInput($id, array $input);

    public function destroy($id);
}

The function names were chosen for two reasons. First, the method names clearly state the action they take and what arguments they may need. Plus, we already are familiar with the Eloquent API so implementing a standard implementation will be relatively straightforward.

Now, let's knock out our Eloquent Repository in src/Rtablada/LaravelFaq/Repostiories/FaqRepositoryEloquent.php:

<?php namespace Rtablada\LaravelFaq\Repositories;

use Rtablada\LaravelFaq\Faq;
use Config;

class FaqRepositoryEloquent implements FaqRepository
{
    protected $faqModel;

    public function __construct(Faq $faqModel)
    {
        $this->faqModel = $faqModel;
    }

    public function newInstance(array $attributes = array())
    {
        if (!isset($attributes['rank'])) {
            $attributes['rank'] = 0;
        }
        return $this->faqModel->newInstance($attributes);
    }

    public function paginate($perPage = 0, $columns = array('*'))
    {
        $perPage = $perPage ?: Config::get('laravel-faq::pagination.length');
        return $this->faqModel->rankedWhere('answered', 1)->paginate($perPage, $columns);
    }

    public function all($columns = array('*'))
    {
        return $this->faqModel->rankedAll($columns);
    }

    public function create(array $attributes)
    {
        return $this->faqModel->create($attributes);
    }

    public function find($id, $columns = array('*'))
    {
        return $this->faqModel->findOrFail($id, $columns);
    }

    public function updateWithIdAndInput($id, array $input)
    {
        $faq = $this->faqModel->find($id);
        return $faq->update($input);
    }

    public function destroy($id)
    {
        return $this->faqModel->destroy($id);
    }
}

As you can see, we used the ranked* syntax from Eloquent Rankable in a few of our methods. Putting this syntax in our controller would be restrictive if someone wanted to create a flat file version of the package. This also takes logic out of our controller which is our initial goal of abstraction. Now if we want to change some of the logic of how we retrieve records, we don't have to hunt down all of our controllers and update them.

Finally, in our src/Rtablada/LaravelFaq/LaravelFaqServiceProvider.php we have to add a binding to tell Laravel that whenever we want an instance of FaqRepository we should actually use the FaqRepositoryEloquent implementation. I like to abstract these bindings to their own methods to allow for logic changes that don't clutter the boot function later. So in boot we will add

$this->bootRepositories();

And then we will create the bootRepositories function:

public function bootRepositories()
{
    $this->app->bind('Rtablada\LaravelFaq\Repositories\FaqRepository', 'Rtablada\LaravelFaq\Repositories\FaqRepositoryEloquent');
}

And that is all, now you have some quick abstraction that allows for fast logic changes or even entire structure changes.