Buy Access to Course
34.

Escribir en una relación de colección

|

Share this awesome video!

|

Keep on Learning!

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

Login Subscribe

Estamos tan cerca de reimplementar completamente nuestra API utilizando estas clases personalizadas... ¡Qué emoción!

Vamos a ejecutar todas las pruebas para ver en qué punto estamos.

symfony php bin/phpunit

Y... todo pasa excepto una. Esta prueba problemática esUserResourceTest::testTreasuresCannotBeStolen. ¡Vamos a comprobarlo!

Abre tests/Functional/UserResourceTest.php y buscatestTreasuresCannotBeStolen(). Aquí lo tienes.

91 lines | tests/Functional/UserResourceTest.php
// ... lines 1 - 10
class UserResourceTest extends ApiTestCase
{
// ... lines 13 - 56
public function testTreasuresCannotBeStolen(): void
{
$user = UserFactory::createOne();
$otherUser = UserFactory::createOne();
$dragonTreasure = DragonTreasureFactory::createOne(['owner' => $otherUser]);
$this->browser()
->actingAs($user)
->patch('/api/users/' . $user->getId(), [
'json' => [
'username' => 'changed',
'dragonTreasures' => [
'/api/treasures/' . $dragonTreasure->getId(),
],
],
'headers' => ['Content-Type' => 'application/merge-patch+json']
])
->assertStatus(422);
}
// ... lines 76 - 89
}

Leamos la historia. Actualizamos un usuario e intentamos cambiar su propiedad dragonTreasurespara que contenga un tesoro propiedad de otra persona. La prueba busca un código de estado 422 -porque queremos evitar el robo de tesoros-, pero la prueba falla con un 200.

Pero aparte de todo el tema del robo, ésta es la primera prueba que hemos visto que escribe en un campo de relación de colección. Y ése es un tema interesante por sí solo.

¿Evitar los campos de colección escribibles?

En primer lugar, si puedes, te recomiendo que no permitas que los campos de relación de colección como éste sean escribibles. Es decir, puedes hacerlo... pero añade complejidad. Por ejemplo, como muestra esta prueba, tenemos que preocuparnos de cómo establecer la propiedad dragonTreasurescambia el propietario de ese tesoro. Y ya existe otra forma de hacerlo: haz una petición patch() a este tesoro y... cambia la propiedadowner. ¡Sencillo!

Pero, si aún quieres permitir que tu relación de colección sea escribible en tu sistema DTO, bien, aquí tienes cómo hacerlo. Es broma, no está tan mal.

Probar la escritura de la colección

Empieza por duplicar esta prueba. Cámbiale el nombre a testTreasuresCanBeRemoved. Lo he escrito mal - el mío dice cannot, que es lo contrario de lo que quiero probar - así que asegúrate de que lo pones bien en tu código.

111 lines | tests/Functional/UserResourceTest.php
// ... lines 1 - 10
class UserResourceTest extends ApiTestCase
{
// ... lines 13 - 56
public function testTreasuresCanBeRemoved(): void
{
// ... lines 59 - 74
}
// ... lines 76 - 109
}

Ahora podemos arreglarlo un poco. Haz que el primer $dragonTreasure pertenezca a$user. Luego crea un segundo $dragonTreasure también propiedad de $user, pero no necesitaremos una variable para él... ya lo verás. Por último, añade un tercer $dragonTreasurellamado $dragonTreasure3 que sea propiedad de $otherUser.

119 lines | tests/Functional/UserResourceTest.php
// ... lines 1 - 10
class UserResourceTest extends ApiTestCase
{
// ... lines 13 - 56
public function testTreasuresCanBeRemoved(): void
{
$user = UserFactory::createOne();
$otherUser = UserFactory::createOne();
$dragonTreasure = DragonTreasureFactory::createOne(['owner' => $user]);
DragonTreasureFactory::createOne(['owner' => $user]);
$dragonTreasure3 = DragonTreasureFactory::createOne(['owner' => $otherUser]);
// ... lines 64 - 82
}
// ... lines 84 - 117
}

Así que tenemos tres dragonTreasures, dos propiedad de $user, y uno de$otherUser. Aquí abajo, parcheamos para modificar $user. Elimina username -no nos importa- y envía dos dragonTreasures: el primero y el tercero:/api/treasures/ $dragonTreasure3->getId() .

119 lines | tests/Functional/UserResourceTest.php
// ... lines 1 - 10
class UserResourceTest extends ApiTestCase
{
// ... lines 13 - 56
public function testTreasuresCanBeRemoved(): void
{
$user = UserFactory::createOne();
$otherUser = UserFactory::createOne();
$dragonTreasure = DragonTreasureFactory::createOne(['owner' => $user]);
DragonTreasureFactory::createOne(['owner' => $user]);
$dragonTreasure3 = DragonTreasureFactory::createOne(['owner' => $otherUser]);
$this->browser()
->actingAs($user)
->patch('/api/users/' . $user->getId(), [
'json' => [
'dragonTreasures' => [
'/api/treasures/' . $dragonTreasure->getId(),
'/api/treasures/' . $dragonTreasure3->getId(),
],
],
'headers' => ['Content-Type' => 'application/merge-patch+json']
])
// ... lines 76 - 81
;
}
// ... lines 84 - 117
}

