API Platform version(s) affected: 2.5.7 (on Symfony 5.2.0-RC1)
Description
When updating the collection of related entities using a PUT operation on a single item from a Symfony functional test case, the API returns a 400 Bad Request response with error Item not found for "/api/accounts/4" while it works from the HTML Web interface.
How to reproduce
Consider the simplified Doctrine ORM entity mapping.
A User entity maintains a list of related Account objects (0 or more accounts).
The Account entity is also exposed as a REST resource using the @ApiResource annotation.
<?php
/**
* ...
* @ApiResource(
* ...
* itemOperations={
* ...
* "put"={
* "security"="is_granted('edit', object)",
* "openapi_context"={
* "description"="Updates the user profile matching the given ID. You can also provide `""me""` as the ID to get the currently-authenticated user.",
* },
* },
* },
* normalizationContext={"groups"={"read", "user_read"}},
* denormalizationContext={"groups"={"write", "user_write"}},
* )
*/
class User
{
/**
* @ORM\Id
* @ORM\Column(type="integer", options={"unsigned": true})
* @ORM\GeneratedValue
*
* @Groups({"read"})
*/
protected ?int $id = null;
/**
* @var Collection<int, AccountInterface>
*
* @ORM\ManyToMany(
* targetEntity=Account::class,
* inversedBy="usersFavorite",
* cascade={"persist"}
* )
* @ORM\JoinTable(name="user_favorite_account")
*
* @ApiSubresource
*
* @Groups({"user_read", "user_write"})
*/
private Collection $favoriteAccounts;
public function __construct()
{
$this->favoriteAccounts = new ArrayCollection();
}
public function addFavoriteAccount(Account $account): void
{
if ($this->favoriteAccounts->contains($account)) {
return;
}
$this->favoriteAccounts->add($account);
$account->addUsersFavorite($this);
}
public function removeFavoriteAccount(Account $account): void
{
if (! $this->favoriteAccounts->contains($account)) {
return;
}
$this->favoriteAccounts->removeElement($account);
$account->removeUsersFavorite($this);
}
}
Using the API, we want the authenticated User to be able to favorite or unfavorite Account resources.
The following HTTP requests work through the HTML Web interface:
# Favoriting first account
curl -X PUT "http://localhost:8080/api/users/me" -H "accept: application/ld+json" -H "Content-Type: application/ld+json" -d "{\"favoriteAccounts\":[\"/api/accounts/5603\"]}"
# Favoriting second account
curl -X PUT "http://localhost:8080/api/users/me" -H "accept: application/ld+json" -H "Content-Type: application/ld+json" -d "{\"favoriteAccounts\":[\"/api/accounts/5603\",\"/api/accounts/6026\"]}"
# Unfavoriting first account
curl -X PUT "http://localhost:8080/api/users/me" -H "accept: application/ld+json" -H "Content-Type: application/ld+json" -d "{\"favoriteAccounts\":[\"/api/accounts/6026\"]}"
The collection of favorite accounts is correctly updated and API Platform successfully invokes the addFavoriteAccount and removeFavoriteAccount methods of my User entity that is being updated.
Not that the JSON payload only updates the favoriteAccounts collection. I'm not updating any other properties / attributes of my User resource.
Then I translate the above requests to a Symfony Web test case as follow:
<?php
final class UserAPITest extends AbstractAPITestCase
{
// ...
public function testUpdateMyListOfFavoriteAccountsWhenLoggedIn(): void
{
$client = static::createClient();
$user = $this->findUserByEmail('salespro@domain.com');
$client->loginUser($user);
// The user from the loaded test fixtures already has a favorite account
$this->assertCount(1, $user->getFavoriteAccounts());
// Converting the list of favorite accounts to IRIs
$favoriteAccounts = [];
foreach ($user->getFavoriteAccounts() as $favoriteAccount) {
$favoriteAccounts[] = $this->findIri($favoriteAccount);
}
// Add "OMHS" account to favorite accounts
$data['favoriteAccounts'] = $favoriteAccounts;
$data['favoriteAccounts'][] = $omhsAccount = $this->findIri($this->findAccountByCode('OMHS'));
$response = $this->makeJsonRequest($client, 'PUT', '/api/users/me', \json_encode($data));
$this->assertResponseIsSuccessful(); // <-- Response fails with a 400 Bad Request
}
}
Why would the API behaves differently from the Web interface (Symfony dev environment) and the functional tests (Symfony test environment). I didn't override any api_platform config or classes for a specific environment.
API Platform version(s) affected: 2.5.7 (on Symfony 5.2.0-RC1)
Description
When updating the collection of related entities using a
PUT
operation on a single item from a Symfony functional test case, the API returns a400 Bad Request
response with errorItem not found for "/api/accounts/4"
while it works from the HTML Web interface.How to reproduce
Consider the simplified Doctrine ORM entity mapping.
A
User
entity maintains a list of relatedAccount
objects (0 or more accounts). TheAccount
entity is also exposed as a REST resource using the@ApiResource
annotation.Using the API, we want the authenticated
User
to be able to favorite or unfavoriteAccount
resources.The following HTTP requests work through the HTML Web interface:
The collection of favorite accounts is correctly updated and API Platform successfully invokes the
addFavoriteAccount
andremoveFavoriteAccount
methods of myUser
entity that is being updated.Not that the JSON payload only updates the
favoriteAccounts
collection. I'm not updating any other properties / attributes of myUser
resource.Then I translate the above requests to a Symfony Web test case as follow:
This is the request the Symfony Client executes:
And this is an extract of the beautified JSON response:
Full stack trace can be found here: https://gist.github.com/hhamon/fabb2b268979b4118eeaedf6649ad6e7
Why would the API behaves differently from the Web interface (Symfony
dev
environment) and the functional tests (Symfonytest
environment). I didn't override anyapi_platform
config or classes for a specific environment.