cult3

Eloquent tricks for better Repositories

Mar 17, 2014

Table of contents:

  1. The structure of a Repository
  2. Abstracting repeating logic
  3. Creating an Eager Loading blueprint
  4. Get by key value
  5. Pagination
  6. Get results that have a particular relationship
  7. Conclusion

One of the great things about writing code is, good practices are often obvious because it’s annoying to not follow them. For example, it’s annoying when you need to write the same thing over and over again. When you feel yourself getting annoyed by typing the same thing out multiple times, its time for an abstraction.

In a typical application you will likely have many Repositories for working with your storage system. When using Laravel, the majority of the time you will be using Eloquent. However, believe me, when you have a lot of Repositories you quickly get annoyed with writing the same methods for accessing data over and over again!

In this tutorial I’m going to look at some patterns for abstracting common methods so that you don’t have to keep repeating the same method implementations in your individual Repositories. I will also look at how we can use the flexibility of Eloquent and it’s Query Builder to write some really nice methods for accessing data.

Of course if you are using the Repository pattern, in theory you could be using any type of persistent storage. So if you aren’t using Eloquent, a lot of the tutorial won’t really be applicable to you. However, if you are using Eloquent, then hopefully this will make your Repositories much better!

The structure of a Repository

In Laravel, the typical Repository pattern structure is a specific storage implementation that is bound to a generic interface. This allows you to throw away the specific storage implementation without changing any of your code.

For example, you would have a UserRepository interface:

interface UserRepository
{
}

And an EloquentUserRepository:

class EloquentUserRepository implements UserRepository
{
}

The specific implementation would be bound to the generic interface and resolved from Laravel’s IoC container using a Service Provider:

/**
 * Register
 */
public function register()
{
    $this->app->bind('Cribbb\Repositories\User\UserRepository', function($app) {
        return new EloquentUserRepository( new User );
    });
}

In the EloquentUserRepository we can then set the injected model as a property on the class:

class EloquentUserRepository implements UserRepository
{
    /**
     * @var Model
     */
    protected $model;

    /**
     * Constructor
     */
    public function __construct(User $model)
    {
        $this->model = $model;
    }
}

Now that we’ve got the injected instance of the model set on the class we can perform queries on the model to return data:

/**
 * Return all users
 *
 * @return Illuminate\Database\Eloquent\Collection
 */
public function all()
{
    return $this->model->all();
}

Hopefully if you have been following along with this series this little recap should all be familiar.

Abstracting repeating logic

When working with Repositories, it’s pretty likely that you will end up repeating a lot of the same methods. For example you will probably want a method on each Repository to return all entities or find a specific entity by id.

Whenever you find yourself repeating the same code you need to abstract it and then reference that abstraction.

In this case it means creating an AbstractEloquentRepository and then using this class as a blueprint to inherit from.

abstract class AbstractEloquentRepository
{
    /**
     * Return all users
     *
     * @return Illuminate\Database\Eloquent\Collection
     */
    public function all()
    {
        $this->model->all();
    }
}

We can then extend the abstract class to inherit the methods that are common to all Repositories:

class EloquentUserRepository extends AbstractEloquentRepository implements UserRepository {
}

Now there is no need to implement the all method on the EloquentUserRepository because it is automatically inherited from the AbstractEloquentRepository class.

Creating an Eager Loading blueprint

As I covered in Eager Loading in Laravel 4, eager loading is extremely useful when you want to load the relationships of an entity to dramatically reduce the number of queries that are performed on the database. Eager Loading is basically a way to optimise queries by specifying what data you want ahead of time.

A common method that is available on a Repository is to find an entity by an id, for example:

/**
 * Find an entity by id
 *
 * @param int $id
 * @return Illuminate\Database\Eloquent\Model
 */
public function getById($id)
{
    return $this->model->find($id);
}

This implementation is fine if you only want to return the entity, but what if you also want to return it’s relationships through eager loading? The only way to accomplish that is to define another method:

/**
 * Find an entity by id and include the posts
 *
 * @param int $id
 * @return Illuminate\Database\Eloquent\Model
 */
