Skip to content

Commit

Permalink
Pre hydrate input before denormalize (#3701)
Browse files Browse the repository at this point in the history
* Add possibility to hydrate input before denormalize

* Fix @soyuka review

* Add test

* Remove useless set context

* Add deep object to populate to avoid missing data

* change name to inputClass to be more explicit

* Fix test

* Rename PreHydrateInput to DataTransformerInitializer

* Add behat test data transformer initializer

* fix tests

Co-authored-by: Alexandre Vinet <contact@alexandrevinet.fr>
  • Loading branch information
soyuka and cacahouwete committed Sep 13, 2020
1 parent 32249e9 commit 6e2538b
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 4 deletions.
24 changes: 24 additions & 0 deletions features/bootstrap/DoctrineContext.php
Expand Up @@ -53,6 +53,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\FooDummy as FooDummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\FourthLevel as FourthLevelDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Greeting as GreetingDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\InitializeInput as InitializeInputDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\MaxDepthDummy as MaxDepthDummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\NetworkPathDummy as NetworkPathDummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\NetworkPathRelationDummy as NetworkPathRelationDummyDocument;
Expand Down Expand Up @@ -117,6 +118,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FooDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FourthLevel;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Greeting;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\InitializeInput;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\InternalUser;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathDummy;
Expand Down Expand Up @@ -1589,6 +1591,20 @@ public function thereAreNetworkPathDummies(int $nb)
$this->manager->flush();
}

/**
* @Given there is an InitializeInput object with id :id
*/
public function thereIsAnInitializeInput(int $id)
{
$initializeInput = $this->buildInitializeInput();
$initializeInput->id = $id;
$initializeInput->manager = 'Orwell';
$initializeInput->name = '1984';

$this->manager->persist($initializeInput);
$this->manager->flush();
}

