Construyendo un RESTFul API con Symfony 2, Parte 2

June 18, 2021

En el post anterior Building a RESTFul API with Symfony 2, Part 1 se explicó de manera sencilla como instalar y configurar los bundles necesarios para la creación de nuestro API Rest con Symfony 2 y se detalló como estará estructurado los directorios para nuestra aplicación. Retomando un poco, la estructura de nuestro proyecto será de la siguiente manera:

  • ToDoExample/
    • backend/
    • frontend/

En esta segunda entrega de esta serie de posts, estaremos trabajando en el backend de nuestra aplicación y explicaremos paso a paso como configurar los métodos GET, POST, PUT y DELETE para implementar todo el CRUD. Para ello aprovecharemos todas las bondades que nos ofrece el bundle "FOSRestBundle" principalmente. Posteriormente estaremos elaborando una tercera entrega donde nos concentraremos en el desarrollo del frontend, el cual se llevará a cabo utilizando AngularJS y donde aprenderemos a como consumir los recursos de nuestro API REST.

Si lo deseas, todo el contenido de la primera parte y de este post se encuentra disponible en el siguiente enlace: https://github.com/javierugalde/Symfony-Api-Rest-Example

Como ya se mencionó en la primera parte, seguiremos los pasos para la creación de nuestra aplicación TO-DO, la cual nos permitirá crear, editar, gestionar y eliminar tareas, con un título, descripción y un estatus.

En la primera parte de este tutorial habíamos creado un nuevo bundle llamado TaskBundle. A continuación debemos cargar dicho bundle en nuestro kernel de Symfony: [prism:php] public function registerBundles() { $bundles = array( … new FOS\RestBundle\FOSRestBundle(), new JMS\SerializerBundle\JMSSerializerBundle(), new Nelmio\CorsBundle\NelmioCorsBundle(), new Rootstack\TaskBundle\RootstackTaskBundle(), ... ); ... } [/prism:php] Posterior a esto procederemos a editar nuestro archivo /app/config/routing.yml para cambiar la definición de nuestro nuevo controlador ya que el mismo debe ser de tipo REST y aprovecharemos para agregarle un prefix a la ruta. Para lograr esto debemos:

Sustituir este código: rootstack_task: resource: "@RootstackTaskBundle/Controller/" type: annotation prefix: /

Por este: rootstack_task: type: rest resource: "@RootstackTaskBundle/Controller/TaskController.php" prefix: /api

De esta manera accederemos a nuestros recursos utilizando como base la url: http://localhost/ToDoExample/backend/app_dev.php/api/{ruta_para_cada_recurso}

Configurando la Base de Datos

El primer paso a seguir antes de empezar a escribir código es configurar todo lo relacionado a nuestra base de datos, para ello debemos realizar lo siguiente:

  1. Editar el archivo parameters.yml, el cual se encuentra ubicado en la carpeta ToDoExample/backend/app/config/

    parameters: database_host: localhost database_port: null database_name: rest_symfony database_user: [your_database] database_password: [your_password] ...

  2. Luego es necesario correr el siguiente comando de Symfony para crear nuestra base de datos.

    $ php app/console doctrine:database:create

Generando la entidad Task

Lo siguiente que necesitamos hacer, es definir cuales serán los campos que estarán presentes en nuestra aplicación. Para este ejemplo basta con definir un título, una descripción y un campo que permita marcar si la tarea esta lista.

Así deberá lucir nuestra entidad Task

[prism:php] <?php // File: ToDoExample/backend/src/Rootstack/TaskBundle/Entity/Task.php

namespace Rootstack\TaskBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**

  • Task
  • @ORM\Table(name="task")
  • @ORM\Entity(repositoryClass="Rootstack\TaskBundle\Repository\TaskRepository") */ class Task { /**

    • @var int
    • @ORM\Column(name="id", type="integer")
    • @ORM\Id
    • @ORM\GeneratedValue(strategy="AUTO") */ private $id;

    /**

    • @var string
    • @ORM\Column(name="title", type="string", length=100) */ private $title;

    /**

    • @var string
    • @ORM\Column(name="description", type="string", length=255) */ private $description;

    /**

    • @var boolen
    • @ORM\Column(name="is_done", type="boolean") */ private $isDone;

    /**

    • Get id
    • @return integer */ public function getId() { return $this->id; }

    /**

    • Set title
    • @param string $title
    • @return Task */ public function setTitle($title) { $this->title = $title;

      return $this; }

    /**

    • Get title
    • @return string */ public function getTitle() { return $this->title; }

    /**

    • Set description
    • @param string $description
    • @return Task */ public function setDescription($description) { $this->description = $description;

      return $this; }

    /**

    • Get description
    • @return string */ public function getDescription() { return $this->description; }

    /**

    • Set isDone
    • @param boolean $isDone
    • @return Task */ public function setIsDone($isDone) { $this->isDone = $isDone;

      return $this; }

    /**

    • Get isDone
    • @return boolean */ public function getIsDone() { return $this->isDone; } } [/prism:php]

