31 Dec 2019
cat: Laravel, PHP
0 Comments

Laravel Repository Pattern Example

Introduction

In this tutorial we will implement the repository pattern in Laravel. Repository contains the logic on how data is persisted and how to query the data. It will be most useful to avoid duplicate codes that execute the same queries and hide the complication from the code that wants the data.

Laravel does not offer an auto-generated repository classes or a way to have a dynamic base class that can help with common methods like findOne, findBy, etc which we will solve in this tutorial.

Base Interface and Base Class

The base interface will force the other repositories to have the common methods. Let us say we have a Post model, these are the methods we will have.

  • findOne($id) – Finds one post by id.
  • findOneBy(array $criteria, array $orderBy = null) – Finds one post base on a set of criteria like Author or User ID, and the post is published.
  • findAll() – Finds all posts.
  • findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)

These methods are useful to have but in cases the queries are much complicated we can create another method in the children repositories. We can also override these method when needed like for example, some properties are queried differently from others.

app/Persistence/Repositories/IRepository.php

namespace App\Persistence\Repositories;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

interface IRepository
{
    /**
     * @param int $id
     * @return Model|null
     */
    public function findOne(int $id): Model;

    /**
     * @param array $criteria
     * @param array|null $orderBy
     * @return Model|null
     */
    public function findOneBy(array $criteria, array $orderBy = null): Model;

    /**
     * @return Model[]|Collection
     */
    public function findAll();

    /**
     * @param array $criteria
     * @param array|null $orderBy
     * @param int|null $limit
     * @param int|null $offset
     * @return Model[]|Collection
     */
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null);
}

app/Persistence/Eloquent/Repository.php

namespace App\Persistence\Eloquent;

use App\Persistence\Repositories\IRepository;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

abstract class Repository implements IRepository
{
    protected abstract function getQueryBuilder(): Builder;

    /**
     * @param int $id
     * @return Model|null
     */
    public function findOne(int $id): Model
    {
        return $this->getQueryBuilder()->find($id);
    }

    /**
     * @param array $criteria
     * @param array|null $orderBy
     * @return Model|null
     */
    public function findOneBy(array $criteria, array $orderBy = null): Model
    {
        $query = $this->getQueryBuilder()
            ->whereRowValues(array_keys($criteria), '=', array_values($criteria));

        if (!empty($orderBy)) {
            foreach ($orderBy as $column => $direction) {
                $query->orderBy($column, $direction);
            }
        }

        return $query->first();
    }

    /**
     * @return Model[]|Collection
     */
    public function findAll()
    {
        return $this->getQueryBuilder()
            ->get();
    }

    /**
     * @param array $criteria
     * @param array|null $orderBy
     * @param int|null $limit
     * @param int|null $offset
     * @return Model[]|Collection
     */
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
    {
        $query = $this->getQueryBuilder()
            ->whereRowValues(array_keys($criteria), '=', array_values($criteria));

        if (!empty($orderBy)) {
            foreach ($orderBy as $column => $direction) {
                $query->orderBy($column, $direction);
            }
        }

        if (isset($limit)) {
            $query->limit($limit);
        }

        if (isset($offset)) {
            $query->offset($offset);
        }

        return $query->get();
    }
}

The base class will be abstract and have the child classes override getQueryBuilder. This is so child classes can specify which data to query and it wants an eloquent builder. To do that we just have to return something like Post::query().

Example Repository

To test our base repository we will create a Post model. Here is the migration file. All source code are available in the “References” section below.

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('title');
            $table->text('content');
            $table->boolean('is_published')->default(0);
            $table->timestamps();
            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

We also want to create another interface for the posts repository to have another example method. This is to show that you can add more queries inside the repository class and re-use the codes.

app/Persistence/Repositories/IPostRepository.php

namespace App\Persistence\Repositories;

use App\Post;
use Illuminate\Support\Collection;

interface IPostRepository extends IRepository
{
    /**
     * @param int $userId
     * @return Post[]|Collection
     */
    public function getAuthorPosts(int $userId);
}

Note that, the post repository interface extends IRepository. This is so that we can just get an instance of the post repository interface and get the methods from the base interface in addition to getAuthorPosts.

The post repository will be simple. We just extend the base abstract class we wrote earlier and implement the IPostRepository.

app/Persistence/Eloquent/PostRepository.php

namespace App\Persistence\Eloquent;

use App\Persistence\Repositories\IPostRepository;
use App\Post;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

class PostRepository extends Repository implements IPostRepository
{
    protected function getQueryBuilder(): Builder
    {
        return Post::query();
    }

    /**
     * @param int $userId
     * @return Post[]|Collection
     */
    public function getAuthorPosts(int $userId)
    {
        return $this->findBy(['user_id' => $userId]);
    }
}

Since the base repository already implemented the common methods, we just implement the required abstract method and the getAuthorPosts.

Last but not the least, we need to register the repository to the container.

app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            \App\Persistence\Repositories\IPostRepository::class,
            \App\Persistence\Eloquent\PostRepository::class
        );
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
    }
}

Trying it out

To test this quickly I am just gonna use artisan tinker. You can always inject the IPostInterface anywhere you can like services classes, controllers, jobs, etc.

Laravel Repository Pattern Example

That is it for this tutorial. Thanks for reading!

References

Be the first to write a comment.

Post A Comment