Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Trucos de serialización

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

En cierto modo, hemos engañado al sistema para que permita un campo textDescription cuando enviamos datos. Esto es posible gracias a nuestro método setTextDescription(), que ejecutanl2br() en la descripción que se envía a nuestra API. Esto significa que el usuario envía un campo textDescription cuando edita o crea un tesoro... pero recibe un campo description cuando lo lee.

... lines 1 - 34
class DragonTreasure
{
... lines 37 - 93
#[Groups(['treasure:write'])]
public function setTextDescription(string $description): self
{
$this->description = nl2br($description);
return $this;
}
... lines 101 - 150
}

Y eso está totalmente bien: está permitido tener diferentes campos de entrada frente a campos de salida. Pero sería un poco más guay si, en este caso, ambos se llamaran simplementedescription.

SerializedName: Controlar el nombre del campo

Entonces... ¿podemos controlar el nombre de un campo? Por supuesto que sí Lo hacemos, como ya habrás previsto, mediante otro maravilloso atributo. Éste se llamaSerializedName. Pásale description:

... lines 1 - 34
class DragonTreasure
{
... lines 37 - 93
#[Groups(['treasure:write'])]
public function setTextDescription(string $description): self
{
$this->description = nl2br($description);
return $this;
}
... lines 101 - 150
}

Esto no cambiará cómo se lee el campo, pero si actualizamos los documentos... y miramos la ruta PUT... ¡sí! Ahora podemos enviar un campo llamado description.

Argumentos del constructor

¿Qué pasa con los argumentos del constructor en nuestra entidad? Cuando hacemos una petición a POST, por ejemplo, sabemos que utiliza los métodos setter para escribir los datos en las propiedades.

Ahora prueba esto: busca setName() y elimínalo. Luego ve al constructor y añade allí un argumentostring $name en su lugar. A continuación, di $this->name = $name.

... lines 1 - 35
class DragonTreasure
{
... lines 38 - 67
public function __construct(string $name)
{
$this->name = $name;
$this->plunderedAt = new \DateTimeImmutable();
}
... lines 73 - 160
}

Desde una perspectiva orientada a objetos, el campo puede pasarse cuando se crea el objeto, pero después es de sólo lectura. Diablos, si quisieras ponerte elegante, podrías añadir readonly a la propiedad.

Veamos qué aspecto tiene esto en nuestra documentación. Abre la ruta POST. ¡Parece que aún podemos enviar un campo name! Haz la prueba pulsando "Probar"... y añadamos un Giant slinky que ganamos a un gigante de la vida real en... una partida de póquer bastante tensa. Es bastante valioso, tiene un coolFactor de 8, y dale undescription. Veamos qué ocurre. Pulsa "Ejecutar" y... ¡ha funcionado! Y podemos ver en la respuesta que se ha fijado el name. ¿Cómo es posible?

Bueno, si bajas y miras la ruta PUT, verás que aquí también se anuncia name. Pero... sube a buscar el id del tesoro que acabamos de crear - para mí es el 4, pon el 4 aquí para editarlo... y luego envía sólo el campo nombre para cambiarlo. Y... ¡no cambió! Sí, igual que con nuestro código, una vez creado un DragonTreasure, el nombre no se puede cambiar.

Pero... ¿cómo hizo la petición a POST para establecer el nombre... si no hay ningún establecedor? La respuesta es que el serializador es lo suficientemente inteligente como para establecer los argumentos del constructor... si el nombre del argumento coincide con el nombre de la propiedad. Sí, el hecho de que el argumento se llame namey la propiedad también se llame name es lo que hace que esto funcione.

Observa: cambia el argumento a treasureName en ambos lugares:

... lines 1 - 35
class DragonTreasure
{
... lines 38 - 67
public function __construct(string $name)
{
$this->name = $name;
$this->plunderedAt = new \DateTimeImmutable();
}
... lines 73 - 160
}

