If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeEn 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 | |
'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
.
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 | |
'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
.
¿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 name
y 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.
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ámetroname
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
// 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
}
}
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:
Great course nonetheless!