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:
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 DatosEl 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:
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] ...
Luego es necesario correr el siguiente comando de Symfony para crear nuestra base de datos.
$ php app/console doctrine:database:create
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;
/**
@ORM\Entity(repositoryClass="Rootstack\TaskBundle\Repository\TaskRepository") */ class Task { /**
/**
/**
/**
/**
/**
@return Task */ public function setTitle($title) { $this->title = $title;
return $this; }
/**
/**
@return Task */ public function setDescription($description) { $this->description = $description;
return $this; }
/**
/**
@return Task */ public function setIsDone($isDone) { $this->isDone = $isDone;
return $this; }
/**
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 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',
]
);
}
/**
}
[/prism:php]
Creando nuestro controladorYa 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 ParamConverterComo 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
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] /**
@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 RestAhora 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:
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:
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?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.