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 SubscribeUtiliza los documentos para consultar la página User
con id=2. Cuando leemos un recurso, podemos decidir exponer cualquier propiedad, y una propiedad que contiene una colección, como cheeseListings
, no es diferente. Exponemos esa propiedad añadiendo@Groups("user:read")
sobre ella. Y como ésta contiene una colección de objetos relacionados, también podemos decidir si la propiedad cheeseListings
debe exponerse como una matriz de cadenas IRI o como una matriz de objetos incrustados, añadiendo este mismo grupo a al menos una propiedad dentro de la propia CheeseListing
.
Genial. ¡Nuevo reto! Podemos leer la propiedad cheeseListings
en User
... pero ¿podríamos también modificar esta propiedad?
Por ejemplo, bueno, es un ejemplo un poco extraño, pero supongamos que un administrador quiere poder editar un User
y convertirlo en propietario de algunos objetosCheeseListing
existentes en el sistema. Esto ya se puede hacer editando unCheeseListing
y cambiando su owner
. Pero, ¿podríamos hacerlo también editando unUser
y pasándole una propiedad cheeseListings
?
En realidad, ¡vamos a ponernos aún más locos! Quiero poder crear un nuevoUser
y especificar uno o más listados de queso que este User
debería poseer... todo en una sola petición.
Ahora mismo, la propiedad cheeseListings
no es modificable. La razón es sencilla: esa propiedad sólo tiene el grupo de lectura. ¡Genial! Haré que ese grupo sea un array y añadiré user:write
.
... lines 1 - 22 | |
class User implements UserInterface | |
{ | |
... lines 25 - 58 | |
/** | |
... line 60 | |
* @Groups({"user:read", "user:write"}) | |
*/ | |
private $cheeseListings; | |
... lines 64 - 184 | |
} |
Ahora, vuelve, refresca los documentos y mira la operación POST: sí tenemos una propiedad cheeseListings
. ¡Vamos a hacerlo! Empieza con la aburrida información del usuario: correo electrónico, la contraseña no importa y el nombre de usuario. Para cheeseListings
, esto tiene que ser un array... porque esta propiedad contiene un array. Dentro, añade sólo un elemento - un IRI - /api/cheeses/1
.
En un mundo perfecto, esto creará un nuevo User
y luego irá a buscar elCheeseListing
con el id 1
y lo cambiará para que sea propiedad de este usuario. Respira hondo. ¡Ejecuta!
¿Ha funcionado? Es decir, ¡ha funcionado! Un código de estado 201: ¡ha creado el nuevo User
y ese User
es ahora el propietario de este CheeseListing
! Espera un segundo... ¿cómo ha funcionado?
Compruébalo: entendemos cómo se manejan email
, password
y username
: cuando hacemos un POST, el serializador llamará a setEmail()
. En este caso, estamos enviando un campocheeseListings
... pero si vamos a buscar setCheeseListings()
, ¡no existe!
En su lugar, busca addCheeseListing()
. Ahhh. El comando make:entity
es inteligente: cuando genera una relación de colección como ésta, en lugar de generar un métodosetCheeseListings()
, genera addCheeseListing()
yremoveCheeseListing()
. Y el serializador es lo suficientemente inteligente como para utilizarlos Ve el único IRI de CheeseListing
que estamos enviando, consulta la base de datos en busca de ese objeto, llama a addCheeseListing()
y lo pasa como argumento.
La razón por la que make:entity
genera el sumador -en lugar de sólosetCheeseListings()
- es que nos permite hacer cosas cuando se añade o elimina un listado de quesos. ¡Y eso es clave! Compruébalo: dentro del código generado, llama a $cheeseListing->setOwner($this)
. Por eso el propietario cambió al nuevo usuario, para este CheeseListing
con id=1. Entonces... ¡todo se guarda!
Siguiente: cuando estemos creando o editando un usuario, en lugar de reasignar unCheeseListing
existente a un nuevo propietario, vamos a hacer posible la creación de listados de quesos totalmente nuevos. Sí, ¡nos estamos volviendo locos! Pero esto nos permitirá aprender aún más sobre cómo piensa y funciona el serializador.
Hi ahmedbhs!
Apologies for the slow reply - vacation last week, and your question was left for me personally! :)
Yes, I think you CAN do this, but it's entirely up to YOUR code to do it. Behind the scenes, iirc, ApiPlatform (via the serializer I believe) looks at the current collection and the submitted collection and finds the "difference". It calls addCheeseListing() for any new items and removeCheeseListing() for any removed items. So, in theory, you could make your removeCheeseListing() do nothing ;) That feels... a bit odd and potentially dangerous to me, but I think that IS the path... I can't think of another way.
Cheers!
Hi,
I have a question about adding items to a collection property in many-to-many relations with extra property in bridge table.
Example is sports players and teams. A player can be in multiple teams and teams can have multiple players. Also team can have player(s) as captains.
Here is the table schema:
`
Players
--
id
name
teams
Teams
--
id
name
players
PlayersTeams
--
player
team
is_captain
`
And here is the shortened Entities:
`
// App/Entity/Players.php
/**
/**
public function addTeam(Teams $team): self {
if (!$this->teams->contains($team)) {
$this->teams[] = $team;
$playerTeam = new PlayersTeams();
$playerTeam->setPlayer($this);
}
return $this;
}
public function removeTeam(Teams $team): self {
if ($this->teams->contains($team)) {
$this->teams->removeElement($team);
if ($team->getPlayer() === $this) {
$team->setPlayer(null);
}
}
return $this;
}
// App/Entity/PlayersTeams.php
/**
/**
/**
/**
public function getId(): ?int {
return $this->id;
}
public function getTeam(): Teams {
return $this->team;
}
public function setTeam(Teams $team): self {
$this->team = $team;
return $this;
}
public function getPlayer(): Players {
return $this->player;
}
public function setPlayer(Players $player): self {
$this->player = $player;
return $this;
}
public function getIsCaptain(): bool {
return $this->isCaptain;
}
public function setIsCaptain(bool $isCaptain): self {
$this->isCaptain = $isCaptain;
return $this;
}
`
When I call /player/1
PUT API, I get the following response: "Expected value of type \"App\Entity\PlayersTeams\" for association field \"App\Entity\Players#$teams\", got \"App\Entity\Teams\" instead."
How can I add items to the embedded objects in this entity relations?
Thank you for your tips!
Hey Sung,
It sounds like you're trying to set a Teams entity instead of PlayersTeams entity to the Players::$teams property. It sounds like you have an invalid annotation mapping for this property, shouldn't it be "App\Entity\Teams" instead? Otherwise, you should set PlayersTeams entity instead of Teams.
Btw, I'd recommend you also to check your Doctrine mapping configuration with the command:
$ bin/console doctrine:schema:validate
Make sure you have a valid mapping in your application. If you do - most probably you have a logic mistake somewhere.
I hope this helps!
Cheers!
// 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
}
}
Hi, is there a way to disable removing in that case, without throwing any exception/validation ? kind of patching my array collection with new data, but i dont want old data be deleted, if I don't provide them inside my PUT payload ?