у меня есть BaseController
это обеспечивает основу для большинства методов HTTP для моего сервера API, например, store
метод:
BaseController.php
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
Затем я расширяю это BaseController
в более конкретном контроллере, таком как UserController
, вот так:
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
}
Это прекрасно работает. Однако теперь я хочу расширить UserController
ввести новый Laravel 5 FormRequest
класс, который заботится о таких вещах, как проверка и аутентификация для User
ресурс. Я хотел бы сделать это так, переписав метод store и используя инъекцию зависимостей подсказок типа Laravel для своего класса Form Request.
UserController.php
public function store(UserFormRequest $request)
{
return parent::store($request);
}
Где UserFormRequest
простирается от Request
который сам по себе простирается от FormRequest
:
UserFormRequest.php
class UserFormRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required'
];
}
}
Проблема в том, что BaseController
требует Illuminate\Http\Request
объект, в то время как я передаю UserFormRequest
объект. Поэтому я получаю эту ошибку:
in UserController.php line 6
at HandleExceptions->handleError('2048', 'Declaration of Bloomon\Bloomapi3\Repositories\User\UserController::store() should be compatible with Bloomon\Bloomapi3\Http\Controllers\BaseController::store(Illuminate\Http\Request $request)', '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php', '6', array('file' => '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php')) in UserController.php line 6
Итак, как я могу напечатать подсказку для вставки UserFormRequest, все еще придерживаясь требования запроса BaseController? Я не могу заставить BaseController требовать UserFormRequest, потому что он должен работать для любого ресурса.
Я мог бы использовать интерфейс как RepositoryFormRequest
в обоих BaseController
и UserController
Но проблема в том, что Ларавел больше не вводит UserFormController
через его тип намекает зависимость зависимости инъекции.
В отличие от многих «реальных» объектно-ориентированных языков, этот тип проектирования хинтинга в переопределенных методах просто невозможен в PHP, см .:
class X {}
class Y extends X {}
class A {
function a(X $x) {}
}
class B extends A {
function a(Y $y) {} // error! Methods with the same name must be compatible with the parent method, this includes the typehints
}
Это приводит к той же ошибке, что и ваш код. Я бы просто не положил store()
метод в вашем BaseController
, Если вы чувствуете, что повторяете код, рассмотрите возможность введения, например, класса обслуживания или, возможно, признака.
Использование класса обслуживания
Ниже представлено решение, которое использует дополнительный класс обслуживания. Это может быть излишним для вашей ситуации. Но если вы добавите больше функциональности к StoringService
s store()
метод (например, проверка), это может быть полезно. Вы также можете добавить больше методов к StoringService
лайк destroy()
, update()
, create()
, но тогда вы, вероятно, захотите назвать сервис по-другому.
class StoringService {
private $repo;
public function __construct(Repository $repo)
{
$this->repo = $repo;
}
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
$service = new StoringService($this->repo); // Or put this in your BaseController's constructor and make $service a member variable
return $service->store($request);
}
}
Используя черту
Вы также можете использовать черту, но вы должны переименовать черту store()
метод тогда:
trait StoringTrait {
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
use {
StoringTrait::store as baseStore;
}
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
return $this->baseStore($request);
}
}
Преимущество этого решения заключается в том, что если вам не нужно добавлять дополнительные функции к store()
метод, вы можете просто use
черта без переименования и вам не нужно писать дополнительные store()
метод.
Использование наследования
На мой взгляд, наследование не очень подходит для повторного использования кода, который вам нужен здесь, по крайней мере, в PHP. Но если вы хотите использовать наследование только для этой проблемы повторного использования кода, укажите store()
метод в вашем BaseController
другое имя, убедитесь, что все классы имеют свои store()
метод и вызвать метод в BaseController
, Что-то вроде этого:
BaseController.php
/**
* Store a newly created resource in storage.
*
* @return Response
*/
protected function createResource(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
UserController.php
public function store(UserFormRequest $request)
{
return $this->createResource($request);
}
Вы можете переместить свою логику из BaseController в черту, сервис, фасад.
Вы не можете переопределить существующую функцию и заставить ее использовать другой тип аргумента, это может привести к поломке. Например, если вы позже напишите это:
function foo(BaseController $baseController, Request $request) {
$baseController->store($request);
}
Это порвало бы с твоим UserController
а также OtherRequest
так как UserController
надеется UserController
не OtherRequest
(который расширяет Request
и является действительным аргументом от foo()
перспективы).
Как уже упоминали другие, вы не можете делать то, что хотите, по целому ряду причин. Как уже упоминалось, вы можете решить эту проблему с чертами или аналогичными. Я представляю альтернативный подход.
Похоже, что вы пытаетесь следовать соглашению об именах, выдвинутому Laravel’s. RESTful Resource Controllers
, что отчасти заставляет вас использовать определенный метод на контроллере, в этом случае, store
,
Глядя на источник ResourceRegistrar.php
мы можем видеть это в getResourceMethods
метод, Laravel делает diff или пересекается с массивом параметров, который вы передаете, и против значений по умолчанию. Тем не менее, эти значения по умолчанию защищены и включают store
,
Это означает, что вы ничего не можете передать Route::resource
принудительно переопределить имена маршрутов. Итак, давайте исключим это.
Более простым подходом было бы просто настроить другой метод только для этого маршрута. Это может быть достигнуто путем:
Route::post('user/save', 'UserController@save');
Route::resource('users', 'UserController');
Примечание. Согласно документации, пользовательские маршруты должны предшествовать вызову Route :: resource.
Декларация UserController::store()
должен быть совместим с BaseController::store()
, что означает (среди прочего), что данные параметры для обоих BaseController
так же как UserController
должно быть точно так же.
На самом деле вы можете заставить BaseController требовать UserFormRequest, это не самое красивое решение, но оно работает.
Перезаписав, вы не сможете заменить Request
с UserFormRequest
так почему бы не использовать оба? Предоставление обоим методам необязательного параметра для введения UserFormRequest
объект. Что приведет к:
BaseController.php
class BaseController {
public function store(Request $request, UserFormRequest $userFormRequest = null)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
public function store(UserFormRequest $request, UserFormRequest $userFormRequest = null)
{
return parent::store($request);
}
}
Таким образом, вы можете игнорировать параметр при использовании BaseController::store()
и ввести его при использовании UserController::store()
,
Самым простым и чистым способом, который я нашел, чтобы обойти эту проблему, было префикс родительских методов с подчеркиванием. Например:
BaseController:
_store(Request $request) { ... }
_update(Request $request) { ... }
UserController:
store(UserFormRequest $request) { return parent::_store($request); }
update(UserFormRequest $request) { return parent::_update($request); }
Я чувствую, что создание поставщиков услуг является излишним. Здесь мы пытаемся обойти не принцип подстановки Лискова, а просто отсутствие правильного отражения PHP. Методы подсказок по типу сами по себе являются хаком.
Это заставит вас вручную реализовать store
а также update
в каждом дочернем контроллере. Я не знаю, мешает ли это вашему дизайну, но в моем случае я использую пользовательские запросы для каждого контроллера, так что мне все равно пришлось это делать.