Unable to deserialize IRI in requestDto to User object #8587
|
I am running into the issue where I am unable to create new objects with a relation in it. In this simplified project I have two entities; Activity and User where every Activity is related to a User. I have a DecorateObjectMapper however it is still unused and I created a standardProcessor which is also quite basic until now. Everything works fine for all (GET/POST/PUT/DELETE) operations. However I'm unable to make a new Activity Object via the API, it fails to resolve the IRI to an User object. The ActivityRequestDto becomes a valid object, however it does not contain the proper values. Anyone a solution? I tried Mapping annotations, map true/false in the operation (ApiResource/Activity.php), running out of ideas now... Project: https://github.com/arthurGrinjo/PostRelation |
Replies: 7 comments 3 replies
|
Possible solution I'm working on right now: Making user property in ActivityRequestDto a string, validate it with Regex validation. Via the standardProcessor it is forwarded to the DecorateObjectMapper where I possibly can convert the User IRI into a user. With this I can create a new Activity Object. The original ObjectMapper fails because while Mapping it converts into a UserResponseDto. |
|
I would keep your request DTO's #[Map(target: User::class)]
public User $user;That So the DTO should model what the request actually contains: final class ActivityRequestDto implements RequestDto
{
#[Assert\Length(min: 4, max: 128)]
public string $name;
#[Assert\NotBlank]
public string $user; // "/users/{uuid}"
}Then, in the processor, resolve that IRI before setting the relation: use ApiPlatform\Metadata\IriConverterInterface;
use App\ApiResource\User as UserResource;
use App\Dto\Activity\Request\ActivityRequestDto;
use App\Entity\User as UserEntity;
public function __construct(
private EntityManagerInterface $entityManager,
private ObjectMapperInterface $objectMapper,
private IriConverterInterface $iriConverter,
// ...
) {}
// inside the Activity POST branch
if ($data instanceof ActivityRequestDto && $entity instanceof ActivityEntity) {
$user = $this->iriConverter->getResourceFromIri(
$data->user,
['resource_class' => UserResource::class],
);
if (!$user instanceof UserEntity) {
throw new RuntimeException('Invalid user IRI.');
}
$entity->setName($data->name);
$entity->setUser($user);
}You can still keep your generic That also explains why your So your proposed string-field approach is the right direction. I would just use If that fixes the POST path, please mark this as answered so the next person does not chase |
|
Interesting but I'd do like this: Let API Platform resolve the IRI, the issue is that The #[Get(
shortName: 'User',
stateOptions: new Options(entityClass: UserEntity::class),
)]
#[Map(source: UserEntity::class)]
final class UserResource
{
public Uuid $id;
}Your DTO is not mapped to anything, it only uses the resource: final class ActivityRequestDto
{
#[Assert\Length(min: 4, max: 128)]
public string $name;
#[Assert\NotNull]
public ?UserResource $user = null;
}Then in your processor you get the entity from the id, no IRI parsing needed: $activity = new ActivityEntity();
$activity->setName($data->name);
$activity->setUser($this->entityManager->getReference(UserEntity::class, $data->user->id));
$this->entityManager->persist($activity);
$this->entityManager->flush();An invalid or unknown IRI is rejected by the serializer before your processor runs. |
|
Wow Antoine! Deep bow! 🙏 I was not able to manage this until now! Will read it more careful to really understand how you did this but it really seems to work. Many thanks!! In this way I'm able to implement this 'DTO-structure' and still rely on the api platform logic. The files under ApiResource are now the new DTO's and I could also add ActivityTestRequest here I suppose? 🧐 Yes I think this is the correct solution to rely on Api-Platform logic and have Dto's implemented. |
|
So finally diving deep I do understand what you did. The transformer reverts the ResponseDto back to the User object. Nice solution. 👌 |
|
@soyuka one question left: Registering a user works however, where does the response come from?! It seems it is not based on E.g. :
Ahh.. I see, it's just returning the sent data. And removes the password with |
|
@soyuka So I continued on your input and came up with one question about this setup. I tried to remove the name from the Ofcourse I am able to fix this with |
Gave it a try, see arthurGrinjo/PostRelation#1
The loop comes from the DTOs: an IRI resolves to what your API exposes, and your
Useroperations exposeUserResponseDto, so that's whatgetResourceFromIri()returns. You'll never get the entity from an IRI when usingstateOptions, and it's by design.Instead, drop the DTOs and make the classes carrying the operations your resources (no
#[ApiResource], one class per shape):