public function getByIdWithPosts($id)
{
    return $this->model->find($id)->with(array('posts'))->first();
}

Urgh, we’re going to have to repeat a lot of the same logic to implement all of these relationships.

Instead of repeating this logic multiple times we can simply create a query blueprint where we specify the relationships to eager load upfront:

/**
 * Make a new instance of the entity to query on
 *
 * @param array $with
 */
public function make(array $with = array())
{
    return $this->model->with($with);
}

In the getById method we can pass an optional $with parameter so that we can specify what relationships to load whenever we call this method:

/**
 * Find an entity by id
 *
 * @param int $id
 * @param array $with
 * @return Illuminate\Database\Eloquent\Model
 */
public function getById($id, array $with = array())
{
    $query = $this->make($with);

    return $query->find($id);
}

Of course this isn’t limited to just the getById method. If you add an optional $with parameter to any of your Repository methods and create the stub query using the make method you now have access to eager loading whenever you need it without having to repeat any logic!

Get by key value

Another common set of methods on a Repository is getting a selection of entities based upon a key and a value.

For example, you might want to find a user by their email address:

/**
 * Find a user by their email address
 *
 * @param string $email
 * @return Illuminate\Database\Eloquent\Model
 */
public function getByEmail($email)
{
    return $this->model->where('email', '=', $email)->first();
}

However if you need to find entities on a Repository using many of these key value searches you end up with a lot of repeated logic.

Instead you can simply pass in the key and a value to a generic method. I actually prefer to create two methods, one for a single entity and one for many entities:

/**
 * Find a single entity by key value
 *
 * @param string $key
 * @param string $value
 * @param array $with
 */
public function getFirstBy($key, $value, array $with = array())
{
    return $this->make($with)->where($key, '=', $value)->first();
}

/**
 * Find many entities by key value
 *
 * @param string $key
 * @param string $value
 * @param array $with
 */
public function getManyBy($key, $value, array $with = array())
{
    return $this->make($with)->where($key, '=', $value)->get();
}

Now you can use the same two methods whenever you want to get find entities by a key and a value.

Pagination

Pagination is another really common thing that you will probably need on multiple Repositories. In this tutorial I showed you a method for manually creating the required data to create pagination in Laravel.

This is another method that you will likely finding yourself repeating that can be abstracted away from your individual Repositories:

/**
 * Get Results by Page
 *
 * @param int $page
 * @param int $limit
 * @param array $with
 * @return StdClass Object with $items and $totalItems for pagination
 */
public function getByPage($page = 1, $limit = 10, $with = array())
{
    $result = new StdClass;
    $result->page = $page;
    $result->limit = $limit;
    $result->totalItems = 0;
    $result->items = array();

    $query = $this->make($with);

    $model = $query->skip($limit * ($page - 1))
        ->take($limit)
        ->get();

    $result->totalItems = $this->model->count();
    $result->items = $model->all();

    return $result;
}

Get results that have a particular relationship

Another common type of method to have on a Repository is where you only want to return results that have a particular relationship.

For example, you might want to only return posts that have comments.

A typical example could be on your EloquentPostRepository:

/**
 * Get Posts with Comments
 *
 * @return Illuminate\Database\Eloquent\Collection
 */
public function getPostsWithComments()
{
    $this->model->has('comments')->get();
}

Again this is a good candidate for abstracting away from the individual Repository:

/**
 * Return all results that have a required relationship
 *
 * @param string $relation
 */
public function has($relation, array $with = array())
{
    $entity = $this->make($with);

    return $entity->has($relation)->get();
}

Conclusion

You will find that you end up repeating the same code a lot in bigger projects. Whenever you feel that something is getting repeated a lot it’s usually a good time for an abstraction. By creating generic methods that suit many purposes we can reduce a lot of the complexity of our repositories and reduce the amount of repeated code and logic.

Whenever you are deciding to abstract a piece of logic, you need to ensure that it makes sense. Often an abstraction will make your code more complicated than it needs to be. There is a fine line between the too much and too little abstraction. Try and write general purpose methods to solve your problems, rather than making your code more complex than it needs to be.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.