diff --git a/src/State/Provider/DeserializeProvider.php b/src/State/Provider/DeserializeProvider.php index 9c281e2f00..6390c86bcd 100644 --- a/src/State/Provider/DeserializeProvider.php +++ b/src/State/Provider/DeserializeProvider.php @@ -126,6 +126,8 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $this->stopwatch?->stop('api_platform.provider.deserialize'); + $request->attributes->set('data', $data); + return $data; } diff --git a/src/State/Provider/ReadProvider.php b/src/State/Provider/ReadProvider.php index 0e1abf17c3..c6c65a3888 100644 --- a/src/State/Provider/ReadProvider.php +++ b/src/State/Provider/ReadProvider.php @@ -99,6 +99,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c } $request?->attributes->set('data', $data); + $request?->attributes->set('read_data', $data); // data may be lost when deserialization occurs, especially when using an Input see DeserializeProvider $request?->attributes->set('previous_data', $this->clone($data)); $this->stopwatch?->stop('api_platform.provider.read'); diff --git a/src/Symfony/Controller/MainController.php b/src/Symfony/Controller/MainController.php index b609d11609..508c9f072d 100644 --- a/src/Symfony/Controller/MainController.php +++ b/src/Symfony/Controller/MainController.php @@ -111,6 +111,7 @@ public function __invoke(Request $request): Response $context['previous_data'] = $request->attributes->get('previous_data'); $context['data'] = $request->attributes->get('data'); + $context['read_data'] = $request->attributes->get('read_data'); if (null === $operation->canWrite()) { $operation = $operation->withWrite(!$request->isMethodSafe()); diff --git a/src/Symfony/EventListener/DeserializeListener.php b/src/Symfony/EventListener/DeserializeListener.php index e4350120e9..bf34cea24c 100644 --- a/src/Symfony/EventListener/DeserializeListener.php +++ b/src/Symfony/EventListener/DeserializeListener.php @@ -79,12 +79,10 @@ public function onKernelRequest(RequestEvent $event): void return; } - $data = $this->provider->provide($operation, $request->attributes->get('_api_uri_variables') ?? [], [ + $this->provider->provide($operation, $request->attributes->get('_api_uri_variables') ?? [], [ 'request' => $request, 'uri_variables' => $request->attributes->get('_api_uri_variables') ?? [], 'resource_class' => $operation->getClass(), ]); - - $request->attributes->set('data', $data); } } diff --git a/src/Symfony/EventListener/WriteListener.php b/src/Symfony/EventListener/WriteListener.php index c74c140d35..9cf8dfd578 100644 --- a/src/Symfony/EventListener/WriteListener.php +++ b/src/Symfony/EventListener/WriteListener.php @@ -81,7 +81,9 @@ public function onKernelView(ViewEvent $event): void 'request' => $request, 'uri_variables' => $uriVariables, 'resource_class' => $operation->getClass(), - 'previous_data' => false === $operation->canRead() ? null : $request->attributes->get('previous_data'), + 'previous_data' => false === $operation->canRead() ? null : $request->attributes->get('previous_data'), // this is a clone + 'read_data' => false === $operation->canRead() ? null : $request->attributes->get('read_data'), // this is what we read + 'data' => false === $operation->canRead() ? null : $request->attributes->get('data'), // this should be the same as getControllerResult but is the result of deserialization ]); if ($data) { diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue7432/OriginalDataWithListeners.php b/tests/Fixtures/TestBundle/ApiResource/Issue7432/OriginalDataWithListeners.php new file mode 100644 index 0000000000..22c1125188 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue7432/OriginalDataWithListeners.php @@ -0,0 +1,53 @@ + + * + * 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\Tests\Fixtures\TestBundle\ApiResource\Issue7432; + +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Patch; + +#[Patch( + provider: [self::class, 'provide'], + uriTemplate: '/original_data_with_listeners/{uuid}/verify', + uriVariables: ['uuid'], + input: UserVerifyInput::class, + processor: [self::class, 'process'] +)] +class OriginalDataWithListeners +{ + public function __construct(public string $uuid, public ?string $code = null) + { + } + + public static function process($data, Operation $operation, array $uriVariables = [], array $context = []) + { + \assert($data instanceof UserVerifyInput); + \assert($context['previous_data'] instanceof self); + \assert($context['request']->attributes->get('data') instanceof UserVerifyInput); + \assert($context['request']->attributes->get('previous_data') instanceof self); + \assert($context['data'] instanceof UserVerifyInput); + $context['previous_data']->code = $data->code; + + return $context['previous_data']; + } + + public static function provide(Operation $operation, array $uriVariables = [], array $context = []) + { + return new self($uriVariables['uuid']); + } +} + +class UserVerifyInput +{ + public string $code; +} diff --git a/tests/Functional/ListenerTest.php b/tests/Functional/ListenerTest.php new file mode 100644 index 0000000000..28fbd1bf38 --- /dev/null +++ b/tests/Functional/ListenerTest.php @@ -0,0 +1,44 @@ + + * + * 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\Tests\Functional; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7432\OriginalDataWithListeners; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +final class ListenerTest extends ApiTestCase +{ + use SetupClassResourcesTrait; + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ + OriginalDataWithListeners::class, + ]; + } + + public function testListener() + { + self::createClient()->request('PATCH', '/original_data_with_listeners/123/verify', [ + 'headers' => ['content-type' => 'application/merge-patch+json'], + 'json' => ['code' => '456'], + ]); + + $this->assertResponseIsSuccessful(); + } +}