Closed informatic-revolution closed 3 years ago
Do Company
properties have @Groups({"establishment:read"})
annotations too?
No the company output code is the following
final class CompanyOutput
{
/**
* @Groups({"company:read"})
*/
public string $name;
/**
* @Groups({"company:read"})
*/
public string $identificationNumber;
/**
* @Groups({"company:read"})
*/
public AddressOutput $address;
}
}
@informatic-revolution Have you been able to solve this problem? I am currently facing the same problem and I have no idea how to solve it properly.
@mfu-aroche you can take a look a the long conversation I had here with weaverryan about this issue
https://symfonycasts.com/screencast/api-platform-extending/collections-readable-link
Thanks @julienkirsch, I'll give it a try.
I may have talk a bit to quickly... my problem isn't exactly the same as I only have one DTO. Here is my code from a dummy project that mimics the real project context (that's why the DTO looks useless):
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\DataTransferObject\ArticleDTO;
use App\Repository\ArticleRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass=ArticleRepository::class)
* @ApiResource(
* attributes={
* "order"={
* "createdAt": "ASC"
* }
* },
* output=ArticleDTO::class
* )
*/
class Article
{
/**
* @ORM\Id
* @ORM\Column(type="uuid")
*/
private UuidInterface $id;
/**
* @ORM\Column(type="string", length=255)
*/
private string $headline;
/**
* @ORM\Column(type="text")
*/
private string $content;
/**
* @ORM\Column(type="datetime_immutable")
*/
private DateTimeImmutable $createdAt;
/**
* @ORM\ManyToOne(targetEntity=Author::class, inversedBy="articles")
* @ORM\JoinColumn(nullable=false)
*/
private Author $author;
public function __construct(
Author $author,
string $headline,
string $content,
UuidInterface $id = null
)
{
$this->id = $id ?? Uuid::uuid4();
$this->author = $author;
$this->headline = $headline;
$this->content = $content;
$this->createdAt = new DateTimeImmutable();
}
// Getters and setters
}
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\AuthorRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass=AuthorRepository::class)
* @ApiResource(
* attributes={
* "order"={
* "lastname": "ASC",
* "firstname": "ASC"
* }
* }
* )
*/
class Author
{
/**
* @ORM\Id
* @ORM\Column(type="uuid")
*/
private UuidInterface $id;
/**
* @ORM\Column(type="string", length=255)
*/
private string $email;
/**
* @ORM\Column(type="string", length=255)
*/
private string $firstname;
/**
* @ORM\Column(type="string", length=255)
*/
private string $lastname;
/**
* @var Collection|Article[]
* @ORM\OneToMany(targetEntity=Article::class, mappedBy="author")
*/
private Collection $articles;
public function __construct(
string $email,
string $firstname,
string $lastname,
UuidInterface $id = null
)
{
$this->id = $id ?? Uuid::uuid4();
$this->email = $email;
$this->firstname = $firstname;
$this->lastname = $lastname;
$this->articles = new ArrayCollection();
}
// Getters and setters
}
<?php
declare(strict_types=1);
namespace App\DataTransferObject;
use App\Entity\Author;
use DateTimeImmutable;
use Ramsey\Uuid\UuidInterface;
class ArticleDTO
{
public UuidInterface $id;
public string $headline;
public string $content;
public DateTimeImmutable $createdAt;
public Author $author;
}
<?php
declare(strict_types=1);
namespace App\DataTransformer;
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use App\DataTransferObject\ArticleDTO;
use App\Entity\Article;
final class ArticleDataTransformer implements DataTransformerInterface
{
/**
* @param Article $object
* @param string $to
* @param array $context
*
* @return object|void
*/
public function transform($object, string $to, array $context = [])
{
if ($to === ArticleDTO::class) {
$output = new ArticleDTO();
$output->id = $object->getId();
$output->author = $object->getAuthor();
$output->headline = $object->getHeadline();
$output->content = $object->getContent();
$output->createdAt = $object->getCreatedAt();
return $output;
}
}
public function supportsTransformation($data, string $to, array $context = []): bool
{
return $data instanceof Article && $to === ArticleDTO::class;
}
}
This is the output I get when not using the DTO:
{
"id": "fa7419dc-9dd7-4f5d-af7d-cebaaaecfefb",
"headline": "First article",
"content": "Lorem ipsum dolor sit amet",
"createdAt": "2021-02-12T14:23:47+01:00",
"author": "/api/authors/90655472-732f-4136-813e-a8c70ea2f7e7"
}
With the DTO enabled:
{
"id": "fa7419dc-9dd7-4f5d-af7d-cebaaaecfefb",
"headline": "First article",
"content": "Lorem ipsum dolor sit amet",
"createdAt": "2021-02-12T14:23:47+01:00",
"author": {
"id": "90655472-732f-4136-813e-a8c70ea2f7e7",
"email": "some.one@example.com",
"firstname": "Some",
"lastname": "One",
"articles": [
"/api/articles/fa7419dc-9dd7-4f5d-af7d-cebaaaecfefb",
"/api/articles/adf64e4b-3319-493a-b0f6-88d38ac993b0"
]
}
}
How do I manage to get the author serialized as an IRI ?
Could you send your composer dependencies please ?
Could you give us a full reproducer please? Not sure to understand the use case here. If you need only the IRI why use a DTO?
@julienkirsch here it is:
"require": {
"php": ">=7.4",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^2.6",
"composer/package-versions-deprecated": "1.11.99.1",
"doctrine/annotations": "^1.0",
"doctrine/doctrine-bundle": "^2.2",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.8",
"nelmio/cors-bundle": "^2.1",
"phpdocumentor/reflection-docblock": "^5.2",
"ramsey/uuid-doctrine": "^1.6",
"symfony/asset": "5.2.*",
"symfony/console": "5.2.*",
"symfony/dotenv": "5.2.*",
"symfony/expression-language": "5.2.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "5.2.*",
"symfony/property-access": "5.2.*",
"symfony/property-info": "5.2.*",
"symfony/proxy-manager-bridge": "5.2.*",
"symfony/security-bundle": "5.2.*",
"symfony/serializer": "5.2.*",
"symfony/twig-bundle": "5.2.*",
"symfony/validator": "5.2.*",
"symfony/yaml": "5.2.*"
},
"require-dev": {
"symfony/debug-bundle": "^5.2",
"symfony/maker-bundle": "^1.28",
"symfony/monolog-bundle": "^3.0",
"symfony/stopwatch": "^5.2",
"symfony/var-dumper": "^5.2",
"symfony/web-profiler-bundle": "^5.2"
},
@soyuka the real project is close to this example, but we do have extra fields (computed) in the DTO though. Like this example, we do have an entity referenced in the DTO. And we want it to be normalized like any other related resources (i.e. IRI by default, or fields if serialization groups are specified). Does it make sense to you?
It's not a deal breaker for us, as we have other options, but it might be useful to have almost the same normalizers for DTO as standard resources (at least regarding embedded/subresources).
@mfu-aroche i don't see anything missing or strange in your composer.json file
Yes, neither do I... that's why it's weird. I'll try to downgrade API Platform. Maybe it's a kind of "regression".
I don't get the use of the DTO here. About the first bug report, the context of non-resources objects are embeded to match the Json-LD specification as we can not create a context for it in the entrypoint.
I'm working on a solution for you!
Few things are wrong here and the first one is that the ValueObject (or DTO) is not a Resource and therefore has no identifiers (maybe added through https://github.com/api-platform/core/pull/3946). No identifiers means no IRI to normalize. I really don't get how your DTO was working before and I wish I had a full reproduction case @informatic-revolution
In the meantime, just use an ApiResource
over the embed DTO, you can disable its operations if needed and you'll have an IRI.
Closing, @mfu-aroche please open a new issue as yours is slightly different.
@soyuka I'm sorry to comment on a closed issue, feel free to let me know if I should open a new one.
Just so that we would all be on the same page, let me rephrase the original problem.
Class Parent
has an output class ParentOutput
. Class Parent
has a *-to-one relationship to class Child
. A custom data transformer ParentToParentOutputTransformer
knows how to transform Parent
to ParentOutput
. Suppose we are exposing both Parent
and Child
via the API. The classes look as follows.
/**
* @ApiResource(output = ParentOutput::class)
*/
class Parent
{
public int $id;
public Child $child;
}
class ParentOutput
{
public int $id;
public Child $child;
}
/**
* @ApiResource
*/
class Child
{
public int $id;
}
When making a GET /parents/42
request, the following is (IMO incorrectly) returned.
{
"@context": { /* ... */ },
"@type": "Parent",
"@id": "/parent/42",
"id": 42,
"child": {
"@context": " ... ",
"@id": "/children/1",
"id": 1,
}
}
I would expect the following to be returned. And this is indeed returned, if Parent
didn't have a different output class.
{
"@context": { /* ... */ },
"@type": "Parent",
"@id": "/parent/42",
"id": 42,
"child": "/children/1"
}
@informatic-revolution is not talking about the related object being a non-resource, they're talking about the root object being a DTO, which references a resource. But instead of generating an IRI, the reference is being serialized.
Is this really the expected behavior?
API Platform version(s) affected: 2.5.7
Description
When using DTO, it does not deserialize an entity as an IRI, using the attribute readableLink of ApiProperty annotation does not have any effect.
How to reproduce
Executing tests give me the following response