Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel - Reusable resource controller

I am modifying the CMS that I use in my projects, and recently I decided to create a controller for default actions BaseController, where all other controllers will extend this controller BaseController.

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;

class BaseController extends Controller
{
    protected $viewFolder = 'admin';

    protected $title;

    protected $model;

    protected $key = 'id';

    protected $files = [];

    public function __construct()
    {
        $this->setVariable('title', $this->title);
    }

    public function index()
    {
        $items = $this->model::paginate();

        $this->setVariable('items', $items);

        return $this->viewRender('index');
    }

    public function create()
    {
        return $this->viewRender('create');
    }

    public function store(ExampleStoreRequestFROMEXAMPLECONTROLLER $request)
    {
        $item = $this->model::create($request->all());

        return redirect()->route($this->viewFolder.'.'.$this->viewType.'.show', $item[$this->key]);
    }

    public function show($id)
    {
        $item = $this->model::where($this->key, $id)->firstOrFail();

        $this->setVariable('item', $item);

        return $this->viewRender('show');
    }

    public function edit($id)
    {
        $item = $this->model::where($this->key, $id)->firstOrFail();

        $this->setVariable('item', $item);

        return $this->viewRender('edit');
    }

    public function update(ExampleUpdateRequestFROMEXAMPLECONTROLLER $request, $id)
    {
        $item = $this->model::where($this->key, $id)->firstOrFail();

        $item->update($request->except(['_token', '_method']));

        return redirect()->route($this->viewFolder.'.'.$this->viewType.'.show', $item[$this->key]);
    }

    public function status(ExampleStatusRequestFROMEXAMPLECONTROLLER $request, $id)
    {
        $this->model::where($this->key, $id)->update($request->except('_method'));

        return response()->json([
            'message' => 'O status foi alterado com sucesso'
        ]);
    }

    public function destroy($id)
    {
        $this->model::where($this->key, $id)->delete();

        return redirect()->route($this->viewFolder.'.'.$this->viewType.'.index');
    }
}

The problem is: I wrote the BaseController using the Form Requests of the UserController and I have no idea how to leave these Request dynamic so that I can implement them from other controllers.

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Requests\ExampleStatusRequest;
use App\Http\Requests\ExampleStoreRequest;
use App\Http\Requests\ExampleUpdateRequest;

class ExampleController extends BaseController
{
    protected $viewType = 'users';

    protected $model = 'App\Example';

    public function index()
    {
        $this->setVariable('title', 'Usuários');
        return parent::index();
    }

    public function create()
    {
        $this->setVariable('title', 'Cadastrar usuário');
        return parent::create();
    }

    public function store(ExampleStoreRequest $request)
    {
        return parent::store($request);
    }

    public function edit($id)
    {
        $this->setVariable('title', 'Editar usuário');
        return parent::edit($id);
    }

    public function update(ExampleUpdateRequest $request, $id)
    {
        return parent::update($request, $id);
    }

    public function status(ExampleStatusRequest $request, $id)
    {
        return parent::status($request, $id);
    }
}

Here is my default controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    protected $viewFolder = '';

    protected $viewType = '';

    protected $viewVariables = [];

    protected function setVariable($key, $value)
    {
        $this->viewVariables[$key] = $value;
    }

    protected function viewRender($view)
    {
        return view($this->viewFolder.'.pages.'.$this->viewType.'.'.$view, $this->viewVariables);
    }
}

Any idea how to solve this?

like image 330
Caio Kawasaki Avatar asked May 04 '18 19:05

Caio Kawasaki


2 Answers

After the comments I could understand better. I had the same issue here and that's my workaround

class BaseController extends Controller
{

     protected function _store($request)
     {
     ....
     }
 ...
 }

 class MyController extends BaseController
 {
 ....
      public function store(MyRequest $request)
      {
          //do something
          return parent::_store($request);
      }
 }
like image 113
Felippe Duarte Avatar answered Sep 16 '22 14:09

Felippe Duarte


Short answer:

Bindings.

One Route::model.

And one Simple Binding for each Form Request type, using Interfaces for the Method Injection in the Controller.

Long answer applied to your example:

Models binding in routes/web.php:

<?php

Route::model('example', App\Example::class);
Route::resource('example', 'Admin\ExampleController');

