Haciendo del Desarrollo y la Arquitectura Web, ciencia y pasión.

Servicios Web Rest con Symfony

Vamos a describir en este articulo la creacion de un servicio web basado en peticiones REST con un servicio de Symfony. A lo largo y ancho de la web encontraremos infinidad de servicios parecidos que entregan los resultados en JSON, XML o texto plano. Los resultados de una operación pueden venir organizados en arrays listo, por ejemplo, para poblar un desplegable mediante una operacion AJAX. Podemos ver como Twitter tiene servicios muy parecidos al que vamos a crear que devuelven los resultados via json. Por ejemplo podemos ver la info que devuelev para un usuario :


https://api.twitter.com/1.1/users/show.json?screen_name=rsarver

{
  "profile_sidebar_fill_color": "F8FCF2",
  "profile_sidebar_border_color": "547980",
  "profile_background_tile": true,
  "name": "Ryan Sarver",
  "profile_image_url": "http://a0.twimg.com/profile_images/1777569006/image1327396628_normal.png",
  "created_at": "Mon Feb 26 18:05:55 +0000 2007",
  "location": "San Francisco, CA",
  "follow_request_sent": false,
  "profile_link_color": "547980",
  "is_translator": false,
  "id_str": "795649",
  "default_profile": false,
  "contributors_enabled": true,
  "favourites_count": 3162,
  "url": null,
  "profile_image_url_https": "https://si0.twimg.com/profile_images/1777569006/image1327396628_normal.png",
[...]
}

En este artículo no nos detendremos en detalles secundarios como instalación del composer, establecimiento de permisos en el sistema de archivos o creación de la bbdd. Empezaremos desde el principio, creando nuestro proyecto desde cero, en la version 2.7 que es la más actual.


daniel@pozuelito:/var/www$ symfony new wserb

 Downloading Symfony...
    4.97 MB/4.97 MB ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  100%

 Preparing project...

 ✔  Symfony 2.7.0 was successfully installed. Now you can:

    * Change your current directory to /var/www/wserb

    * Configure your application in app/config/parameters.yml file.

    * Run your application:
        1. Execute the php app/console server:run command.
        2. Browse to the http://localhost:8000 URL.

    * Read the documentation at http://symfony.com/doc

Con esto quedaria instalado nuestro proyecto totalmente limpio. Para la creacion de nuestro proyecto nos apoyaremos en el Bundle de FriendsOfSymfony para generar un API tipo Rest. Este Bundle necesita de un serializador. Instalaremos pues los siguientes Bundles:


composer require friendsofsymfony/rest-bundle "@dev"
composer require "jms/serializer-bundle" "@dev"

Empezamos a crear el Bundle que contendrá el código de nuestro servicio web:


daniel@pozuelito:/var/www/wserb$ php app/console generate:bundle

                                            
  Welcome to the Symfony2 bundle generator  
                                            


Your application code must be written in bundles. This command helps
you generate them easily.

Each bundle is hosted under a namespace (like Acme/Bundle/BlogBundle).
The namespace should begin with a "vendor" name like your company name, your
project name, or your client name, followed by one or more optional category
sub-namespaces, and it should end with the bundle name itself
(which must have Bundle as a suffix).

See http://symfony.com/doc/current/cookbook/bundles/best_practices.html#index-1 for more
details on bundle naming conventions.

Use / instead of \  for the namespace delimiter to avoid any problem.

Bundle namespace: wservice/wsBundle

In your code, a bundle is often referenced by its name. It can be the
concatenation of all namespace parts but it's really up to you to come
up with a unique name (a good practice is to start with the vendor name).
Based on the namespace, we suggest wservicewsBundle.

Bundle name [wservicewsBundle]: 

The bundle can be generated anywhere. The suggested default directory uses
the standard conventions.

Target directory [/var/www/wserb/src]: 

Determine the format to use for the generated configuration.

Configuration format (yml, xml, php, or annotation): yml

To help you get started faster, the command can generate some
code snippets for you.

Do you want to generate the whole directory structure [no]? yes

                             
  Summary before generation  
                             

You are going to generate a "wservice\wsBundle\wservicewsBundle" bundle
in "/var/www/wserb/src/" using the "yml" format.

Do you confirm generation [yes]? 

                     
  Bundle generation  
                     

Generating the bundle code: OK
Checking that the bundle is autoloaded: OK
Confirm automatic update of your Kernel [yes]? 
Enabling the bundle inside the Kernel: OK
Confirm automatic update of the Routing [yes]? 
Importing the bundle routing resource: OK

                                               
  You can now start using the generated code!  
                                               

Posteriormente vamos a generar las entidades sobre las que trabajara nuestro servicio. Generaremos una entidad y posteriormente crearemos la base de datos que dara soporte a nuestra entidad. Nuestra entidad, productos, muy sencilla, el id, obligarorio por Symfony, nombre y descripcion.