private function isOrm(): bool
{
return null !== $this->schemaTool;
Expand Down Expand Up @@ -2006,4 +2022,12 @@ private function buildNetworkPathRelationDummy()
{
return $this->isOrm() ? new NetworkPathRelationDummy() : new NetworkPathRelationDummyDocument();
}

/**
* @return InitializeInput|InitializeInputDocument
*/
private function buildInitializeInput()
{
return $this->isOrm() ? new InitializeInput() : new InitializeInputDocument();
}
}
23 changes: 23 additions & 0 deletions features/jsonld/input_output.feature
Expand Up @@ -317,3 +317,26 @@ Feature: JSON-LD DTO input and output
"data": 123
}
"""

@createSchema
Scenario: Initialize input data with a DataTransformerInitializer
Given there is an InitializeInput object with id 1
When I send a "PUT" request to "/initialize_inputs/1" with body:
"""
{
"name": "La peste"
}
"""
Then the response status code should be 200
And the response should be in JSON
And the JSON should be equal to:
"""
{
"@context": "/contexts/InitializeInput",
"@id": "/initialize_inputs/1",
"@type": "InitializeInput",
"id": 1,
"manager": "Orwell",
"name": "La peste"
}
"""
Expand Up @@ -30,6 +30,7 @@
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface;
use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface;
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\GraphQl\Error\ErrorHandlerInterface;
use ApiPlatform\Core\GraphQl\Resolver\MutationResolverInterface;
Expand Down Expand Up @@ -688,6 +689,9 @@ private function registerDataTransformerConfiguration(ContainerBuilder $containe
{
$container->registerForAutoconfiguration(DataTransformerInterface::class)
->addTag('api_platform.data_transformer');

$container->registerForAutoconfiguration(DataTransformerInitializerInterface::class)
->addTag('api_platform.data_transformer');
}

private function registerSecurityConfiguration(ContainerBuilder $container, XmlFileLoader $loader): void
Expand Down
26 changes: 26 additions & 0 deletions src/DataTransformer/DataTransformerInitializerInterface.php
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\DataTransformer;

interface DataTransformerInitializerInterface extends DataTransformerInterface
{
/**
* Creates a new DTO object that the data will then be serialized into (using object_to_populate).
*
* This is useful to "initialize" the DTO object based on the current resource's data.
*
* @return object|null
*/
public function initialize(string $inputClass, array $context = []);
}
5 changes: 5 additions & 0 deletions src/Serializer/AbstractItemNormalizer.php
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface;
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\Exception\InvalidValueException;
Expand Down Expand Up @@ -197,6 +198,10 @@ public function denormalize($data, $class, $format = null, array $context = [])
if (!$this->serializer instanceof DenormalizerInterface) {
throw new LogicException('Cannot denormalize the input because the injected serializer is not a denormalizer');
}
if ($dataTransformer instanceof DataTransformerInitializerInterface) {
$context[AbstractObjectNormalizer::OBJECT_TO_POPULATE] = $dataTransformer->initialize($inputClass, $context);
$context[AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE] = true;
}
$denormalizedInput = $this->serializer->denormalize($data, $inputClass, $format, $context);
if (!\is_object($denormalizedInput)) {
throw new \UnexpectedValueException('Expected denormalized input to be an object.');
Expand Down
Expand Up @@ -63,6 +63,7 @@
use ApiPlatform\Core\DataProvider\Pagination;
use ApiPlatform\Core\DataProvider\PaginationOptions;
use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface;
use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface;
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\Exception\FilterValidationException;
use ApiPlatform\Core\Exception\InvalidArgumentException;
Expand Down Expand Up @@ -1145,7 +1146,11 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo

$containerBuilderProphecy->registerForAutoconfiguration(DataTransformerInterface::class)
->willReturn($this->childDefinitionProphecy)->shouldBeCalledTimes(1);
$this->childDefinitionProphecy->addTag('api_platform.data_transformer')->shouldBeCalledTimes(1);

$containerBuilderProphecy->registerForAutoconfiguration(DataTransformerInitializerInterface::class)
->willReturn($this->childDefinitionProphecy)->shouldBeCalledTimes(1);

$this->childDefinitionProphecy->addTag('api_platform.data_transformer')->shouldBeCalledTimes(2);

$containerBuilderProphecy->addResource(Argument::type(DirectoryResource::class))->shouldBeCalled();

Expand Down
@@ -0,0 +1,62 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\DataTransformer;

use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface;
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\InitializeInput as InitializeInputDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\InitializeInputDto;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\InitializeInput;

final class InitializeInputDataTransformer implements DataTransformerInitializerInterface
{
/**
* {@inheritdoc}
*/
public function transform($object, string $to, array $context = [])
{
/** @var InitializeInputDto */
$data = $object;

/** @var InitializeInput|InitializeInputDocument */
$resourceObject = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE] ?? new $context['resource_class']();
$resourceObject->name = $data->name;

return $resourceObject;
}

/**
* {@inheritdoc}
*/
public function initialize(string $inputClass, array $context = [])
{
$currentResource = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE] ?? null;
if (!$currentResource) {
return new InitializeInputDto();
}

$dto = new InitializeInputDto();
$dto->manager = $currentResource->manager;

return $dto;
}

/**
* {@inheritdoc}
*/
public function supportsTransformation($data, string $to, array $context = []): bool
{
return (InitializeInput::class === $to || InitializeInputDocument::class === $to) && InitializeInputDto::class === ($context['input']['class'] ?? null);
}
}
40 changes: 40 additions & 0 deletions tests/Fixtures/TestBundle/Document/InitializeInput.php
@@ -0,0 +1,40 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\InitializeInputDto;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
* @ApiResource(input=InitializeInputDto::class)
* @ODM\Document
*/
class InitializeInput
{
/**
* @ODM\Id(strategy="NONE", type="integer")
*/
public $id;

/**
* @ODM\Field
*/
public $manager;

/**
* @ODM\Field
*/
public $name;
}
27 changes: 27 additions & 0 deletions tests/Fixtures/TestBundle/Dto/InitializeInputDto.php
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto;

class InitializeInputDto
{
/**
* @var string
*/
public $name;

/**
* @var string
*/
public $manager;
}
41 changes: 41 additions & 0 deletions tests/Fixtures/TestBundle/Entity/InitializeInput.php
@@ -0,0 +1,41 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\InitializeInputDto;
use Doctrine\ORM\Mapping as ORM;

/**
* @ApiResource(input=InitializeInputDto::class)
* @ORM\Entity
*/
class InitializeInput
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
public $id;

/**
* @ORM\Column
*/
public $manager;

/**
* @ORM\Column
*/
public $name;
}
6 changes: 6 additions & 0 deletions tests/Fixtures/app/config/config_common.yml
Expand Up @@ -291,6 +291,12 @@ services:
tags:
- { name: 'api_platform.data_transformer' }

app.data_transformer.initialize_input:
class: 'ApiPlatform\Core\Tests\Fixtures\TestBundle\DataTransformer\InitializeInputDataTransformer'
public: false
tags:
- { name: 'api_platform.data_transformer' }

app.messenger_handler.messenger_with_response:
class: 'ApiPlatform\Core\Tests\Fixtures\TestBundle\MessengerHandler\MessengerWithResponseHandler'
public: false
Expand Down

0 comments on commit 6e2538b

Please sign in to comment.