Vamos a comprobar dos cosas. En primer lugar, que se elimine el segundo tesoro de este usuario. Piénsalo: $user empezó con estos dos tesoros... y el hecho de que no se envíe el IRI de este segundo tesoro significa que queremos que se elimine de $user.

En segundo lugar, he añadido $dragonTreasure3 temporalmente para demostrar que los tesoros se pueden robar. Actualmente es propiedad de $otherUser, pero lo pasamos a dragonTreasures... y vamos a comprobar que el propietario de $dragonTreasure3 cambia de$otherUser a $user. No es el comportamiento final que queremos, pero nos ayudará a que funcione toda la escritura de relaciones. Luego nos preocuparemos de evitarlo.

Aquí abajo, ->assertStatus(200) y luego ampliaremos la prueba diciendo->get('/api/users/' . $user->getId()) y ->dump().

119 lines | tests/Functional/UserResourceTest.php
// ... lines 1 - 10
class UserResourceTest extends ApiTestCase
{
// ... lines 13 - 56
public function testTreasuresCanBeRemoved(): void
{
$user = UserFactory::createOne();
$otherUser = UserFactory::createOne();
$dragonTreasure = DragonTreasureFactory::createOne(['owner' => $user]);
DragonTreasureFactory::createOne(['owner' => $user]);
$dragonTreasure3 = DragonTreasureFactory::createOne(['owner' => $otherUser]);
$this->browser()
->actingAs($user)
->patch('/api/users/' . $user->getId(), [
'json' => [
'dragonTreasures' => [
'/api/treasures/' . $dragonTreasure->getId(),
'/api/treasures/' . $dragonTreasure3->getId(),
],
],
'headers' => ['Content-Type' => 'application/merge-patch+json']
])
->assertStatus(200)
->get('/api/users/' . $user->getId())
->dump()
// ... lines 79 - 81
;
}
// ... lines 84 - 117
}

Quiero ver qué aspecto tiene el usuario después de la actualización. Por último, afirma que el length del campo dragonTreasures -necesito comillas en esto- es 2, para los tesoros 1 y 3. Luego afirma que dragonTreasures[0] es igual a'/api/treasures/'., seguido de $dragonTreasure->getId(). Cópialo, pégalo y afirma que la clave 1 es $dragonTreasure3.

119 lines | tests/Functional/UserResourceTest.php
// ... lines 1 - 10
class UserResourceTest extends ApiTestCase
{
// ... lines 13 - 56
public function testTreasuresCanBeRemoved(): void
{
$user = UserFactory::createOne();
$otherUser = UserFactory::createOne();
$dragonTreasure = DragonTreasureFactory::createOne(['owner' => $user]);
DragonTreasureFactory::createOne(['owner' => $user]);
$dragonTreasure3 = DragonTreasureFactory::createOne(['owner' => $otherUser]);
$this->browser()
->actingAs($user)
->patch('/api/users/' . $user->getId(), [
'json' => [
'dragonTreasures' => [
'/api/treasures/' . $dragonTreasure->getId(),
'/api/treasures/' . $dragonTreasure3->getId(),
],
],
'headers' => ['Content-Type' => 'application/merge-patch+json']
])
->assertStatus(200)
->get('/api/users/' . $user->getId())
->dump()
->assertJsonMatches('length("dragonTreasures")', 2)
->assertJsonMatches('dragonTreasures[0]', '/api/treasures/' . $dragonTreasure->getId())
->assertJsonMatches('dragonTreasures[1]', '/api/treasures/' . $dragonTreasure3->getId())
;
}
// ... lines 84 - 117
}

¡Estupendo! Esa prueba ha costado trabajo, pero será superútil. Vamos... ¡a ejecutarla y a ver qué pasa! Copia el nombre del método y, en tu terminal, ejecútalo:

symfony php bin/phpunit --filter=testTreasuresCanBeRemoved

Y con "no se puede eliminar", quiero decir, por supuesto, que se puede eliminar. Vaya locura de copiar y pegar. Ya está. Y... falla, en la línea 81. Esto significa que la petición se ha realizado correctamente... pero losdragonTreasures siguen siendo los dos originales: /api/treasures/2 en lugar de/api/treasures/3. No se han realizado cambios en los tesoros.

¿Por qué? Averigüémoslo a continuación y aprovechemos el componente accesor de propiedades para asegurarnos de que los cambios se guardan correctamente.