daniel@pozuelito:/var/www/wserb$ php app/console doctrine:generate:entity 

                                             
  Welcome to the Doctrine2 entity generator  
                                             


This command helps you generate Doctrine2 entities.

First, you need to give the entity name you want to generate.
You must use the shortcut notation like AcmeBlogBundle:Post.

The Entity shortcut name: wservicewsBundle
 The entity name must contain a : ("wservicewsBundle" given, expecting something like AcmeBlogBundle:Blog/Post) 
The Entity shortcut name: wservicewsBundle:Product

Determine the format to use for the mapping information.

Configuration format (yml, xml, php, or annotation) [annotation]: 

Instead of starting with a blank entity, you can add some fields now.
Note that the primary key will be added automatically (named id).

Available types: array, simple_array, json_array, object, 
boolean, integer, smallint, bigint, string, text, datetime, datetimetz, 
date, time, decimal, float, blob, guid.

New field name (press  to stop adding fields): name
Field type [string]: 
Field length [255]: 

New field name (press  to stop adding fields): Description
Field type [string]: text

New field name (press  to stop adding fields): 

Do you want to generate an empty repository class [no]? yes

                             
  Summary before generation  
                             

You are going to generate a "wservicewsBundle:Product" Doctrine2 entity
using the "annotation" format.

Do you confirm generation [yes]? 

                     
  Entity generation  
                     

Generating the entity code: OK

                                               
  You can now start using the generated code!  

Una vez creado el modelo procedemos a la creacion de la base de datos. Como tenemos configurado nuestro archivo de configuracion parameters.yml Symfony ya sabe el nombre que tendra. Con esto bastaran los dos siguientes comandos uno para crear la base de datos en nuestro servidor y otro para plasmar el esquema de la entidad.


php app/console doctrine:database:create
php app/console doctrine:schema:create

Si entramos en nuestra base de datos nos encontraremos que Symfony ha hecho todo el trabajo por nosotros creando las tablas de nuestra entidad.


mysql> use products
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+--------------------+
| Tables_in_products |
+--------------------+
| product               |
+--------------------+
1 row in set (0.00 sec)

mysql> 

Hasta aquí la parte de la base de datos, Symfony vuelve a nuestro auxilio y nos facilita las herramientas para crear el controlador para nuestro servicio. Usaremos ahora el formato rapido del comando.


daniel@pozuelito:/var/www/wserb$ php app/console generate:controller --no-interaction --controller=wservicewsBundle:Product

                         
  Controller generation  
                         

Generating the bundle code: OK

                                               
  You can now start using the generated code!  
 

Nuestro controlador quedaria de la siguiente manera, hay que notar que el controlador ahora heredara de FOSRestController, a diferencia de los controladores normales que lo haran de Controller:


namespace wservice\wsBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use FOS\RestBundle\Controller\FOSRestController;

class ProductController extends FOSRestController {
}

El siguiente paso sera configurar el archivo de rutas. Dentro de app/config/routing.yml


wservicews:
    resource: "@wservicewsBundle/Resources/config/routing.yml"
    prefix:   /api
    type: rest

Que nos llevara a la carpeta de recursos/config:


wserv_Product:
    type: rest
    prefix: /v1
    resource: wservice\wsBundle\Controller\ProductController
    name_prefix:  api_1_ 

Con esto queda definido el formato de la url de nuestro servicio sera de la forma /api/v1/products/1 Ahora nos resta dar contenido a nuestro controlador, crearemos un metodo precedido por el 'verbo' que podra ser get, post, etc, al estilo REST. Nuestro método esta basado en un servicio definido en el archivo de configuración services.yml


    public function getProductAction($id) {
        $product = $this->container
                ->get('wservice_ws.product.handler')
                ->get($id);

        return $product;
    }

Nuestro archivo de configuración de servicios app/config.yml, define el servicio y los parametros.


parameters:
    wservice_ws.product.handler.class: wservice\wsBundle\Services\ProductHandler
    wservice_ws.product.class: wservice\wsBundle\Entity\Product

services:
    wservice_ws.product.handler:
        class: %wservice_ws.product.handler.class%
        arguments: ["@doctrine.orm.entity_manager",%wservice_ws.product.class%]

Y finalmente nuestro servicio dentro del bundle wsBundle/Resources/Services/ProductHandler.php


class ProductHandler { //implements ProductHandlerInterface {

    public function __construct(ORM $om, $entityClass) {
        $this->om = $om;
        $this->entityClass = $entityClass;
        $this->repository = $this->om->getRepository($this->entityClass);
    }

    public function get($id) {
        return $this->repository->find($id);
    }

}

Con lo que el servicio:


http://ws.local/app_dev.php/api/v1/products/1.xml123423
       


http://ws.local/app_dev.php/api/v1/products/1.json


[{"id":1,"title":"234","body":"23"}]