Continuamos con esta serie de posts donde explico de manera detallada cómo construir un RESTful API con Symfony 4 + JWT.
En el **post anterior, explico brevemente lo que es RESTful** y su principal ventaja al momento de implementar esta arquitectura en cada uno de nuestros desarrollos, así como también diseñamos nuestra estructura de datos a tomar en cuenta. También definimos las URI´s que nuestro API va a proveer para consumir los servicios.
También, diseñamos la estructura que tendrá nuestras entidades dentro de la aplicación y las rutas que estarán accesibles por nuestro API para el consumo de los servicios RESTful que definiremos más adelante.
Una vez diseñada de forma sencilla lo que queremos lograr con nuestra aplicación, vamos a proceder a empezar a armar las piezas necesarias para completar poco a poco lo anteriormente planeado. En este post abarcaremos lo siguiente:
Instalación de Symfony 4
Instalación de bundles y componentes necesarios para nuestra aplicación.
Creación de las entidades.
1. Instalación de Symfony 4
Con la llegada de Symfony 4, ya no se utilizará el instalador de Symfony que usábamos para crear nuevas instancias del framework. Ahora, utilizaremos para generar el skeleton de una aplicación Symfony 4, donde inicialmente se instalará lo básico y necesario y que luego, a medida que vayamos requiriendo, iremos instalando nuevos componentes y paquetes utilizando las recetas de Symfony Flex.
Symfony Flex se basa en la composición en vez de herencia. En si, es un simple pero poderoso plugin de composer que permite instalar y auto configurar componentes y paquetes.
Antes de comenzar, quiero aclarar que esta serie de post esta diseñada principalmente para reproducirse en ambientes bajo macOS y Linux. En Windows la cosa cambia un poco por el tema de la consola de comandos, al menos de que utilicen el bash de linux que se puede instalar desde Windows 10.
Para instalar Symfony 4 lo primero que debemos hacer es situarnos sobre el directorio donde queremos que se genere la estructura de nuestro proyecto, generalmente suele ser la raíz de nuestro web server, en mi caso seria algo como:
cd /home/fjugaldev/Sites
Luego podemos proceder a ejecutar el comando de composer para generar el skeleton de Symfony 4.
composer create-project symfony/skeleton my_kanban
Esto generará entonces la estructura de Symfony en un directorio llamado “my_kanban”, ustedes pueden cambiar este último parámetro por el que deseen.
Si todo sale bien, veremos al final de todo el proceso el siguiente mensaje:

