0

I am implementing a repository pattern in Laravel, and it seems to be very tedious. For example, let's say I have products then I have to create a ProductRepository interface then a ProductRepository class that implements that interface, now I have some very generic methods on the ProductRepository like:

  • retrieveAll
  • store
  • update
  • delete

And now I have to do the same thing for ingredients. It would be nice if I could simply create a ModelRepository interface with all those generic methods and implement it by passing a generic data type (namely the model), something similar to Java Generics:

<?php
interface ModelRepositoryInterface<T> {
    function retrieveAll(): Collection<T>;
    function store(T $item);
    function update(int $id, T $data);
    function delete(int $id);
}

But since php doesn't support generics how can I achieve this simplicity?

Stanislav Kralin
  • 11,070
  • 4
  • 35
  • 58
chinloyal
  • 1,023
  • 14
  • 42
  • it depends on what data products/incredients have, and do they have anything in common other than the methods ? – Emad Ha Feb 13 '20 at 14:11
  • I don't believe the data they have is relevant but a product would have, for example, name, code, unit_cost... and ingredient's would have name, measurement_unit and reorder_level. – chinloyal Feb 13 '20 at 14:17
  • You know that interfaces can also "extend" other interfaces? – B001ᛦ Feb 13 '20 at 14:33
  • Yeah but that would only would be useful for the delete method, which doesn't accept or output a specific model. @B001ᛦ – chinloyal Feb 13 '20 at 14:36
  • Maybe take a look at "Interface Segregation" – B001ᛦ Feb 13 '20 at 14:38
  • interfaces are just contracts, they don't have codes in them, more of guiding rules, abstract classes can have codes in them – Emad Ha Feb 13 '20 at 15:47

1 Answers1

1

You can create a RepositoryServiceProvider to bind your repository interfaces to actual classes.

You can create a abstract Repository class with retrieveAll, store, update, delete and extend your Repositories and implement the interface. I have included below example with magic functions to be able to eloquent methods if I don't have any customization.

The below is not tested but its just to get the idea.

<?php


namespace App\Repositories;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

abstract class AbstractRepository implements RepositoryInterface
{
    /**
     * @var Builder|Model
     */
    protected $model;
    /**
     * @return mixed
     */
    public function getModel()
    {
        return $this->model;
    }

    /**
     * @param array $columns
     * @return \Illuminate\Database\Eloquent\Collection|Model[]
     */
    public function all($columns = ['*'])
    {
        return $this->model->all($columns);
    }

    /**
     * @param $name
     * @param $arguments
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        return $this->model->{$name}($arguments);
    }
}

OrderRepository

<?php

namespace App\Repositories;

use App\Models\Order;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;

class OrderRepository extends AbstractRepository implements OrderRepositoryInterface
{
    /**
     * OrderRepository constructor.
     * @param Order $model
     */
    public function __construct(Order $model)
    {
        $this->model = $model;
    }

    public function countPaid(): int
    {
        return $this->model->paid()->count();
    }

    /**
     * @return int
     */
    public function countReady(): int
    {
        return $this->model->ready()->count();
    }

    /**
     * @return int
     */
    public function countCancelled(): int
    {
        return $this->model->cancelled()->count();
    }

}

OrderRepositoryInterface

<?php


namespace App\Repositories;


interface OrderRepositoryInterface
{

}

RepositoryServiceProvider

<?php

namespace App\Providers;

use App\Repositories\OrderRepository;
use App\Repositories\OrderRepositoryInterface;
use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(OrderRepositoryInterface::class, OrderRepository::class);
    }

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

RepositoryInterface

<?php


namespace App\Repositories;


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

interface RepositoryInterface
{
    function retrieveAll(): Collection;
    function store(Model $item);
    function update(int $id, Model $data);
    function delete(int $id);
}
Ersin Demirtas
  • 666
  • 6
  • 14
  • How would you pass it to the controller since the OrderRepository accepts a model in the constructor – chinloyal Feb 13 '20 at 15:44
  • The repository is binded and the constructor already has the model type. So in your controller you can do. public function show(OrderRepositoryInterface $orderRepsitory){ $orders = $orderRepsitory->retrieveAll(); } – Ersin Demirtas Feb 13 '20 at 15:58