validar update method formulario form example crear php laravel dependency-injection laravel-5 ioc-container

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() ).