Con esto tenemos creado correctamente el skeleton de Symfony 4 para nuestra aplicación
2. Instalación de bundles y componentes necesarios para nuestra aplicación.
Ahora procederemos a instalar los componentes de Symfony y de terceros, necesarios para la creación de nuestro RESTful API. Los componentes a instalar son:
symfony/orm-pack: Paquete ORM para instalar el bundle de Doctrine.
lexik/jwt-authentication-bundle: Bundle para proteger nuestros servicios mediante JSON Web Tokens.
lcobucci/jwt: Libreria complementaria para utilizar con lexik/jwt-authentication-bundle para proveer de muchísimos más encoders para los json web tokens a generar
jms/serializer-bundle: Bundle que permite serializar nuestra data de salida en un formato personalizado (xml, json, entre otros).
friendsofsymfony/rest-bundle: Bundle para convertir nuestros métodos dentro de los controladores, en recursos o servicios http (GET, POST, PUT, entre otros) utilizando anotaciones.
sensio/framework-extra-bundle: Bundle necesario por FOSRestBundle para el soporte de rutas.
nelmio/api-doc-bundle: Bundle para generar documentación de nuestro RESTful API.
Componente Twig de Symfony (requerido para utilizar nelmio/api-doc-bundle)
Componente Asset de Symfony (requerido para utilizar nelmio/api-doc-bundle)
Para instalar este listado de bundles y componentes ejecutaremos los siguientes comandos:
**composer req orm**
composer req jwt-auth
composer require lcobucci/jwt
composer config extra.symfony.allow-contrib true
composer require jms/serializer-bundle
composer require friendsofsymfony/rest-bundle
composer require sensio/framework-extra-bundle
composer require nelmio/api-doc-bundle
composer req twig
composer require symfony/asset
Hasta aquí hemos instalado todo lo necesario para seguir adelante con el desarrollo de nuestro RESTful API, pero seguramente ustedes se preguntarán, si Symfony instala un skeleton básico del framework, ¿En que momento se instalaron los componentes del framework necesarios para poder llevar a cabo nuestro proyecto?, por ejemplo el componente de routing, security, framework, entre otros.
Aquí es donde entra en escena nuestro nuevo protagonista, Symfony Flex, ya que gracias a el y al uso de las recetas, es capaz de saber que componentes son requeridos por los bundles a instalar y se encarga de descargarlos, instalarlos y configurarlos por nosotros, aquí es donde hace magia Symfony 4.
Recordando un poco lo nuevo de Symfony 4 tenemos que la nueva estructura de directorios queda inicialmente de la siguiente manera:
├── bin/
│ └── console
├── config/
│ ├── autoload.php
│ ├── AppKernel.php
│ ├── config/
│ └── Resources/
│ └── views/
├── public/
├── src/
├── Controllers/
└── Entity/
├── var/
│ ├── cache/
│ └── logs/
└── vendor/
Y luego al ir añadiendo código propio pueden crearse nuevos directorios como por ejemplo:
├── bin/
│ └── console
├── config/
│ ├── autoload.php
│ ├── AppKernel.php
│ ├── config/
│ └── Resources/
│ └── views/
├── public/
├── src/
├── Controllers/
└── Entity/
├── templates/
├── translations/
├── var/
│ ├── cache/
│ └── logs/
└── vendor/
Otro aspecto a resaltar es que Symfony recomienda como buena práctica, poner toda la lógica de nuestra aplicación en el directorio de src y sin necesidad de crear un bundle base como anteriormente solíamos usar el famoso AppBundle/. De hecho ahora los namespaces dentro de este directorio estarán precedidos por App\, Ejemplo: App\Controller, App\Entity.
Y por último y no menos importante, quiero recordar otro cambio significativo, y es que ahora nuestro controlador frontal fue renombrado a index.php, por lo que ya no existen los archivos app.php y app_dev.php, que entre otras cosas, servía para diferenciar el entorno en el que estaríamos ejecutando nuestra aplicación.
Dicho esto, surge un nuevo archivo ubicado en la raíz del proyecto, llamado .env en el cual se definen, entre otras cosas, el entorno ( APP_ENV), nuestro hash secreto ( APP_SECRET), los datos de acceso a nuestra base de datos, entre otras cosas más.
3. Creación de las entidades.
Ahora procederemos a crear las entidades necesarias para poder terminar de configurar y desarrollar nuestro RESTful API el cual proveerá un conjunto de endpoints básicos para simular nuestro tablero Kanban.
Antes de crear nuestras entidades, vamos a configurar nuestro proyecto para definir los datos de acceso a la base de datos que utilizaremos para el API. Para lograr esto, hay que editar el archivo .env ubicado en la raiz del proyecto.
Ubicamos el parámetro DATABASE_URL y reemplazaremos los datos de acceso a nuestro servidor MySQL y el nombre de la base de datos a utilizar.
Originalmente el parámetro DATABASE_URL viene por defecto de la siguiente manera:
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
En mi caso lo definí asi:
DATABASE_URL=mysql://root:123456@127.0.0.1:3306/my_kanban_d
Seguidamente, ejecutamos el siguiente comando para decirle a doctrine que cree la base de datos en caso de que no la hayamos creado anteriormente:
php bin/console doctrine:database:create
Ahora podemos proceder a crear nuestras entidades y generar dicho esquema en la base de datos que vamos a utilizar para el proyecto. En el post anterior, diseñamos las entidades que usaremos en nuestra aplicación, retomando lo definido anteriormente, serian las siguientes:
User: Almacenará los usuarios de la aplicación.
Board: Almacenará los tableros creados por cada usuario.
Task: Almacenará las tareas definidas para cada board y el estado en que se encuentra cada una.
Una vez implementada la estructura de datos diseñada previamente en archivos de clases para definir nuestras entidades, dejo a continuación el código de cada una. Vale la pena destacar que nuestras entidades deben residir en el directorio src/Entity
Entidad User.php:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
use Symfony\Component\Security\Core\User\UserInterface;
use JMS\Serializer\Annotation as Serializer;
class User implements UserInterface
{
protected $id;
protected $name;
protected $email;
protected $username;
protected $salt;
protected $password;
protected $plainPassword;
protected $roles = [];
protected $createdAt;
protected $updatedAt;
protected $boards;
public function __construct()
{
$this->boards = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function setEmail($email)
{
$this->email = $email;
return $this;
}
public function getEmail()
{
return $this->email;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
return $this;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($plainPassword)
{
$this->plainPassword = $plainPassword;
$this->password = null;
}
public function setRoles($roles)
{
$this->roles = $roles;
return $this;
}
public function getRoles()
{
return ["ROLE_USER"];
}
public function getSalt() {}
public function eraseCredentials() {}
public function getCreatedAt()
{
return $this->createdAt;
}
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt()
{
return $this->updatedAt;
}
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
public function updatedTimestamps()
{
$dateTimeNow = new DateTime('now');
$this->setUpdatedAt($dateTimeNow);
if ($this->getCreatedAt() === null) {
$this->setCreatedAt($dateTimeNow);
}
}
public function getBoards()
{
return $this->boards->toArray();
}
}
Entidad Board.php:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
use JMS\Serializer\Annotation as Serializer;
class Board
{
protected $id;
protected $name;
protected $user;
protected $createdAt;
protected $updatedAt;
protected $tasks;
public function __construct()
{
$this->tasks = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getUser()
{
return $this->user;
}
public function setUser($user)
{
$this->user = $user;
return $this;
}
public function getCreatedAt()
{
return $this->createdAt;
}
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt()
{
return $this->updatedAt;
}
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
public function updatedTimestamps()
{
$dateTimeNow = new DateTime('now');
$this->setUpdatedAt($dateTimeNow);
if ($this->getCreatedAt() === null) {
$this->setCreatedAt($dateTimeNow);
}
}
public function getTasks()
{
return $this->tasks;
}
}
Entidad Task.php:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
use JMS\Serializer\Annotation as Serializer;
class Task
{
protected $id;
protected $title;
protected $description;
protected $board;
protected $status;
protected $priority;
protected $createdAt;
protected $updatedAt;
public function getId()
{
return $this->id;
}
public function getTitle()
{
return $this->title;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
}
public function getBoard()
{
return $this->board;
}
public function setBoard($board)
{
$this->board = $board;
}
public function getStatus()
{
return $this->status;
}
public function setStatus($status)
{
$this->status = $status;
}
public function getPriority()
{
return $this->priority;
}
public function setPriority($priority)
{
$this->priority = $priority;
}
public function getCreatedAt()
{
return $this->createdAt;
}
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
}
public function getUpdatedAt()
{
return $this->updatedAt;
}
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
}
public function updatedTimestamps()
{
$dateTimeNow = new DateTime('now');
$this->setUpdatedAt($dateTimeNow);
if ($this->getCreatedAt() === null) {
$this->setCreatedAt($dateTimeNow);
}
}
}
Lo siguiente a realizar es indicarle a Doctrine que reproduzca nuestra estructura de entidades en el esquema de nuestra base de datos, para esto, hay que ejecutar el siguiente comando:
php bin/console doctrine:schema:update
Ya con esto tenemos lista nuestra estructura de datos y podemos entonces enfocarnos en configurar e implementar nuestro RESTful API
Hasta aquí dejaré este post para no extenderlo demasiado. Puedes ir a Como construir un **RESTful API con Symfony 4 + JWT — Parte 3**, para continuar con esta serie de post.
Si te gustó este post, ayúdame a que pueda servirle a muchas más personas, compartiendo mis contenidos en tus redes sociales.
Espero que este post haya sido de gran ayuda para ti, y como siempre, cualquier inquietud o duda que tengas, puedes contactarme por cualquiera de las vías disponibles, o dejando tus comentarios al final de este post. También puedes sugerir que temas o post te gustaría leer a futuro.