Servicios REST usando Silex micro-framework 2/3

Dando continuidad al artículo anterior sobre servicios REST usando el micro-framework Silex, hoy hablaremos sobre la implementación del código que fue publicado en GitHub como base de este proyecto.

NOTA: En este artículo veremos lo esencial del código usado para este
proyecto pero se aconseja primeramente una lectura de la documentación
oficial de Silex
. En caso de conocer como funciona Symfony2 esto te
será muy familiar.

Objetivo y alcance del Proyecto

El proyecto que usaremos se basará en crear una tabla de comentarios y realizar servicios REST para lograr un CRUD (create, read, update, delete) de la misma.

NOTA: La sentencia SQL para crear la tabla y otras utilidades las
podrán encontrar en la WIKI del proyecto cuya dirección se encuentra
en el archivo README.md en la raíz del proyecto.

Tendremos 4 servicios REST definidos en el proyecto:

  • /ver-comentarios.json: Nos devolverá un listado de comentarios existentes en la tabla en formato JSON. Se ejecuta con el método GET de HTTP.

  • /crear-comentario.html: Nos permitirá enviar los datos para crear un comentario dentro de nuestra tabla. Se ejecuta con el método POST de HTTP.

  • /actualizar-comentario/{id}.html: Nos permitirá enviar los datos para modificar el contenido de un comentario definido por {id}. Se ejecuta con el método PUT de HTTP.

  • /eliminar-comentario/{id}.html: Nos permitirá eliminar de la tabla el comentario definido por {id}. Se ejecuta con el método DELETE de HTTP.

En los cuatro casos explicados arriba se procesará el pedido y devolverá información. Hay que recordar que de los 4 métodos HTTP vistos, solo el GET tiene la idea de devolver información solicitada y por ese motivo lo usaremos para el servicio ver-comentarios que devolverá un listado de comentarios.

Siempre se devolverán codigos de respuesta de Apache para indicar el estado del proceso y los headers del response, información que nos será útil para consumir los servicios con un cliente REST.

El alcance de este proyecto será hasta la creación del servidor REST y no así del cliente que consumirá los servicios. Este último será el objetivo de otro Artículo.

Para probar los servicios usaremos el programa CURL que nos permitirá obtener el resultado de los servicios. Los ejemplos de la utilización del comando CURL para probar los servicios se encuentran también en la WIKI de GitHub.

NOTA: Para este proyecto estoy usando ubuntu 11.10 y se necesita tener
instalado el paquete de CURL que puede ser descargado con sudo apt-get
install curl
.

Para proteger el acceso a los servicios REST haremos que se deba acceder por medio de un usuario y contraseña a los mismos.

Configuraciones iniciales

Una vez descargado el proyecto en zip o mediante el comando “git clone git@github.com:micayael/com.micayael.blog.ServidorRestSilex.git“, renombraremos el directorio a “ServidorRestSilex”.

Para asegurarnos que el framework silex se encuentre actualizado podemos ejecutar dentro de la carpeta del proyecto

$ php vendor/silex.phar check
You are using the latest Silex version.

En caso que nos diga que hay una nueva versión podemos ejecutar el siguiente comando para que nos actualice por Internet a la última versión

$ php vendor/silex.phar update

Una vez que tenemos esto, podremos encontrar en el archivo README.md la dirección de la WIKI de GitHub donde se encuentran ciertas explicaciones útiles, entre ellas el código para crear un Virtual Host de Apache para probar el proyecto. Lo que tendremos que hacer será agregar ese código a la configuración del Apache y podremos ingresar desde el navegador usando la URL: http://local.ServidorRestSilex/ o lo que sería lo mismo http://local.ServidorRestSilex/index.php que nos deberá mostrar un error “No route found for GET /” porque no habrá una ruta creada.

Ejemplo de pantalla de error 404

NOTA: No hay que olvidar agregar una línea al archivo hosts (en ubuntu
/etc/hosts) para indicar que esa URL no sea buscada en Internet sino
que lo busque en localhost

127.0.0.1 local.ServidorRestSilex

Estructura de archivos y carpetas del proyecto