Finalizada la definición de nuestra entidad Task, procedemos a ejecutar el siguiente comando para crear el schema en nuestra base de datos:

$ php app/console doctrine:schema:update --force

Nota: Si todo hasta aquí va bien, ya debemos tener creada nuestra base de datos, con una tabla llamada Task la cual inicialmente está vacía. Si gustas puedes ingresar algunos registros para posteriormente ir probando la aplicación.

Creando un FormType para capturar y procesar datos.

Antes de crear nuestra clase Controller, necesitaremos crear un FormType. Muchos de ustedes se preguntaran: ¿Por qué debo crear un formulario?, pues simplemente para poder aprovechar el objeto FormType de Symfony y de esta manera capturar los parámetros enviados a nuestro método a través del objeto request, pasarle este objeto al formulario, y que este se encargue de cargar o editar el contenido de nuestra entidad task.

Así quedaría nuestro formulario TaskType: [prism:php] <?php // ToDoExample/Backend/src/Rootstack/TaskBundle/Form/Type/TaskType.php

namespace Rootstack\TaskBundle\Form\Type;

use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver;

class TaskType extends AbstractType { /**

  • @param FormBuilderInterface $builder
  • @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title') ->add('description') ->add('isDone') ; }

    /**

  • @param OptionsResolver $resolver */ public function configureOptions(OptionsResolver $resolver) {

    $resolver->setDefaults(
        [
            'csrf_protection' => false,
            'data_class' => 'Rootstack\TaskBundle\Entity\Task',
        ]
    );

    }

    /**

  • @return string */ public function getName() { return ""; }

}

[/prism:php]

Creando nuestro controlador

Ya hasta aquí tenemos la estructura de datos que tendrá nuestra aplicación y nuestro formulario. Es hora de crear nuestro controlador, que es donde estarán todas las funciones para cada método de nuestro Api Rest (GET, POST, PUT, DELETE).

El código de nuestro controlador debe ser el siguiente: [prism:php] <?php // ToDoExample/Backend/src/Rootstack/TaskBundle/Controller/TaskController.php

namespace Rootstack\TaskBundle\Controller;

use FOS\RestBundle\Controller\Annotations\Get; use FOS\RestBundle\Controller\Annotations\Post; use FOS\RestBundle\Controller\Annotations\Delete; use FOS\RestBundle\Controller\Annotations\Put; use FOS\RestBundle\Controller\Annotations\View; use FOS\RestBundle\Controller\FOSRestController; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\HttpFoundation\Request; use Rootstack\TaskBundle\Entity\Task; use Rootstack\TaskBundle\Form\Type\TaskType;

class TaskController extends FOSRestController {

/**
* Get all the tasks
 * @return array
 *
 * @View()
 * @Get("/tasks")
 */
public function getTasksAction(){

    $tasks = $this->getDoctrine()->getRepository("RootstackTaskBundle:Task")
        ->findAll();

    return array('tasks' => $tasks);
}

/**
 * Get a task by ID
 * @param Task $task
 * @return array
 *
 * @View()
 * @ParamConverter("task", class="RootstackTaskBundle:Task")
 * @Get("/task/{id}",)
 */
public function getTaskAction(Task $task){

    return array('task' => $task);

}

/**
 * Create a new Task
 * @var Request $request
 * @return View|array
 *
 * @View()
 * @Post("/task")
 */
public function postTaskAction(Request $request)
{
    $task = new Task();
    $form = $this->createForm(new TaskType(), $task);
    $form->handleRequest($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($task);
        $em->flush();

        return array("task" => $task);

    }

    return array(
        'form' => $form,
    );
}

/**
 * Edit a Task
 * Put action
 * @var Request $request
 * @var Task $task
 * @return array
 *
 * @View()
 * @ParamConverter("task", class="RootstackTaskBundle:Task")
 * @Put("/task/{id}")
 */
public function putTaskAction(Request $request, Task $task)
{
    $form = $this->createForm(new TaskType(), $task);
    $form->submit($request);
    $form->handleRequest($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();

        $em->persist($task);
        $em->flush();

        return array("task" => $task);
    }

    return array(
        'form' => $form,
    );
}

/**
 * Delete a Task
 * Delete action
 * @var Task $task
 * @return array
 *
 * @View()
 * @ParamConverter("task", class="RootstackTaskBundle:Task")
 * @Delete("/task/{id}")
 */
public function deleteTaskAction(Task $task)
{
    $em = $this->getDoctrine()->getManager();
    $em->remove($task);
    $em->flush();

    return array("status" => "Deleted");
}

} [/prism:php]

El código mostrado anteriormente, ya define los métodos para nuestro CRUD y a su vez definimos las rutas que necesitaremos para probar o consumir nuestro Api Rest.

