Уровень 5: Подсказка типов класса FormRequest внутри контроллера, который расширяется от BaseController

у меня есть 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 через его тип намекает зависимость зависимости инъекции.

25

Решение

В отличие от многих «реальных» объектно-ориентированных языков, этот тип проектирования хинтинга в переопределенных методах просто невозможен в 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, Если вы чувствуете, что повторяете код, рассмотрите возможность введения, например, класса обслуживания или, возможно, признака.

Использование класса обслуживания

Ниже представлено решение, которое использует дополнительный класс обслуживания. Это может быть излишним для вашей ситуации. Но если вы добавите больше функциональности к StoringServices 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);
}
8

Другие решения

Вы можете переместить свою логику из BaseController в черту, сервис, фасад.

Вы не можете переопределить существующую функцию и заставить ее использовать другой тип аргумента, это может привести к поломке. Например, если вы позже напишите это:

function foo(BaseController $baseController, Request $request) {
$baseController->store($request);
}

Это порвало бы с твоим UserController а также OtherRequest так как UserController надеется UserControllerне OtherRequest (который расширяет Request и является действительным аргументом от foo() перспективы).

4

Как уже упоминали другие, вы не можете делать то, что хотите, по целому ряду причин. Как уже упоминалось, вы можете решить эту проблему с чертами или аналогичными. Я представляю альтернативный подход.

Похоже, что вы пытаетесь следовать соглашению об именах, выдвинутому 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.

4

Декларация 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(),

2

Самым простым и чистым способом, который я нашел, чтобы обойти эту проблему, было префикс родительских методов с подчеркиванием. Например:

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 в каждом дочернем контроллере. Я не знаю, мешает ли это вашему дизайну, но в моем случае я использую пользовательские запросы для каждого контроллера, так что мне все равно пришлось это делать.

2
По вопросам рекламы [email protected]