Home > Software design >  DRY controllers in laravel
DRY controllers in laravel

Time:02-21

Should I use DRY principles in controllers or code in different controllers can be repeated? For example, now I have something like this:

class FirstController extends Controller
{
  public function store(FirstRequest $request)
  {
    $result = First::create($request->validated());
    
    return response()->json(['message' => 'Successfully created'], 200);
   }
}
    
class SecondController extends Controller
{
  public function store(SecondRequest $request)
  {
    $result = Second::create($request->validated());
    
    return response()->json(['message' => 'Successfully created'], 200);
  }
}

...

class NController extends Controller
{
  public function store(NRequest $request)
  {
    $result = N::create($request->validated());
    
    return response()->json(['message' => 'Successfully created'], 200);
  }
}

How can I make them DRY?

Should I use main controller class which would be extended by FirsrController, SecondController etc? Something like this:

class MainController extends Controller
{
  protected $model;
  public function store($request)
  {
    $result = $this->model::create($request->validated());
                    
    return response()->json(['message' => 'Successfully created'], 200);
   }
}
            
class FirstController extends MainController
{
  public function __construct()
  {
    $this->model = new First();
  }
}
            
class SecondController extends MainController
{
  public function __construct()
  {
    $this->model = new Second();
  }
}

And how can I use different request for them in that case?

CodePudding user response:

You can do something like return YourCustomResponse::success(); in both controllers, where success() will be a named constructor.

CodePudding user response:

First define two interfaces (contracts) like so:

interface ActionContract{
    public function store();
}

interface RequestContract
{
    public function rules();
}

Provide the implementations like so:

class MainRequest{
    public function authorize()
    {
        return true;
    }
}

class FirstRequest extends MainRequest implements RequestContract
{

    public function rules()
    {
        return [
          'email'    => 'required',
          'password' => 'required',
        ];
    }
}

class FirstAction implements ActionContract{

    public function store()
    {

    }
}
// SecondAction implements ActionContract
// ThirdAction implements ActionContract

Instead of using multiple controllers use one central controller in which you will be injecting the interfaces NOT concrete implementations:

class CentralController extends Controller
{
    public function store(ActionContract $action, RequestContract $request)
    {
        $action->store($request->validated());
        return response()->json(['message' => 'Successfully created'], 200);
    }
}

Define a service provider which will be responsible to provide the correct implementation; example:

class MyProvider extends ServiceProvider
{
// define a lookup table in a service provider
// and resolve corresponding Model and Request
// depending upon the request
protected array $actionImplementations
                  = [
  '/first' =>
    [
      'model'        => Firsrt::class,
      'formRequest' => FirstRequest::class,
    ],

  '/second' =>
    [
      'model'        => Second::class,
      'formRequest' => SecondRequest::class,
    ],
];

public function register()
{
    app()->singleton(ActionContract::class, function ($app) {
        $class = $this->getAuthImplementation()['model'];
        return new $class;
    });

    app()->singleton(RequestContract::class, function ($app) {
        $class = $this->getAuthImplementation()['formRequest'];
        return new $class;
    });

}

public function getAuthImplementation(): array
{
    return $this->authImplementations[$this->requestedUrl()];
}


protected function requestedUrl(): string
{
    return parse_url(request()->url(), PHP_URL_PATH);
}

Now your controller isn't worried about which model it is dealing with; and when you want to create a brand new NthController make it regular class and implement from ActionContract and make an entry in service provider so that it can resolve the correct implementation.

  • Related