El proyecto consta de 3 carpetas básicamente siguiendo la misma temática de Symfony2:

  • vendor/: Carpeta donde tendremos el framework silex y las librerías de terceros
  • silex.phar: el empaquetado del framework. El .phar vendría a ser un empaquetador para PHP usando la misma idea de los archivos .jar de JAVA

  • doctrine-common y doctrine-dbal: Archivos para utilizar Doctrine dentro de nuestro proyecto. Estos archivos se obtienen de la carpeta “vendor/” de un proyecto Symfony2 descargado. Descargar la versión estándar.

  • web/: Archivos públicos del proyecto como las páginas que serán accedidas por los usuarios clientes (controladores frontales), imágenes, css, js, etc.

  • index.php: Archivo de ingreso al proyecto. Podríamos llegar a crear más archivos como estos si se necesita más adelante. Este archivo sería un controlador frontal conocido dentro de los proyectos Symfony2.

  • .htaccess: Nos permitirá obviar en la URL el nombre del index.php. Para que esto funcione se debe tener activo el mod_rewrite del Apache.

  • src/: Archivo del código del proyecto

  • app.php: los controladores frontales (index.php) invocarán a este archivo para centralizar las importaciones necesarias y levantar configuraciones propias del proyecto. Este archivo importará los otros 4 contenidos en esta misma carpeta

  • bootstrap.php: este archivo será ejecutado para levantar el framework y las configuraciones vinculadas al mismo

  • config.php: Archivo para definir constantes de configuración

  • util.php: Archivo de utilidades para el proyecto

  • controllers.php: Aquí se encontrarán los actions a ser ejecutados

Entendamos los archivos

Para permitir el versionamiento de los archivos, en caso de modificaciones para ir mejorando el proyecto base, las explicaciones en detalle se encuentran dentro del código mismo de los archivos por lo que pasaremos a continuación a entender que hace cada archivo ya a un nivel más de arquitectura del proyecto.

Archivo: src/util.php

Este archivo contendría funciones útiles para poder reutilizarlas. En un principio tenemos que entender que usaremos JSON para retornar datos al cliente en caso de ser necesarios como el ejemplo de ver-comentarios.json por lo que tendremos que aplicar siempre la función utf8_encode() a fin de no tener problemas con el código devuelto. Este archivo contendrá una función utf8_converter() que podrá recibir un array y ejecutará el utf8_encode() a cada valor del mismo en forma recursiva.

Archivo: src/Entities/Comment.php

Este archivo lo usaremos para simular la abstracción de la base de datos y será una clase que nos permitirá crear sentencias SQL para no agregar más código a nuestras acciones dentro del archivo src/controllers.php, permitiéndonos abstraernos de esto al momento de trabajar con los servicios. En un principio esta clase contiene 5 métodos para trabajar con la tabla comments:

  • getInsertSQL(): Retorna el SQL para insert
  • getUpdateSQL(): Retorna el SQL para update
  • getDeleteSQL(): Retorna el SQL para delete
  • find(): Retorna el SQL para buscar un comentario
  • findAll(): Retorna el SQL para obtener todos los comentarios

Archivo: web/index.php

Este archivo conocido generalmente como “controlador frontal” será el archivo al que se accederá como boca de entrada principal de la aplicación y será el que derivará a los servicios.

Contiene la importación de todo el resto del proyecto, las configuraciones que queramos hacer mediante el contenedor $app y finalmente la ejecución de la aplicación.

El contenedor $app nos permite almacenar datos que luego usaremos durante el proyecto. Se debería pensar como si se tratara de un array que nos permite cargar datos para luego usarlos.

Entre el código de este archivo podemos notar que definimos las siguientes configuraciones:

  • $app['debug']: Esta configuración puede contener un valor booleano. En caso de ser true nos muestra mucha más información de errores por ejemplo. Es muy útil cuando desarrollamos la aplicación pero es altamente recomendable ponerlo como false cuando pasemos nuestra aplicación a producción ya que revelería información útil para que personas malintencionadas ataquen nuestro servidor.
  • $app['auth.user']: Almacenaremos aquí el usuario para acceder al servicio.
  • $app['auth.pass']: Contendrá la contraseña para acceder al servicio.

Archivo:src/app.php

