From 48b509a308b9ca3ec4fae94ef639f3057ef61be2 Mon Sep 17 00:00:00 2001 From: Vincent Chalamon <407859+vincentchalamon@users.noreply.github.com> Date: Sun, 12 May 2024 21:10:25 +0200 Subject: [PATCH 1/4] chore: update api-platform/core to 3.3.3 --- api/composer.json | 2 +- api/composer.lock | 20 +++++++++----------- api/config/packages/api_platform.yaml | 2 +- api/tests/Api/ReviewTest.php | 2 +- helm/api-platform/Chart.yaml | 4 ++-- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/api/composer.json b/api/composer.json index d8e2461d..aa19bdf1 100644 --- a/api/composer.json +++ b/api/composer.json @@ -5,7 +5,7 @@ "php": ">=8.3", "ext-ctype": "*", "ext-iconv": "*", - "api-platform/core": "^3.3.0@beta", + "api-platform/core": "^3.3", "doctrine/common": "^3.4", "doctrine/doctrine-bundle": "^2.11", "doctrine/doctrine-fixtures-bundle": "^3.5", diff --git a/api/composer.lock b/api/composer.lock index 635283ee..90b5e3ce 100644 --- a/api/composer.lock +++ b/api/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9f2dfa8ccc5d28f990dc89c88b3dda99", + "content-hash": "3dbd90619cb5a752adb8a236de0d9462", "packages": [ { "name": "api-platform/core", - "version": "v3.3.0-beta.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "1a120ee98db27b9fb3237637579c52e80fe6fadd" + "reference": "59dca65e4f9aa2be6fd85fa944482a7d73eb5fc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/1a120ee98db27b9fb3237637579c52e80fe6fadd", - "reference": "1a120ee98db27b9fb3237637579c52e80fe6fadd", + "url": "https://api.github.com/repos/api-platform/core/zipball/59dca65e4f9aa2be6fd85fa944482a7d73eb5fc2", + "reference": "59dca65e4f9aa2be6fd85fa944482a7d73eb5fc2", "shasum": "" }, "require": { @@ -99,7 +99,7 @@ "symfony/maker-bundle": "^1.24", "symfony/mercure-bundle": "*", "symfony/messenger": "^6.4 || ^7.0", - "symfony/phpunit-bridge": "^6.4 || ^7.0", + "symfony/phpunit-bridge": "^6.4.1 || ^7.0", "symfony/routing": "^6.4 || ^7.0", "symfony/security-bundle": "^6.4 || ^7.0", "symfony/security-core": "^6.4 || ^7.0", @@ -190,9 +190,9 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v3.3.0-beta.2" + "source": "https://github.com/api-platform/core/tree/v3.3.3" }, - "time": "2024-04-13T07:20:28+00:00" + "time": "2024-05-10T11:16:59+00:00" }, { "name": "brick/math", @@ -10327,9 +10327,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "api-platform/core": 10 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/api/config/packages/api_platform.yaml b/api/config/packages/api_platform.yaml index 1d4bc4c7..a20c62be 100644 --- a/api/config/packages/api_platform.yaml +++ b/api/config/packages/api_platform.yaml @@ -1,6 +1,6 @@ api_platform: title: API Platform's demo - version: 3.3.0 + version: 3.3.3 description: | This is a demo application of the [API Platform](https://api-platform.com) framework. [Its source code](https://github.com/api-platform/demo) includes various examples, check it out! diff --git a/api/tests/Api/ReviewTest.php b/api/tests/Api/ReviewTest.php index d29a94f2..9f44ba33 100644 --- a/api/tests/Api/ReviewTest.php +++ b/api/tests/Api/ReviewTest.php @@ -232,7 +232,7 @@ public function asAUserICannotAddAReviewWithValidDataOnAnInvalidBook(): void self::assertJsonContains([ '@type' => 'hydra:Error', 'hydra:title' => 'An error occurred', - 'hydra:description' => 'Invalid uri variables.', + 'hydra:description' => 'Invalid identifier value or configuration.', ]); } diff --git a/helm/api-platform/Chart.yaml b/helm/api-platform/Chart.yaml index e6b8a94a..a54bd99b 100644 --- a/helm/api-platform/Chart.yaml +++ b/helm/api-platform/Chart.yaml @@ -17,12 +17,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 3.3.0 +version: 3.3.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 3.3.0 +appVersion: 3.3.3 dependencies: - name: postgresql From 63093e3d8ea00a3a557d17ffc9008a5c2eb1dc94 Mon Sep 17 00:00:00 2001 From: Vincent Chalamon <407859+vincentchalamon@users.noreply.github.com> Date: Mon, 13 May 2024 09:25:24 +0200 Subject: [PATCH 2/4] fix: fix deprecations --- api/src/DataFixtures/Factory/BookmarkFactory.php | 1 - api/src/DataFixtures/Factory/ReviewFactory.php | 1 - api/src/Doctrine/Orm/Filter/NameFilter.php | 3 +-- api/src/Security/Http/Protection/ResourceResourceHandler.php | 2 +- api/src/Serializer/IriTransformerNormalizer.php | 4 ++-- api/src/State/Processor/BookRemoveProcessor.php | 4 ++-- api/src/State/Processor/MercureProcessor.php | 4 ++-- api/src/State/Processor/ReviewRemoveProcessor.php | 4 ++-- 8 files changed, 10 insertions(+), 13 deletions(-) diff --git a/api/src/DataFixtures/Factory/BookmarkFactory.php b/api/src/DataFixtures/Factory/BookmarkFactory.php index efc712a8..b58cfd37 100644 --- a/api/src/DataFixtures/Factory/BookmarkFactory.php +++ b/api/src/DataFixtures/Factory/BookmarkFactory.php @@ -9,7 +9,6 @@ use Zenstruck\Foundry\ModelFactory; use Zenstruck\Foundry\Proxy; use Zenstruck\Foundry\RepositoryProxy; - use function Zenstruck\Foundry\lazy; /** diff --git a/api/src/DataFixtures/Factory/ReviewFactory.php b/api/src/DataFixtures/Factory/ReviewFactory.php index 4ee375c5..cdcaa001 100644 --- a/api/src/DataFixtures/Factory/ReviewFactory.php +++ b/api/src/DataFixtures/Factory/ReviewFactory.php @@ -10,7 +10,6 @@ use Zenstruck\Foundry\ModelFactory; use Zenstruck\Foundry\Proxy; use Zenstruck\Foundry\RepositoryProxy; - use function Zenstruck\Foundry\lazy; /** diff --git a/api/src/Doctrine/Orm/Filter/NameFilter.php b/api/src/Doctrine/Orm/Filter/NameFilter.php index cb31c64f..93361f86 100644 --- a/api/src/Doctrine/Orm/Filter/NameFilter.php +++ b/api/src/Doctrine/Orm/Filter/NameFilter.php @@ -7,7 +7,6 @@ use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; use ApiPlatform\Doctrine\Orm\PropertyHelperTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use Doctrine\ORM\QueryBuilder; @@ -73,7 +72,7 @@ protected function normalizeValues($value, string $property): ?array if (empty($values)) { $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('At least one value is required, multiple values should be in "%1$s[]=firstvalue&%1$s[]=secondvalue" format', $property)), + 'exception' => new \InvalidArgumentException(sprintf('At least one value is required, multiple values should be in "%1$s[]=firstvalue&%1$s[]=secondvalue" format', $property)), ]); return null; diff --git a/api/src/Security/Http/Protection/ResourceResourceHandler.php b/api/src/Security/Http/Protection/ResourceResourceHandler.php index ef51abaf..46b6d9c6 100644 --- a/api/src/Security/Http/Protection/ResourceResourceHandler.php +++ b/api/src/Security/Http/Protection/ResourceResourceHandler.php @@ -4,9 +4,9 @@ namespace App\Security\Http\Protection; -use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\UrlGeneratorInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; diff --git a/api/src/Serializer/IriTransformerNormalizer.php b/api/src/Serializer/IriTransformerNormalizer.php index 85b0e787..50f2ef70 100644 --- a/api/src/Serializer/IriTransformerNormalizer.php +++ b/api/src/Serializer/IriTransformerNormalizer.php @@ -4,10 +4,10 @@ namespace App\Serializer; -use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\JsonLd\Serializer\ItemNormalizer; +use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface; +use ApiPlatform\Metadata\UrlGeneratorInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; diff --git a/api/src/State/Processor/BookRemoveProcessor.php b/api/src/State/Processor/BookRemoveProcessor.php index eb6fc6e1..eac2d8f5 100644 --- a/api/src/State/Processor/BookRemoveProcessor.php +++ b/api/src/State/Processor/BookRemoveProcessor.php @@ -4,11 +4,11 @@ namespace App\State\Processor; -use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\Doctrine\Common\State\RemoveProcessor; +use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\UrlGeneratorInterface; use ApiPlatform\State\ProcessorInterface; use App\Entity\Book; use Symfony\Component\DependencyInjection\Attribute\Autowire; diff --git a/api/src/State/Processor/MercureProcessor.php b/api/src/State/Processor/MercureProcessor.php index 7d3ade91..bf8cf934 100644 --- a/api/src/State/Processor/MercureProcessor.php +++ b/api/src/State/Processor/MercureProcessor.php @@ -4,11 +4,11 @@ namespace App\State\Processor; -use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException; +use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\UrlGeneratorInterface; use ApiPlatform\State\ProcessorInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Mercure\Exception\InvalidArgumentException; diff --git a/api/src/State/Processor/ReviewRemoveProcessor.php b/api/src/State/Processor/ReviewRemoveProcessor.php index a0dccbd3..5980569c 100644 --- a/api/src/State/Processor/ReviewRemoveProcessor.php +++ b/api/src/State/Processor/ReviewRemoveProcessor.php @@ -4,11 +4,11 @@ namespace App\State\Processor; -use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\Doctrine\Common\State\RemoveProcessor; +use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\UrlGeneratorInterface; use ApiPlatform\State\ProcessorInterface; use App\Entity\Review; use App\Security\Http\Protection\ResourceHandlerInterface; From f72007115d19c237eb3d06bb1db2294f99377a1c Mon Sep 17 00:00:00 2001 From: Vincent Chalamon <407859+vincentchalamon@users.noreply.github.com> Date: Mon, 13 May 2024 09:31:57 +0200 Subject: [PATCH 3/4] feat: use mercure option --- api/.php-cs-fixer.dist.php | 1 - .../DataFixtures/Factory/BookmarkFactory.php | 1 + .../DataFixtures/Factory/ReviewFactory.php | 1 + api/src/Entity/Book.php | 12 +- api/src/Entity/Review.php | 14 ++- .../State/Processor/BookPersistProcessor.php | 19 +-- .../State/Processor/BookRemoveProcessor.php | 30 +---- api/src/State/Processor/MercureProcessor.php | 73 ----------- .../Processor/ReviewPersistProcessor.php | 15 --- .../State/Processor/ReviewRemoveProcessor.php | 28 +---- api/tests/Api/Admin/BookTest.php | 43 ++----- api/tests/Api/Admin/ReviewTest.php | 30 ++--- api/tests/Api/ReviewTest.php | 31 ++--- .../IriTransformerNormalizerTest.php | 2 +- .../Processor/BookPersistProcessorTest.php | 15 --- .../Processor/BookRemoveProcessorTest.php | 63 +--------- .../State/Processor/MercureProcessorTest.php | 119 ------------------ .../Processor/ReviewPersistProcessorTest.php | 27 ---- .../Processor/ReviewRemoveProcessorTest.php | 59 --------- compose.override.yaml | 2 +- 20 files changed, 45 insertions(+), 540 deletions(-) delete mode 100644 api/src/State/Processor/MercureProcessor.php delete mode 100644 api/tests/State/Processor/MercureProcessorTest.php diff --git a/api/.php-cs-fixer.dist.php b/api/.php-cs-fixer.dist.php index aca1fcd1..6db5292e 100644 --- a/api/.php-cs-fixer.dist.php +++ b/api/.php-cs-fixer.dist.php @@ -33,7 +33,6 @@ 'phpdoc_to_comment' => false, 'void_return' => true, 'concat_space' => ['spacing' => 'one'], - 'braces' => ['allow_single_line_closure' => true], 'nullable_type_declaration' => true, ]) ->setFinder($finder) diff --git a/api/src/DataFixtures/Factory/BookmarkFactory.php b/api/src/DataFixtures/Factory/BookmarkFactory.php index b58cfd37..efc712a8 100644 --- a/api/src/DataFixtures/Factory/BookmarkFactory.php +++ b/api/src/DataFixtures/Factory/BookmarkFactory.php @@ -9,6 +9,7 @@ use Zenstruck\Foundry\ModelFactory; use Zenstruck\Foundry\Proxy; use Zenstruck\Foundry\RepositoryProxy; + use function Zenstruck\Foundry\lazy; /** diff --git a/api/src/DataFixtures/Factory/ReviewFactory.php b/api/src/DataFixtures/Factory/ReviewFactory.php index cdcaa001..4ee375c5 100644 --- a/api/src/DataFixtures/Factory/ReviewFactory.php +++ b/api/src/DataFixtures/Factory/ReviewFactory.php @@ -10,6 +10,7 @@ use Zenstruck\Foundry\ModelFactory; use Zenstruck\Foundry\Proxy; use Zenstruck\Foundry\RepositoryProxy; + use function Zenstruck\Foundry\lazy; /** diff --git a/api/src/Entity/Book.php b/api/src/Entity/Book.php index d1977e71..a3aca2f2 100644 --- a/api/src/Entity/Book.php +++ b/api/src/Entity/Book.php @@ -15,6 +15,7 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; +use ApiPlatform\Metadata\UrlGeneratorInterface; use App\Enum\BookCondition; use App\Repository\BookRepository; use App\State\Processor\BookPersistProcessor; @@ -44,7 +45,6 @@ paginationClientItemsPerPage: true ), new Post( - // Mercure publish is done manually in MercureProcessor through BookPersistProcessor processor: BookPersistProcessor::class, itemUriTemplate: '/admin/books/{id}{._format}' ), @@ -54,12 +54,10 @@ // https://github.com/api-platform/admin/issues/370 new Put( uriTemplate: '/admin/books/{id}{._format}', - // Mercure publish is done manually in MercureProcessor through BookPersistProcessor processor: BookPersistProcessor::class ), new Delete( uriTemplate: '/admin/books/{id}{._format}', - // Mercure publish is done manually in MercureProcessor through BookRemoveProcessor processor: BookRemoveProcessor::class ), ], @@ -71,7 +69,13 @@ AbstractNormalizer::GROUPS => ['Book:write'], ], collectDenormalizationErrors: true, - security: 'is_granted("OIDC_ADMIN")' + security: 'is_granted("OIDC_ADMIN")', + mercure: [ + 'topics' => [ + '@=iri(object, ' . UrlGeneratorInterface::ABS_URL . ', get_operation(object, "/admin/books/{id}{._format}"))', + '@=iri(object, ' . UrlGeneratorInterface::ABS_URL . ', get_operation(object, "/books/{id}{._format}"))', + ], + ] )] #[ApiResource( types: ['https://schema.org/Book', 'https://schema.org/Offer'], diff --git a/api/src/Entity/Review.php b/api/src/Entity/Review.php index 9c53b0b7..cdd05b96 100644 --- a/api/src/Entity/Review.php +++ b/api/src/Entity/Review.php @@ -14,6 +14,7 @@ use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; +use ApiPlatform\Metadata\UrlGeneratorInterface; use ApiPlatform\State\CreateProvider; use App\Repository\ReviewRepository; use App\Security\Voter\OidcTokenPermissionVoter; @@ -56,12 +57,10 @@ // https://github.com/api-platform/admin/issues/370 new Put( uriTemplate: '/admin/reviews/{id}{._format}', - // Mercure publish is done manually in MercureProcessor through ReviewPersistProcessor processor: ReviewPersistProcessor::class ), new Delete( uriTemplate: '/admin/reviews/{id}{._format}', - // Mercure publish is done manually in MercureProcessor through ReviewRemoveProcessor processor: ReviewRemoveProcessor::class ), ], @@ -77,7 +76,13 @@ AbstractNormalizer::GROUPS => ['Review:write', 'Review:write:admin'], ], collectDenormalizationErrors: true, - security: 'is_granted("OIDC_ADMIN")' + security: 'is_granted("OIDC_ADMIN")', + mercure: [ + 'topics' => [ + '@=iri(object, ' . UrlGeneratorInterface::ABS_URL . ', get_operation(object, "/admin/reviews/{id}{._format}"))', + '@=iri(object, ' . UrlGeneratorInterface::ABS_URL . ', get_operation(object, "/books/{bookId}/reviews/{id}{._format}"))', + ], + ] )] #[ApiResource( types: ['https://schema.org/Review'], @@ -100,7 +105,6 @@ ), new Post( security: 'is_granted("OIDC_USER")', - // Mercure publish is done manually in MercureProcessor through ReviewPersistProcessor processor: ReviewPersistProcessor::class, provider: CreateProvider::class, itemUriTemplate: '/books/{bookId}/reviews/{id}{._format}', @@ -114,7 +118,6 @@ ], /** @see OidcTokenPermissionVoter */ security: 'is_granted("OIDC_USER", request.getRequestUri())', - // Mercure publish is done manually in MercureProcessor through ReviewPersistProcessor processor: ReviewPersistProcessor::class ), new Delete( @@ -125,7 +128,6 @@ ], /** @see OidcTokenPermissionVoter */ security: 'is_granted("OIDC_USER", request.getRequestUri())', - // Mercure publish is done manually in MercureProcessor through ReviewRemoveProcessor processor: ReviewRemoveProcessor::class ), ], diff --git a/api/src/State/Processor/BookPersistProcessor.php b/api/src/State/Processor/BookPersistProcessor.php index 7d41be46..5fb1a3d6 100644 --- a/api/src/State/Processor/BookPersistProcessor.php +++ b/api/src/State/Processor/BookPersistProcessor.php @@ -20,13 +20,10 @@ { /** * @param PersistProcessor $persistProcessor - * @param MercureProcessor $mercureProcessor */ public function __construct( #[Autowire(service: PersistProcessor::class)] private ProcessorInterface $persistProcessor, - #[Autowire(service: MercureProcessor::class)] - private ProcessorInterface $mercureProcessor, private HttpClientInterface $client, private DecoderInterface $decoder ) { @@ -49,21 +46,7 @@ public function process(mixed $data, Operation $operation, array $uriVariables = } // save entity - $data = $this->persistProcessor->process($data, $operation, $uriVariables, $context); - - // publish on Mercure - foreach (['/admin/books/{id}{._format}', '/books/{id}{._format}'] as $uriTemplate) { - $this->mercureProcessor->process( - $data, - $operation, - $uriVariables, - $context + [ - 'item_uri_template' => $uriTemplate, - ] - ); - } - - return $data; + return $this->persistProcessor->process($data, $operation, $uriVariables, $context); } private function getData(string $uri): array diff --git a/api/src/State/Processor/BookRemoveProcessor.php b/api/src/State/Processor/BookRemoveProcessor.php index eac2d8f5..6aa937d6 100644 --- a/api/src/State/Processor/BookRemoveProcessor.php +++ b/api/src/State/Processor/BookRemoveProcessor.php @@ -5,10 +5,7 @@ namespace App\State\Processor; use ApiPlatform\Doctrine\Common\State\RemoveProcessor; -use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\UrlGeneratorInterface; use ApiPlatform\State\ProcessorInterface; use App\Entity\Book; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -19,16 +16,11 @@ final readonly class BookRemoveProcessor implements ProcessorInterface { /** - * @param RemoveProcessor $removeProcessor - * @param MercureProcessor $mercureProcessor + * @param RemoveProcessor $removeProcessor */ public function __construct( #[Autowire(service: RemoveProcessor::class)] private ProcessorInterface $removeProcessor, - #[Autowire(service: MercureProcessor::class)] - private ProcessorInterface $mercureProcessor, - private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, - private IriConverterInterface $iriConverter ) { } @@ -37,27 +29,7 @@ public function __construct( */ public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void { - $object = clone $data; - // remove entity $this->removeProcessor->process($data, $operation, $uriVariables, $context); - - // publish on Mercure - foreach (['/admin/books/{id}{._format}', '/books/{id}{._format}'] as $uriTemplate) { - $iri = $this->iriConverter->getIriFromResource( - $object, - UrlGeneratorInterface::ABS_URL, - $this->resourceMetadataCollectionFactory->create(Book::class)->getOperation($uriTemplate) - ); - $this->mercureProcessor->process( - $object, - $operation, - $uriVariables, - $context + [ - 'item_uri_template' => $uriTemplate, - MercureProcessor::DATA => json_encode(['@id' => $iri]), - ] - ); - } } } diff --git a/api/src/State/Processor/MercureProcessor.php b/api/src/State/Processor/MercureProcessor.php deleted file mode 100644 index bf8cf934..00000000 --- a/api/src/State/Processor/MercureProcessor.php +++ /dev/null @@ -1,73 +0,0 @@ - - */ -final readonly class MercureProcessor implements ProcessorInterface -{ - public const DATA = 'mercure_data'; - - public function __construct( - private SerializerInterface $serializer, - private HubRegistry $hubRegistry, - private IriConverterInterface $iriConverter, - private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, - #[Autowire('%api_platform.formats%')] - private array $formats - ) { - } - - /** - * @param array{id?: string, type?: string, private?: bool, retry?: int, item_uri_template?: string, topics?: array, mercure_data?: string} $context - * - * @throws InvalidArgumentException - * @throws ResourceClassNotFoundException - * @throws RuntimeException - */ - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed - { - if (isset($context['item_uri_template'])) { - $operation = $this->resourceMetadataCollectionFactory->create($data::class)->getOperation($context['item_uri_template']); - } - if (!isset($context['topics'])) { - $context['topics'] = [$this->iriConverter->getIriFromResource($data, UrlGeneratorInterface::ABS_URL, $operation)]; - } - if (!isset($context[self::DATA])) { - $context[self::DATA] = $this->serializer->serialize( - $data, - key($this->formats), - ($operation->getNormalizationContext() ?? []) + (isset($context['item_uri_template']) ? [ - 'item_uri_template' => $context['item_uri_template'], - ] : []) - ); - } - - $this->hubRegistry->getHub()->publish(new Update( - topics: $context['topics'], - data: $context[self::DATA], - id: $context['id'] ?? null, - type: $context['type'] ?? null, - private: $context['private'] ?? false, - retry: $context['retry'] ?? null, - )); - - return $data; - } -} diff --git a/api/src/State/Processor/ReviewPersistProcessor.php b/api/src/State/Processor/ReviewPersistProcessor.php index 5ffe5761..75cf3df2 100644 --- a/api/src/State/Processor/ReviewPersistProcessor.php +++ b/api/src/State/Processor/ReviewPersistProcessor.php @@ -21,13 +21,10 @@ { /** * @param PersistProcessor $persistProcessor - * @param MercureProcessor $mercureProcessor */ public function __construct( #[Autowire(service: PersistProcessor::class)] private ProcessorInterface $persistProcessor, - #[Autowire(service: MercureProcessor::class)] - private ProcessorInterface $mercureProcessor, private Security $security, private ClockInterface $clock, private ResourceHandlerInterface $resourceHandler, @@ -65,18 +62,6 @@ public function process(mixed $data, Operation $operation, array $uriVariables = } } - // publish on Mercure - foreach (['/admin/reviews/{id}{._format}', '/books/{bookId}/reviews/{id}{._format}'] as $uriTemplate) { - $this->mercureProcessor->process( - $data, - $operation, - $uriVariables, - $context + [ - 'item_uri_template' => $uriTemplate, - ] - ); - } - return $data; } } diff --git a/api/src/State/Processor/ReviewRemoveProcessor.php b/api/src/State/Processor/ReviewRemoveProcessor.php index 5980569c..e951a8d7 100644 --- a/api/src/State/Processor/ReviewRemoveProcessor.php +++ b/api/src/State/Processor/ReviewRemoveProcessor.php @@ -5,10 +5,7 @@ namespace App\State\Processor; use ApiPlatform\Doctrine\Common\State\RemoveProcessor; -use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\UrlGeneratorInterface; use ApiPlatform\State\ProcessorInterface; use App\Entity\Review; use App\Security\Http\Protection\ResourceHandlerInterface; @@ -20,16 +17,11 @@ final readonly class ReviewRemoveProcessor implements ProcessorInterface { /** - * @param RemoveProcessor $removeProcessor - * @param MercureProcessor $mercureProcessor + * @param RemoveProcessor $removeProcessor */ public function __construct( #[Autowire(service: RemoveProcessor::class)] private ProcessorInterface $removeProcessor, - #[Autowire(service: MercureProcessor::class)] - private ProcessorInterface $mercureProcessor, - private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, - private IriConverterInterface $iriConverter, private ResourceHandlerInterface $resourceHandler, ) { } @@ -50,23 +42,5 @@ public function process(mixed $data, Operation $operation, array $uriVariables = 'operation_name' => '/books/{bookId}/reviews/{id}{._format}', ]); } - - // publish on Mercure - foreach (['/admin/reviews/{id}{._format}', '/books/{bookId}/reviews/{id}{._format}'] as $uriTemplate) { - $iri = $this->iriConverter->getIriFromResource( - $object, - UrlGeneratorInterface::ABS_URL, - $this->resourceMetadataCollectionFactory->create(Review::class)->getOperation($uriTemplate) - ); - $this->mercureProcessor->process( - $object, - $operation, - $uriVariables, - $context + [ - 'item_uri_template' => $uriTemplate, - MercureProcessor::DATA => json_encode(['@id' => $iri]), - ] - ); - } } } diff --git a/api/tests/Api/Admin/BookTest.php b/api/tests/Api/Admin/BookTest.php index e107e4c2..47f1b21d 100644 --- a/api/tests/Api/Admin/BookTest.php +++ b/api/tests/Api/Admin/BookTest.php @@ -395,10 +395,10 @@ public function asAdminUserICanCreateABook(): void $id = preg_replace('/^.*\/(.+)$/', '$1', $response->toArray()['@id']); /** @var Book $book */ $book = self::getContainer()->get(BookRepository::class)->find($id); - self::assertCount(2, self::getMercureMessages()); + self::assertCount(1, self::getMercureMessages()); self::assertEquals( new Update( - topics: ['http://localhost/admin/books/' . $book->getId()], + topics: ['http://localhost/admin/books/' . $book->getId(), 'http://localhost/books/' . $book->getId()], data: self::serialize( $book, 'jsonld', @@ -407,17 +407,6 @@ public function asAdminUserICanCreateABook(): void ), self::getMercureMessage() ); - self::assertEquals( - new Update( - topics: ['http://localhost/books/' . $book->getId()], - data: self::serialize( - $book, - 'jsonld', - self::getOperationNormalizationContext(Book::class, '/books/{id}{._format}') - ), - ), - self::getMercureMessage(1) - ); } #[Test] @@ -542,10 +531,10 @@ public function asAdminUserICanUpdateABook(): void 'author' => 'Isaac Asimov', ]); self::assertMatchesJsonSchema(file_get_contents(__DIR__ . '/schemas/Book/item.json')); - self::assertCount(2, self::getMercureMessages()); + self::assertCount(1, self::getMercureMessages()); self::assertEquals( new Update( - topics: ['http://localhost/admin/books/' . $book->getId()], + topics: ['http://localhost/admin/books/' . $book->getId(), 'http://localhost/books/' . $book->getId()], data: self::serialize( $book->object(), 'jsonld', @@ -554,17 +543,6 @@ public function asAdminUserICanUpdateABook(): void ), self::getMercureMessage() ); - self::assertEquals( - new Update( - topics: ['http://localhost/books/' . $book->getId()], - data: self::serialize( - $book->object(), - 'jsonld', - self::getOperationNormalizationContext(Book::class, '/books/{id}{._format}') - ), - ), - self::getMercureMessage(1) - ); } #[Test] @@ -626,20 +604,13 @@ public function asAdminUserICanDeleteABook(): void self::assertResponseStatusCodeSame(Response::HTTP_NO_CONTENT); self::assertEmpty($response->getContent()); BookFactory::assert()->notExists(['title' => 'Hyperion']); - self::assertCount(2, self::getMercureMessages()); + self::assertCount(1, self::getMercureMessages()); self::assertEquals( new Update( - topics: ['http://localhost/admin/books/' . $id], - data: json_encode(['@id' => 'http://localhost/admin/books/' . $id]), + topics: ['http://localhost/admin/books/' . $id, 'http://localhost/books/' . $id], + data: json_encode(['@id' => '/admin/books/' . $id, '@type' => ['https://schema.org/Book', 'https://schema.org/Offer']]), ), self::getMercureMessage() ); - self::assertEquals( - new Update( - topics: ['http://localhost/books/' . $id], - data: json_encode(['@id' => 'http://localhost/books/' . $id]), - ), - self::getMercureMessage(1) - ); } } diff --git a/api/tests/Api/Admin/ReviewTest.php b/api/tests/Api/Admin/ReviewTest.php index 464cc1f3..85db18a8 100644 --- a/api/tests/Api/Admin/ReviewTest.php +++ b/api/tests/Api/Admin/ReviewTest.php @@ -262,6 +262,7 @@ public function asAdminUserICanUpdateAReview(): void $book = BookFactory::createOne(); $review = ReviewFactory::createOne(['book' => $book]); $user = UserFactory::createOneAdmin(); + self::getMercureHub()->reset(); $token = self::getContainer()->get(TokenGenerator::class)->generateToken([ 'email' => $user->email, @@ -291,10 +292,10 @@ public function asAdminUserICanUpdateAReview(): void // ensure user hasn't changed self::assertNotEquals($user, $review->object()->user); self::assertMatchesJsonSchema(file_get_contents(__DIR__ . '/schemas/Review/item.json')); - self::assertCount(2, self::getMercureMessages()); + self::assertCount(1, self::getMercureMessages()); self::assertEquals( new Update( - topics: ['http://localhost/admin/reviews/' . $review->getId()], + topics: ['http://localhost/admin/reviews/' . $review->getId(), 'http://localhost/books/' . $review->book->getId() . '/reviews/' . $review->getId()], data: self::serialize( $review->object(), 'jsonld', @@ -303,17 +304,6 @@ public function asAdminUserICanUpdateAReview(): void ), self::getMercureMessage() ); - self::assertEquals( - new Update( - topics: ['http://localhost/books/' . $review->book->getId() . '/reviews/' . $review->getId()], - data: self::serialize( - $review->object(), - 'jsonld', - self::getOperationNormalizationContext(Review::class, '/books/{bookId}/reviews/{id}{._format}') - ), - ), - self::getMercureMessage(1) - ); } #[Test] @@ -363,6 +353,7 @@ public function asAdminUserICanDeleteAReview(): void $review = ReviewFactory::createOne(['body' => 'Best book ever!']); $id = $review->getId(); $bookId = $review->book->getId(); + self::getMercureHub()->reset(); $token = self::getContainer()->get(TokenGenerator::class)->generateToken([ 'email' => UserFactory::createOneAdmin()->email, @@ -375,20 +366,13 @@ public function asAdminUserICanDeleteAReview(): void self::assertResponseStatusCodeSame(Response::HTTP_NO_CONTENT); self::assertEmpty($response->getContent()); ReviewFactory::assert()->notExists(['body' => 'Best book ever!']); - self::assertCount(2, self::getMercureMessages()); + self::assertCount(1, self::getMercureMessages()); self::assertEquals( new Update( - topics: ['http://localhost/admin/reviews/' . $id], - data: json_encode(['@id' => 'http://localhost/admin/reviews/' . $id]), + topics: ['http://localhost/admin/reviews/' . $id, 'http://localhost/books/' . $bookId . '/reviews/' . $id], + data: json_encode(['@id' => '/admin/reviews/' . $id, '@type' => 'https://schema.org/Review']), ), self::getMercureMessage() ); - self::assertEquals( - new Update( - topics: ['http://localhost/books/' . $bookId . '/reviews/' . $id], - data: json_encode(['@id' => 'http://localhost/books/' . $bookId . '/reviews/' . $id]), - ), - self::getMercureMessage(1) - ); } } diff --git a/api/tests/Api/ReviewTest.php b/api/tests/Api/ReviewTest.php index 9f44ba33..b72cd84d 100644 --- a/api/tests/Api/ReviewTest.php +++ b/api/tests/Api/ReviewTest.php @@ -281,17 +281,12 @@ public function asAUserICanAddAReviewOnABook(): void $id = preg_replace('/^.*\/(.+)$/', '$1', $response->toArray()['@id']); /** @var Review $review */ $review = self::getContainer()->get(ReviewRepository::class)->find($id); - self::assertCount(2, self::getMercureMessages()); + self::assertCount(1, self::getMercureMessages()); self::assertMercureUpdateMatchesJsonSchema( update: self::getMercureMessage(), - topics: ['http://localhost/admin/reviews/' . $review->getId()], + topics: ['http://localhost/admin/reviews/' . $review->getId(), 'http://localhost/books/' . $book->getId() . '/reviews/' . $review->getId()], jsonSchema: file_get_contents(__DIR__ . '/Admin/schemas/Review/item.json') ); - self::assertMercureUpdateMatchesJsonSchema( - update: self::getMercureMessage(1), - topics: ['http://localhost/books/' . $book->getId() . '/reviews/' . $review->getId()], - jsonSchema: file_get_contents(__DIR__ . '/schemas/Review/item.json') - ); } #[Test] @@ -474,17 +469,12 @@ public function asAUserICanUpdateMyBookReview(): void 'rating' => 5, ]); self::assertMatchesJsonSchema(file_get_contents(__DIR__ . '/schemas/Review/item.json')); - self::assertCount(2, self::getMercureMessages()); + self::assertCount(1, self::getMercureMessages()); self::assertMercureUpdateMatchesJsonSchema( update: self::getMercureMessage(), - topics: ['http://localhost/admin/reviews/' . $review->getId()], + topics: ['http://localhost/admin/reviews/' . $review->getId(), 'http://localhost/books/' . $review->book->getId() . '/reviews/' . $review->getId()], jsonSchema: file_get_contents(__DIR__ . '/Admin/schemas/Review/item.json') ); - self::assertMercureUpdateMatchesJsonSchema( - update: self::getMercureMessage(1), - topics: ['http://localhost/books/' . $review->book->getId() . '/reviews/' . $review->getId()], - jsonSchema: file_get_contents(__DIR__ . '/schemas/Review/item.json') - ); } #[Test] @@ -567,20 +557,13 @@ public function asAUserICanDeleteMyBookReview(): void self::assertResponseStatusCodeSame(Response::HTTP_NO_CONTENT); self::assertEmpty($response->getContent()); ReviewFactory::assert()->notExists(['body' => 'Best book ever!']); - self::assertCount(2, self::getMercureMessages()); + self::assertCount(1, self::getMercureMessages()); self::assertEquals( new Update( - topics: ['http://localhost/admin/reviews/' . $id], - data: json_encode(['@id' => 'http://localhost/admin/reviews/' . $id]), + topics: ['http://localhost/admin/reviews/' . $id, 'http://localhost/books/' . $bookId . '/reviews/' . $id], + data: json_encode(['@id' => '/admin/reviews/' . $id, '@type' => 'https://schema.org/Review']), ), self::getMercureMessage() ); - self::assertEquals( - new Update( - topics: ['http://localhost/books/' . $bookId . '/reviews/' . $id], - data: json_encode(['@id' => 'http://localhost/books/' . $bookId . '/reviews/' . $id]), - ), - self::getMercureMessage(1) - ); } } diff --git a/api/tests/Serializer/IriTransformerNormalizerTest.php b/api/tests/Serializer/IriTransformerNormalizerTest.php index aef28b88..d96dea61 100644 --- a/api/tests/Serializer/IriTransformerNormalizerTest.php +++ b/api/tests/Serializer/IriTransformerNormalizerTest.php @@ -4,7 +4,7 @@ namespace App\Tests\Serializer; -use ApiPlatform\Api\IriConverterInterface; +use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface; use App\Serializer\IriTransformerNormalizer; diff --git a/api/tests/State/Processor/BookPersistProcessorTest.php b/api/tests/State/Processor/BookPersistProcessorTest.php index 69b31bb9..91beb3fd 100644 --- a/api/tests/State/Processor/BookPersistProcessorTest.php +++ b/api/tests/State/Processor/BookPersistProcessorTest.php @@ -18,7 +18,6 @@ final class BookPersistProcessorTest extends TestCase { private MockObject|ProcessorInterface $persistProcessorMock; - private MockObject|ProcessorInterface $mercureProcessorMock; private HttpClientInterface|MockObject $clientMock; private MockObject|ResponseInterface $responseMock; private DecoderInterface|MockObject $decoderMock; @@ -29,7 +28,6 @@ final class BookPersistProcessorTest extends TestCase protected function setUp(): void { $this->persistProcessorMock = $this->createMock(ProcessorInterface::class); - $this->mercureProcessorMock = $this->createMock(ProcessorInterface::class); $this->clientMock = $this->createMock(HttpClientInterface::class); $this->responseMock = $this->createMock(ResponseInterface::class); $this->decoderMock = $this->createMock(DecoderInterface::class); @@ -39,7 +37,6 @@ protected function setUp(): void $this->processor = new BookPersistProcessor( $this->persistProcessorMock, - $this->mercureProcessorMock, $this->clientMock, $this->decoderMock ); @@ -126,18 +123,6 @@ public function itUpdatesBookDataBeforeSaveAndSendMercureUpdates(): void ->with($expectedData, $this->operationMock, [], []) ->willReturn($expectedData) ; - $this->mercureProcessorMock - ->expects($this->exactly(2)) - ->method('process') -// ->withConsecutive( -// [$expectedData, $this->operationMock, [], ['item_uri_template' => '/admin/books/{id}{._format}']], -// [$expectedData, $this->operationMock, [], ['item_uri_template' => '/books/{id}{._format}']], -// ) - ->willReturnOnConsecutiveCalls( - $expectedData, - $expectedData, - ) - ; $this->assertEquals($expectedData, $this->processor->process($this->objectMock, $this->operationMock)); } diff --git a/api/tests/State/Processor/BookRemoveProcessorTest.php b/api/tests/State/Processor/BookRemoveProcessorTest.php index 02628ee4..ff416a09 100644 --- a/api/tests/State/Processor/BookRemoveProcessorTest.php +++ b/api/tests/State/Processor/BookRemoveProcessorTest.php @@ -4,11 +4,9 @@ namespace App\Tests\State\Processor; -use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\State\ProcessorInterface; use App\Entity\Book; @@ -20,10 +18,7 @@ final class BookRemoveProcessorTest extends TestCase { private MockObject|ProcessorInterface $removeProcessorMock; - private MockObject|ProcessorInterface $mercureProcessorMock; - private MockObject|ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactoryMock; private ResourceMetadataCollection $resourceMetadataCollection; - private IriConverterInterface|MockObject $iriConverterMock; private Book|MockObject $objectMock; private MockObject|Operation $operationMock; private BookRemoveProcessor $processor; @@ -31,22 +26,14 @@ final class BookRemoveProcessorTest extends TestCase protected function setUp(): void { $this->removeProcessorMock = $this->createMock(ProcessorInterface::class); - $this->mercureProcessorMock = $this->createMock(ProcessorInterface::class); - $this->resourceMetadataCollectionFactoryMock = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); $this->resourceMetadataCollection = new ResourceMetadataCollection(Book::class, [ new ApiResource(operations: [new Get('/admin/books/{id}{._format}')]), new ApiResource(operations: [new Get('/books/{id}{._format}')]), ]); - $this->iriConverterMock = $this->createMock(IriConverterInterface::class); $this->objectMock = $this->createMock(Book::class); $this->operationMock = $this->createMock(Operation::class); - $this->processor = new BookRemoveProcessor( - $this->removeProcessorMock, - $this->mercureProcessorMock, - $this->resourceMetadataCollectionFactoryMock, - $this->iriConverterMock - ); + $this->processor = new BookRemoveProcessor($this->removeProcessorMock); } #[Test] @@ -57,54 +44,6 @@ public function itRemovesBookAndSendMercureUpdates(): void ->method('process') ->with($this->objectMock, $this->operationMock, [], []) ; - $this->resourceMetadataCollectionFactoryMock - ->expects($this->exactly(2)) - ->method('create') -// ->withConsecutive( -// [Book::class], -// [Book::class], -// ) - ->willReturnOnConsecutiveCalls( - $this->resourceMetadataCollection, - $this->resourceMetadataCollection, - ) - ; - $this->iriConverterMock - ->expects($this->exactly(2)) - ->method('getIriFromResource') -// ->withConsecutive( -// [$this->objectMock, UrlGeneratorInterface::ABS_URL, new Get('/admin/books/{id}{._format}')], -// [$this->objectMock, UrlGeneratorInterface::ABS_URL, new Get('/books/{id}{._format}')], -// ) - ->willReturnOnConsecutiveCalls( - '/admin/books/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6', - '/books/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6', - ) - ; - $this->mercureProcessorMock - ->expects($this->exactly(2)) - ->method('process') -// ->withConsecutive( -// [ -// $this->objectMock, -// $this->operationMock, -// [], -// [ -// 'item_uri_template' => '/admin/books/{id}{._format}', -// MercureProcessor::DATA => json_encode(['@id' => '/admin/books/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6']), -// ], -// ], -// [ -// $this->objectMock, -// $this->operationMock, -// [], -// [ -// 'item_uri_template' => '/books/{id}{._format}', -// MercureProcessor::DATA => json_encode(['@id' => '/books/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6']), -// ], -// ], -// ) - ; $this->processor->process($this->objectMock, $this->operationMock); } diff --git a/api/tests/State/Processor/MercureProcessorTest.php b/api/tests/State/Processor/MercureProcessorTest.php deleted file mode 100644 index 72520651..00000000 --- a/api/tests/State/Processor/MercureProcessorTest.php +++ /dev/null @@ -1,119 +0,0 @@ -serializerMock = $this->createMock(SerializerInterface::class); - $this->hubMock = $this->createMock(HubInterface::class); - $this->hubRegistry = new HubRegistry($this->hubMock); - $this->resourceMetadataCollectionFactoryMock = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); - $this->resourceMetadataCollection = new ResourceMetadataCollection(Book::class, [ - new ApiResource(operations: [new Get('/admin/books/{id}{._format}')]), - new ApiResource(operations: [new Get('/books/{id}{._format}')]), - ]); - $this->iriConverterMock = $this->createMock(IriConverterInterface::class); - $this->objectMock = $this->createMock(Book::class); - $this->operationMock = $this->createMock(Operation::class); - - $this->processor = new MercureProcessor( - $this->serializerMock, - $this->hubRegistry, - $this->iriConverterMock, - $this->resourceMetadataCollectionFactoryMock, - ['jsonld' => null, 'json' => null] - ); - } - - #[Test] - public function itSendsAMercureUpdate(): void - { - $this->resourceMetadataCollectionFactoryMock->expects($this->never())->method('create'); - $this->iriConverterMock - ->expects($this->once()) - ->method('getIriFromResource') - ->willReturnOnConsecutiveCalls( - 'https://example.com/books/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6', - ) - ; - $this->operationMock - ->expects($this->once()) - ->method('getNormalizationContext') - ->willReturn(null) - ; - $this->serializerMock - ->expects($this->once()) - ->method('serialize') - ->with($this->objectMock, 'jsonld', []) - ->willReturn(json_encode(['foo' => 'bar'])) - ; - $this->hubMock - ->expects($this->once()) - ->method('publish') - ->with($this->equalTo(new Update( - topics: ['https://example.com/books/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6'], - data: json_encode(['foo' => 'bar']), - ))) - ; - - $this->processor->process($this->objectMock, $this->operationMock); - } - - #[Test] - public function itSendsAMercureUpdateWithContextOptions(): void - { - $this->resourceMetadataCollectionFactoryMock - ->expects($this->once()) - ->method('create') - ->with($this->objectMock::class) - ->willReturn($this->resourceMetadataCollection) - ; - $this->iriConverterMock->expects($this->never())->method('getIriFromResource'); - $this->operationMock->expects($this->never())->method('getNormalizationContext'); - $this->serializerMock->expects($this->never())->method('serialize'); - $this->hubMock - ->expects($this->once()) - ->method('publish') - ->with($this->equalTo(new Update( - topics: ['https://example.com/admin/books/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6'], - data: json_encode(['bar' => 'baz']), - ))) - ; - - $this->processor->process($this->objectMock, $this->operationMock, [], [ - 'item_uri_template' => '/admin/books/{id}{._format}', - 'topics' => ['https://example.com/admin/books/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6'], - MercureProcessor::DATA => json_encode(['bar' => 'baz']), - ]); - } -} diff --git a/api/tests/State/Processor/ReviewPersistProcessorTest.php b/api/tests/State/Processor/ReviewPersistProcessorTest.php index 05dbfb46..734e56d6 100644 --- a/api/tests/State/Processor/ReviewPersistProcessorTest.php +++ b/api/tests/State/Processor/ReviewPersistProcessorTest.php @@ -21,7 +21,6 @@ final class ReviewPersistProcessorTest extends TestCase { private MockObject|ProcessorInterface $persistProcessorMock; - private MockObject|ProcessorInterface $mercureProcessorMock; private MockObject|Security $securityMock; private MockObject|User $userMock; private MockObject|Review $objectMock; @@ -32,7 +31,6 @@ final class ReviewPersistProcessorTest extends TestCase protected function setUp(): void { $this->persistProcessorMock = $this->createMock(ProcessorInterface::class); - $this->mercureProcessorMock = $this->createMock(ProcessorInterface::class); $this->securityMock = $this->createMock(Security::class); $this->userMock = $this->createMock(User::class); $this->objectMock = $this->createMock(Review::class); @@ -41,7 +39,6 @@ protected function setUp(): void $this->processor = new ReviewPersistProcessor( $this->persistProcessorMock, - $this->mercureProcessorMock, $this->securityMock, $this->clockMock, $this->resourceHandlerMock @@ -76,18 +73,6 @@ public function itUpdatesReviewDataFromOperationBeforeSaveAndSendMercureUpdates( 'operation_name' => '/books/{bookId}/reviews/{id}{._format}', ]) ; - $this->mercureProcessorMock - ->expects($this->exactly(2)) - ->method('process') -// ->withConsecutive( -// [$expectedData, $operation, [], ['item_uri_template' => '/admin/reviews/{id}{._format}']], -// [$expectedData, $operation, [], ['item_uri_template' => '/books/{bookId}/reviews/{id}{._format}']], -// ) - ->willReturnOnConsecutiveCalls( - $expectedData, - $expectedData, - ) - ; $this->assertEquals($expectedData, $this->processor->process($this->objectMock, $operation)); } @@ -121,18 +106,6 @@ public function itUpdatesReviewDataFromContextBeforeSaveAndSendMercureUpdates(): ->expects($this->never()) ->method('create') ; - $this->mercureProcessorMock - ->expects($this->exactly(2)) - ->method('process') -// ->withConsecutive( -// [$expectedData, $operation, [], $context + ['item_uri_template' => '/admin/reviews/{id}{._format}']], -// [$expectedData, $operation, [], $context + ['item_uri_template' => '/books/{bookId}/reviews/{id}{._format}']], -// ) - ->willReturnOnConsecutiveCalls( - $expectedData, - $expectedData, - ) - ; $this->assertEquals($expectedData, $this->processor->process($this->objectMock, $operation, [], $context)); } diff --git a/api/tests/State/Processor/ReviewRemoveProcessorTest.php b/api/tests/State/Processor/ReviewRemoveProcessorTest.php index acedcd2d..b7820781 100644 --- a/api/tests/State/Processor/ReviewRemoveProcessorTest.php +++ b/api/tests/State/Processor/ReviewRemoveProcessorTest.php @@ -4,11 +4,9 @@ namespace App\Tests\State\Processor; -use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\State\ProcessorInterface; use App\Entity\Review; @@ -22,10 +20,7 @@ final class ReviewRemoveProcessorTest extends TestCase { private MockObject|ProcessorInterface $removeProcessorMock; - private MockObject|ProcessorInterface $mercureProcessorMock; - private MockObject|ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactoryMock; private ResourceMetadataCollection $resourceMetadataCollection; - private IriConverterInterface|MockObject $iriConverterMock; private MockObject|Review $objectMock; private MockObject|Operation $operationMock; private ResourceHandlerInterface|MockObject $resourceHandlerMock; @@ -34,22 +29,16 @@ final class ReviewRemoveProcessorTest extends TestCase protected function setUp(): void { $this->removeProcessorMock = $this->createMock(ProcessorInterface::class); - $this->mercureProcessorMock = $this->createMock(ProcessorInterface::class); - $this->resourceMetadataCollectionFactoryMock = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); $this->resourceHandlerMock = $this->createMock(ResourceHandlerInterface::class); $this->resourceMetadataCollection = new ResourceMetadataCollection(Review::class, [ new ApiResource(operations: [new Get('/admin/reviews/{id}{._format}')]), new ApiResource(operations: [new Get('/books/{bookId}/reviews/{id}{._format}')]), ]); - $this->iriConverterMock = $this->createMock(IriConverterInterface::class); $this->objectMock = $this->createMock(Review::class); $this->operationMock = $this->createMock(Operation::class); $this->processor = new ReviewRemoveProcessor( $this->removeProcessorMock, - $this->mercureProcessorMock, - $this->resourceMetadataCollectionFactoryMock, - $this->iriConverterMock, $this->resourceHandlerMock ); } @@ -62,30 +51,6 @@ public function itRemovesBookAndSendMercureUpdates(): void ->method('process') ->with($this->objectMock, $this->operationMock, [], []) ; - $this->resourceMetadataCollectionFactoryMock - ->expects($this->exactly(2)) - ->method('create') -// ->withConsecutive( -// [Review::class], -// [Review::class], -// ) - ->willReturnOnConsecutiveCalls( - $this->resourceMetadataCollection, - $this->resourceMetadataCollection, - ) - ; - $this->iriConverterMock - ->expects($this->exactly(2)) - ->method('getIriFromResource') -// ->withConsecutive( -// [$this->objectMock, UrlGeneratorInterface::ABS_URL, new Get('/admin/reviews/{id}{._format}')], -// [$this->objectMock, UrlGeneratorInterface::ABS_URL, new Get('/books/{bookId}/reviews/{id}{._format}')], -// ) - ->willReturnOnConsecutiveCalls( - '/admin/reviews/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6', - '/books/8ad70d36-abaf-4c9b-aeaa-7ec63e6ca6f3/reviews/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6', - ) - ; $this->objectMock->user = $this->createMock(User::class); $this->objectMock->user->email = 'john.doe@example.com'; $this->resourceHandlerMock @@ -95,30 +60,6 @@ public function itRemovesBookAndSendMercureUpdates(): void 'operation_name' => '/books/{bookId}/reviews/{id}{._format}', ]) ; - $this->mercureProcessorMock - ->expects($this->exactly(2)) - ->method('process') -// ->withConsecutive( -// [ -// $this->objectMock, -// $this->operationMock, -// [], -// [ -// 'item_uri_template' => '/admin/reviews/{id}{._format}', -// MercureProcessor::DATA => json_encode(['@id' => '/admin/reviews/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6']), -// ], -// ], -// [ -// $this->objectMock, -// $this->operationMock, -// [], -// [ -// 'item_uri_template' => '/books/{bookId}/reviews/{id}{._format}', -// MercureProcessor::DATA => json_encode(['@id' => '/books/8ad70d36-abaf-4c9b-aeaa-7ec63e6ca6f3/reviews/9aff4b91-31cf-4e91-94b0-1d52bbe23fe6']), -// ], -// ], -// ) - ; $this->processor->process($this->objectMock, $this->operationMock); } diff --git a/compose.override.yaml b/compose.override.yaml index d40ade42..53221d8f 100644 --- a/compose.override.yaml +++ b/compose.override.yaml @@ -15,7 +15,7 @@ services: environment: MERCURE_EXTRA_DIRECTIVES: demo # See https://xdebug.org/docs/all_settings#mode - XDEBUG_MODE: "${XDEBUG_MODE:-off}" + XDEBUG_MODE: debug extra_hosts: # Ensure that host.docker.internal is correctly defined on Linux - host.docker.internal:host-gateway From 4315909c6452c53efa4b98eaaaf5586a553dd1d1 Mon Sep 17 00:00:00 2001 From: Vincent Chalamon <407859+vincentchalamon@users.noreply.github.com> Date: Mon, 13 May 2024 10:48:03 +0200 Subject: [PATCH 4/4] feat: use ApiProperty::uriTemplate option --- api/src/Entity/Book.php | 15 +++++++++++++-- api/src/Entity/Review.php | 2 +- api/src/Serializer/BookNormalizer.php | 5 ----- api/tests/Serializer/BookNormalizerTest.php | 18 +----------------- compose.override.yaml | 2 +- 5 files changed, 16 insertions(+), 26 deletions(-) diff --git a/api/src/Entity/Book.php b/api/src/Entity/Book.php index a3aca2f2..d606adda 100644 --- a/api/src/Entity/Book.php +++ b/api/src/Entity/Book.php @@ -20,6 +20,8 @@ use App\Repository\BookRepository; use App\State\Processor\BookPersistProcessor; use App\State\Processor\BookRemoveProcessor; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; @@ -159,14 +161,18 @@ class Book /** * An IRI of reviews. * + * @var Collection + * * @see https://schema.org/reviews */ #[ApiProperty( types: ['https://schema.org/reviews'], - example: '/books/6acacc80-8321-4d83-9b02-7f2c7bf6eb1d/reviews' + example: '/books/6acacc80-8321-4d83-9b02-7f2c7bf6eb1d/reviews', + uriTemplate: '/books/{bookId}/reviews{._format}' )] #[Groups(groups: ['Book:read', 'Bookmark:read'])] - public ?string $reviews = null; + #[ORM\OneToMany(targetEntity: Review::class, mappedBy: 'book')] + public Collection $reviews; /** * The overall rating, based on a collection of reviews or ratings, of the item. @@ -180,6 +186,11 @@ class Book #[Groups(groups: ['Book:read', 'Book:read:admin', 'Bookmark:read'])] public ?int $rating = null; + public function __construct() + { + $this->reviews = new ArrayCollection(); + } + public function getId(): ?Uuid { return $this->id; diff --git a/api/src/Entity/Review.php b/api/src/Entity/Review.php index cdd05b96..25c9648b 100644 --- a/api/src/Entity/Review.php +++ b/api/src/Entity/Review.php @@ -174,7 +174,7 @@ class Review #[ApiProperty(types: ['https://schema.org/itemReviewed'])] #[Assert\NotNull] #[Groups(groups: ['Review:read', 'Review:write:admin'])] - #[ORM\ManyToOne(targetEntity: Book::class)] + #[ORM\ManyToOne(targetEntity: Book::class, inversedBy: 'reviews')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] public ?Book $book = null; diff --git a/api/src/Serializer/BookNormalizer.php b/api/src/Serializer/BookNormalizer.php index 792967e5..a6c1684a 100644 --- a/api/src/Serializer/BookNormalizer.php +++ b/api/src/Serializer/BookNormalizer.php @@ -8,7 +8,6 @@ use App\Repository\ReviewRepository; use Doctrine\Persistence\ObjectRepository; use Symfony\Component\DependencyInjection\Attribute\Autowire; -use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -21,7 +20,6 @@ final class BookNormalizer implements NormalizerInterface, NormalizerAwareInterf * @param ReviewRepository $repository */ public function __construct( - private RouterInterface $router, #[Autowire(service: ReviewRepository::class)] private ObjectRepository $repository ) { @@ -32,9 +30,6 @@ public function __construct( */ public function normalize(mixed $object, ?string $format = null, array $context = []): array { - $object->reviews = $this->router->generate('_api_/books/{bookId}/reviews{._format}_get_collection', [ - 'bookId' => $object->getId(), - ]); $object->rating = $this->repository->getAverageRating($object); return $this->normalizer->normalize($object, $format, [self::class => true] + $context); diff --git a/api/tests/Serializer/BookNormalizerTest.php b/api/tests/Serializer/BookNormalizerTest.php index 86bde68e..05cc01f4 100644 --- a/api/tests/Serializer/BookNormalizerTest.php +++ b/api/tests/Serializer/BookNormalizerTest.php @@ -11,14 +11,11 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Uid\Uuid; final class BookNormalizerTest extends TestCase { private MockObject|NormalizerInterface $normalizerMock; - private MockObject|RouterInterface $routerMock; private MockObject|ReviewRepository $repositoryMock; private Book|MockObject $objectMock; private BookNormalizer $normalizer; @@ -26,11 +23,10 @@ final class BookNormalizerTest extends TestCase protected function setUp(): void { $this->normalizerMock = $this->createMock(NormalizerInterface::class); - $this->routerMock = $this->createMock(RouterInterface::class); $this->repositoryMock = $this->createMock(ReviewRepository::class); $this->objectMock = $this->createMock(Book::class); - $this->normalizer = new BookNormalizer($this->routerMock, $this->repositoryMock); + $this->normalizer = new BookNormalizer($this->repositoryMock); $this->normalizer->setNormalizer($this->normalizerMock); } @@ -56,20 +52,8 @@ public function itSupportsValidObjectClassAndContext(): void public function itNormalizesData(): void { $expectedObject = $this->objectMock; - $expectedObject->reviews = '/books/a528046c-7ba1-4acc-bff2-b5390ab17d41/reviews'; $expectedObject->rating = 3; - $this->objectMock - ->expects($this->once()) - ->method('getId') - ->willReturn(Uuid::fromString('a528046c-7ba1-4acc-bff2-b5390ab17d41')) - ; - $this->routerMock - ->expects($this->once()) - ->method('generate') - ->with('_api_/books/{bookId}/reviews{._format}_get_collection', ['bookId' => 'a528046c-7ba1-4acc-bff2-b5390ab17d41']) - ->willReturn('/books/a528046c-7ba1-4acc-bff2-b5390ab17d41/reviews') - ; $this->repositoryMock ->expects($this->once()) ->method('getAverageRating') diff --git a/compose.override.yaml b/compose.override.yaml index 53221d8f..d40ade42 100644 --- a/compose.override.yaml +++ b/compose.override.yaml @@ -15,7 +15,7 @@ services: environment: MERCURE_EXTRA_DIRECTIVES: demo # See https://xdebug.org/docs/all_settings#mode - XDEBUG_MODE: debug + XDEBUG_MODE: "${XDEBUG_MODE:-off}" extra_hosts: # Ensure that host.docker.internal is correctly defined on Linux - host.docker.internal:host-gateway