Como ya se puede intuir hablaremos de cómo utilizamos en Bodaclick el potente sistema de
formularios de Symfony 2 para mapear las peticiones REST (PUT y POST) de nuestra API
(potenciada por FOSRestBundle) al modelo de datos pasando por DataTransformers y el
sistema de validación
7. deSymfony 2013Formularios y REST
● Web: symfony-madrid.es
● Twitter: @symfony_madrid
● Comunidad G+: Symfony Madrid
● Youtube: youtube.com/symfonymadrid
● Lista de correo: groups.google.com/group/symfony_madrid
7 / 62
8. deSymfony 2013Formularios y REST
WE WANT YOU!WE WANT YOU!
● Web: symfony-madrid.es
● Twitter: @symfony_madrid
● Comunidad G+: Symfony Madrid
● Youtube: youtube.com/symfonymadrid
● Lista de correo: groups.google.com/group/symfony_madrid
8 / 62
9. ¿Qué vamos a ver?
Breve introducción a REST
Cómo montar uno con Symfony2
FOSRestBundle
Uso de formularios en REST
Ejemplos de código
deSymfony 2013Formularios y REST 9 / 62
11. ¿Qué es REST?
“Architectural Styles and the Design of Network-
based Software Architectures” and describes a series
of constraints that exemplify how the web’s design
emerged utilizing the Hyper Text Transfer Protocol.”
by Roy Fielding’s Doctoral Thesis
deSymfony 2013Formularios y REST 11 / 62
12. ¿Qué es REST?
● Protocolo cliente/servidor sin estado
● Operaciones POST, PUT, GET y DELETE
● Sintaxis universal
● Hipermedios (HATEOAS)
deSymfony 2013Formularios y REST 12 / 62
13. ¿Qué es REST?
deSymfony 2013Formularios y REST 13 / 62
HATEOAS
(Hypermedia as the Engine of Application State)
{
nombre: 'Moises',
apellido: 'Gallego',
…..
direccion: {
calle: 'falsa',
numero: 123,
Uri: '/api/v1/addesses/1'
}
}
Charlie don't code
(http://charliedontcode.com/rest/2012/09/27/rest-apis-hateoas.html)
15. REST en Symfony2
El método Artesanal
REQUEST
Creación de rutas y métodos apropiados con el
Router de Symfony2
Sintaxis Universal
methods:
[GET, PUT, POST, DELETE]
deSymfony 2013Formularios y REST 15 / 62
16. REST en Symfony2
El método Artesanal
Ejemplo GET
get_resources:
path: /api/v1/resources
defaults: { _controller: AcmeDemoBundle:Main:getResources }
methods: [GET]
Ejemplo POST
post_resources:
path: /api/v1/resources
defaults: { _controller: AcmeDemoBundle:Main:postResources }
methods: [POST]
deSymfony 2013Formularios y REST 16 / 62
17. REST en Symfony2
El método Artesanal
CONTROLADOR HTTP
Deserialización del Request
Manejar cabeceras
HTTP Status Codes
deSymfony 2013Formularios y REST 17 / 62
18. REST en Symfony2
El método Artesanal
CONTROLADOR
Adaptamos la petición que recibimos cliente
Lógica de negocio
Manejar errores
Crear una vista
Pasar el modelo a la vista
deSymfony 2013Formularios y REST 18 / 62
19. REST en Symfony2
El método Artesanal
deSymfony 2013Formularios y REST 19 / 62
20. REST en Symfony2
El método Artesanal
deSymfony 2013Formularios y REST 20 / 62
21. REST en Symfony2
El método Artesanal
deSymfony 2013Formularios y REST 21 / 62
22. REST en Symfony2
El método Artesanal
deSymfony 2013Formularios y REST
Symfony REST and RAD
http://www.beberlei.de/talks/symfony-rest
22 / 62
25. FOSRestBundle
¿Qué ofrece?
●Capa de vista con formato agnóstico de salida
●Cargador de rutas adaptado
●Decodificación de peticiones HTTP y Accept Headers
●Control de excepciones a través de códigos de estado HTTP
●...
deSymfony 2013Formularios y REST 25 / 62
26. FOSRestBundle
REQUEST
Métodos a través de anotaciones
FOSRestBundleControllerAnnotationsGet
FOSRestBundleControllerAnnotationsPost
FOSRestBundleControllerAnnotationsPut
FOSRestBundleControllerAnnotationsDelete
deSymfony 2013Formularios y REST 26 / 62
27. Ejemplo
/**
* Lists resources.
*
* @Get("api/v1/public/resource/{id}")
*/
public function getResourceAction($id)
{
….
}
deSymfony 2013Formularios y REST
FOSRestBundle
REQUEST
FOSRestBundle
REQUEST
27 / 62
28. Decodificación de peticiones HTTP
@ParamFetcher
(FOSRestBundleRequestParamFetcher)
@QueryParam
(FOSRestBundleControllerAnnotationsQueryParam)
deSymfony 2013Formularios y REST
FOSRestBundle
REQUEST
28 / 62
30. Ejemplo QueryParam
/**
* Lists resources.
*
* @QueryParam(name="page", requirements="d+", default="1",
description="Page of the overview.")
*/
public function getResourceAction($page)
{
….
}
deSymfony 2013Formularios y REST
FOSRestBundle
REQUEST
FOSRestBundle
REQUEST
30 / 62
31. Podemos crear acciones en los controladores del tipo
getRecursoAction, postRecursoAction, etc y
automáticamente FOSRestBundle genera los métodos
y rutas e incluso crea automáticamente los plurales.
deSymfony 2013Formularios y REST
FOSRestBundle
Un poquito de magia
31 / 62
32. <?php
class UsersController
{
public function getUserAction($slug)
{} // "get_user" [GET] /users/{slug}
public function getUsersAction()
{} // "get_users" [GET] /users
public function postUsersAction()
{} // "post_users" [POST] /users
public function patchUsersAction()
{} // "patch_users" [PATCH] /users
deSymfony 2013Formularios y REST
FOSRestBundle
Un poquito de magia
32 / 62
33. Con una anotación del tipo
@RouteResource(“Recurso”) en el controlador
podemos crear acciones llamadas getAction,
postAction, etc y él se encarga de el resto
deSymfony 2013Formularios y REST
FOSRestBundle
Un poquito de magia
33 / 62
34. /**
* @RouteResource("User")
*/
class FooController
{
public function cgetAction()
{} // "get_users" [GET] /users
public function newAction()
{} // "new_users" [GET] /users/new
public function getAction($slug)
{} // "get_user" [GET] /users/{slug}
..
public function getCommentsAction($slug)
{} // "get_user_comments" [GET] /users/{slug}/comments
..
}
deSymfony 2013Formularios y REST
FOSRestBundle
Un poquito de magia
34 / 62
35. Mas sencillo aún que el Request
Creamos una vista
$view = FOSView::create();
La devolvemos
return $view->setStatusCode(Codes::HTTP_OK)->setData($data);
deSymfony 2013Formularios y REST
FOSRestBundle
RESPONSE
35 / 62
36. Ejemplo
public function getResourceAction($id)
{
$view = FOSView::create();
...
//cargamos una variable $data por ejemplo con Doctrine
$data = $em->getRepository('...Repository')
->findOneById($id);
...
return $view->setStatusCode(200)->setData($data);
}
deSymfony 2013Formularios y REST
FOSRestBundle
RESPONSE
36 / 62
38. ¿Y cómo tratamos lo que
recibimos por POST y PUT?
deSymfony 2013Formularios y REST
Formularios
38 / 62
39. Formularios
¿Que ventajas aportan?
Podemos usarlos como recursos e incluso subrecursos.
La carga de datos del request al modelo es automática a
través de los métodos Bind que implementa.
Simplifica el uso de validadores.
DataTransformers.
Uso parecido en el lado cliente
Eventos
deSymfony 2013Formularios y REST 39 / 62
42. Obtenemos el Request
public function postUsersAction(Request $request)
{
$view = FOSView::create();
$data = $request->request->all();
…
}
Formularios
Servidor
deSymfony 2013Formularios y REST 42 / 62
43. Adaptamos el Request
class RequestToNewDataTransformer
{
public static function transform(array $array, ...)
{
//Realizamos la transformación de los datos
return $newArray;
}
}
Formularios
Servidor
deSymfony 2013Formularios y REST 43 / 62
44. Adaptamos el Request
public function postUsersAction(Request $request)
{
$view = FOSView::create();
$data = $this->reMap(
$request->request->all(),
…
);
...
}
Formularios
Servidor
deSymfony 2013Formularios y REST 44 / 62
45. Creamos el formulario
Y lo validamos
public function postUsersAction(Request $request)
{
$view = FOSView::create();
…
$form = $this->createForm(
new $formType(),
$resource,
['validation_groups' => ['Create', 'Default']]
);
}
Formularios
Servidor
deSymfony 2013Formularios y REST 45 / 62
46. ¡Ojo!
El Required del FormType no hace una
validación real, solo se usa para realizar una
validación a través de HTML5, pero no en el
lado del servidor.
Formularios
Servidor
deSymfony 2013Formularios y REST 46 / 62
47. Procesamos el formulario
Y devolvemos la vista
public function postUsersAction(Request $request)
{
$view = FOSView::create();
…
$formHandler = new ResourceFormHandler($form, $data);
if ($formHandler->process()) {
return $view->setStatusCode(Codes::HTTP_CREATED);
}
return $view->setStatusCode(Codes::HTTP_BAD_REQUEST)
->setData($formHandler->serializeFormErrors());
}
Formularios
Servidor
deSymfony 2013Formularios y REST 47 / 62
49. Formularios
Cliente
El formulario se trata como un formulario normal de
Symfony2.
A la hora de enviarlo a la API lo convertimos a un array.
$modelFormClass->ToArray().
Guzzle para comunicación.
deSymfony 2013Formularios y REST 49 / 62
62. Gracias
deSymfony 2013Formularios y REST
Twitter: @moisesgallego
Slideshare: http://www.slideshare.net/moisesgallego
¿Qué te ha parecido?
Joind.in: https://joind.in/talk/view/8838
62 / 62
Notas del editor
Buenos días Gracias por asistir. Voy a hablaros de ...
Lo primero de todo es agradecer a todos nuestros patrocinadores el apoyo que dan a este evento, ya que sin ellos no sería posible
Trabajo en BDK Nuevo proyecto api Experiencias reales Compañeros otras charlas
Usuario twitter Usuario github.
Soy miembro de SF Madrid Reunión mensual Cañas
Cuentas mas activa lista de correo Videos en hangout
Así que no dejes de pasarte si eres de madrid o estás de paso.
Breve. Habrá amas charlas y no quiero ser pesado.
¿Que es REST?
¿en que consiste este sistema? ¿Que necesitamos?
Hipermedia como el motor de estado de la aplicación
¿Pero como podemos crear una api REST con Symfony2? Pues os voy a hablar de dos posibilidades, la primera es el que he llamado “El método artesanal”
.
Lo único que cambia es el método y la acción.
Decodificación del Request. Obtenemos toda la petición. Queremos saber qué quiere el cliente. Debemos responder con los estados correspondientes.
No trabajamos así en BDK Código imcompleto
Aquí cogemos el contenType y el formato del request para devolverle al controlador el tipo de vista que requiere el cliente.
Finalmente obtenemos los datos que solicita el cliente, creamos la vista si lo que quiere es un HTML y se los devolvemos, en caso de que quiera un JSON o XML usamos el serializer para devolver. Fijaos también como a parte de valernos del Response para devolver el objeto serializado tenemos que setear el estado de HTTP que corresponda.
Ya os he comentado anteriormente que nosotros no usamos esta forma “artesanal” de trabajar, el código que os he mostrado tampoco es nuestro. Así que os dejo un enlace a una presentación donde se profundiza bastante mas en esta forma de trabajar y donde se encuentra todo el código completo. No os preocupéis por copiar el enlace ahora, espero publicar cuanto antes esta charla en slideshare.
Ya hemos visto el método artesanal, ahora es hora de que veamos algo un poco más mágico. Nuestro camino de baldosas amarillas. El que nosotros hemos decidido seguir.
FOSRestBundle
Así lo hacemos nosotros No nos fiamos de la mágia.
Sencillo
A través de listener POST GET (filtros)
Opciones de los parámetros
Vamos a ver un poco de la mágia que os he comentado antes. Un ejemplo de esta magia se encuentra en el sistema de rutas.
Montar API con FOS sencillo No abusar de mágia Limitaciones en las rutas Da para otra charla
FOSRestUtil constantes
Se lo traga todo MongoDB arrayCollection, Posible hidratación
Solo usamos los dos primeros Sandbox Último inestable
Hasta ahora hemos visto como recibir las llamadas y como contestar, pero ¿Y como tratamos las llamadas POST y PUT? ¿Qué hacemos con los datos que recibimos en las llamadas?¿Como debemos tratarlos? Con formularios, o esa es la opción por la que nosostros nos hemos decantado.
Type fuera de controller Manejador Objeto de dominio Charla de Ignacio Velazquez
Hablemos ahora del flujo del lado servidor, de cómo podemos tratar los datos que hemos recibido por el request para poder adaptarlos para realizar la lógica de negocio necesaria. Tratar el modelo y devolver lo que tengamos que devolver al cliente.
En la mayoría de los casos, en los más comunes nos bastará con coger el request siimplemente. Si el modelo no es demasiado complejo y es fácil adaptar un modelo “cliente” que recibir posiblemente nos bastará con coger los datos del request.
Es el servidor el que dice como crear el request Modelo de datos complejo Objetos de dominio
Mapeador.
Explicar RECURSO. No entrar en los validadores.
OJO. Error común Lo cometí la primera vez. Estamos en REST/Server Curl / Guzzle No tiene por que SF2 cliente
¿Y en el lado cliente?
Recordad que es el servidor el que dice como debe ser la estructura El server espara siempre un array
Veamos un par de casos reales. Os voy a mostrar la parte esencial del código, la chicha, no todo el código.
Por ejemplo, En la versión 2.1 que es en la que desarrollamos esta funcionalidad, los formularios no pueden implementar el método PATCH. ¿Qué significa esto? Que cualquier dato que no le pasemos el sistema pensará que viene a nulo, por lo cual lo eliminará, cuando en realidad lo que queremos es que lo deje como estaba. ¿Como hemos implementado esto? Pues hemos creado un evento que salta justo antes de realizar el Bind del formulario. Carga dos arrays, el primero con los datos nuevos, el segundo llamando a una función unBind. Una vez tenemos los dos arrays los recoremos comparandolos y cambiamos solo los datos que realmente sean diferentes y distintos de nulo.
En la función unBind lo que hace es recorrer recursivamente el formulario y cargar un array a través del método getClientData() los datos de antes del Bind. Como veis la potencia que nos dan los eventos es enorme, pero por si aún no estáis convencidos os voy a enseñar un caso que programamos hace tan solo unos días, así que si veis algún refactor es normal, y me lo podéis decir para meterlo ;)
¿Que pasa por ejemplo cuando queremos pasar imágenes a la api? Ese es el caso que os voy a contar ahora para finalizar mostrando un poco de toda la potencia del sistema de formularios en una api. Vayamos primero a la parte del cliente. Lo que hemos hecho ha sido crear una función recursiva en el manejador del formulario. Lo que hacemos es recorrer todos los campos y coger los que son de tipo file. Los que son de tipo file los recargamos con el binario de la imagen. Los que son de tipo collection los pasamos de nuevo por la función, ya que podemos tener subformularios con imágenes, como por ejemplo en el caso real teníamos hasta un tercer nivel de formulario con listado de imágenes. Una vez tenemos todo el formulario con los binarios se los enviamos mediante un array al servidor.
Esperamos un array, sin tipado.
Pues muy sencillo, para poder filtrarlo. Al igual que hicimos en el cliente montamos un evento en el preBind.
En ese evento lo que hacemos es coger todos los que sean de tipo TextFile y llamar al servicio que manipula el binario, guardando de nuevo en el formulario antes del bind la cadena de texto con el nombre del fichero. Alguno pensará el ver el ćodigo ¿Por qué no es recurivo al igual que en cliente?¿Que pasa con los campos de segundo y tercer nivel? Pues muy sencillo el sistema de formularios lo hace por nosotros, ya que al tratarse de un evento de formuario se lanzará para el formulario padre y paras sus hijos y nietos. Aquí también debemos tener en cuenta que si viene a nulo hay que tener en cuenta el valor anterior, ya que no podemos obligar al cliente a que pase siempre el binario en cada actualización
Aquí podéis ver como se crea el subscriber y como se le pasa el servicio, así como se setea el tipo de campo a las imágenes. ¿Sencillo verdad? Pero... ¿y que pasa cuando respondemos al cliente? Recordad que en el cliente el campo es de tipo file, si le devuelvo un texto con el nombre del fichero saltará un error. Y eso lo digo por experiencia ;)
Pues aquí entra otra de las ventajas del uso de formularios. Los transformadores. Lo que decidimos crear fue transformador, uno muy senciĺlo. La transformación es, venga lo que venga mete un null en el campo y la inversa es que simplemente devuelva el dato del campo, en nuestro caso el binario.
Finalmente añadimos el transformador a los campos de tipo file y se acabó.
Como hemos visto durante este rato, ya sea a través de un camino de tierra como pueda ser el crear una api desde 0 o siguiendo un camino mágico de baldosas amarillas con algo de magía y acompañados de amigos como ApiDoc, serializer y el sistema de formularios. Symfony2 es una opción muy válida, potente y aunque a veces compleja es una opción muy a tener en cuenta para este tipo de trabajos, a la misma altura o incluso por encima de otros. Muchas gracias.
¿Preguntas? Gracias.
Aquí os dejo de nuevo mis datos por si queréis consultarme cualquier cosa, mi cuenta de slideshare por si queréis consultar la ponencia. Por otro lado este año la organización ha creado una cuenta de joind para que podáis opinar. Cualquier critica es bienvenida. Gracias de nuevo a todos.