api-platform – Relation IRIs in DTOs using JSON-LD

I’m using api-platform only with DTOs and am currently struggling generating some IRIs, and to be honest currently not sure which is correct in a JSON-LD context.

My Question is: Why are these outputs so different and how can I force api platform to generate the IRIs correctly with DTOs? (Note: readableLink should not be necessary when the normalized relation data is empty)

This is what I get, when I serialize my collection without any DTO and serialization groups through normalizationContext involved.

{
  "@context": "/contexts/Item",
  "@id": "/items",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/items/002e223b-fee5-4f60-a601-6271108579f2",
      "@type": "Item",
      "uuid": "002e223b-fee5-4f60-a601-6271108579f2",
      "collection": "/collections/26a1a031-d43b-4f2e-875f-3963c050ef55"
    },
    ...
  ]
}

And this is what is generated on the same collection with DTOs involved (note that "collection" is now an empty array and not a IRI:

{
  "@context": "/contexts/Item",
  "@id": "/items",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@type": "Item",
      "@id": "/items/002e223b-fee5-4f60-a601-6271108579f2",
      "collection": [],
    },
    ...
  ]
}

Here are my entities:

// src/App/Entity/Collection.php
<?php

namespace AppEntity;

use AppDtoCollectionOutput;
use AppEntityCollectionItem;
use ApiPlatformCoreAnnotationApiResource;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineORMMapping as ORM;
use SymfonyBridgeDoctrineIdGeneratorUuidV4Generator;

/**
 * @ORMEntity
 */
#[ApiResource(
    output: CollectionOutput::class,
)]
class Collection
{
    /**
     * @ORMId
     * @ORMColumn(type="uuid")
     * @ORMGeneratedValue(strategy="CUSTOM")
     * @ORMCustomIdGenerator(class=UuidV4Generator::class)
     */
    private string $uuid;

    /**
     * @ORMOneToMany(targetEntity=Item::class, mappedBy="collection")
     */
    private iterable $items;

    public function __construct() {
        $this->items = new ArrayCollection();
    }

    public function getUuid(): string
    {
        return $this->uuid;
    }

    public function getItems(): iterable
    {
        return $this->items;
    }
}
// src/App/Entity/Collection/Item.php
<?php

namespace AppEntityCollection;

use ApiPlatformCoreAnnotationApiResource;
use AppDtoCollectionItemOutput;
use AppEntityCollection;
use DoctrineORMMapping as ORM;
use SymfonyBridgeDoctrineIdGeneratorUuidV4Generator;
use SymfonyComponentSerializerAnnotationGroups;

/**
 * @ORMEntity
 */
#[ApiResource(
    denormalizationContext: [
        'groups' => ['item:read'],
    ],
    normalizationContext: [
        'groups' => ['item:read'],
    ],
    output: ItemOutput::class,
)]
class Item
{
    /**
     * @ORMId
     * @ORMColumn(type="uuid")
     * @ORMGeneratedValue(strategy="CUSTOM")
     * @ORMCustomIdGenerator(class=UuidV4Generator::class)
     */
    private string $uuid;

    /**
     * @ORMManyToOne(targetEntity=Collection::class, inversedBy="items")
     * @ORMJoinColumn(name="collection", referencedColumnName="uuid")
     */
    private Collection $collection;
    
    public function getUuid(): string
    {
        return $this->uuid;
    }

    public function getCollection(): Collection
    {
        return $this->collection;
    }

    public function setCollection(Collection $collection): void
    {
        $this->collection = $collection;
    }
}

The ItemOutput and CollectionOutput DTOs used in Item and Collection entity:

// src/App/Dto/Collection/ItemOutput.php
<?php

namespace AppDtoCollection;

use AppEntityCollection;
use SymfonyComponentSerializerAnnotationGroups;

final class ItemOutput
{
    public function __construct(
        /**
         * @Groups({"item:read"})
         */
        private Collection $collection,
    ) { }

    public function getCollection(): Collection
    {
        return $this->collection;
    }
}
// src/App/Dto/CollectionOutput.php
<?php

namespace AppDto;

final class CollectionOutput
{
    public function __construct(
        private string $name,
    ) { }

    public function getName(): string
    {
        return $this->name;
    }
}

And the used DataTransformer to transform an Item entity:

// src/App/DataTransformer/Collection/ItemOutputDataTransformer.php
<?php

namespace AppDataTransformerCollection;

use ApiPlatformCoreDataTransformerDataTransformerInterface;
use AppDtoCollectionItemOutput;
use AppEntityCollectionItem;

final class ItemOutputDataTransformer implements DataTransformerInterface
{
    /**
     * @param Item $object
     */
    public function transform($object, string $to, array $context = [])
    {
        return new ItemOutput(
            $object->getCollection(),
        );
    }

    public function supportsTransformation($data, string $to, array $context = []): bool
    {
        return $data instanceof Item;
    }
}

What I tried so far

  • Adding #[ApiProperty(readableLink: false)] on collection property or getter
  • Adding the UUID to CollectionOutput and annotating the property with #[ApiProperty(identifier: true)]

Source: Ask PHP

LEAVE A COMMENT