Laravel - многоразовый контроллер ресурсов

Я изменяю CMS, который я использую в своих проектах, и недавно я решил создать контроллер для действий по умолчанию BaseController, где все остальные контроллеры расширят этот контроллер 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');
    }
}

Проблема в том, что я написал BaseController используя Запросы формы UserController и я не знаю, как оставить динамический запрос, чтобы я мог реализовать их с других контроллеров.

<?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);
    }
}

Вот мой контроллер по умолчанию:

<?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);
    }
}

Любая идея, как это решить?

Ответ 1

После комментариев я мог понять лучше. У меня была такая же проблема здесь и что мой обходной путь

class BaseController extends Controller
{

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

 class MyController extends BaseController
 {
 ....
      public function store(MyRequest $request)
      {
          //do something
          return parent::_store($request);
      }
 }

Ответ 2

Любой CustomFormRequest который вы будете использовать, будет экземпляром FormRequest.. класса, который, в свою очередь, расширяет класс Request. Поэтому в вашем BaseController выполните следующее:

BaseController.php

use Illuminate\Http\Request;

// some code

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

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

// The rest of your code..

Затем в дочернем контроллере:

AChildController.php

class AChildController extends BaseController
{

use App\Http\Requests\ACustomFormRequest;

// Some code

  public function store(ACustomFormRequest $request)
  {
      //do something
      return parent::_store($request);
  }

}

Попробуйте.

Ответ 3

class BaseController extends Controller
{

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

 class MyController extends BaseController
 {
 ....
      public function store(MyRequest $request)
      {
          //do something
          return parent::_store($request);
      }
 }

Ответ 4

Короткий ответ:

Наручники.

Один Route::model.

И одно простое связывание для каждого типа запроса формы, используя интерфейсы для инъекции метода в контроллере.

На ваш пример применяется длинный ответ:

Модели, routes/web.php:

<?php

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

Запросы привязки в базе контроллера:

<?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...
}

И пример контроллера:

<?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...
}

Таким образом, интерфейсы запросов выглядят примерно так:

<?php

namespace App\Http\Requests;

interface StoreRequestInterface
{
}

И вы должны использовать его в качестве интерфейса в своих запросах формы:

<?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 [
            //
        ];
    }
}

Мнение альтернативного ответа:

Общая база контроллера:

<?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);
        }
    }
}

База контроллера ресурсов:

<?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'),
        ]);
    }
}

Контроллер ресурсов для Example модели:

<?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,
    ];
}

Файл английского языка в 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',
    ],
];

Файл португальского языка в 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',
    ],
];

Веб-маршруты в routes/web.php:

<?php

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

Интерфейсы запроса (для привязки):

<?php

namespace App\Http\Requests;

interface StoreRequestInterface
{
}

То же самое для App\Http\Requests\UpdateRequestInterface и App\Http\Requests\StatusRequestInterface...

Пример запроса формы:

<?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 [
            //
        ];
    }
}

То же самое для App\Http\Requests\Example\UpdateRequest и App\Http\Requests\Example\StatusRequest...

Документация, применяемая для этого ответа:

Примечание: Этот ответ предназначен для Laravel 5.6. Некоторый код, используемый в этом ответе, не поддерживается в версиях предварительного просмотра. Если вы хотите получить определенную версию, сообщите мне, и я попытаюсь ее адаптировать.