diff --git a/.env.test b/.env.test new file mode 100644 index 00000000..9e7162f0 --- /dev/null +++ b/.env.test @@ -0,0 +1,6 @@ +# define your env variables for the test env here +KERNEL_CLASS='App\Kernel' +APP_SECRET='$ecretf0rt3st' +SYMFONY_DEPRECATIONS_HELPER=999999 +PANTHER_APP_ENV=panther +PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots diff --git a/config/api_resources/web_response.yml b/config/api_resources/web_response.yml index cf9513c9..6a903423 100644 --- a/config/api_resources/web_response.yml +++ b/config/api_resources/web_response.yml @@ -1,41 +1,2 @@ RZ\Roadiz\CoreBundle\Api\Model\WebResponse: - operations: - getByPath: - class: ApiPlatform\Metadata\Get - method: 'GET' - uriTemplate: '/web_response_by_path' - read: false - controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController - pagination_enabled: false - normalizationContext: - enable_max_depth: true - pagination_enabled: false - groups: - - get - - web_response - - position - - walker - - walker_level - - meta - - children - - children_count - - nodes_sources - - node_listing - - urls - - tag_base - - translation_base - - document_display - - node_attributes - - document_display_sources - openapiContext: - summary: Get a resource by its path wrapped in a WebResponse object - description: | - Get a resource by its path wrapped in a WebResponse - parameters: - - type: string - name: path - in: query - required: true - description: Resource path, or `/` for home page - schema: - type: string + operations: [] diff --git a/config/services.yaml b/config/services.yaml index 32653476..64f8df1a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -50,6 +50,7 @@ services: $maxVersionsShowed: '%roadiz_core.max_versions_showed%' $recaptchaPublicKey: '%roadiz_core.medias.recaptcha_public_key%' $recaptchaPrivateKey: '%roadiz_core.medias.recaptcha_private_key%' + $webResponseClass: '%roadiz_core.web_response_class%' RZ\Roadiz\CoreBundle\: resource: '../src/' diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..c0f0120e --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + src/Test + + + + + + src + + + + + + + + + + + + diff --git a/src/Api/Controller/GetWebResponseByPathController.php b/src/Api/Controller/GetWebResponseByPathController.php index 60c75c60..d6ecf84b 100644 --- a/src/Api/Controller/GetWebResponseByPathController.php +++ b/src/Api/Controller/GetWebResponseByPathController.php @@ -7,7 +7,9 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Exception\ResourceClassNotFoundException; +use ApiPlatform\Metadata\Exception\OperationNotFoundException; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use Psr\Log\LoggerInterface; use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\CoreBundle\Api\DataTransformer\WebResponseDataTransformerInterface; use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface; @@ -30,6 +32,7 @@ public function __construct( private readonly IriConverterInterface $iriConverter, private readonly PreviewResolverInterface $previewResolver, private readonly ApiResourceOperationNameGenerator $apiResourceOperationNameGenerator, + private readonly LoggerInterface $logger ) { } @@ -46,18 +49,34 @@ public function __invoke(?Request $request): ?WebResponseInterface $request, (string) $request->query->get('path') ); - $request->attributes->set('data', $resource); $request->attributes->set('id', $resource->getId()); - /* - * Force API Platform to look for real resource configuration and serialization - * context. You must define "%entity%_get_by_path" operation for your API resource configuration. - */ - $resourceClass = get_class($resource); - $operationName = $this->apiResourceOperationNameGenerator->generateGetByPath($resourceClass); - $operation = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($operationName); - $request->attributes->set('_api_operation', $operation); - $request->attributes->set('_api_operation_name', $operationName); - $request->attributes->set('_api_resource_class', $resourceClass); + $request->attributes->set('path', (string) $request->query->get('path')); + $request->attributes->set('_route_params', [ + ...$request->attributes->get('_route_params', []), + 'path' => (string) $request->query->get('path'), + ]); + + try { + /* + * Force API Platform to look for real resource configuration and serialization + * context. You must define "%entity%_get_by_path" operation for your WebResponse resource configuration. + * It should be generated automatically by Roadiz when you create new reachable NodeTypes. + */ + $resourceClass = get_class($resource); + $operationName = $this->apiResourceOperationNameGenerator->generateGetByPath($resourceClass); + $webResponseClass = $request->attributes->get('_api_resource_class'); + $operation = $this->resourceMetadataCollectionFactory + ->create($webResponseClass) + ->getOperation($operationName); + $request->attributes->set('_api_operation', $operation); + $request->attributes->set('_web_response_item_class', $resourceClass); + $request->attributes->set('_api_operation_name', $operationName); + } catch (OperationNotFoundException $exception) { + // Do not fail if operation is not found + // But warn in logs about missing operation configuration for this resource + $this->logger->warning($exception->getMessage()); + } + $request->attributes->set('_stateless', true); if ($resource instanceof NodesSources) { diff --git a/src/Api/DataTransformer/WebResponseDataTransformerInterface.php b/src/Api/DataTransformer/WebResponseDataTransformerInterface.php index 1c4924b1..424d02a3 100644 --- a/src/Api/DataTransformer/WebResponseDataTransformerInterface.php +++ b/src/Api/DataTransformer/WebResponseDataTransformerInterface.php @@ -10,10 +10,11 @@ interface WebResponseDataTransformerInterface { /** - * @param PersistableInterface $object + * @template T of PersistableInterface + * @param T $object * @param string $to * @param array $context - * @return WebResponseInterface|null + * @return WebResponseInterface|null */ public function transform(PersistableInterface $object, string $to, array $context = []): ?WebResponseInterface; diff --git a/src/Api/DataTransformer/WebResponseOutputDataTransformer.php b/src/Api/DataTransformer/WebResponseOutputDataTransformer.php index d4d46109..7cd9a1e1 100644 --- a/src/Api/DataTransformer/WebResponseOutputDataTransformer.php +++ b/src/Api/DataTransformer/WebResponseOutputDataTransformer.php @@ -11,7 +11,6 @@ use RZ\Roadiz\CoreBundle\Api\Model\BlocksAwareWebResponseInterface; use RZ\Roadiz\CoreBundle\Api\Model\NodesSourcesHeadFactoryInterface; use RZ\Roadiz\CoreBundle\Api\Model\RealmsAwareWebResponseInterface; -use RZ\Roadiz\CoreBundle\Api\Model\WebResponse; use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface; use RZ\Roadiz\CoreBundle\Api\TreeWalker\AutoChildrenNodeSourceWalker; use RZ\Roadiz\CoreBundle\Api\TreeWalker\TreeWalkerGenerator; @@ -27,6 +26,16 @@ class WebResponseOutputDataTransformer implements WebResponseDataTransformerInte use BlocksAwareWebResponseOutputDataTransformerTrait; use RealmsAwareWebResponseOutputDataTransformerTrait; + /** + * @param NodesSourcesHeadFactoryInterface $nodesSourcesHeadFactory + * @param BreadcrumbsFactoryInterface $breadcrumbsFactory + * @param WalkerContextInterface $walkerContext + * @param CacheItemPoolInterface $cacheItemPool + * @param UrlGeneratorInterface $urlGenerator + * @param RealmResolverInterface $realmResolver + * @param TreeWalkerGenerator $treeWalkerGenerator + * @param class-string $webResponseClass + */ public function __construct( protected readonly NodesSourcesHeadFactoryInterface $nodesSourcesHeadFactory, protected readonly BreadcrumbsFactoryInterface $breadcrumbsFactory, @@ -34,7 +43,8 @@ public function __construct( protected readonly CacheItemPoolInterface $cacheItemPool, protected readonly UrlGeneratorInterface $urlGenerator, protected readonly RealmResolverInterface $realmResolver, - protected readonly TreeWalkerGenerator $treeWalkerGenerator + protected readonly TreeWalkerGenerator $treeWalkerGenerator, + private readonly string $webResponseClass ) { } @@ -73,7 +83,7 @@ protected function getRealmResolver(): RealmResolverInterface public function createWebResponse(): WebResponseInterface { - return new WebResponse(); + return new ($this->webResponseClass)(); } public function transform(PersistableInterface $object, string $to, array $context = []): ?WebResponseInterface diff --git a/src/Api/Model/WebResponseInterface.php b/src/Api/Model/WebResponseInterface.php index 8a43399b..61ff1727 100644 --- a/src/Api/Model/WebResponseInterface.php +++ b/src/Api/Model/WebResponseInterface.php @@ -7,12 +7,23 @@ use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\CoreBundle\Api\Breadcrumbs\BreadcrumbsInterface; +/** + * @template T of PersistableInterface + */ interface WebResponseInterface { public function setHead(?NodesSourcesHeadInterface $head): self; public function setBreadcrumbs(?BreadcrumbsInterface $breadcrumbs): self; + + /** + * @param T|null $item + * @return self + */ public function setItem(?PersistableInterface $item): self; public function setPath(?string $path): self; + /** + * @return T|null + */ public function getItem(): ?PersistableInterface; public function getMaxAge(): ?int; public function setMaxAge(?int $maxAge): self; diff --git a/src/Api/Model/WebResponseTrait.php b/src/Api/Model/WebResponseTrait.php index 141afde7..8acf455f 100644 --- a/src/Api/Model/WebResponseTrait.php +++ b/src/Api/Model/WebResponseTrait.php @@ -14,7 +14,12 @@ trait WebResponseTrait { - #[ApiProperty(identifier: true)] + #[ApiProperty( + description: "The path of the current WebResponse.", + readable: true, + writable: false, + identifier: true + )] public ?string $path = null; #[Serializer\Groups(["web_response"])] diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 89834aef..ff7e7d64 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -4,6 +4,7 @@ namespace RZ\Roadiz\CoreBundle\DependencyInjection; +use RZ\Roadiz\CoreBundle\Api\Model\WebResponse; use RZ\Roadiz\CoreBundle\Controller\DefaultNodeSourceController; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeDefinition; @@ -40,6 +41,9 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('defaultNodeSourceController') ->defaultValue(DefaultNodeSourceController::class) ->end() + ->scalarNode('webResponseClass') + ->defaultValue(WebResponse::class) + ->end() ->booleanNode('useNativeJsonColumnType') ->defaultValue(true) ->end() diff --git a/src/DependencyInjection/RoadizCoreExtension.php b/src/DependencyInjection/RoadizCoreExtension.php index 25442d1f..e0608f74 100644 --- a/src/DependencyInjection/RoadizCoreExtension.php +++ b/src/DependencyInjection/RoadizCoreExtension.php @@ -66,6 +66,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('roadiz_core.use_typed_node_names', $config['useTypedNodeNames']); $container->setParameter('roadiz_core.hide_roadiz_version', $config['hideRoadizVersion']); $container->setParameter('roadiz_core.use_accept_language_header', $config['useAcceptLanguageHeader']); + $container->setParameter('roadiz_core.web_response_class', $config['webResponseClass']); /* * Assets config diff --git a/src/NodeType/ApiResourceGenerator.php b/src/NodeType/ApiResourceGenerator.php index d42bfc0b..4ab4ff65 100644 --- a/src/NodeType/ApiResourceGenerator.php +++ b/src/NodeType/ApiResourceGenerator.php @@ -11,16 +11,24 @@ use Psr\Log\LoggerInterface; use RZ\Roadiz\Contracts\NodeType\NodeTypeInterface; use RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController; +use RZ\Roadiz\CoreBundle\Api\Model\WebResponseInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\String\UnicodeString; use Symfony\Component\Yaml\Yaml; final class ApiResourceGenerator { + /** + * @param ApiResourceOperationNameGenerator $apiResourceOperationNameGenerator + * @param string $apiResourcesDir + * @param LoggerInterface $logger + * @param class-string $webResponseClass + */ public function __construct( private readonly ApiResourceOperationNameGenerator $apiResourceOperationNameGenerator, private readonly string $apiResourcesDir, - private readonly LoggerInterface $logger + private readonly LoggerInterface $logger, + private readonly string $webResponseClass ) { } @@ -37,6 +45,26 @@ public function generate(NodeTypeInterface $nodeType): ?string } $resourcePath = $this->getResourcePath($nodeType); + $webResponseResourcePath = $this->getWebResponseResourcePath(); + + if (!$filesystem->exists($webResponseResourcePath)) { + $filesystem->dumpFile( + $webResponseResourcePath, + Yaml::dump([ + $this->webResponseClass => [ + 'operations' => [], + ] + ], 6) + ); + } + $filesystem->dumpFile( + $webResponseResourcePath, + Yaml::dump($this->addWebResponseResourceOperation($nodeType, $webResponseResourcePath), 6) + ); + $this->logger->info('API WebResponse config file has been updated.', [ + 'file' => $webResponseResourcePath, + ]); + \clearstatcache(true, $webResponseResourcePath); if (!$filesystem->exists($resourcePath)) { $filesystem->dumpFile( @@ -63,6 +91,18 @@ public function remove(NodeTypeInterface $nodeType): void } $resourcePath = $this->getResourcePath($nodeType); + $webResponseResourcePath = $this->getWebResponseResourcePath(); + + if ($filesystem->exists($webResponseResourcePath)) { + $filesystem->dumpFile( + $webResponseResourcePath, + Yaml::dump($this->removeWebResponseResourceOperation($nodeType, $webResponseResourcePath), 6) + ); + $this->logger->info('API WebResponse config file has been updated.', [ + 'file' => $webResponseResourcePath, + ]); + \clearstatcache(true, $webResponseResourcePath); + } if ($filesystem->exists($resourcePath)) { $filesystem->remove($resourcePath); @@ -74,7 +114,7 @@ public function remove(NodeTypeInterface $nodeType): void } } - protected function getResourcePath(NodeTypeInterface $nodeType): string + public function getResourcePath(NodeTypeInterface $nodeType): string { return $this->apiResourcesDir . '/' . (new UnicodeString($nodeType->getName())) ->lower() @@ -83,6 +123,11 @@ protected function getResourcePath(NodeTypeInterface $nodeType): string ->toString(); } + protected function getWebResponseResourcePath(): string + { + return $this->apiResourcesDir . '/web_response.yml'; + } + protected function getResourceName(string $nodeTypeName): string { return (new UnicodeString($nodeTypeName)) @@ -112,6 +157,104 @@ protected function getApiResourceDefinition(NodeTypeInterface $nodeType): array ]]; } + protected function addWebResponseResourceOperation(NodeTypeInterface $nodeType, string $webResponseResourcePath): array + { + $getByPathOperationName = $this->apiResourceOperationNameGenerator->generateGetByPath( + $nodeType->getSourceEntityFullQualifiedClassName() + ); + $webResponseResource = Yaml::parseFile($webResponseResourcePath); + + if (!\array_key_exists($this->webResponseClass, $webResponseResource)) { + $webResponseResource = [ + $this->webResponseClass => [ + 'operations' => [], + ] + ]; + } + + if (\array_key_exists('operations', $webResponseResource[$this->webResponseClass])) { + $operations = $webResponseResource[$this->webResponseClass]['operations']; + } else { + $operations = []; + } + + if (!$nodeType->isReachable()) { + // Do not add operation if node-type is not reachable + return $webResponseResource; + } + if (\array_key_exists($getByPathOperationName, $operations)) { + // Do not add operation if already exists + return $webResponseResource; + } + + $groups = $this->getItemOperationSerializationGroups($nodeType); + $operations[$getByPathOperationName] = [ + 'method' => 'GET', + 'class' => Get::class, + 'uriTemplate' => '/web_response_by_path', + 'read' => false, + 'controller' => GetWebResponseByPathController::class, + 'normalizationContext' => [ + 'pagination_enabled' => false, + 'enable_max_depth' => true, + 'groups' => [ + $getByPathOperationName, + ...array_values(array_filter(array_unique($groups))), + ...[ + 'web_response', + 'walker', + 'children', + ] + ] + ], + 'openapiContext' => [ + 'tags' => ['WebResponse'], + 'summary' => 'Get a ' . $nodeType->getName() . ' by its path wrapped in a WebResponse object', + 'description' => 'Get a ' . $nodeType->getName() . ' by its path wrapped in a WebResponse', + 'parameters' => [ + [ + 'type' => 'string', + 'name' => 'path', + 'in' => 'query', + 'required' => true, + 'description' => 'Resource path, or `/` for home page', + 'schema' => [ + 'type' => 'string', + ], + ] + ] + ] + ]; + + $webResponseResource[$this->webResponseClass]['operations'] = $operations; + return $webResponseResource; + } + + protected function removeWebResponseResourceOperation(NodeTypeInterface $nodeType, string $webResponseResourcePath): array + { + $getByPathOperationName = $this->apiResourceOperationNameGenerator->generateGetByPath( + $nodeType->getSourceEntityFullQualifiedClassName() + ); + $webResponseResource = Yaml::parseFile($webResponseResourcePath); + + if (!\array_key_exists($this->webResponseClass, $webResponseResource)) { + return $webResponseResource; + } + if (\array_key_exists('operations', $webResponseResource[$this->webResponseClass])) { + $operations = $webResponseResource[$this->webResponseClass]['operations']; + } else { + return $webResponseResource; + } + if (!\array_key_exists($getByPathOperationName, $operations)) { + // Do not remove operation if it does not exist + return $webResponseResource; + } + + unset($operations[$getByPathOperationName]); + $webResponseResource[$this->webResponseClass]['operations'] = array_filter($operations); + return $webResponseResource; + } + protected function getCollectionOperations(NodeTypeInterface $nodeType): array { $operations = []; @@ -176,9 +319,9 @@ protected function getCollectionOperations(NodeTypeInterface $nodeType): array return $operations; } - protected function getItemOperations(NodeTypeInterface $nodeType): array + protected function getItemOperationSerializationGroups(NodeTypeInterface $nodeType): array { - $groups = [ + return [ "nodes_sources", "node_listing", "urls", @@ -189,11 +332,16 @@ protected function getItemOperations(NodeTypeInterface $nodeType): array "document_display_sources", ...$this->getGroupedFieldsSerializationGroups($nodeType) ]; + } + + protected function getItemOperations(NodeTypeInterface $nodeType): array + { + $groups = $this->getItemOperationSerializationGroups($nodeType); $itemOperationName = $this->apiResourceOperationNameGenerator->generate( $nodeType->getSourceEntityFullQualifiedClassName(), 'get' ); - $operations = [ + return [ $itemOperationName => [ 'method' => 'GET', 'class' => Get::class, @@ -203,53 +351,6 @@ protected function getItemOperations(NodeTypeInterface $nodeType): array ], ] ]; - - /* - * Create itemOperation for WebResponseController action - */ - if ($nodeType->isReachable()) { - $operationName = $this->apiResourceOperationNameGenerator->generateGetByPath( - $nodeType->getSourceEntityFullQualifiedClassName() - ); - $operations[$operationName] = [ - 'method' => 'GET', - 'class' => Get::class, - 'uriTemplate' => '/web_response_by_path', - 'read' => false, - 'controller' => GetWebResponseByPathController::class, - 'normalizationContext' => [ - 'pagination_enabled' => false, - 'enable_max_depth' => true, - 'groups' => array_merge(array_values(array_filter(array_unique($groups))), [ - 'web_response', - 'walker', - 'walker_level', - 'walker_metadata', - 'meta', - 'children', - ]) - ], - 'openapiContext' => [ - 'tags' => ['WebResponse'], - 'summary' => 'Get a resource by its path wrapped in a WebResponse object', - 'description' => 'Get a resource by its path wrapped in a WebResponse', - 'parameters' => [ - [ - 'type' => 'string', - 'name' => 'path', - 'in' => 'query', - 'required' => true, - 'description' => 'Resource path, or `/` for home page', - 'schema' => [ - 'type' => 'string', - ], - ] - ] - ] - ]; - } - - return $operations; } protected function getGroupedFieldsSerializationGroups(NodeTypeInterface $nodeType): array diff --git a/src/Serializer/CircularReferenceHandler.php b/src/Serializer/CircularReferenceHandler.php index d53c7666..8b5055d7 100644 --- a/src/Serializer/CircularReferenceHandler.php +++ b/src/Serializer/CircularReferenceHandler.php @@ -6,18 +6,11 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Metadata\Operation; final class CircularReferenceHandler { - private IriConverterInterface $iriConverter; - - /** - * @param IriConverterInterface $iriConverter - */ - public function __construct(IriConverterInterface $iriConverter) + public function __construct(private readonly IriConverterInterface $iriConverter) { - $this->iriConverter = $iriConverter; } public function __invoke(mixed $object, string $format, array $context): ?string diff --git a/src/Serializer/Normalizer/DocumentNormalizer.php b/src/Serializer/Normalizer/DocumentNormalizer.php index c5b1c8a7..b11d4d95 100644 --- a/src/Serializer/Normalizer/DocumentNormalizer.php +++ b/src/Serializer/Normalizer/DocumentNormalizer.php @@ -18,18 +18,13 @@ */ final class DocumentNormalizer extends AbstractPathNormalizer { - private FilesystemOperator $documentsStorage; - private EmbedFinderFactory $embedFinderFactory; - public function __construct( - FilesystemOperator $documentsStorage, NormalizerInterface $decorated, UrlGeneratorInterface $urlGenerator, - EmbedFinderFactory $embedFinderFactory + private readonly FilesystemOperator $documentsStorage, + private readonly EmbedFinderFactory $embedFinderFactory ) { parent::__construct($decorated, $urlGenerator); - $this->documentsStorage = $documentsStorage; - $this->embedFinderFactory = $embedFinderFactory; } /** diff --git a/src/Serializer/Normalizer/DocumentSourcesNormalizer.php b/src/Serializer/Normalizer/DocumentSourcesNormalizer.php index 49b03c0c..e52f4004 100644 --- a/src/Serializer/Normalizer/DocumentSourcesNormalizer.php +++ b/src/Serializer/Normalizer/DocumentSourcesNormalizer.php @@ -11,15 +11,12 @@ final class DocumentSourcesNormalizer extends AbstractPathNormalizer { - protected DocumentFinderInterface $documentFinder; - public function __construct( NormalizerInterface $decorated, UrlGeneratorInterface $urlGenerator, - DocumentFinderInterface $documentFinder + private readonly DocumentFinderInterface $documentFinder ) { parent::__construct($decorated, $urlGenerator); - $this->documentFinder = $documentFinder; } /** diff --git a/src/Serializer/Normalizer/RealmSerializationGroupNormalizer.php b/src/Serializer/Normalizer/RealmSerializationGroupNormalizer.php index 5c11a5de..8c7ea103 100644 --- a/src/Serializer/Normalizer/RealmSerializationGroupNormalizer.php +++ b/src/Serializer/Normalizer/RealmSerializationGroupNormalizer.php @@ -9,26 +9,20 @@ use RZ\Roadiz\CoreBundle\Entity\Realm; use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\RealmVoter; use Symfony\Bundle\SecurityBundle\Security; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; final class RealmSerializationGroupNormalizer implements NormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; private const ALREADY_CALLED = 'REALM_SERIALIZER_NORMALIZER_ALREADY_CALLED'; - private Security $security; - private ManagerRegistry $managerRegistry; - /** - * @param Security $security - * @param ManagerRegistry $managerRegistry - */ - public function __construct(Security $security, ManagerRegistry $managerRegistry) - { - $this->security = $security; - $this->managerRegistry = $managerRegistry; + public function __construct( + private readonly Security $security, + private readonly ManagerRegistry $managerRegistry + ) { } /** diff --git a/src/Serializer/TranslationAwareContextBuilder.php b/src/Serializer/TranslationAwareContextBuilder.php index 2af808c8..66867a64 100644 --- a/src/Serializer/TranslationAwareContextBuilder.php +++ b/src/Serializer/TranslationAwareContextBuilder.php @@ -13,18 +13,11 @@ final class TranslationAwareContextBuilder implements SerializerContextBuilderInterface { - private ManagerRegistry $managerRegistry; - private SerializerContextBuilderInterface $decorated; - private PreviewResolverInterface $previewResolver; - public function __construct( - SerializerContextBuilderInterface $decorated, - ManagerRegistry $managerRegistry, - PreviewResolverInterface $previewResolver + private readonly SerializerContextBuilderInterface $decorated, + private readonly ManagerRegistry $managerRegistry, + private readonly PreviewResolverInterface $previewResolver ) { - $this->decorated = $decorated; - $this->managerRegistry = $managerRegistry; - $this->previewResolver = $previewResolver; } /** * @inheritDoc diff --git a/src/Test/NodeType/ApiResourceGeneratorTest.php b/src/Test/NodeType/ApiResourceGeneratorTest.php new file mode 100644 index 00000000..138a8efc --- /dev/null +++ b/src/Test/NodeType/ApiResourceGeneratorTest.php @@ -0,0 +1,152 @@ +getContainer()->get(ApiResourceOperationNameGenerator::class); + + return new ApiResourceGenerator( + $apiResourceOperationNameGenerator, + static::getGeneratedPath(), + new NullLogger(), + WebResponse::class + ); + } + + public function testGenerate(): void + { + $apiResourceGenerator = $this->getApiResourceGenerator(); + + $nodeType = new NodeType(); + $nodeType->setName('Test'); + + $apiResourceGenerator->generate($nodeType); + $resourcePath = $apiResourceGenerator->getResourcePath($nodeType); + $this->assertFileExists($resourcePath); + $this->assertFileEquals( + dirname(__DIR__) . '/../../tests/expected_api_resources/nstest.yml', + $resourcePath + ); + } + + public function testReachableGenerate(): void + { + $apiResourceGenerator = $this->getApiResourceGenerator(); + + $nodeType = new NodeType(); + $nodeType->setName('Test'); + $nodeType->setReachable(true); + + $apiResourceGenerator->generate($nodeType); + $resourcePath = $apiResourceGenerator->getResourcePath($nodeType); + $this->assertFileExists($resourcePath); + $this->assertFileExists(dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml'); + $this->assertFileEquals( + dirname(__DIR__) . '/../../tests/expected_api_resources/nstest.yml', + $resourcePath + ); + $this->assertFileEquals( + dirname(__DIR__) . '/../../tests/expected_api_resources/web_response.yml', + dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml', + ); + } + + public function testMultipleGenerate(): void + { + $apiResourceGenerator = $this->getApiResourceGenerator(); + + $nodeType = new NodeType(); + $nodeType->setName('Test'); + $nodeType->setReachable(true); + + $nodeType2 = new NodeType(); + $nodeType2->setName('SecondTest'); + $nodeType2->setReachable(true); + + $apiResourceGenerator->generate($nodeType); + $resourcePath = $apiResourceGenerator->getResourcePath($nodeType); + $this->assertFileExists($resourcePath); + $this->assertFileEquals( + dirname(__DIR__) . '/../../tests/expected_api_resources/nstest.yml', + $resourcePath + ); + + $apiResourceGenerator->generate($nodeType2); + $resourcePath2 = $apiResourceGenerator->getResourcePath($nodeType2); + $this->assertFileExists($resourcePath2); + $this->assertFileEquals( + dirname(__DIR__) . '/../../tests/expected_api_resources/nssecondtest.yml', + $resourcePath2 + ); + + $this->assertFileExists(dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml'); + $this->assertFileEquals( + dirname(__DIR__) . '/../../tests/expected_api_resources/web_response_multiple.yml', + dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml', + ); + } + + public function testRemoveGenerate(): void + { + $apiResourceGenerator = $this->getApiResourceGenerator(); + + $nodeType = new NodeType(); + $nodeType->setName('Test'); + $nodeType->setReachable(true); + + $nodeType2 = new NodeType(); + $nodeType2->setName('SecondTest'); + $nodeType2->setReachable(true); + + $apiResourceGenerator->generate($nodeType); + $apiResourceGenerator->generate($nodeType2); + + // Remove second node-type to check if operation + // is removed from web_response + $apiResourceGenerator->remove($nodeType2); + $resourcePath2 = $apiResourceGenerator->getResourcePath($nodeType2); + $this->assertFileDoesNotExist($resourcePath2); + + $this->assertFileExists(dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml'); + $this->assertFileEquals( + dirname(__DIR__) . '/../../tests/expected_api_resources/web_response.yml', + dirname(__DIR__) . '/../../tests/generated_api_resources/web_response.yml', + ); + } + + protected function setUp(): void + { + parent::setUp(); + + $filesystem = new Filesystem(); + $filesystem->mkdir(static::getGeneratedPath()); + } + + + protected function tearDown(): void + { + parent::tearDown(); + + $filesystem = new Filesystem(); + $filesystem->remove(static::getGeneratedPath()); + } +} diff --git a/tests/expected_api_resources/nssecondtest.yml b/tests/expected_api_resources/nssecondtest.yml new file mode 100644 index 00000000..28427bd6 --- /dev/null +++ b/tests/expected_api_resources/nssecondtest.yml @@ -0,0 +1,33 @@ +App\GeneratedEntity\NSSecondTest: + types: + - SecondTest + operations: + secondtest_get_collection: + method: GET + class: ApiPlatform\Metadata\GetCollection + shortName: SecondTest + normalizationContext: + enable_max_depth: true + groups: + - nodes_sources_base + - nodes_sources_default + - urls + - tag_base + - translation_base + - document_display + - document_thumbnails + - document_display_sources + secondtest_get: + method: GET + class: ApiPlatform\Metadata\Get + shortName: SecondTest + normalizationContext: + groups: + - nodes_sources + - node_listing + - urls + - tag_base + - translation_base + - document_display + - document_thumbnails + - document_display_sources diff --git a/tests/expected_api_resources/nstest.yml b/tests/expected_api_resources/nstest.yml new file mode 100644 index 00000000..727aed77 --- /dev/null +++ b/tests/expected_api_resources/nstest.yml @@ -0,0 +1,33 @@ +App\GeneratedEntity\NSTest: + types: + - Test + operations: + test_get_collection: + method: GET + class: ApiPlatform\Metadata\GetCollection + shortName: Test + normalizationContext: + enable_max_depth: true + groups: + - nodes_sources_base + - nodes_sources_default + - urls + - tag_base + - translation_base + - document_display + - document_thumbnails + - document_display_sources + test_get: + method: GET + class: ApiPlatform\Metadata\Get + shortName: Test + normalizationContext: + groups: + - nodes_sources + - node_listing + - urls + - tag_base + - translation_base + - document_display + - document_thumbnails + - document_display_sources diff --git a/tests/expected_api_resources/web_response.yml b/tests/expected_api_resources/web_response.yml new file mode 100644 index 00000000..1789d94d --- /dev/null +++ b/tests/expected_api_resources/web_response.yml @@ -0,0 +1,31 @@ +RZ\Roadiz\CoreBundle\Api\Model\WebResponse: + operations: + test_get_by_path: + method: GET + class: ApiPlatform\Metadata\Get + uriTemplate: /web_response_by_path + read: false + controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController + normalizationContext: + pagination_enabled: false + enable_max_depth: true + groups: + - test_get_by_path + - nodes_sources + - node_listing + - urls + - tag_base + - translation_base + - document_display + - document_thumbnails + - document_display_sources + - web_response + - walker + - children + openapiContext: + tags: + - WebResponse + summary: 'Get a Test by its path wrapped in a WebResponse object' + description: 'Get a Test by its path wrapped in a WebResponse' + parameters: + - { type: string, name: path, in: query, required: true, description: 'Resource path, or `/` for home page', schema: { type: string } } diff --git a/tests/expected_api_resources/web_response_multiple.yml b/tests/expected_api_resources/web_response_multiple.yml new file mode 100644 index 00000000..34270be0 --- /dev/null +++ b/tests/expected_api_resources/web_response_multiple.yml @@ -0,0 +1,60 @@ +RZ\Roadiz\CoreBundle\Api\Model\WebResponse: + operations: + test_get_by_path: + method: GET + class: ApiPlatform\Metadata\Get + uriTemplate: /web_response_by_path + read: false + controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController + normalizationContext: + pagination_enabled: false + enable_max_depth: true + groups: + - test_get_by_path + - nodes_sources + - node_listing + - urls + - tag_base + - translation_base + - document_display + - document_thumbnails + - document_display_sources + - web_response + - walker + - children + openapiContext: + tags: + - WebResponse + summary: 'Get a Test by its path wrapped in a WebResponse object' + description: 'Get a Test by its path wrapped in a WebResponse' + parameters: + - { type: string, name: path, in: query, required: true, description: 'Resource path, or `/` for home page', schema: { type: string } } + secondtest_get_by_path: + method: GET + class: ApiPlatform\Metadata\Get + uriTemplate: /web_response_by_path + read: false + controller: RZ\Roadiz\CoreBundle\Api\Controller\GetWebResponseByPathController + normalizationContext: + pagination_enabled: false + enable_max_depth: true + groups: + - secondtest_get_by_path + - nodes_sources + - node_listing + - urls + - tag_base + - translation_base + - document_display + - document_thumbnails + - document_display_sources + - web_response + - walker + - children + openapiContext: + tags: + - WebResponse + summary: 'Get a SecondTest by its path wrapped in a WebResponse object' + description: 'Get a SecondTest by its path wrapped in a WebResponse' + parameters: + - { type: string, name: path, in: query, required: true, description: 'Resource path, or `/` for home page', schema: { type: string } }