Ahora, gira, actualiza y comprueba la ruta POST. El campo ha desaparecido. API Platform ve que tenemos un argumento treasureName que podría enviarse, pero como treasureName no corresponde a ninguna propiedad, ese campo no tiene ningún grupo de serialización. Así que no se utiliza. Lo cambiaré de nuevo a name:

... lines 1 - 35
class DragonTreasure
{
... lines 38 - 67
public function __construct(string $name)
{
$this->name = $name;
$this->plunderedAt = new \DateTimeImmutable();
}
... lines 73 - 160
}

Al utilizar name, mira la propiedad name, y lee sus grupos de serialización.

Args del constructor opcionales frente a obligatorios

Sin embargo, sigue habiendo un problema con los argumentos del constructor que debes tener en cuenta. Actualiza la documentación.

¿Qué pasaría si nuestro usuario no pasara ningún name? Pulsa "Ejecutar" para averiguarlo. De acuerdo Obtenemos un error con un código de estado 400... pero no es un error muy bueno. Dice

No se puede crear una instancia de App\Entity\DragonTreasure a partir de datos serializados porque su constructor requiere que el parámetro name esté presente.

Eso es... en realidad demasiado técnico. Lo que realmente queremos es permitir que la validación se encargue de esto... y hablaremos de la validación en breve. Pero para que la validación funcione, el serializador tiene que poder hacer su trabajo: tiene que poder instanciar el objeto:

... lines 1 - 35
class DragonTreasure
{
... lines 38 - 67
public function __construct(string $name = null)
{
$this->name = $name;
$this->plunderedAt = new \DateTimeImmutable();
}
... lines 73 - 160
}

Vale, inténtalo ahora... ¡mejor! Vale, es peor: un error 500, pero lo arreglaremos con la validación dentro de unos minutos. El caso es que el serializador ha sido capaz de crear nuestro objeto.

A continuación: Para ayudarnos mientras desarrollamos, vamos a añadir un rico conjunto de accesorios de datos. Luego jugaremos con una gran función que API Platform nos ofrece gratuitamente: la paginación

Leave a comment!

2
Login or Register to join the conversation
Julien-M Avatar
Julien-M Avatar Julien-M | posted hace 6 meses

The code in the SerializedName part doesn't seem to be the right one since this attribute is nowhere to be seen.
I suppose it should be like:

    #[Groups(['treasure:write'])]
    #[SerializedName('description')]
    public function setTextDescription(string $description): self

Great course nonetheless!

Reply

Hey,

Whoops. Sorry for that, somehow several code blocks on that page were duplicated. I already fixed them. Thanks for the report!

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.0", // v3.0.8
        "doctrine/annotations": "^1.0", // 1.14.2
        "doctrine/doctrine-bundle": "^2.8", // 2.8.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.0
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.64.1
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.15.3
        "symfony/asset": "6.2.*", // v6.2.0
        "symfony/console": "6.2.*", // v6.2.3
        "symfony/dotenv": "6.2.*", // v6.2.0
        "symfony/expression-language": "6.2.*", // v6.2.2
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.3
        "symfony/property-access": "6.2.*", // v6.2.3
        "symfony/property-info": "6.2.*", // v6.2.3
        "symfony/runtime": "6.2.*", // v6.2.0
        "symfony/security-bundle": "6.2.*", // v6.2.3
        "symfony/serializer": "6.2.*", // v6.2.3
        "symfony/twig-bundle": "6.2.*", // v6.2.3
        "symfony/ux-react": "^2.6", // v2.6.1
        "symfony/validator": "6.2.*", // v6.2.3
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.0
        "symfony/yaml": "6.2.*" // v6.2.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.2.*", // v6.2.1
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/stopwatch": "6.2.*", // v6.2.0
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.4
        "zenstruck/foundry": "^1.26" // v1.26.0
    }
}
userVoice