php - method - update request laravel
Laravel 5: sugerencia de tipo de una clase FormRequest dentro de un controlador que se extiende desde BaseController (5)
A diferencia de muchos lenguajes orientados a objetos "reales", este tipo de diseño de sugerencias tipo en métodos anulados simplemente no es posible en PHP, ver:
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
}
Esto produce el mismo tipo de error que tu código. Simplemente no pondría un método store()
en tu BaseController
. Si sientes que estás repitiendo el código, considera introducir, por ejemplo, una clase de servicio o tal vez un rasgo.
Usando una clase de servicio
Debajo de una solución que hace uso de una clase de servicio extra. Esto podría ser excesivo para su situación. Pero si agrega más funcionalidad al StoringService
store()
StoringService
(como la validación), podría ser útil. También puede agregar más métodos al StoringService
como destroy()
, update()
, create()
, pero luego probablemente desee nombrar el servicio de manera diferente.
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);
}
}
Usando un rasgo
También puede usar un rasgo, pero debe cambiar el nombre del método store()
del rasgo y luego:
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);
}
}
La ventaja de esta solución es que si no tiene que agregar funcionalidad extra al método store()
, puede use
el rasgo sin renombrar y no tiene que escribir un método extra store()
.
Usando herencia
En mi opinión, la herencia no es tan adecuada para el tipo de reutilización de código que necesita aquí, al menos no en PHP. Pero si solo quiere usar la herencia para este problema de reutilización de código, proporcione al método store()
en su BaseController
otro nombre, asegúrese de que todas las clases tengan su propio método store()
y llame al método en el BaseController
. Algo como esto:
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);
}
Tengo un BaseController
que proporciona la base para la mayoría de los métodos HTTP para mi servidor API, por ejemplo, el método de la 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);
}
Luego extiendo en este BaseController
en un controlador más específico, como el UserController
, así:
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
}
Esto funciona genial Sin embargo, ahora quiero extender UserController
para inyectar la nueva clase FormRequest
Laravel 5, que se ocupa de cosas como la validación y autenticación para el recurso de User
. Me gustaría hacerlo así, sobrescribiendo el método de tienda y utilizando la inyección de dependencia de sugerencia tipo Laravel para su clase de Solicitud de forma.
UserController.php
public function store(UserFormRequest $request)
{
return parent::store($request);
}
Donde UserFormRequest
extiende desde Request
, que a su vez se extiende desde 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''
];
}
}
El problema es que BaseController
requiere un objeto Illuminate/Http/Request
mientras que paso un objeto UserFormRequest
. Por lo tanto, me sale este error:
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
Entonces, ¿cómo puedo tipear hint para insertar UserFormRequest mientras sigo cumpliendo con los requisitos de solicitud de BaseController? No puedo forzar que BaseController requiera UserFormRequest, porque debería funcionar para cualquier recurso.
Podría usar una interfaz como RepositoryFormRequest
tanto en el BaseController
como en el UserController
, pero entonces el problema es que Laravel ya no inyecta el UserFormController
través de su tipo que indica la inyección de dependencia.
Como otros han mencionado, no puede hacer lo que quiere hacer por una serie de razones. Como se mencionó, puede resolver este problema con rasgos o similar. Estoy presentando un enfoque alternativo.
En una suposición, parece que estás tratando de seguir la convención de nomenclatura presentada por los RESTful Resource Controllers
de Laravel, que te está forzando a usar un método particular en un controlador, en este caso, store
.
Mirando la fuente de ResourceRegistrar.php
podemos ver que en el método getResourceMethods
, Laravel hace una diferencia o se cruza con la matriz de opciones que pasa y contra los valores predeterminados. Sin embargo, los valores predeterminados están protegidos e incluyen la store
.
Lo que esto significa es que no se puede pasar nada a Route::resource
para forzar la anulación de los nombres de las rutas. Así que descartemos eso.
Un enfoque más simple sería simplemente configurar un método diferente solo para esta ruta. Esto se puede lograr haciendo:
Route::post(''user/save'', ''UserController@save'');
Route::resource(''users'', ''UserController'');
Nota: Según la documentación, las rutas personalizadas deben ser anteriores a la llamada de recurso Ruta ::.
La declaración de UserController::store()
debería ser compatible con BaseController::store()
, lo que significa (entre otras cosas) que los parámetros dados tanto para BaseController
como para UserController
deberían ser exactamente los mismos.
De hecho, puedes forzar al BaseController a requerir un UserFormRequest, no es la solución más bonita, pero funciona.
Al sobrescribir no hay forma de que pueda reemplazar la Request
con UserFormRequest
, entonces ¿por qué no utilizar ambos? Dando a ambos métodos un parámetro opcional para inyectar el objeto UserFormRequest
. Lo que resultaría en:
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);
}
}
De esta forma puede ignorar el parámetro cuando usa BaseController::store()
e inyectarlo cuando usa UserController::store()
.
La forma más fácil y limpia que encontré para eludir ese problema fue prefijar los métodos principales con un guión bajo. Por ejemplo:
BaseController:
-
_store(Request $request) { ... }
-
_update(Request $request) { ... }
UserController:
-
store(UserFormRequest $request) { return parent::_store($request); }
-
update(UserFormRequest $request) { return parent::_update($request); }
Siento que crear proveedores de servicios es una exageración. Lo que intentamos eludir aquí no es el principio de sustitución de Liskov, sino simplemente la falta de una reflexión PHP adecuada. Los métodos de sugerencia de tipo son, en sí mismos, un hackeo después de todo.
Esto te obligará a implementar manualmente una store
y update
en cada controlador secundario. No sé si eso es molesto para su diseño, pero en el mío, uso solicitudes personalizadas para cada controlador, así que tuve que hacerlo de todos modos.
Puede mover su lógica de BaseController a rasgo, servicio, fachada.
No puede anular la función existente y forzarla a utilizar un tipo de argumento diferente, rompería cosas. Por ejemplo, si luego escribes esto:
function foo(BaseController $baseController, Request $request) {
$baseController->store($request);
}
OtherRequest
con su UserController
y OtherRequest
porque UserController
espera UserController
, no OtherRequest
(que extiende Request
y es un argumento válido desde la perspectiva de foo()
).