Requests bindings in the Controller Base:

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class BaseController extends Controller
{
    // Your code...

    /**
     * @var string[]|callable[]
     */
    protected $bindings = [];

    /**
     * Controller constructor.
     */
    public function __construct()
    {
        $this->addBindings();
    }

    /**
     * Add controller specific bindings.
     */
    protected function addBindings()
    {
        $app = Container::getInstance();

        foreach ($this->getBindings() as $abstract => $concrete) {
            $app->bind($abstract, $concrete);
        }
    }

    // Your code...

    public function store(Requests\StoreRequestInterface $request)
    {
        // Your code...
    }

    public function show(Model $item)
    {
        // Your code...
    }

    public function edit(Model $item)
    {
        // Your code...
    }

    public function update(Requests\UpdateRequestInterface $request, Model $model)
    {
        // Your code...
    }

    public function status(Requests\StatusRequestInterface $request, Model $model)
    {
        // Your code...
    }

    public function destroy(Model $item)
    {
        // Your code...
    }

    // Your code...
}

And the Example Controller:

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Requests;

class ExampleController extends BaseController
{
    // Your code...

    /**
     * @var string[]|callable[]
     */
    protected $bindings = [
        Requests\StatusRequestInterface::class => Requests\ExampleStatusRequest::class,
        Requests\StoreRequestInterface::class  => Requests\ExampleStoreRequest::class,
        Requests\UpdateRequestInterface::class => Requests\ExampleUpdateRequest::class,
    ];

    // Your code...
}

So the Requests interfaces would look something like:

<?php

namespace App\Http\Requests;

interface StoreRequestInterface
{
}

And you should use it as an interface in your Form Requests:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ExampleStoreRequest extends FormRequest implements StoreRequestInterface
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

Opinionated alternative answer:

General controller base:

<?php

namespace App\Http\Controllers;

use Illuminate\Container\Container;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

abstract class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    /**
     * The view subdirectory that must be used.
     *
     * @var string
     */
    protected $viewDir;

    /**
     * Bindings
     *
     * @var string[]|callable[]
     */
    protected $bindings = [];

    /**
     * Controller constructor.
     */
    public function __construct()
    {
        $this->addViewPath();
        $this->addBindings();
        $this->init();
    }

    /**
     * @return void
     */
    protected function init()
    {
        //
    }

    /**
     * @return string
     */
    protected function getViewDir()
    {
        return $this->viewDir;
    }

    /**
     * @return callable[]|string[]
     */
    protected function getBindings()
    {
        return $this->bindings;
    }

    /**
     * Add controller specific view path.
     */
    protected function addViewPath()
    {
        if (null !== ($dir = $this->getViewDir()) && ($path = realpath(base_path('resources/views/' . $dir)))) {
            view()->getFinder()->addLocation($path);
        }
    }

    /**
     * Add controller specific bindings.
     */
    protected function addBindings()
    {
        $app = Container::getInstance();

        foreach ($this->getBindings() as $abstract => $concrete) {
            $app->bind($abstract, $concrete);
        }
    }
}

Resource Controller Base:

<?php

namespace App\Http\Controllers;

use Illuminate\Database\Eloquent\Model;
use App\Http\Requests\StoreRequestInterface;
use App\Http\Requests\UpdateRequestInterface;
use App\Http\Requests\StatusRequestInterface;

abstract class ResourceController extends Controller
{
    /**
     * Model class
     *
     * @var string
     */
    protected $modelClass;

    /**
     * @var string
     */
    protected $viewDirPrefix;

    /**
     * @return string
     */
    protected function getViewDir()
    {
        return $this->viewDir ?:
            ltrim($this->viewDirPrefix . DIRECTORY_SEPARATOR . 'pages', DIRECTORY_SEPARATOR) .
            DIRECTORY_SEPARATOR . strtolower(class_basename($this->modelClass));
    }

    /**
     * @return \Illuminate\Database\Eloquent\Builder
     */
    protected function getQuery()
    {
        return call_user_func($this->modelClass . '::query');
    }

