Skip to content

IRI's not returned for a relation #282

@fpetrovic

Description

@fpetrovic

I have custom provider in api-platform that uses automapper. My expectation would be that automapper handles IRI's where needed, but it does not do that. It works well if needs to return an object, but totally fails with IRI's.

In example below, I expected media to be returned as array of iri strings, when I call records collection, and it fails to do that.

PHP: 8.3.20
Symfony: 7.2.5
jolicode/automapper 9.4.1
api-platform: 4.1.7

#[ApiResource(
    shortName: 'Media',
    operations: [new Get()],
    normalizationContext: ['openapi_definition_name' => 'item-Read', 'groups' => ['media:item:read']],
    denormalizationContext: ['openapi_definition_name' => 'item-Write', 'groups' => ['media:item:write']],
    provider: EntityToDtoStateProvider::class,
    processor: EntityClassDtoStateProcessor::class,
    stateOptions: new Options(
        entityClass: RecordMedia::class,
    ),
)]
class MediaApi implements ApiInterface
{
    #[Groups(['media:list', 'media:item:read'])]
    private string $id;

    // rest of props

    public function getId(): string
    {
        return $this->id;
    }

    public function setId(string $id): void
    {
        $this->id = $id;
    }
   ///  rest of methods
}

#[ApiResource(
    shortName: 'Record',
    operations: [
        new Get(
            security: 'is_granted("CAN_VIEW_RECORD", object)',
        ),
        new GetCollection(
            normalizationContext: [
                'openapi_definition_name' => 'list',
                'groups' => RecordApi::RECORD_LIST_NORMALIZATION_GROUP,
            ],
            security: 'is_granted("CAN_VIEW_RECORD", object)',
        ),
    ],
    normalizationContext: [
        'openapi_definition_name' => 'item-Read',
        'groups' => RecordApi::RECORD_ITEM_NORMALIZATION_GROUP,
    ],
    denormalizationContext: ['openapi_definition_name' => 'item-Write', 'groups' => ['record:item:write']],
    provider: RecordProvider::class,
    processor: EntityClassDtoStateProcessor::class,
    stateOptions: new Options(
        entityClass: Record::class,
    ),
)]
class RecordApi implements ApiInterface
{
    public const array RECORD_LIST_NORMALIZATION_GROUP = [
        'record:list',
    ];

    public const array RECORD_ITEM_NORMALIZATION_GROUP = [
        'record:item:read',
        'media:item:read',
    ];

    #[Groups(['record:item:read', 'record:list'])]
    private string $id;

    // automapper check singularity of property plural: Media - Medium for adder and remover methods
    // that is the reason why we have addMedium/removeMedium methods
    // ideally, it should use some reflection class check, but it is what it is
    // adder and remover are essential for automapper to work for Collection to array
    /**
     * @var MediaApi[]$media
     */
    #[Groups(['record:item:read', 'record:list'])]
    #[MapFrom(property: 'recordMedia')]
    private array $media;

    public function __construct()
    {
        $this->media = [];
    }

    public function getId(): string
    {
        return $this->id;
    }
// rest of methods


readonly class RecordProvider implements ProviderInterface
{
    public function __construct(
       // consructor arguments
    ) {
    }

    /**
     * @param MapperContextArray $context
     *
     * @throws \Exception
     */
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        /** @var class-string<object> $resourceClass */
        $resourceClass = $operation->getClass();

        if ($operation instanceof CollectionOperationInterface) {
            // some logic

            $recordsResponse = $this->recordsRepository->findFromExternalApiRequest($indexRecordsRequest);

            $dtos = [];
            foreach ($recordsResponse->getResults() as $entity) {
                $dtos[] = $this->mapEntityToDto($entity, $resourceClass, $context);
            }

            return new TraversablePaginator(
                new \ArrayIterator($dtos),
                $currentPage,
                $itemsPerPage,
                 $recordsResponse->getTotalResultCount(),
            );
        }

        $entity = $this->itemProvider->provide($operation, $uriVariables, $context);

        if (!$entity) {
            return null;
        }

        return $this->mapEntityToDto($entity, $resourceClass, $context);
    }

    /**
     * @param class-string<object> $resourceClass
     * @param MapperContextArray   $context
     *
     * @throws \Exception
     */
    private function mapEntityToDto(object $entity, string $resourceClass, array $context): object
    {
        $mapped = $this->autoMapper->map($entity, $resourceClass, $context);
        assert(is_object($mapped));

        return $mapped;
    }
}```

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions