Repository Pattern in Laravel - PHP Design Pattern

| Tags: #php #laravel #design-patterns

Repositories are classes or components that contain the logic needed to access data sources. Repositories provide centralized functionality for accessing data, allowing better management and separation of the infrastructure or technology used to access data from the domain model. If you’re using an object-relational mapping (ORM) like the Laravel Framework, the code you need to implement is simplified thanks to Eloquent and strong typing. This allows you to focus on the data persistence logic rather than the data access helper functions.

Let’s take an example in Laravel.

We usually implement the repository pattern with an interface. An interface can reduce dependencies between classes, and the implemented class will use the same functionality as the interface.

And so let’s create our PostRepositoryInterface in the app/Interfaces/ folder

namespace App\Interfaces;

interface PostRepositoryInterface
{
    public function list();
    public function findById(int $postId);
    public function create(array $details);
    public function update(int $postId, array $details);
    public function delete(int $postId);
}

And now let’s create the PostRepository in the app/Repositories folder which will implements the PostRepositoryInterface

namespace App\Repositories;

use App\Interfaces\PostRepositoryInterface;
use App\Models\Post;

class PostRepository implements PostRepositoryInterface
{
    public function list()
    {
        return Post::all();
    }

    public function findById(int $postId)
    {
        return Post::query()->findOrFail($postId);
    }

    public function create(array $details)
    {
        return Post::create($details);
    }

    public function update(int $postId, array $details)
    {
        return Post::query()->where('id', $postId)->update($details);
    }

    public function delete(int $postId)
    {
        return Post::query()->where('id', $postId)->delete();
    }
}

Next in our class, through dependency injection, we use our PostRepositoryInterface

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\Api\PostCreateRequest;
use App\Http\Requests\Api\PostUpdateRequest;
use App\Interfaces\PostRepositoryInterface;

class PostController extends Controller
{
    private PostRepositoryInterface $postRepository;
    
    public function __construct(PostRepositoryInterface $postRepository)
    {
        $this->postRepository = $postRepository;
    }

    public function list()
    {
        return $this->postRepository->list();
    }

    public function findById(int $postId)
    {
        return $this->postRepository->findById($postId);
    }

    public function create(PostCreateRequest $request)
    {
        return $this->postRepository->create($request->validated());
    }

    public function update(PostUpdateRequest $request, int $postId)
    {
        return $this->postRepository->update($postId, $request->validated());
    }

    public function delete(int $postId)
    {
        return $this->postRepository->delete($postId);
    }
}

But in order for PostRepositoryInterface to refer to PostRepository we need to bind the interface to the repository.

Go to the command line and run the following command at the root of the application to create a RepositoryServiceProvider.

php artisan make:provider RepositoryServiceProvider
// Provider created successfully.

And this command should create a file RepositoryServiceProvider.php in the app/Providers.

Inside the RepositoryServiceProvider class, in the register() method, we will bind the interface to the repository. We will also bind all our other interfaces to repositories in this provider.

class RepositoryServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(PostRepositoryInterface::class, PostRepository::class);
        // $this->app->bind(CommentRepositoryInterface::class, CommentRepository::class);
        // $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
    }
}

The last step is to register this service provider in our config/app.php. Open this file and add to the providers array our provider App\Providers\RepositoryServiceProvider::class

'providers' => [
    // ...other declared providers
    App\Providers\RepositoryServiceProvider::class,
];

And it’s all! Now our application knows what class it should use when we type objects by its interfaces.

Repository and ORM

Perhaps you have a question: why use a repository if I use an ORM? After all, ORM can work with data, operating with business objects.

However, there may be many cases where data storage is something more complex or specific than just an ORM. And then such a data layer is encapsulated using the repository pattern:

Conclusion

After we implemented the Repository Pattern, our code became more structured, more readable, and, importantly, easier to develop and more reusable in the future.