Anotaciones de FOSRestBundle y ParamConverter

Como podrán observar, el controlador descrito anteriormente posee anotaciones en cada uno de sus métodos. Hay anotaciones de FOSRestBundle que son las que nos permiten convertir estos métodos del controlador como recursos del Api Rest. De esta manera se restringe la forma como son accedidos según el http method. Adicional a esto se utilizaron anotaciones de la clase ParamConverter. Ambos tipos de anotaciones serán descritas a continuación:

Anotaciones FOSRestBundle

  1. Para definir los métodos del controlador como recursos del Api Rest
  • @Get("/ruta_de_acceso") - Para obtener registros
  • @Post("/ruta_de_acceso") - Para crear registros
  • @Put("/ruta_de_acceso") - Para editar registros
  • @Delete("/ruta_de_acceso") - Para eliminar registros

Anotaciones de la clase ParamConverter de Symfony Esta anotación realiza un llamado para convertir los parámetros de la petición en objetos para luego ser inyectados como argumentos de los métodos del controlador

Ejemplo:

[prism:php] /**

  • Get a task by ID
  • @param Task $task
  • @return array
  • @ParamConverter("task", class="RootstackTaskBundle:Task") */ public function getTaskAction(Task $task){

    return array('task' => $task);

    } [/prism:php] Utilizando esta anotación, nos ahorramos una linea de código ya que no tendríamos que hacer uso del entity manager para ejecutar el médodo find() o cualquier otro método necesario para obtener un objeto de la entidad deseada.

Estaríamos ahorrándonos este paso: [prism:php] $this->getDoctrine()->getRepository("RootstackTaskBundle:Task")->find($task); [/prism:php]

Probando nuestro Api Rest

Ahora si, tenemos todo listo del lado de nuestro backend. Ya definimos nuestro Api Rest que nos servirá entonces como CRUD de nuestra aplicación.

Para proceder a probar nuestro Api Rest tenemos varias opciones:

  1. Utilizando Postman, una aplicación para Google Chrome que actúa como cliente http y que nos permite probar nuestro Api Rest, enviando peticiones para cada Http Method y con la posibilidad de enviar los respectivos parámetros en JSON. Puedes obtener más información desde este enlace: https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en
  2. Utilizando httpie, un cliente http por comando (CLI HTTP CLIENT), el cual nos permite probar nuestro Api Rest via terminal. Puedes obtener más información desde este enlace: https://github.com/jkbrzt/httpie
  3. Si utilizas phpStorrm, este permite descargar el plugin llamado REST Client. Es un plugin que funciona bastante parecido a Postman solo que estaría integrado en nuestro IDE de desarrollo.

Cualquiera de estas opciones funciona, solo es cuestión de gustos.

Para probar nuestro api rest estas serían las url de nuestros recursos con sus respectivos http method:

  1. GET http://localhost/ToDoExample/backend/app_dev.php/api/tasks [ Obtiene todas las tareas creadas ]
  2. GET http://localhost/ToDoExample/backend/app_dev.php/api/taskk/{id} [ Obtiene una tarea según su ID ]
  3. POST http://localhost/ToDoExample/backend/app_dev.php/api/task [ Crea una nueva tarea ]
  4. PUT http://localhost/ToDoExample/backend/app_dev.php/api/task/{id} [ Edita una tarea según su ID ]
  5. DELETE http://localhost/ToDoExample/backend/app_dev.php/api/task/{id] [ Elimina una tarea según su ID]

NOTA: Recuerda que debes ingresarle data inicialmente a la tabla para poder probar los métodos GET.

Vale la pena resaltar que para los métodos POST y PUT debemos establecer en el BODY de la petición a crear con nuestro cliente Rest, los parámetros: title, description y isDone ya que son los que se enviarán y serán recibidos por el objeto Request $request de Symfony.

Con esto ya tenemos todo lo referente al backend de nuestra aplicación y la definición de cada recurso de nuestro Api Rest.

¿Dónde nos encontramos hasta ahora?
  • Ya tenemos definida nuestra estructura en la base de datos.
  • Ya creamos nuestro controlador y mediante anotaciones definimos nuestros recursos del Api Rest.
  • Hasta ahora ya tenemos todo el backend de nuestro Api Rest listo para ser consumido por nuestro frontend.
¿Qué sigue?
  • En la próxima parte estaremos desarrollando el frontend de la aplicación. Para esto utilizaremos AngularJS y veremos como consumir los recursos de nuestro Api Rest previamente creados. Mientras tanto puedes ir practicando un poco más para mejorar tus habilidades en Symfony y si es posible, leer un poco sobre AngularJS para que obtengas conocimientos básicos de este framework javascript.

  • En otro post futuro también hablaremos del Security Bundle de Symfony a tomar en cuenta al momento de desarrollar los endpoints.