    /**
     * @return string
     */
    protected function getName()
    {
        return __(class_basename($this->modelClass));
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    protected function index()
    {
        $title = __('resource.title.index', ['name' => str_plural($this->getName())]);
        $models = $this->getQuery()->paginate();

        return view('index', compact('title', 'models'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    protected function create()
    {
        $title = __('resource.title.create', ['name' => $this->getName()]);

        return view('create', compact('title'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request|StoreRequestInterface $request
     *
     * @return \Illuminate\Http\Response
     */
    public function store(StoreRequestInterface $request)
    {
        $model = $this->getQuery()->create($request->all());

        return redirect()
            ->route(substr($request->route()->getName(), 0, -5) . 'show', $model);
    }

    /**
     * Display the specified resource.
     *
     * @param  \Illuminate\Database\Eloquent\Model $model
     *
     * @return \Illuminate\Http\Response
     */
    public function show(Model $model)
    {
        $title = __('resource.title.show', ['name' => $this->getName()]);

        return view('show', compact('title', 'model'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \Illuminate\Database\Eloquent\Model $model
     *
     * @return \Illuminate\Http\Response
     */
    public function edit(Model $model)
    {
        $title = __('resource.title.edit', ['name' => $this->getName()]);

        return view('edit', compact('title', 'model'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request|UpdateRequestInterface $request
     * @param  \Illuminate\Database\Eloquent\Model             $model
     *
     * @return \Illuminate\Http\Response
     */
    public function update(UpdateRequestInterface $request, Model $model)
    {
        $model->update($request->except(['_token', '_method']));

        return redirect()
            ->route(substr($request->route()->getName(), 0, -6) . 'show', $model);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \Illuminate\Database\Eloquent\Model $model
     *
     * @return \Illuminate\Http\Response
     * @throws \Exception
     */
    public function destroy(Model $model)
    {
        $model->delete();

        return redirect()
            ->route(substr(request()->route()->getName(), 0, -7) . 'index');
    }

    /**
     * @param \Illuminate\Http\Request|StatusRequestInterface $request
     * @param \Illuminate\Database\Eloquent\Model             $model
     *
     * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
     */
    protected function status(StatusRequestInterface $request, Model $model)
    {
        $model->update($request->except('_method'));

        return response()->json([
            'message' => __('resource.status.success'),
        ]);
    }
}

Resource controller for Example model:

<?php

namespace App\Http\Controllers\Admin;

use App\Example;
use App\Http\Requests;
use App\Http\Controllers\ResourceController;

class ExampleController extends ResourceController
{
    /**
     * Model class
     *
     * @var string
     */
    protected $modelClass = Example::class;

    /**
     * @var string
     */
    protected $viewDirPrefix = 'admin';

    /**
     * @var string[]|callable[]
     */
    protected $bindings = [
        Requests\StatusRequestInterface::class => Requests\Example\StatusRequest::class,
        Requests\StoreRequestInterface::class  => Requests\Example\StoreRequest::class,
        Requests\UpdateRequestInterface::class => Requests\Example\UpdateRequest::class,
    ];
}

English Language file in resources/lang/en/resource.php:

<?php

return [
    'title'  => [
        'index'  => 'All :Name',
        'create' => 'Create :Name',
        'show'   => 'Show :Name',
        'edit'   => 'Edit :Name',
    ],
    'status' => [
        'success' => 'Status updated successfully',
    ],
];

Portuguese Language file in resources/lang/pt/resource.php:

<?php

return [
    'title'  => [
        'index'  => 'Todos os :Name',
        'create' => 'Cadastrar :Name',
        'show'   => 'Show :Name',
        'edit'   => 'Editar :Name',
    ],
    'status' => [
        'success' => 'O status foi alterado com sucesso',
    ],
];

Web Routes in routes/web.php:

<?php

Route::group([
    'as'        => 'admin.',
    'prefix'    => 'admin',
    'namespace' => 'Admin',
], function () {
    Route::model('example', App\Example::class);
    Route::resource('example', 'ExampleController');
});

Request interfaces (for binding):

<?php

namespace App\Http\Requests;

interface StoreRequestInterface
{
}

Same for App\Http\Requests\UpdateRequestInterface and App\Http\Requests\StatusRequestInterface...

Example Form Request:

<?php

namespace App\Http\Requests\Example;

use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\StoreRequestInterface;

class StoreRequest extends FormRequest implements StoreRequestInterface
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

Same for App\Http\Requests\Example\UpdateRequest and App\Http\Requests\Example\StatusRequest...

Documentation applied for this reply:

  • https://laravel.com/docs/5.6/routing#route-model-binding
  • https://laravel.com/docs/5.6/container#binding
  • https://laravel.com/docs/5.6/controllers#dependency-injection-and-controllers
  • https://laravel.com/docs/5.6/validation#form-request-validation
  • https://laravel.com/docs/5.6/views
  • https://laravel.com/docs/5.6/controllers#resource-controllers
  • https://laravel.com/docs/5.6/localization

Notice: This reply is for Laravel 5.6. Some code used in this reply is not supported in previews versions. If you want it for a specific version, let me know and I will try to adapt it.

like image 33
Gonzalo Avatar answered Sep 17 '22 14:09

Gonzalo