Este archivo será el que creará realmente la aplicación silex y utilizaremos 3 métodos bien interesantes:

  • $app->before(): Se ejecutará antes de cualquier otra acción y por lo tanto lo utilizaremos para controlar que nos haya sido enviado el usuario y contraseña por medio de HTTP Basic Authentication. Obtendrá los valores enviados que vienen dentro del array superglobal $_SERVER que puede ser accedido con Silex por medio de $request->server->get() y los comparará con el usuario y contraseña que hemos almacenado en el archivo web/index.php. En caso de no corresponder retornará el código de error HTTP 403 – Unauthorized. De lo contrario continuará. Ver más información.
  • $app->after(): Se ejecutará después de cualquier acción y a modo de ejemplo lo uso para evaluar un valor {format} que viene como parámetro a fin de devolver el CONTENT-TYPE más adecuado. Ver más información.

  • $app->error(): Nos permitirá gestionar los errores que no los hemos manejado con un try…catch. En caso de estar con $app[‘debug’] como true significará que dejaremos al framework mostrarnos toda la información posible para detectar mejor el error pero si ya estamos en producción mostremos mensajes definidos por nosotros mismos. Ver más información.

Archivo: src/bootstrap.php

Este archivo es el boot loader del proyecto, es decir el que se encarga de cargar el framework y asignar las configuraciones del mismo. A diferencias de las configuraciones definidas en src/app.php, aquí se definen las configuraciones vinculadas al framework Silex como por ejemplo la configuración para la utilización de Doctrine y demás servicios mencionados en el apartado “Build-in Service Providers” en la documentación oficial.

Archivo: src/controllers.php

Este archivo contendrá la programación en sí de los servicios o acciones a ejecutarse y son definidos como siguen:

  • $app->get(‘/ver-comentarios.{format}’, …): Como vemos este servicio será llamado usando el método GET por lo que en este ejemplo será el único que podremos probarlo por medio del navegador directamente accediendo a la URL http://local.ServidorRestSilex/ver-comentarios.json lo cual sería lo mismo que ingresa a http://local.ServidorRestSilex/index.php/ver-comentarios.json
  • $app->post(‘/crear-comentario.{format}’, …): Este servicio será invocado usando el método POST y servirá para crear un nuevo comentario

  • $app->put(‘actualizar-comentario/{id}.{format}’, …): Este servicio invoca al método PUT para actualización de datos

  • $app->delete(‘eliminar-comentario/{id}.{format}’, …): Nos permitirá eliminar un comentario y es por eso la utilización del método DELETE

El parámetro {format} será evaluado en el método $app->after() utilizado en el archivo src/app.php como lo comentamos más arriba.

Para realizar las pruebas de estos servicios recuerden que utilizaremos la funcionalidad del comando CURL. Los ejemplos de como invocarlos se encuentran en la WIKI y la explicación en detalle se encuentra en el código por medio de comentarios.

Para ejecutar los ejemplo abramos nuestras consolas y ejecutemos el código mostrado en la WIKI y veremos el resultado del HEADER perteneciente al response, el código de HTTP y el resultado devuelto.

Espero que les haya servido este artículo y espero sus comentarios y opiniones para ir mejorando nuestro proyecto base.

Puedes ver el siguiente capítulo sobre esta serie de REST & Silex aquí donde hablo sobre la creación del cliente.

