Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Nuestro primer ApiRecurso

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Pregunta: ¿has ido alguna vez a la tienda y has comprado accidentalmente demasiado queso? Es la historia de mi vida. O tal vez tengas el problema contrario: ¡estás organizando una gran fiesta y no tienes suficiente queso! Esta es nuestra nueva idea del billón de dólares: una plataforma en la que puedes vender ese trozo extra de Brie que nunca te acabaste o comprar un camión lleno de camembert a alguien que va demasiado excitado al mercado del queso. Sí, es lo que el mundo está pidiendo: un mercado de queso entre iguales. Lo llamamos: Cheese Whiz.

Para el sitio, quizá lo convirtamos en una aplicación de una sola página construida en React o Vue... o quizá sea un poco más tradicional: una mezcla de páginas HTML y JavaScript que haga peticiones AJAX. Y tal vez incluso tengamos una aplicación móvil. En realidad no importa, porque todas estas opciones significan que tenemos que ser capaces de exponer nuestra funcionalidad principal a través de una API.

Generar la entidad

Pero para empezar: olvídate de la API y haz como si este fuera un proyecto Symfony normal y aburrido. El paso 1 es... hmm, probablemente crear algunas entidades de la base de datos.

Abramos nuestro archivo .env y modifiquemos el DATABASE_URL. Mi equipo utilizaroot sin contraseña... y qué tal cheese_whiz para el nombre de la base de datos.

33 lines .env
... lines 1 - 30
DATABASE_URL=mysql://root:@127.0.0.1:3306/cheese_whiz
... lines 32 - 33

También puedes crear un archivo .env.local y anular allí DATABASE_URL. Usarroot y sin contraseña es bastante estándar, así que me gusta añadirlo a .env y confirmarlo como predeterminado.

¡Genial! A continuación, en tu terminal, ejecuta

composer require maker:1.11 --dev

para obtener el MakerBundle de Symfony... para que podamos ser perezosos y generar nuestra entidad. Cuando termine, ejecuta

php bin/console make:entity

Llama a la primera entidad: CheeseListing, que representará cada "queso" que esté a la venta en el sitio. Pulsa enter y... ¡oh! te pide:

¿Marcar esta clase como un recurso de la Plataforma API?

MakerBundle pregunta esto porque se ha dado cuenta de que la Plataforma API está instalada. Di "sí". Y antes de añadir ningún campo, ¡vamos a ver qué ha hecho! En mi editor, ¡sí! Esto creó los habituales CheeseListing y CheeseListingRepository. No hay nada especial. Ahora mismo, la única propiedad que tiene la entidad es id. Entonces, ¿qué nos dio la respuesta afirmativa a la pregunta sobre el recurso de la Plataforma API? Esta pequeña anotación de aquí: @ApiResource

... lines 1 - 7
/**
* @ApiResource()
... line 10
*/
class CheeseListing
... lines 13 - 111

La verdadera pregunta es: ¿qué activa eso? Lo veremos pronto.

Pero primero, vamos a añadir algunos campos. Veamos, cada listado de quesos probablemente necesite un title, string, 255, no anulable, un description, que será un gran campo de texto, price, que haré un integer -este será el precio en céntimos- por lo que 10 dólares serían 1000, createdAt como datetime y un isPublishedbooleano. Bien: ¡buen comienzo! Pulsa enter para terminar.

¡Enhorabuena! Tenemos una clase CheeseEntity perfectamente aburrida: 7 propiedades con getters y setters

... lines 1 - 11
class CheeseListing
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $title;
/**
* @ORM\Column(type="text")
*/
private $description;
/**
* @ORM\Column(type="integer")
*/
private $price;
/**
* @ORM\Column(type="datetime")
*/
private $createdAt;
/**
* @ORM\Column(type="boolean")
*/
private $isPublished;
... lines 45 - 109
}

A continuación, genera la migración con:

php bin/console make:migration

¡Oh! ¡Migraciones no está instalado todavía! No hay problema, sigue la recomendación:

composer require migrations:2.0.0

Pero antes de intentar generarla de nuevo, tengo que asegurarme de que mi base de datos existe:

php bin/console doctrine:database:create

Y ahora ejecuta make:migration:

php bin/console make:migration

Vamos a comprobarlo para asegurarnos de que no hay ninguna sorpresa:

CREATE TABLE cheese_listing...

... lines 1 - 12
final class Version20190508193750 extends AbstractMigration
{
... lines 15 - 19
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE cheese_listing (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) NOT NULL, description LONGTEXT NOT NULL, price INT NOT NULL, created_at DATETIME NOT NULL, is_published TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
}
... lines 27 - 34
}

¡Sí! ¡Tiene buena pinta! Cierra eso y ejecuta:

php bin/console doctrine:migrations:migrate

¡Saluda a tu API!

¡Genial! Llegados a este punto, tenemos una entidad Doctrine completamente tradicional, excepto por ésta, la anotación @ApiResource(). Pero esto lo cambia todo. Esto le dice a la Plataforma API que quieres exponer esta clase como una API.

Compruébalo: actualiza la página /api. ¡Vaya! ¡De repente esto dice que tenemos cinco nuevas rutas, u "operaciones"! Una operación GET para recuperar una colección de CheeseListings, una operación POST para crear una nueva, GET para recuperar una solaCheeseListing, DELETE para... ya sabes... borrar y PUT para actualizar una CheeseListing existente. ¡Eso es un CRUD completo, basado en la API!

Y esto no es sólo documentación: estas nuevas rutas ya funcionan. Vamos a comprobarlos a continuación, a saludar a algo llamado JSON-LD y a aprender un poco sobre cómo funciona esta magia entre bastidores.

Leave a comment!

Este tutorial funciona muy bien para Symfony 5 y la Plataforma API 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.3
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.10.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.5
        "nesbot/carbon": "^2.17", // 2.19.2
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/console": "4.2.*", // v4.2.12
        "symfony/dotenv": "4.2.*", // v4.2.12
        "symfony/expression-language": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/flex": "^1.1", // v1.17.6
        "symfony/framework-bundle": "4.2.*", // v4.2.12
        "symfony/security-bundle": "4.2.*|4.3.*", // v4.3.3
        "symfony/twig-bundle": "4.2.*|4.3.*", // v4.2.12
        "symfony/validator": "4.2.*|4.3.*", // v4.3.11
        "symfony/yaml": "4.2.*" // v4.2.12
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.11", // v1.11.6
        "symfony/stopwatch": "4.2.*|4.3.*", // v4.2.9
        "symfony/web-profiler-bundle": "4.2.*|4.3.*" // v4.2.9
    }
}