Search code examples
laravelcrudtraitscode-reuse

Laravel trait crud controller with request validation and resources


I'm trying to refactor my code to be more reusable.

I created a trait CrudControllerTrait to implement the index,show,store,update,destroy methods. But I found 2 problems:

BrandController.php

public function store(BrandNewRequest $request)
{
    $requestData = $request->validated();
    return new BrandResource($this->brands->store($requestData));
}

ProductController.php

public function store(ProductNewRequest $request)
{
    $requestData = $request->validated();
    return new ProductResource($this->products->store($requestData));
}

The trait method would be:

public function store(xxxxx $request)
{
    $requestData = $request->validated();
    return new xxxxxResource($this->repository()->store($requestData));
}

Problem1: The hint type. How can I abstract them? If I remove it shows that errror:

"message": "Too few arguments to function App\\Http\\Controllers\\BrandController::store(), 0 passed and exactly 1 expected"

Problem2: Return the resource. How can create the new resource? On the collection I can solve it doing this:

public function index()
{
    $models = $this->repository()->index();
    return $this->resource()::collection($models);
}

The resource is on the controller who uses the trait:

public function resource()
{
    return BrandResource::class;
}

But with single resource didn't know how to do it...

The idea is, that I have so much controllers using the same pattern: BrandController, ProductController, etc. I'd love to reuse these 5 crud methods on the same trait...


Solution

  • The only way I found is creating an abstract method.

    trait CrudRepositoryTrait
    {
        abstract function model();
    
        public function index()
        {
            return $this->model()::with($this->with())->get();
        }
    
        public function find($id)
        {
            return $this->model()::findOrFail($id);
        }
    
        public function store($data)
        {
            $request = $this->dtoRequest($data);
            return $this->model()::create($request);
        }
        
        (...)
    }
    

    And then, an example how to use this treat:

    class ProductRepository implements ProductRepositoryContract
    {
        use CrudRepositoryTrait;
    
        function model()
        {
            return Product::class;
        }
        
        (...)
    }
    

    By this way I could reuse a lot of code.