10 comentarios en “Servicios REST usando Silex micro-framework 2/3”

  1. Estupendo artículo, de mucha utilidad, la verdad. Un apunte, para los “vagos” a los que no les apetezca usar CURL para probar una API, hay un plugin para Firefox, que se llama REST Client (también está disponible como aplicación de escritorio y creo que también para Chrome) que te permite realizar las llamadas a la API.

  2. Muy buen post.
    La única cosa que no termina de convencerme es el “hardcodeo” del usuario y la contraseña en el index.php, aunque entiendo que nada te evitaría tener la lógica de contraste de datos en el before de la aplicación y que utilizase la bbdd para la autenticación ¿crees que sería correcto hacerlo de ese modo?

    1. @penguinjournals me parece interesante tu comentario. Te comento como lo pensé yo.

      Evalué esa posibilidad que mencionas pero intenté centralizar las variables de configuración en un solo archivo y pensé en el index.php por ser el punto de acceso. Aunque ciertamente en el bootstrap.php también estamos usando los datos de conexión de la base de datos los separé para que la configuración del “framework” sea levantada en el bootstrap.php y la configuración que el programador utilizaría para varios lugares estén en el index.php en un lugar diferente. Esto ya que también evalué meter todo en el bootstrap.php.

      Me parecería más interesante, analizando nuevamente, crear un archivo config.php que sea importado antes de la línea “require_once (BASE_DIR . ‘/src/bootstrap.php’);” del archivo app.php como si se tratase de un .ini o un archivo properties. Ahí podríamos tener todos los valores de configuración de la aplicación dentro de constantes y de esa manera lo centralizaríamos mejor.

      No quisiera meter estos datos dentro de la base datos, así evito que cada vez que alguien acceda tenga que consultar a la base sino que ya lo tenga incluído. Creo que sería mejor para la performance. Pero centralizamos los datos “hardcodeados” como lo comentas cosa que me parece muy bien.

      Voy a hacer estos cambios y los subo al repositorio.

      Coméntame que te parece.

  3. Muy interesante. En realidad yo te planteaba el escenario mas bien pensando en crear una API con autenticación y roles basada en usuario. No se si me explico, lo siento. Algo así como “Yo como usuario puedo ver si un libro está cogido en la biblioteca” pero “Yo como bibliotecario puedo ver si el libro está cogido y por quien” y que la aplicación controle si puedo realizar una petición a cierta url de la api.

    Pero bueno, centralizar todo lo hardcodeado desde luego parece una buena idea a para empezar 😉

    1. Entiendo tu punto y técnicamente sin problema se podría hacer. Lo que habría que evaluar es si estaría bien, tomando en cuenta la idea de los servicios, meter esa lógica dentro…

      Mi lógica me dice que cuando tenemos un servicio que devuelve datos es justamente ese su objetivo.

      Voy a investigar ese tema un poco más a fondo, gracias por la pregunta 😉

  4. muy buen post me esta ayudando mucho

    pienso que se podria crear un servicio aparte para la autenticacion y que el mismo te devuelva las credenciales para saber a que servicios tiene acceso ese usuario o aplicacion, eso te permitiria darle seguridad a varios servicios, creo que es mas o menos lo que plantea penguinjournals

  5. Hola, muchas gracias por el esfuerzo, solo tengo una duda si quiero acceder a este servicio web por ejemplo el get que devuelve todos los comentarios, pero desde mi aplicacion symfony2 como los recibo? http://local.ServidorRestSilex/ver-comentarios.json

    Estan en formato json como los convierto por ejemplo en un array de entidades comentarios? o si deseo un solo comentario como lo convierto en una entidad para pasarselo a la vista desde mi controlador de symfony2 ? por favor necesito ayuda con esto tendras algun ejemplo en github o que me puedas facilitar de como consumir el servicio? Muchas gracias de antemano.

    1. Hola Andre.

      La verdad que estoy detrás de un ejemplo justamente de como consumir ese servicio. Con el desarrollo de la guía de Symfony2 estuve un poco atrasado con el ejemplo.

      Lo que podrías hacer es utilizar las funciones de CURL para PHP y con eso podrías consumir el JSON. Una vez que tengas el JSON, procesándolo con un json_encode() tendrás el array que lo representa. Una vez que tengas el array, ver alguna manera de simular los data hydrators de doctrine. La manera más sencilla y entendible sería recorrer el array e ir creando objetos cargándolos en un array y devolverlo.

      Haz me saber si lo entendiste y mientras yo buscaré algún otra manera 🙂

    2. Andre, me ha gustado bastante este tema en realidad y lo estuve pensando mejor.

      Mi opinión realmente es obtener el json, convertirlo a array y utilizarlo así. No lo convertiría a un array de Entidades ya que hay un tema bastante importante. Al ser un cliente quizá ni siquiera tendría librerías para conexión a la base de datos.

      Estoy preparando un nuevo ejemplo para el cual voy a crear un nuevo artículo pero te dejo el adelanto: https://github.com/micayael/com.micayael.blog.ClienteRestSilex

Comenta este artículo