Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
source and target names

### Fixed
- Resolve API Platform IRIs in every JSON format, not only JSON-LD, so a relation sent as an IRI on `application/json` or `application/merge-patch+json` no longer reaches the mapper as a raw string
- Use the `MapFrom` attribute reference instead of `MapTo` when resolving transformers in `MapFromListener`
- Do not run a transformation on a null source value when the target is not nullable, a `TypeError` is thrown for typed properties instead of the transformation crashing on the null value
- Create backed enum from scalar source value instead of assigning the raw scalar
Expand Down
8 changes: 7 additions & 1 deletion src/Provider/ApiPlatform/IriProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@

final readonly class IriProvider implements ProviderInterface
{
/**
* Formats where a relation is carried as an IRI string, so not only JSON-LD:
* API Platform's default patch_formats maps application/merge-patch+json to the "json" format.
*/
private const array SUPPORTED_FORMATS = ['jsonld', 'json', 'jsonhal', 'jsonapi'];

public function __construct(
private IriConverterInterface $iriConverter,
private ResourceClassResolverInterface $resourceClassResolver,
Expand All @@ -20,7 +26,7 @@ public function __construct(

public function provide(string $targetType, mixed $source, array $context, mixed $id): ?object
{
if (($context[MapperContext::NORMALIZER_FORMAT] ?? false) !== 'jsonld') {
if (!\in_array($context[MapperContext::NORMALIZER_FORMAT] ?? null, self::SUPPORTED_FORMATS, true)) {
return null;
}

Expand Down
19 changes: 19 additions & 0 deletions tests/Bundle/ApiPlatformTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,25 @@ public function testUpdateBook(): void
]);
}

public function testCreateWithIriRelationInPlainJson(): void
{
$response = static::createClient()->request('POST', '/reviews', [
'json' => [
'rating' => 5,
'body' => 'A great book.',
'author' => 'Someone',
'book' => '/books/1',
],
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]);

$this->assertResponseIsSuccessful();
$this->assertSame('/books/1', $response->toArray()['book']);
}

protected function tearDown(): void
{
parent::tearDown();
Expand Down
5 changes: 4 additions & 1 deletion tests/Bundle/Resources/App/Api/Entity/Review.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
namespace AutoMapper\Tests\Bundle\Resources\App\Api\Entity;

use ApiPlatform\Metadata\ApiResource;
use AutoMapper\Attribute\Mapper;
use AutoMapper\Tests\Bundle\Resources\App\Api\Processor\ReviewProcessor;

/** A book. */
#[ApiResource]
#[ApiResource(processor: ReviewProcessor::class)]
#[Mapper(source: 'array', target: 'array')]
class Review
{
/** The ID of this review. */
Expand Down
17 changes: 17 additions & 0 deletions tests/Bundle/Resources/App/Api/Processor/ReviewProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace AutoMapper\Tests\Bundle\Resources\App\Api\Processor;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use AutoMapper\Tests\Bundle\Resources\App\Api\Entity\Review;

final readonly class ReviewProcessor implements ProcessorInterface
{
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ?Review
{
return $data instanceof Review ? $data : null;
}
}
Loading