diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c1ee5e737b..850a4afb8b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,15 +7,17 @@ on: env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERAGE: '0' + SYMFONY_DEPRECATIONS_HELPER: disabled=1 jobs: php-cs-fixer: + name: PHP-cs-fixer (PHP ${{ matrix.php }}) runs-on: ubuntu-latest timeout-minutes: 20 strategy: matrix: php: - - '7.4' + - '8' fail-fast: false env: PHP_CS_FIXER_FUTURE_MODE: '1' @@ -34,16 +36,17 @@ jobs: run: php-cs-fixer fix --dry-run --diff --ansi phpstan: - name: PHPStan + name: PHPStan (PHP ${{ matrix.php }}) runs-on: ubuntu-latest timeout-minutes: 20 strategy: matrix: php: - - '7.4' + - '8' fail-fast: false env: APP_DEBUG: '1' # https://github.com/phpstan/phpstan-symfony/issues/37 + SYMFONY_PHPUNIT_VERSION: '9.5' steps: - name: Checkout uses: actions/checkout@v2 @@ -69,8 +72,6 @@ jobs: - name: Require Symfony components run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi - name: Install PHPUnit - env: - SYMFONY_PHPUNIT_VERSION: '9.5' run: vendor/bin/simple-phpunit --version - name: Cache PHPStan results uses: actions/cache@v2 @@ -134,10 +135,6 @@ jobs: composer remove --dev --no-interaction --no-progress --no-update --ansi \ doctrine/mongodb-odm \ doctrine/mongodb-odm-bundle \ - - name: Set Composer platform config - if: (startsWith(matrix.php, '8.0')) - run: | - composer config platform.php 7.4.99 - name: Update project dependencies run: composer update --no-interaction --no-progress --ansi - name: Require Symfony components @@ -146,16 +143,12 @@ jobs: - name: Install PHPUnit run: vendor/bin/simple-phpunit --version - name: Clear test app cache - if: (!startsWith(matrix.php, '8.0')) run: tests/Fixtures/app/console cache:clear --ansi - - name: Clear test app cache (php 8.0) - if: (startsWith(matrix.php, '8.0')) - run: rm -Rf tests/Fixtures/app/var/cache/* - name: Run PHPUnit tests run: | mkdir -p build/logs/phpunit if [ "$COVERAGE" = '1' ]; then - vendor/bin/simple-phpunit --coverage-clover build/logs/phpunit/clover.xml --log-junit build/logs/phpunit/junit.xml + vendor/bin/simple-phpunit --log-junit build/logs/phpunit/junit.xml --coverage-clover build/logs/phpunit/clover.xml else vendor/bin/simple-phpunit --log-junit build/logs/phpunit/junit.xml fi @@ -247,17 +240,27 @@ jobs: if: (startsWith(matrix.php, '8.0')) run: rm -Rf tests/Fixtures/app/var/cache/* - name: Run Behat tests + if: (!startsWith(matrix.php, '8.0')) run: | mkdir -p build/logs/behat if [ "$COVERAGE" = '1' ]; then - vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default-coverage --no-interaction + vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default-coverage --no-interaction --tags='~@php8' else if [ "${{ matrix.php }}" = '7.1' ]; then - vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction --tags='~@symfony/uid' + vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction --tags='~@symfony/uid&&~@php8' else - vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction + vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction --tags='~@php8' fi fi + - name: Run Behat tests + if: (startsWith(matrix.php, '8.0')) + run: | + mkdir -p build/logs/behat + if [ "$COVERAGE" = '1' ]; then + vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default-coverage --no-interaction + else + vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction + fi - name: Merge code coverage reports if: matrix.coverage run: | @@ -389,7 +392,7 @@ jobs: run: tests/Fixtures/app/console cache:clear --ansi - name: Run Behat tests # @TODO remove the tag "@symfony/uid" in 3.0 - run: vendor/bin/behat --out=std --format=progress --profile=default --no-interaction --tags='~@symfony/uid' + run: vendor/bin/behat --out=std --format=progress --profile=default --no-interaction --tags='~@symfony/uid&&~php8' postgresql: name: Behat (PHP ${{ matrix.php }}) (PostgreSQL) @@ -440,7 +443,7 @@ jobs: run: tests/Fixtures/app/console cache:clear --ansi - name: Run Behat tests run: | - vendor/bin/behat --out=std --format=progress --profile=postgres --no-interaction -vv + vendor/bin/behat --out=std --format=progress --profile=postgres --no-interaction -vv --tags='~php8' mysql: name: Behat (PHP ${{ matrix.php }}) (MySQL) @@ -500,7 +503,7 @@ jobs: strategy: matrix: php: - - '7.4' + - '8' fail-fast: false env: APP_ENV: mongodb @@ -858,5 +861,10 @@ jobs: - name: Clear test app cache run: tests/Fixtures/app/console cache:clear --ansi - name: Run Behat tests - run: vendor/bin/behat --out=std --format=progress --profile=default --no-interaction + run: | + if ( "${{ matrix.php }}" -eq '7.4' ) { + vendor/bin/behat --out=std --format=progress --profile=default --no-interaction --tags='~@php8' + } else { + vendor/bin/behat --out=std --format=progress --profile=default --no-interaction + } diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 4791c0ce82b..4f4f0ef7608 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -14,7 +14,7 @@ $finder = PhpCsFixer\Finder::create() ->in(__DIR__) ->exclude([ - 'src/Bridge/Symfony/Maker/Resources/skeleton', + 'src/Core/Bridge/Symfony/Maker/Resources/skeleton', 'tests/Fixtures/app/var', ]) ->notPath('src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php') diff --git a/CHANGELOG.md b/CHANGELOG.md index f797246a41a..62f84437384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,15 @@ * Exception: Add the ability to customize multiple status codes based on the validation exception (#4017) * GraphQL: Fix graphql fetching with Elasticsearch (#4217) * ApiLoader: Support `_format` resolving (#4292) +* Metadata: new namespace `ApiPlatform\Metadata` instead of `ApiPlatform\Core\Metadata`, for example `ApiPlatform\Metadata\ApiResource` (#4351) +* Metadata: deprecation of `ApiPlatform\Core\Annotation` (#4351) +* Metadata: `ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface` is deprecated in favor of `ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface` (#4351) +* Metadata: item and collection prefixes for operations are deprecated, as well as the `ApiPlatform\Core\Api\OperationType` class (#4351) +* Graphql: `ApiPlatform\Metadata\GraphQl` follow the same metadata conventions (a Subscription operation is available and isn't hidden behind an update Mutation anymore), interfaces got simplified (beeing @experimental) (#4351) +* IriConverter: new interface for `ApiPlatform\Bridge\Symfony\Routing\IriConverter` that adds an operationName, same for `ApiPlatform\Api\IdentifiersExtractor` (#4351) +* DataProvider: new `ApiPlatform\State\ProviderInterface` that replaces DataProviders (#4351) +* DataPersister: new `ApiPlatform\State\ProcessorInterface` that replaces DataPersisters (#4351) +* A new configuration is available to keep old services (IriConverter, IdentifiersExtractor and OpenApiFactory) `metadata_backward_compatibility_layer` (defaults to false) (#4351) ## 2.6.5 diff --git a/composer.json b/composer.json index ae80aa92421..1ce166293bb 100644 --- a/composer.json +++ b/composer.json @@ -110,12 +110,16 @@ }, "autoload": { "psr-4": { - "ApiPlatform\\Core\\": "src/" - } + "ApiPlatform\\": "src/" + }, + "files": [ + "src/deprecation.php" + ] }, "autoload-dev": { "psr-4": { - "ApiPlatform\\Core\\Tests\\": "tests/", + "ApiPlatform\\Core\\Tests\\": "tests/Core/", + "ApiPlatform\\Tests\\": "tests/", "App\\": "tests/Fixtures/app/var/tmp/src/" } }, diff --git a/docs/adr/0002-resource-definition.md b/docs/adr/0002-resource-definition.md index 7cdfd846950..2ed9d7a8acc 100644 --- a/docs/adr/0002-resource-definition.md +++ b/docs/adr/0002-resource-definition.md @@ -24,8 +24,7 @@ In API Platform, this resource identifier is also named [IRI (Internationalized ```php 9, a signature changed in this file - - src/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php + - src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php # Imported code (temporary) - - src/Bridge/Symfony/Bundle/Test/BrowserKitAssertionsTrait.php - - tests/Bridge/Symfony/Bundle/Test/WebTestCaseTest.php - - tests/ProphecyTrait.php - - tests/Behat/CoverageContext.php + - src/Core/Bridge/Symfony/Bundle/Test/BrowserKitAssertionsTrait.php + - tests/Core/Bridge/Symfony/Bundle/Test/WebTestCaseTest.php + - tests/Core/ProphecyTrait.php + - tests/Core/Behat/CoverageContext.php - tests/Fixtures/TestBundle/Security/AbstractSecurityUser.php # Templates for Maker - - src/Bridge/Symfony/Maker/Resources/skeleton + - src/Core/Bridge/Symfony/Maker/Resources/skeleton earlyTerminatingMethodCalls: PHPUnit\Framework\Constraint\Constraint: - fail + ApiPlatform\Metadata\Resource\ResourceMetadataCollection: + - handleNotFound ignoreErrors: # False positives + - '#Parameter \#1 \$callback of function call_user_func expects callable\(\): mixed, non-empty-string given\.#' + - + message: '#Unreachable statement - code above always terminates.#' + paths: + - tests/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageTest.php - message: "#Parameter \\#2 \\$dqlPart of method Doctrine\\\\ORM\\\\QueryBuilder::add\\(\\) expects array<'join'\\|int, array\\|string>\\|object\\|string, array\\('o' => Doctrine\\\\ORM\\\\Query\\\\Expr\\\\Join\\) given\\.#" paths: - - tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php - - '#Parameter \#1 \$function of function call_user_func expects callable\(\): mixed, non-empty-string given\.#' + - tests/Core/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php - message: '#Strict comparison using !== between .+ and .+ will always evaluate to false\.#' paths: - - src/Bridge/Doctrine/Common/PropertyHelperTrait.php + - src/Core/Bridge/Doctrine/Common/PropertyHelperTrait.php - '#Access to an undefined property Prophecy\\Prophecy\\ObjectProphecy<(\\?[a-zA-Z0-9_]+)+>::\$[a-zA-Z0-9_]+#' - message: '#Call to an undefined method Doctrine\\Persistence\\ObjectManager::getConnection\(\)#' - path: src/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php + path: src/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php # https://github.com/willdurand/Negotiation/issues/89#issuecomment-513283286 - message: '#Call to an undefined method Negotiation\\AcceptHeader::getType\(\)\.#' - path: src/EventListener/AddFormatListener.php + path: src/Core/EventListener/AddFormatListener.php - '#Parameter \#1 \$vars of class GraphQL\\Language\\AST\\(IntValue|ObjectField|ObjectValue|BooleanValue|ListValue|StringValue)Node constructor expects array, array given\.#' - '#Parameter \#1 \$defaultContext of class Symfony\\Component\\Serializer\\Encoder\\Json(De|En)code constructor expects array, (int|true) given\.#' - '#Parameter \#(2|3) \$(resourceMetadataFactory|pagination) of class ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\PaginationExtension constructor expects (ApiPlatform\\Core\\Metadata\\Resource\\Factory\\ResourceMetadataFactoryInterface\|Symfony\\Component\\HttpFoundation\\RequestStack|ApiPlatform\\Core\\DataProvider\\Pagination\|ApiPlatform\\Core\\Metadata\\Resource\\Factory\\ResourceMetadataFactoryInterface), stdClass given\.#' - message: '#Parameter \#[0-9] \$filterLocator of class .+ constructor expects ApiPlatform\\Core\\Api\\FilterCollection|Psr\\Container\\ContainerInterface, ArrayObject given\.#' paths: - - tests/Bridge/Doctrine/Orm/Extension/FilterExtensionTest.php - - tests/Hydra/Serializer/CollectionFiltersNormalizerTest.php - - tests/Swagger/Serializer/DocumentationNormalizerV2Test.php - - tests/Swagger/Serializer/DocumentationNormalizerV3Test.php + - tests/Core/Bridge/Doctrine/Orm/Extension/FilterExtensionTest.php + - tests/Core/Hydra/Serializer/CollectionFiltersNormalizerTest.php + - tests/Core/Swagger/Serializer/DocumentationNormalizerV2Test.php + - tests/Core/Swagger/Serializer/DocumentationNormalizerV3Test.php - message: '#Parameter \#1 \$objectValue of method GraphQL\\Type\\Definition\\InterfaceType::resolveType\(\) expects object, array()? given.#' path: tests/GraphQl/Type/TypeBuilderTest.php # https://github.com/phpstan/phpstan-doctrine/issues/115 - - message: '#Property ApiPlatform\\Core\\Test\\DoctrineMongoDbOdmFilterTestCase::\$repository \(Doctrine\\ODM\\MongoDB\\Repository\\DocumentRepository\) does not accept Doctrine\\ORM\\EntityRepository\.#' - path: src/Test/DoctrineMongoDbOdmFilterTestCase.php + message: '#Property ApiPlatform\\Core\\Test\\DoctrineMongoDbOdmFilterTestCase::\$repository \(Doctrine\\ODM\\MongoDB\\Repository\\DocumentRepository\) does not accept Doctrine\\ORM\\EntityRepository\.#' + path: src/Core/Test/DoctrineMongoDbOdmFilterTestCase.php - message: "#Call to method PHPUnit\\\\Framework\\\\Assert::assertSame\\(\\) with array\\('(collection_context|item_context|subresource_context)'\\) and array\\|bool\\|float\\|int\\|string\\|null will always evaluate to false\\.#" - path: tests/Bridge/Symfony/Bundle/DataCollector/RequestDataCollectorTest.php + path: tests/Core/Bridge/Symfony/Bundle/DataCollector/RequestDataCollectorTest.php # https://github.com/phpstan/phpstan-phpunit/issues/62 - message: '#Call to method PHPUnit\\Framework\\Assert::assertSame\(\) with 2 and int will always evaluate to false\.#' - path: tests/Identifier/Normalizer/IntegerDenormalizerTest.php + path: tests/Core/Identifier/Normalizer/IntegerDenormalizerTest.php - message: '#Call to method PHPUnit\\Framework\\Assert::assertSame\(\) with array\(.+\) and array\(.+\) will always evaluate to false\.#' - path: tests/Util/SortTraitTest.php + path: tests/Core/Util/SortTraitTest.php # https://github.com/phpstan/phpstan-symfony/issues/76 - message: '#Service "test" is not registered in the container\.#' path: tests/GraphQl/Type/TypesContainerTest.php # Expected, due to PHP 8 attributes - - '#ReflectionProperty::getAttributes\(\)#' - - '#ReflectionMethod::getAttributes\(\)#' - - '#ReflectionClass::getAttributes\(\)#' - '#Constructor of class ApiPlatform\\Core\\Annotation\\ApiResource has an unused parameter#' - '#Constructor of class ApiPlatform\\Core\\Annotation\\ApiProperty has an unused parameter#' @@ -120,4 +123,4 @@ parameters: # Expected, due to backward compatibility - message: "#Call to function method_exists\\(\\) with ApiPlatform\\\\Core\\\\JsonApi\\\\Serializer\\\\ItemNormalizer and 'setCircularReferenc…' will always evaluate to false\\.#" - path: tests/JsonApi/Serializer/ItemNormalizerTest.php + path: tests/Core/JsonApi/Serializer/ItemNormalizerTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c5df9424cdc..a738dcc45db 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,8 @@ tests + tests/Metadata/Resource/Factory + tests/Metadata/Resource/Factory diff --git a/src/Api/IdentifiersExtractor.php b/src/Api/IdentifiersExtractor.php index 55927239b0d..4117df530da 100644 --- a/src/Api/IdentifiersExtractor.php +++ b/src/Api/IdentifiersExtractor.php @@ -11,12 +11,14 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Api; +namespace ApiPlatform\Api; +use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Util\ResourceClassInfoTrait; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -33,84 +35,84 @@ final class IdentifiersExtractor implements IdentifiersExtractorInterface private $propertyMetadataFactory; private $propertyAccessor; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null) { + $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->resourceClassResolver = $resourceClassResolver; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); - $this->resourceClassResolver = $resourceClassResolver; - - if (null === $this->resourceClassResolver) { - @trigger_error(sprintf('Not injecting %s in the IdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), \E_USER_DEPRECATED); - } } /** * {@inheritdoc} + * + * TODO: 3.0 identifiers should be stringable? */ - public function getIdentifiersFromResourceClass(string $resourceClass): array + public function getIdentifiersFromItem($item, string $operationName = null, array $context = []): array { $identifiers = []; - foreach ($properties = $this->propertyNameCollectionFactory->create($resourceClass) as $property) { - if ($this->propertyMetadataFactory->create($resourceClass, $property)->isIdentifier() ?? false) { - $identifiers[] = $property; + $resourceClass = $this->getResourceClass($item, true); + $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName); + + foreach ($operation->getIdentifiers() as $parameterName => [$class, $property]) { + $identifierValue = $this->resolveIdentifierValue($item, $class, $property); + + if (null === $identifierValue) { + throw new RuntimeException('No identifier value found, did you forgot to persist the entity?'); } - } - if (!$identifiers) { - if (\in_array('id', iterator_to_array($properties), true)) { - return ['id']; + // TODO: php 8 remove method_exists + if (is_scalar($identifierValue) || method_exists($identifierValue, '__toString') || $identifierValue instanceof \Stringable) { + $identifiers[$parameterName] = (string) $identifierValue; + continue; + } + + // we could recurse to find correct identifiers until there it is a scalar but this is not really supported and adds a lot of complexity + // instead we're deprecating this behavior in favor of something that can be transformed to a string + if ($this->isResourceClass($relatedResourceClass = $this->getObjectClass($identifierValue))) { + trigger_deprecation('api-platform/core', '2.7', 'Using a resource class as identifier is deprecated, please make this identifier Stringable'); + $relatedOperation = $this->resourceMetadataFactory->create($relatedResourceClass)->getOperation(); + $relatedIdentifiers = $relatedOperation->getIdentifiers(); + if (1 === \count($relatedIdentifiers)) { + $identifierValue = $this->resolveIdentifierValue($identifierValue, $relatedResourceClass, current($relatedIdentifiers)[1]); + + if (is_scalar($identifierValue) || method_exists($identifierValue, '__toString') || $identifierValue instanceof \Stringable) { + $identifiers[$parameterName] = (string) $identifierValue; + continue; + } + } } - throw new RuntimeException(sprintf('No identifier defined in "%s". You should add #[\ApiPlatform\Core\Annotation\ApiProperty(identifier: true)]" on the property identifying the resource."', $resourceClass)); + throw new RuntimeException(sprintf('We were not able to resolve the identifier matching parameter "%s".', $parameterName)); } return $identifiers; } - /** - * {@inheritdoc} - */ - public function getIdentifiersFromItem($item): array + private function resolveIdentifierValue($item, string $class, string $property) { - $identifiers = []; - $resourceClass = $this->getResourceClass($item, true); - $identifierProperties = $this->getIdentifiersFromResourceClass($resourceClass); + if ($item instanceof $class) { + return $this->propertyAccessor->getValue($item, $property); + } + $resourceClass = $this->getResourceClass($item, true); foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { - if (!\in_array($propertyName, $identifierProperties, true)) { - continue; - } - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); - $identifier = $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); - - if (!\is_object($identifier)) { + $type = $propertyMetadata->getType(); + if (!$type) { continue; } - if (null === $relatedResourceClass = $this->getResourceClass($identifier)) { - continue; + if ($type->getClassName() === $class) { + return $this->propertyAccessor->getValue($item, "$propertyName.$property"); } - $relatedItem = $identifier; - unset($identifiers[$propertyName]); - foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName); - if ($propertyMetadata->isIdentifier()) { - if (isset($identifiers[$propertyName])) { - throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); - } - - $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName); - } - } - - if (!isset($identifiers[$propertyName])) { - throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); + if ($type->isCollection() && ($collectionValueType = $type->getCollectionValueType()) && $collectionValueType->getClassName() === $class) { + return $this->propertyAccessor->getValue($item, sprintf('%s[0].%s', $propertyName, $property)); } } - return $identifiers; + throw new \RuntimeException('Not able to retrieve identifiers.'); } } diff --git a/src/Api/IdentifiersExtractorInterface.php b/src/Api/IdentifiersExtractorInterface.php index 1fd2afa48fd..67597387c6b 100644 --- a/src/Api/IdentifiersExtractorInterface.php +++ b/src/Api/IdentifiersExtractorInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Api; +namespace ApiPlatform\Api; use ApiPlatform\Core\Exception\RuntimeException; @@ -22,11 +22,6 @@ */ interface IdentifiersExtractorInterface { - /** - * Finds identifiers from a Resource class. - */ - public function getIdentifiersFromResourceClass(string $resourceClass): array; - /** * Finds identifiers from an Item (object). * @@ -34,5 +29,5 @@ public function getIdentifiersFromResourceClass(string $resourceClass): array; * * @throws RuntimeException */ - public function getIdentifiersFromItem($item): array; + public function getIdentifiersFromItem($item, ?string $operationName = null, array $context = []): array; } diff --git a/src/Api/IriConverterInterface.php b/src/Api/IriConverterInterface.php index 65f1fbf759a..fb7b6842d63 100644 --- a/src/Api/IriConverterInterface.php +++ b/src/Api/IriConverterInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Api; +namespace ApiPlatform\Api; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Exception\ItemNotFoundException; @@ -42,26 +42,12 @@ public function getItemFromIri(string $iri, array $context = []); * @throws InvalidArgumentException * @throws RuntimeException */ - public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; + public function getIriFromItem($item, string $operationName = null, int $referenceType = UrlGeneratorInterface::ABS_PATH, array $context = []): string; /** * Gets the IRI associated with the given resource collection. * * @throws InvalidArgumentException */ - public function getIriFromResourceClass(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; - - /** - * Gets the item IRI associated with the given resource. - * - * @throws InvalidArgumentException - */ - public function getItemIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; - - /** - * Gets the IRI associated with the given resource subresource. - * - * @throws InvalidArgumentException - */ - public function getSubresourceIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; + public function getIriFromResourceClass(string $resourceClass, string $operationName = null, int $referenceType = UrlGeneratorInterface::ABS_PATH, array $context = []): string; } diff --git a/src/Api/UrlGeneratorInterface.php b/src/Api/UrlGeneratorInterface.php index dc427b16384..dc8db8e1592 100644 --- a/src/Api/UrlGeneratorInterface.php +++ b/src/Api/UrlGeneratorInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Api; +namespace ApiPlatform\Api; /** * UrlGeneratorInterface is the interface that all URL generator classes must implement. diff --git a/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php b/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php deleted file mode 100644 index 25defbded04..00000000000 --- a/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php +++ /dev/null @@ -1,213 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider; - -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\FilterLocatorTrait; -use ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser; -use ApiPlatform\Core\Bridge\Symfony\Routing\OperationMethodResolverInterface; -use ApiPlatform\Core\Documentation\Documentation; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use Nelmio\ApiDocBundle\Annotation\ApiDoc; -use Nelmio\ApiDocBundle\Extractor\AnnotationsProviderInterface; -use Psr\Container\ContainerInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Creates Nelmio ApiDoc annotations for the api platform. - * - * @author Kévin Dunglas - * @author Teoh Han Hui - * - * @deprecated since version 2.2, to be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ -final class ApiPlatformProvider implements AnnotationsProviderInterface -{ - use FilterLocatorTrait; - - private $resourceNameCollectionFactory; - private $documentationNormalizer; - private $resourceMetadataFactory; - private $operationMethodResolver; - - /** - * @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection - */ - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, NormalizerInterface $documentationNormalizer, ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator, OperationMethodResolverInterface $operationMethodResolver) - { - @trigger_error('The '.__CLASS__.' class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.', \E_USER_DEPRECATED); - - $this->setFilterLocator($filterLocator); - - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->documentationNormalizer = $documentationNormalizer; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->operationMethodResolver = $operationMethodResolver; - } - - /** - * {@inheritdoc} - */ - public function getAnnotations(): array - { - $resourceNameCollection = $this->resourceNameCollectionFactory->create(); - - $hydraDoc = $this->documentationNormalizer->normalize(new Documentation($resourceNameCollection)); - if (!\is_array($hydraDoc)) { - throw new \UnexpectedValueException('Expected data to be an array'); - } - - if (empty($hydraDoc)) { - return []; - } - - $entrypointHydraDoc = $this->getResourceHydraDoc($hydraDoc, '#Entrypoint'); - if (null === $entrypointHydraDoc) { - return []; - } - - $annotations = []; - foreach ($resourceNameCollection as $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - $prefixedShortName = ($iri = $resourceMetadata->getIri()) ? $iri : '#'.$resourceMetadata->getShortName(); - $resourceHydraDoc = $this->getResourceHydraDoc($hydraDoc, $prefixedShortName); - if (null === $resourceHydraDoc) { - continue; - } - - if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) { - foreach ($collectionOperations as $operationName => $operation) { - $annotations[] = $this->getApiDoc(true, $resourceClass, $resourceMetadata, $operationName, $resourceHydraDoc, $entrypointHydraDoc); - } - } - - if (null !== $itemOperations = $resourceMetadata->getItemOperations()) { - foreach ($itemOperations as $operationName => $operation) { - $annotations[] = $this->getApiDoc(false, $resourceClass, $resourceMetadata, $operationName, $resourceHydraDoc); - } - } - } - - return $annotations; - } - - /** - * Builds ApiDoc annotation from ApiPlatform data. - */ - private function getApiDoc(bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $resourceHydraDoc, array $entrypointHydraDoc = []): ApiDoc - { - if ($collection) { - $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); - $route = $this->operationMethodResolver->getCollectionOperationRoute($resourceClass, $operationName); - $operationHydraDoc = $this->getCollectionOperationHydraDoc($resourceMetadata->getShortName(), $method, $entrypointHydraDoc); - } else { - $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); - $route = $this->operationMethodResolver->getItemOperationRoute($resourceClass, $operationName); - $operationHydraDoc = $this->getOperationHydraDoc($method, $resourceHydraDoc); - } - - $data = [ - 'resource' => $route->getPath(), - 'description' => $operationHydraDoc['hydra:title'] ?? '', - 'resourceDescription' => $resourceHydraDoc['hydra:title'] ?? '', - 'section' => $resourceHydraDoc['hydra:title'] ?? '', - ]; - - if (isset($operationHydraDoc['expects']) && 'owl:Nothing' !== $operationHydraDoc['expects']) { - $data['input'] = sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, $resourceClass, $operationName); - } - - if (isset($operationHydraDoc['returns']) && 'owl:Nothing' !== $operationHydraDoc['returns']) { - $data['output'] = sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, $resourceClass, $operationName); - } - - if ($collection && 'GET' === $method) { - $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); - - $data['filters'] = []; - foreach ($resourceFilters as $filterId) { - if ($filter = $this->getFilter($filterId)) { - foreach ($filter->getDescription($resourceClass) as $name => $definition) { - $data['filters'][] = ['name' => $name] + $definition; - } - } - } - } - - $apiDoc = new ApiDoc($data); - $apiDoc->setRoute($route); - - return $apiDoc; - } - - /** - * Gets Hydra documentation for the given resource. - */ - private function getResourceHydraDoc(array $hydraApiDoc, string $prefixedShortName): ?array - { - if (!isset($hydraApiDoc['hydra:supportedClass']) || !\is_array($hydraApiDoc['hydra:supportedClass'])) { - return null; - } - - foreach ($hydraApiDoc['hydra:supportedClass'] as $supportedClass) { - if (isset($supportedClass['@id']) && $supportedClass['@id'] === $prefixedShortName) { - return $supportedClass; - } - } - - return null; - } - - /** - * Gets the Hydra documentation of a given operation. - */ - private function getOperationHydraDoc(string $method, array $hydraDoc): array - { - if (!isset($hydraDoc['hydra:supportedOperation']) || !\is_array($hydraDoc['hydra:supportedOperation'])) { - return []; - } - - foreach ($hydraDoc['hydra:supportedOperation'] as $supportedOperation) { - if ($supportedOperation['hydra:method'] === $method) { - return $supportedOperation; - } - } - - return []; - } - - /** - * Gets the Hydra documentation for the collection operation. - */ - private function getCollectionOperationHydraDoc(string $shortName, string $method, array $hydraEntrypointDoc): array - { - if (!isset($hydraEntrypointDoc['hydra:supportedProperty']) || !\is_array($hydraEntrypointDoc['hydra:supportedProperty'])) { - return []; - } - - $propertyName = '#Entrypoint/'.lcfirst($shortName); - - foreach ($hydraEntrypointDoc['hydra:supportedProperty'] as $supportedProperty) { - if (isset($supportedProperty['hydra:property']['@id']) - && $supportedProperty['hydra:property']['@id'] === $propertyName) { - return $this->getOperationHydraDoc($method, $supportedProperty['hydra:property']); - } - } - - return []; - } -} diff --git a/src/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php b/src/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php deleted file mode 100644 index 702bc07eb4a..00000000000 --- a/src/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php +++ /dev/null @@ -1,275 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Core\Bridge\NelmioApiDoc\Parser; - -use ApiPlatform\Core\Exception\ResourceClassNotFoundException; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use Nelmio\ApiDocBundle\DataTypes; -use Nelmio\ApiDocBundle\Parser\ParserInterface; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - -/** - * Extract input and output information for the NelmioApiDocBundle. - * - * @author Kévin Dunglas - * @author Teoh Han Hui - * - * @deprecated since version 2.2, to be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ -final class ApiPlatformParser implements ParserInterface -{ - public const IN_PREFIX = 'api_platform_in'; - public const OUT_PREFIX = 'api_platform_out'; - public const TYPE_IRI = 'IRI'; - public const TYPE_MAP = [ - Type::BUILTIN_TYPE_BOOL => DataTypes::BOOLEAN, - Type::BUILTIN_TYPE_FLOAT => DataTypes::FLOAT, - Type::BUILTIN_TYPE_INT => DataTypes::INTEGER, - Type::BUILTIN_TYPE_STRING => DataTypes::STRING, - ]; - - private $resourceMetadataFactory; - private $propertyNameCollectionFactory; - private $propertyMetadataFactory; - private $nameConverter; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, NameConverterInterface $nameConverter = null) - { - @trigger_error('The '.__CLASS__.' class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.', \E_USER_DEPRECATED); - - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->nameConverter = $nameConverter; - } - - /** - * {@inheritdoc} - */ - public function supports(array $item) - { - $data = explode(':', $item['class'], 3); - if (!\in_array($data[0], [self::IN_PREFIX, self::OUT_PREFIX], true)) { - return false; - } - if (!isset($data[1])) { - return false; - } - - try { - $this->resourceMetadataFactory->create($data[1]); - - return true; - } catch (ResourceClassNotFoundException $e) { - // return false - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function parse(array $item): array - { - [$io, $resourceClass, $operationName] = explode(':', $item['class'], 3); - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - $classOperations = $this->getGroupsForItemAndCollectionOperation($resourceMetadata, $operationName, $io); - - if (!empty($classOperations['serializer_groups'])) { - return $this->getPropertyMetadata($resourceMetadata, $resourceClass, $io, [], $classOperations); - } - - return $this->parseResource($resourceMetadata, $resourceClass, $io); - } - - /** - * Parses a class. - * - * @param string[] $visited - */ - private function parseResource(ResourceMetadata $resourceMetadata, string $resourceClass, string $io, array $visited = []): array - { - $visited[] = $resourceClass; - - $options = []; - $attributes = $resourceMetadata->getAttributes(); - - if (isset($attributes['normalization_context'][AbstractNormalizer::GROUPS])) { - $options['serializer_groups'] = (array) $attributes['normalization_context'][AbstractNormalizer::GROUPS]; - } - - if (isset($attributes['denormalization_context'][AbstractNormalizer::GROUPS])) { - if (isset($options['serializer_groups'])) { - $options['serializer_groups'] += $attributes['denormalization_context'][AbstractNormalizer::GROUPS]; - } else { - $options['serializer_groups'] = (array) $attributes['denormalization_context'][AbstractNormalizer::GROUPS]; - } - } - - return $this->getPropertyMetadata($resourceMetadata, $resourceClass, $io, $visited, $options); - } - - private function getGroupsContext(ResourceMetadata $resourceMetadata, string $operationName, bool $isNormalization): array - { - $groupsContext = $isNormalization ? 'normalization_context' : 'denormalization_context'; - $itemOperationAttribute = $resourceMetadata->getItemOperationAttribute($operationName, $groupsContext, [AbstractNormalizer::GROUPS => []], true)[AbstractNormalizer::GROUPS]; - $collectionOperationAttribute = $resourceMetadata->getCollectionOperationAttribute($operationName, $groupsContext, [AbstractNormalizer::GROUPS => []], true)[AbstractNormalizer::GROUPS]; - - return [ - $groupsContext => [ - AbstractNormalizer::GROUPS => array_merge((array) ($itemOperationAttribute ?? []), (array) ($collectionOperationAttribute ?? [])), - ], - ]; - } - - /** - * Returns groups of item & collection. - */ - private function getGroupsForItemAndCollectionOperation(ResourceMetadata $resourceMetadata, string $operationName, string $io): array - { - $operation = $this->getGroupsContext($resourceMetadata, $operationName, true); - $operation += $this->getGroupsContext($resourceMetadata, $operationName, false); - - if (self::OUT_PREFIX === $io) { - return [ - 'serializer_groups' => !empty($operation['normalization_context']) ? $operation['normalization_context'][AbstractNormalizer::GROUPS] : [], - ]; - } - - if (self::IN_PREFIX === $io) { - return [ - 'serializer_groups' => !empty($operation['denormalization_context']) ? $operation['denormalization_context'][AbstractNormalizer::GROUPS] : [], - ]; - } - - return []; - } - - /** - * Returns a property metadata. - * - * @param string[] $visited - * @param string[] $options - */ - private function getPropertyMetadata(ResourceMetadata $resourceMetadata, string $resourceClass, string $io, array $visited, array $options): array - { - $data = []; - - foreach ($this->propertyNameCollectionFactory->create($resourceClass, $options) as $propertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); - if ( - ($propertyMetadata->isReadable() && self::OUT_PREFIX === $io) || - ($propertyMetadata->isWritable() && self::IN_PREFIX === $io) - ) { - $normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName, $resourceClass) : $propertyName; - $data[$normalizedPropertyName] = $this->parseProperty($resourceMetadata, $propertyMetadata, $io, null, $visited); - } - } - - return $data; - } - - /** - * Parses a property. - * - * @param string $io - * @param string[] $visited - */ - private function parseProperty(ResourceMetadata $resourceMetadata, PropertyMetadata $propertyMetadata, $io, Type $type = null, array $visited = []): array - { - $data = [ - 'dataType' => null, - 'required' => $propertyMetadata->isRequired(), - 'description' => $propertyMetadata->getDescription(), - 'readonly' => !$propertyMetadata->isWritable(), - ]; - - if (null === $type && null === $type = $propertyMetadata->getType()) { - // Default to string - $data['dataType'] = DataTypes::STRING; - - return $data; - } - - if ($type->isCollection()) { - $data['actualType'] = DataTypes::COLLECTION; - - if ($collectionType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType()) { - $subProperty = $this->parseProperty($resourceMetadata, $propertyMetadata, $io, $collectionType, $visited); - if (self::TYPE_IRI === $subProperty['dataType']) { - $data['dataType'] = 'array of IRIs'; - $data['subType'] = DataTypes::STRING; - - return $data; - } - - $data['subType'] = $subProperty['subType'] ?? null; - if (isset($subProperty['children'])) { - $data['children'] = $subProperty['children']; - } - } - - return $data; - } - - $builtinType = $type->getBuiltinType(); - if ('object' === $builtinType) { - $className = $type->getClassName(); - - if (is_a($className, \DateTimeInterface::class, true)) { - $data['dataType'] = DataTypes::DATETIME; - $data['format'] = sprintf('{DateTime %s}', \DateTime::RFC3339); - - return $data; - } - - try { - $this->resourceMetadataFactory->create($className); - } catch (ResourceClassNotFoundException $e) { - $data['actualType'] = DataTypes::MODEL; - $data['subType'] = $className; - - return $data; - } - - if ( - (self::OUT_PREFIX === $io && true !== $propertyMetadata->isReadableLink()) || - (self::IN_PREFIX === $io && true !== $propertyMetadata->isWritableLink()) - ) { - $data['dataType'] = self::TYPE_IRI; - $data['actualType'] = DataTypes::STRING; - - return $data; - } - - $data['actualType'] = DataTypes::MODEL; - $data['subType'] = $className; - $data['children'] = \in_array($className, $visited, true) ? [] : $this->parseResource($resourceMetadata, $className, $io, $visited); - - return $data; - } - - $data['dataType'] = self::TYPE_MAP[$builtinType] ?? DataTypes::STRING; - - return $data; - } -} diff --git a/src/Bridge/Symfony/Routing/IriConverter.php b/src/Bridge/Symfony/Routing/IriConverter.php index 0fa11e3e53e..cef4cd5eb3d 100644 --- a/src/Bridge/Symfony/Routing/IriConverter.php +++ b/src/Bridge/Symfony/Routing/IriConverter.php @@ -11,57 +11,53 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Bridge\Symfony\Routing; +namespace ApiPlatform\Bridge\Symfony\Routing; -use ApiPlatform\Core\Api\IdentifiersExtractor; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\Api\OperationType; +use ApiPlatform\Api\IdentifiersExtractorInterface; +use ApiPlatform\Api\IriConverterInterface; +use ApiPlatform\Api\UrlGeneratorInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Api\UrlGeneratorInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\OperationDataProviderTrait; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Exception\InvalidIdentifierException; use ApiPlatform\Core\Exception\ItemNotFoundException; use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Identifier\CompositeIdentifierParser; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Identifier\ContextAwareIdentifierConverterInterface; use ApiPlatform\Core\Util\AttributesExtractor; use ApiPlatform\Core\Util\ResourceClassInfoTrait; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\State\ProviderInterface; use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface; use Symfony\Component\Routing\RouterInterface; /** * {@inheritdoc} * - * @author Kévin Dunglas + * @experimental + * + * @author Antoine Bluchet */ final class IriConverter implements IriConverterInterface { - use OperationDataProviderTrait; use ResourceClassInfoTrait; - private $routeNameResolver; + private $stateProvider; private $router; private $identifiersExtractor; + private $identifierConverter; + private $resourceMetadataCollectionFactory; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ItemDataProviderInterface $itemDataProvider, RouteNameResolverInterface $routeNameResolver, RouterInterface $router, PropertyAccessorInterface $propertyAccessor = null, IdentifiersExtractorInterface $identifiersExtractor = null, SubresourceDataProviderInterface $subresourceDataProvider = null, IdentifierConverterInterface $identifierConverter = null, ResourceClassResolverInterface $resourceClassResolver = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null) + public function __construct(ProviderInterface $stateProvider, RouterInterface $router, IdentifiersExtractorInterface $identifiersExtractor, ContextAwareIdentifierConverterInterface $identifierConverter, ResourceClassResolverInterface $resourceClassResolver, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory) { - $this->itemDataProvider = $itemDataProvider; - $this->routeNameResolver = $routeNameResolver; + $this->stateProvider = $stateProvider; $this->router = $router; - $this->subresourceDataProvider = $subresourceDataProvider; $this->identifierConverter = $identifierConverter; + $this->identifiersExtractor = $identifiersExtractor; + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; + // For the ResourceClassInfoTrait $this->resourceClassResolver = $resourceClassResolver; - $this->identifiersExtractor = $identifiersExtractor ?: new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, $propertyAccessor ?? PropertyAccess::createPropertyAccessor()); - $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->resourceMetadataFactory = $resourceMetadataCollectionFactory; } /** @@ -75,35 +71,43 @@ public function getItemFromIri(string $iri, array $context = []) throw new InvalidArgumentException(sprintf('No route matches "%s".', $iri), $e->getCode(), $e); } - if (!isset($parameters['_api_resource_class'])) { + if (!isset($parameters['_api_resource_class'], $parameters['_api_operation_name'])) { throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri)); } - if (isset($parameters['_api_collection_operation_name'])) { + $context['operation'] = $operation = $parameters['_api_operation'] = $this->resourceMetadataCollectionFactory->create($parameters['_api_resource_class'])->getOperation($parameters['_api_operation_name']); + + if ($operation->isCollection()) { throw new InvalidArgumentException(sprintf('The iri "%s" references a collection not an item.', $iri)); } $attributes = AttributesExtractor::extractAttributes($parameters); + $shouldParseCompositeIdentifiers = $operation->getCompositeIdentifier() && \count($operation->getIdentifiers()) > 1; + $identifiers = []; + foreach ($operation->getIdentifiers() as $parameterName => $identifiedBy) { + if (!isset($parameters[$parameterName])) { + if (!isset($parameters['id'])) { + throw new InvalidIdentifierException(sprintf('Parameter "%s" not found, check the identifiers configuration.', $parameterName)); + } + + $parameterName = 'id'; + } - try { - $identifiers = $this->extractIdentifiers($parameters, $attributes); - } catch (InvalidIdentifierException $e) { - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); - } + if ($shouldParseCompositeIdentifiers) { + $identifiers = CompositeIdentifierParser::parse($parameters[$parameterName]); + break; + } - if ($this->identifierConverter) { - $context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] = true; + $identifiers[$parameterName] = $parameters[$parameterName]; } - if (isset($attributes['subresource_operation_name'])) { - if (($item = $this->getSubresourceData($identifiers, $attributes, $context)) && !\is_array($item)) { - return $item; - } - - throw new ItemNotFoundException(sprintf('Item not found for "%s".', $iri)); + try { + $identifiers = $this->identifierConverter->convert($identifiers, $attributes['resource_class'], ['identifiers' => $operation->getIdentifiers()]); + } catch (InvalidIdentifierException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } - if ($item = $this->getItemData($identifiers, $attributes, $context)) { + if ($item = $this->stateProvider->provide($attributes['resource_class'], $identifiers, $attributes['operation_name'], $context)) { return $item; } @@ -113,26 +117,56 @@ public function getItemFromIri(string $iri, array $context = []) /** * {@inheritdoc} */ - public function getIriFromItem($item, int $referenceType = null): string + public function getIriFromItem($item, string $operationName = null, int $referenceType = UrlGeneratorInterface::ABS_PATH, array $context = []): string { $resourceClass = $this->getResourceClass($item, true); + $operationName = $context['operation_name'] ?? $operationName; + + // These are special cases were we want to find the related operation + // `ResourceMetadataCollection::getOperation` retrieves the first safe operation if no $operationName is given + if (isset($context['operation'])) { + $operation = $context['operation']; + if ( + ($operation->getExtraProperties()['is_legacy_subresource'] ?? false) || + ($operation->getExtraProperties()['user_defined_uri_template'] ?? false) || + ($operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false) || + // When we want the Iri from an object, we don't want the collection uriTemplate, for this we use getIriFromResourceClass + $operation->isCollection() + ) { + unset($context['operation']); + $operationName = null; + } + } try { - $identifiers = $this->identifiersExtractor->getIdentifiersFromItem($item); + $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName); + } catch (OperationNotFoundException $e) { + $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass); + foreach ($resourceMetadataCollection as $resource) { + foreach ($resource->getOperations() as $name => $operation) { + if ($operationName === $name && !$operation->isCollection()) { + break 2; + } + } + } + + if (!isset($operation)) { + throw $e; + } + } + + try { + $identifiers = $this->identifiersExtractor->getIdentifiersFromItem($item, $operation->getName(), ['operation' => $operation]); } catch (RuntimeException $e) { throw new InvalidArgumentException(sprintf('Unable to generate an IRI for the item of type "%s"', $resourceClass), $e->getCode(), $e); } - return $this->getItemIriFromResourceClass($resourceClass, $identifiers, $this->getReferenceType($resourceClass, $referenceType)); - } + if ($operation->getCompositeIdentifier() && \count($identifiers) > 1) { + $identifiers = [key($operation->getIdentifiers()) => CompositeIdentifierParser::stringify($identifiers)]; + } - /** - * {@inheritdoc} - */ - public function getIriFromResourceClass(string $resourceClass, int $referenceType = null): string - { try { - return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, OperationType::COLLECTION), [], $this->getReferenceType($resourceClass, $referenceType)); + return $this->router->generate($operation->getName(), $identifiers, $operation->getUrlGenerationStrategy() ?? $referenceType); } catch (RoutingExceptionInterface $e) { throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e); } @@ -141,41 +175,45 @@ public function getIriFromResourceClass(string $resourceClass, int $referenceTyp /** * {@inheritdoc} */ - public function getItemIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = null): string + public function getIriFromResourceClass(string $resourceClass, string $operationName = null, int $referenceType = UrlGeneratorInterface::ABS_PATH, array $context = []): string { - $routeName = $this->routeNameResolver->getRouteName($resourceClass, OperationType::ITEM); - $metadata = $this->resourceMetadataFactory->create($resourceClass); + // TODO: 3.0 remove the condition + if ($context['extra_properties']['is_legacy_subresource'] ?? false) { + trigger_deprecation('api-platform/core', '2.7', 'The IRI will change and match the first operation of the resource. Switch to an alternate resource when possible instead of using subresources.'); + $operation = $this->resourceMetadataFactory->create($resourceClass)->getOperation($context['extra_properties']['legacy_subresource_operation_name']); + } - if (\count($identifiers) > 1 && true === $metadata->getAttribute('composite_identifier', true)) { - $identifiers = ['id' => CompositeIdentifierParser::stringify($identifiers)]; + $operation = $context['operation'] ?? null; + + if ($operation) { + // TODO: 2.7 should we deprecate this behavior ? example : Entity\Foo.php should take it's own operation? As it's a custom operation can we now? + if ($operation->getExtraProperties()['is_legacy_subresource'] ?? false) { + trigger_deprecation('api-platform/core', '2.7', 'The IRI will change and match the first operation of the resource. Switch to an alternate resource when possible instead of using subresources.'); + $operationName = $operation->getExtraProperties()['legacy_subresource_operation_name']; + $operation = null; + } elseif ($operation->getExtraProperties()['user_defined_uri_template'] ?? false) { + $operation = null; + $operationName = null; + } } - try { - return $this->router->generate($routeName, $identifiers, $this->getReferenceType($resourceClass, $referenceType)); - } catch (RoutingExceptionInterface $e) { - throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e); + if (!$operation) { + $operation = $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName, $context['force_collection'] ?? true); } - } - /** - * {@inheritdoc} - */ - public function getSubresourceIriFromResourceClass(string $resourceClass, array $context, int $referenceType = null): string - { - try { - return $this->router->generate($this->routeNameResolver->getRouteName($resourceClass, OperationType::SUBRESOURCE, $context), $context['subresource_identifiers'], $this->getReferenceType($resourceClass, $referenceType)); - } catch (RoutingExceptionInterface $e) { - throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e); + if (!$operation) { + throw new InvalidArgumentException(sprintf('Unable to find an operation for %s.', $resourceClass)); } - } - private function getReferenceType(string $resourceClass, ?int $referenceType): ?int - { - if (null === $referenceType && null !== $this->resourceMetadataFactory) { - $metadata = $this->resourceMetadataFactory->create($resourceClass); - $referenceType = $metadata->getAttribute('url_generation_strategy'); + $identifiers = $context['identifiers_values'] ?? []; + if ($operation->getCompositeIdentifier() && \count($identifiers) > 1) { + $identifiers = [key($operation->getIdentifiers()) => CompositeIdentifierParser::stringify($identifiers)]; } - return $referenceType ?? UrlGeneratorInterface::ABS_PATH; + try { + return $this->router->generate($operation->getName(), $identifiers, $operation->getUrlGenerationStrategy() ?? $referenceType); + } catch (RoutingExceptionInterface $e) { + throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e); + } } } diff --git a/src/Action/EntrypointAction.php b/src/Core/Action/EntrypointAction.php similarity index 100% rename from src/Action/EntrypointAction.php rename to src/Core/Action/EntrypointAction.php diff --git a/src/Action/ExceptionAction.php b/src/Core/Action/ExceptionAction.php similarity index 74% rename from src/Action/ExceptionAction.php rename to src/Core/Action/ExceptionAction.php index 7f227e9eafd..2aed2d17f8e 100644 --- a/src/Action/ExceptionAction.php +++ b/src/Core/Action/ExceptionAction.php @@ -16,6 +16,8 @@ use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Util\ErrorFormatGuesser; use ApiPlatform\Core\Util\RequestAttributesExtractor; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Util\OperationRequestInitiatorTrait; use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Request; @@ -30,21 +32,33 @@ */ final class ExceptionAction { + use OperationRequestInitiatorTrait; + private $serializer; private $errorFormats; private $exceptionToStatus; + /** + * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface|null + */ private $resourceMetadataFactory; /** - * @param array $errorFormats A list of enabled error formats - * @param array $exceptionToStatus A list of exceptions mapped to their HTTP status code + * @param array $errorFormats A list of enabled error formats + * @param array $exceptionToStatus A list of exceptions mapped to their HTTP status code + * @param mixed|null $resourceMetadataFactory */ - public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = [], ?ResourceMetadataFactoryInterface $resourceMetadataFactory = null) + public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = [], $resourceMetadataFactory = null) { $this->serializer = $serializer; $this->errorFormats = $errorFormats; $this->exceptionToStatus = $exceptionToStatus; $this->resourceMetadataFactory = $resourceMetadataFactory; + + if (null !== $resourceMetadataFactory && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } else { + $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; + } } /** @@ -54,12 +68,13 @@ public function __construct(SerializerInterface $serializer, array $errorFormats */ public function __invoke($exception, Request $request): Response { + $operation = $this->initializeOperation($request); $exceptionClass = $exception->getClass(); $statusCode = $exception->getStatusCode(); $exceptionToStatus = array_merge( $this->exceptionToStatus, - $this->getOperationExceptionToStatus($request) + $operation ? $operation->getExceptionToStatus() : $this->getOperationExceptionToStatus($request) ); foreach ($exceptionToStatus as $class => $status) { diff --git a/src/Action/NotFoundAction.php b/src/Core/Action/NotFoundAction.php similarity index 100% rename from src/Action/NotFoundAction.php rename to src/Core/Action/NotFoundAction.php diff --git a/src/Action/PlaceholderAction.php b/src/Core/Action/PlaceholderAction.php similarity index 100% rename from src/Action/PlaceholderAction.php rename to src/Core/Action/PlaceholderAction.php diff --git a/src/Annotation/ApiFilter.php b/src/Core/Annotation/ApiFilter.php similarity index 100% rename from src/Annotation/ApiFilter.php rename to src/Core/Annotation/ApiFilter.php diff --git a/src/Annotation/ApiProperty.php b/src/Core/Annotation/ApiProperty.php similarity index 96% rename from src/Annotation/ApiProperty.php rename to src/Core/Annotation/ApiProperty.php index a61629624f4..9b3d6398b4b 100644 --- a/src/Annotation/ApiProperty.php +++ b/src/Core/Annotation/ApiProperty.php @@ -94,6 +94,9 @@ final class ApiProperty */ public $example; + public $types; + public $builtinTypes; + /** * @param string $description * @param bool $readable @@ -139,7 +142,10 @@ public function __construct( ?bool $push = null, ?string $security = null, ?array $swaggerContext = null, - ?string $securityPostDenormalize = null + ?string $securityPostDenormalize = null, + + ?array $types = [], + ?array $builtinTypes = [] ) { if (!\is_array($description)) { // @phpstan-ignore-line Doctrine annotations support [$publicProperties, $configurableAttributes] = self::getConfigMetadata(); diff --git a/src/Annotation/ApiResource.php b/src/Core/Annotation/ApiResource.php similarity index 100% rename from src/Annotation/ApiResource.php rename to src/Core/Annotation/ApiResource.php diff --git a/src/Annotation/ApiSubresource.php b/src/Core/Annotation/ApiSubresource.php similarity index 100% rename from src/Annotation/ApiSubresource.php rename to src/Core/Annotation/ApiSubresource.php diff --git a/src/Annotation/AttributesHydratorTrait.php b/src/Core/Annotation/AttributesHydratorTrait.php similarity index 100% rename from src/Annotation/AttributesHydratorTrait.php rename to src/Core/Annotation/AttributesHydratorTrait.php diff --git a/src/Api/CachedIdentifiersExtractor.php b/src/Core/Api/CachedIdentifiersExtractor.php similarity index 100% rename from src/Api/CachedIdentifiersExtractor.php rename to src/Core/Api/CachedIdentifiersExtractor.php diff --git a/src/Api/Entrypoint.php b/src/Core/Api/Entrypoint.php similarity index 100% rename from src/Api/Entrypoint.php rename to src/Core/Api/Entrypoint.php diff --git a/src/Api/FilterCollection.php b/src/Core/Api/FilterCollection.php similarity index 100% rename from src/Api/FilterCollection.php rename to src/Core/Api/FilterCollection.php diff --git a/src/Api/FilterCollectionFactory.php b/src/Core/Api/FilterCollectionFactory.php similarity index 100% rename from src/Api/FilterCollectionFactory.php rename to src/Core/Api/FilterCollectionFactory.php diff --git a/src/Api/FilterInterface.php b/src/Core/Api/FilterInterface.php similarity index 100% rename from src/Api/FilterInterface.php rename to src/Core/Api/FilterInterface.php diff --git a/src/Api/FilterLocatorTrait.php b/src/Core/Api/FilterLocatorTrait.php similarity index 100% rename from src/Api/FilterLocatorTrait.php rename to src/Core/Api/FilterLocatorTrait.php diff --git a/src/Api/FormatMatcher.php b/src/Core/Api/FormatMatcher.php similarity index 100% rename from src/Api/FormatMatcher.php rename to src/Core/Api/FormatMatcher.php diff --git a/src/Api/FormatsProvider.php b/src/Core/Api/FormatsProvider.php similarity index 100% rename from src/Api/FormatsProvider.php rename to src/Core/Api/FormatsProvider.php diff --git a/src/Api/FormatsProviderInterface.php b/src/Core/Api/FormatsProviderInterface.php similarity index 100% rename from src/Api/FormatsProviderInterface.php rename to src/Core/Api/FormatsProviderInterface.php diff --git a/src/Core/Api/IdentifiersExtractor.php b/src/Core/Api/IdentifiersExtractor.php new file mode 100644 index 00000000000..beabf436b3b --- /dev/null +++ b/src/Core/Api/IdentifiersExtractor.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Api; + +use ApiPlatform\Api\IdentifiersExtractor as NewIdentifiersExtractor; +use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Core\Util\ResourceClassInfoTrait; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +/** + * {@inheritdoc} + * + * @author Antoine Bluchet + */ +final class IdentifiersExtractor implements IdentifiersExtractorInterface +{ + use ResourceClassInfoTrait; + + private $propertyNameCollectionFactory; + private $propertyMetadataFactory; + private $propertyAccessor; + + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null) + { + trigger_deprecation('api-platform/core', '2.7', sprintf('The service "%s" is deprecated, use %s instead.', self::class, NewIdentifiersExtractor::class)); + $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; + $this->propertyMetadataFactory = $propertyMetadataFactory; + $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); + $this->resourceClassResolver = $resourceClassResolver; + + if (null === $this->resourceClassResolver) { + @trigger_error(sprintf('Not injecting %s in the IdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), \E_USER_DEPRECATED); + } + } + + /** + * {@inheritdoc} + */ + public function getIdentifiersFromResourceClass(string $resourceClass): array + { + $identifiers = []; + foreach ($properties = $this->propertyNameCollectionFactory->create($resourceClass) as $property) { + if ($this->propertyMetadataFactory->create($resourceClass, $property)->isIdentifier() ?? false) { + $identifiers[] = $property; + } + } + + if (!$identifiers) { + if (\in_array('id', iterator_to_array($properties), true)) { + return ['id']; + } + + throw new RuntimeException(sprintf('No identifier defined in "%s". You should add #[\ApiPlatform\Core\Annotation\ApiProperty(identifier: true)]" on the property identifying the resource."', $resourceClass)); + } + + return $identifiers; + } + + /** + * {@inheritdoc} + */ + public function getIdentifiersFromItem($item): array + { + $identifiers = []; + $resourceClass = $this->getResourceClass($item, true); + $identifierProperties = $this->getIdentifiersFromResourceClass($resourceClass); + + foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { + if (!\in_array($propertyName, $identifierProperties, true)) { + continue; + } + + $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); + $identifier = $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); + + if (!\is_object($identifier)) { + continue; + } + + if (null === $relatedResourceClass = $this->getResourceClass($identifier)) { + continue; + } + + $relatedItem = $identifier; + unset($identifiers[$propertyName]); + foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) { + $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName); + if ($propertyMetadata->isIdentifier()) { + if (isset($identifiers[$propertyName])) { + throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); + } + + $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName); + } + } + + if (!isset($identifiers[$propertyName])) { + throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); + } + } + + return $identifiers; + } +} diff --git a/src/Core/Api/IdentifiersExtractorInterface.php b/src/Core/Api/IdentifiersExtractorInterface.php new file mode 100644 index 00000000000..1fd2afa48fd --- /dev/null +++ b/src/Core/Api/IdentifiersExtractorInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Api; + +use ApiPlatform\Core\Exception\RuntimeException; + +/** + * Extracts identifiers for a given Resource according to the retrieved Metadata. + * + * @author Antoine Bluchet + */ +interface IdentifiersExtractorInterface +{ + /** + * Finds identifiers from a Resource class. + */ + public function getIdentifiersFromResourceClass(string $resourceClass): array; + + /** + * Finds identifiers from an Item (object). + * + * @param object $item + * + * @throws RuntimeException + */ + public function getIdentifiersFromItem($item): array; +} diff --git a/src/Core/Api/IriConverterInterface.php b/src/Core/Api/IriConverterInterface.php new file mode 100644 index 00000000000..2b3bf3fdd11 --- /dev/null +++ b/src/Core/Api/IriConverterInterface.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Api; + +use ApiPlatform\Api\UrlGeneratorInterface; +use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Exception\ItemNotFoundException; +use ApiPlatform\Core\Exception\RuntimeException; + +/** + * Converts item and resources to IRI and vice versa. + * + * @author Kévin Dunglas + */ +interface IriConverterInterface +{ + /** + * Retrieves an item from its IRI. + * + * @throws InvalidArgumentException + * @throws ItemNotFoundException + * + * @return object + */ + public function getItemFromIri(string $iri, array $context = []); + + /** + * Gets the IRI associated with the given item. + * + * @param object $item + * + * @throws InvalidArgumentException + * @throws RuntimeException + */ + public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; + + /** + * Gets the IRI associated with the given resource collection. + * + * @throws InvalidArgumentException + */ + public function getIriFromResourceClass(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; + + /** + * Gets the item IRI associated with the given resource. + * + * @throws InvalidArgumentException + */ + public function getItemIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; + + /** + * Gets the IRI associated with the given resource subresource. + * + * @throws InvalidArgumentException + */ + public function getSubresourceIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string; +} diff --git a/src/Api/OperationAwareFormatsProviderInterface.php b/src/Core/Api/OperationAwareFormatsProviderInterface.php similarity index 100% rename from src/Api/OperationAwareFormatsProviderInterface.php rename to src/Core/Api/OperationAwareFormatsProviderInterface.php diff --git a/src/Api/OperationMethodResolverInterface.php b/src/Core/Api/OperationMethodResolverInterface.php similarity index 100% rename from src/Api/OperationMethodResolverInterface.php rename to src/Core/Api/OperationMethodResolverInterface.php diff --git a/src/Api/OperationType.php b/src/Core/Api/OperationType.php similarity index 70% rename from src/Api/OperationType.php rename to src/Core/Api/OperationType.php index f1daa8b016b..8d303cf46bf 100644 --- a/src/Api/OperationType.php +++ b/src/Core/Api/OperationType.php @@ -13,6 +13,10 @@ namespace ApiPlatform\Core\Api; +use ApiPlatform\Metadata\Operation; + +trigger_deprecation('api-platform', '2.7', sprintf('%s is deprecated, an operation can be a collection using the %s::collection property.', OperationType::class, Operation::class)); + final class OperationType { public const ITEM = 'item'; diff --git a/src/Api/OperationTypeDeprecationHelper.php b/src/Core/Api/OperationTypeDeprecationHelper.php similarity index 100% rename from src/Api/OperationTypeDeprecationHelper.php rename to src/Core/Api/OperationTypeDeprecationHelper.php diff --git a/src/Api/ResourceClassResolver.php b/src/Core/Api/ResourceClassResolver.php similarity index 100% rename from src/Api/ResourceClassResolver.php rename to src/Core/Api/ResourceClassResolver.php diff --git a/src/Api/ResourceClassResolverInterface.php b/src/Core/Api/ResourceClassResolverInterface.php similarity index 100% rename from src/Api/ResourceClassResolverInterface.php rename to src/Core/Api/ResourceClassResolverInterface.php diff --git a/src/Bridge/Doctrine/Common/DataPersister.php b/src/Core/Bridge/Doctrine/Common/DataPersister.php similarity index 100% rename from src/Bridge/Doctrine/Common/DataPersister.php rename to src/Core/Bridge/Doctrine/Common/DataPersister.php diff --git a/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php rename to src/Core/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php diff --git a/src/Bridge/Doctrine/Common/Filter/DateFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/DateFilterInterface.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/DateFilterInterface.php rename to src/Core/Bridge/Doctrine/Common/Filter/DateFilterInterface.php diff --git a/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/DateFilterTrait.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php rename to src/Core/Bridge/Doctrine/Common/Filter/DateFilterTrait.php diff --git a/src/Bridge/Doctrine/Common/Filter/ExistsFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterInterface.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/ExistsFilterInterface.php rename to src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterInterface.php diff --git a/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php rename to src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php diff --git a/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php rename to src/Core/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php diff --git a/src/Bridge/Doctrine/Common/Filter/OrderFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/OrderFilterInterface.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/OrderFilterInterface.php rename to src/Core/Bridge/Doctrine/Common/Filter/OrderFilterInterface.php diff --git a/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php rename to src/Core/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php diff --git a/src/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php rename to src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php diff --git a/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php rename to src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php diff --git a/src/Bridge/Doctrine/Common/Filter/SearchFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterInterface.php similarity index 100% rename from src/Bridge/Doctrine/Common/Filter/SearchFilterInterface.php rename to src/Core/Bridge/Doctrine/Common/Filter/SearchFilterInterface.php diff --git a/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php similarity index 95% rename from src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php rename to src/Core/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php index 8169b2fc719..600aba26d1f 100644 --- a/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php +++ b/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php @@ -13,7 +13,8 @@ namespace ApiPlatform\Core\Bridge\Doctrine\Common\Filter; -use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Api\IriConverterInterface; +use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; use ApiPlatform\Core\Exception\InvalidArgumentException; use Psr\Log\LoggerInterface; @@ -108,7 +109,10 @@ abstract protected function getProperties(): ?array; abstract protected function getLogger(): LoggerInterface; - abstract protected function getIriConverter(): IriConverterInterface; + /** + * @return IriConverterInterface|LegacyIriConverterInterface + */ + abstract protected function getIriConverter(); abstract protected function getPropertyAccessor(): PropertyAccessorInterface; diff --git a/src/Bridge/Doctrine/Common/PropertyHelperTrait.php b/src/Core/Bridge/Doctrine/Common/PropertyHelperTrait.php similarity index 100% rename from src/Bridge/Doctrine/Common/PropertyHelperTrait.php rename to src/Core/Bridge/Doctrine/Common/PropertyHelperTrait.php diff --git a/src/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php b/src/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php similarity index 100% rename from src/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php rename to src/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php diff --git a/src/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php b/src/Core/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php similarity index 90% rename from src/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php rename to src/Core/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php index 807c76724e1..7bdedf47400 100644 --- a/src/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php +++ b/src/Core/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php @@ -13,16 +13,17 @@ namespace ApiPlatform\Core\Bridge\Doctrine\EventListener; -use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Api\UrlGeneratorInterface; use ApiPlatform\Core\Bridge\Symfony\Messenger\DispatchTrait; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface as GraphQlMercureSubscriptionIriGeneratorInterface; -use ApiPlatform\Core\GraphQl\Subscription\SubscriptionManagerInterface as GraphQlSubscriptionManagerInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Util\ResourceClassInfoTrait; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface as GraphQlMercureSubscriptionIriGeneratorInterface; +use ApiPlatform\GraphQl\Subscription\SubscriptionManagerInterface as GraphQlSubscriptionManagerInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\Common\EventArgs; use Doctrine\ODM\MongoDB\Event\OnFlushEventArgs as MongoDbOdmOnFlushEventArgs; use Doctrine\ORM\Event\OnFlushEventArgs as OrmOnFlushEventArgs; @@ -72,7 +73,7 @@ final class PublishMercureUpdatesListener * @param array $formats * @param HubRegistry|callable $hubRegistry */ - public function __construct(ResourceClassResolverInterface $resourceClassResolver, IriConverterInterface $iriConverter, ResourceMetadataFactoryInterface $resourceMetadataFactory, SerializerInterface $serializer, array $formats, MessageBusInterface $messageBus = null, $hubRegistry = null, ?GraphQlSubscriptionManagerInterface $graphQlSubscriptionManager = null, ?GraphQlMercureSubscriptionIriGeneratorInterface $graphQlMercureSubscriptionIriGenerator = null, ExpressionLanguage $expressionLanguage = null) + public function __construct(ResourceClassResolverInterface $resourceClassResolver, IriConverterInterface $iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, SerializerInterface $serializer, array $formats, MessageBusInterface $messageBus = null, $hubRegistry = null, ?GraphQlSubscriptionManagerInterface $graphQlSubscriptionManager = null, ?GraphQlMercureSubscriptionIriGeneratorInterface $graphQlMercureSubscriptionIriGenerator = null, ExpressionLanguage $expressionLanguage = null) { if (null === $messageBus && null === $hubRegistry) { throw new InvalidArgumentException('A message bus or a hub registry must be provided.'); @@ -80,6 +81,7 @@ public function __construct(ResourceClassResolverInterface $resourceClassResolve $this->resourceClassResolver = $resourceClassResolver; $this->iriConverter = $iriConverter; + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->serializer = $serializer; $this->formats = $formats; @@ -98,7 +100,7 @@ public function __construct(ResourceClassResolverInterface $resourceClassResolve new ExpressionFunction('iri', static function (string $apiResource, int $referenceType = UrlGeneratorInterface::ABS_URL): string { return sprintf('iri(%s, %d)', $apiResource, $referenceType); }, static function (array $arguments, $apiResource, int $referenceType = UrlGeneratorInterface::ABS_URL) use ($iriConverter): string { - return $iriConverter->getIriFromItem($apiResource, $referenceType); + return $iriConverter->getIriFromItem($apiResource, null, $referenceType); }) ); } @@ -171,7 +173,11 @@ private function storeObjectToPublish($object, string $property): void return; } - $options = $this->resourceMetadataFactory->create($resourceClass)->getAttribute('mercure', false); + try { + $options = $this->resourceMetadataFactory->create($resourceClass)->getOperation()->getMercure() ?? false; + } catch (OperationNotFoundException $e) { + return; + } if (\is_string($options)) { if (null === $this->expressionLanguage) { @@ -240,7 +246,7 @@ private function storeObjectToPublish($object, string $property): void if ('deletedObjects' === $property) { $this->deletedObjects[(object) [ 'id' => $this->iriConverter->getIriFromItem($object), - 'iri' => $this->iriConverter->getIriFromItem($object, UrlGeneratorInterface::ABS_URL), + 'iri' => $this->iriConverter->getIriFromItem($object, null, UrlGeneratorInterface::ABS_URL), ]] = $options; return; @@ -263,9 +269,9 @@ private function publishUpdate($object, array $options, string $type): void $data = json_encode(['@id' => $object->id]); } else { $resourceClass = $this->getObjectClass($object); - $context = $options['normalization_context'] ?? $this->resourceMetadataFactory->create($resourceClass)->getAttribute('normalization_context', []); + $context = $options['normalization_context'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation()->getNormalizationContext(); - $iri = $options['topics'] ?? $this->iriConverter->getIriFromItem($object, UrlGeneratorInterface::ABS_URL); + $iri = $options['topics'] ?? $this->iriConverter->getIriFromItem($object, null, UrlGeneratorInterface::ABS_URL); $data = $options['data'] ?? $this->serializer->serialize($object, key($this->formats), $context); } diff --git a/src/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php b/src/Core/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php similarity index 96% rename from src/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php rename to src/Core/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php index 5d685683e11..548c1bbb9bf 100644 --- a/src/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php +++ b/src/Core/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php @@ -13,11 +13,12 @@ namespace ApiPlatform\Core\Bridge\Doctrine\EventListener; -use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\HttpCache\PurgerInterface; +use ApiPlatform\Exception\OperationNotFoundException; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; @@ -118,6 +119,7 @@ private function gatherResourceAndItemTags($entity, bool $purgeItem): void $this->addTagForItem($entity); } } catch (InvalidArgumentException $e) { + } catch (OperationNotFoundException $e) { } } @@ -155,6 +157,7 @@ private function addTagsFor($value): void private function addTagForItem($value): void { try { + //TODO: test if this is a resource class $iri = $this->iriConverter->getIriFromItem($value); $this->tags[$iri] = $iri; } catch (InvalidArgumentException $e) { diff --git a/src/Bridge/Doctrine/EventListener/WriteListener.php b/src/Core/Bridge/Doctrine/EventListener/WriteListener.php similarity index 100% rename from src/Bridge/Doctrine/EventListener/WriteListener.php rename to src/Core/Bridge/Doctrine/EventListener/WriteListener.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php b/src/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php similarity index 84% rename from src/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php index 2c0381e444f..298edf161eb 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php @@ -18,7 +18,7 @@ use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Exception\OperationNotFoundException; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\Persistence\ManagerRegistry; @@ -39,7 +39,7 @@ final class CollectionDataProvider implements CollectionDataProviderInterface, R /** * @param AggregationCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataFactoryInterface $resourceMetadataFactory, iterable $collectionExtensions = []) + public function __construct(ManagerRegistry $managerRegistry, $resourceMetadataFactory, iterable $collectionExtensions = []) { $this->managerRegistry = $managerRegistry; $this->resourceMetadataFactory = $resourceMetadataFactory; @@ -76,7 +76,12 @@ public function getCollection(string $resourceClass, string $operationName = nul } $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $resourceMetadata->getCollectionOperationAttribute($operationName, 'doctrine_mongodb', [], true); + try { + $operation = $context['operation'] ?? (isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName)); + $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; + } catch (OperationNotFoundException $e) { + $attribute = $resourceMetadata->getOperation(null, true)->getExtraProperties()['doctrine_mongodb'] ?? []; + } $executeOptions = $attribute['execute_options'] ?? []; return $aggregationBuilder->hydrate($resourceClass)->execute($executeOptions); diff --git a/src/Bridge/Doctrine/MongoDbOdm/Extension/AggregationCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationCollectionExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Extension/AggregationCollectionExtensionInterface.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationCollectionExtensionInterface.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Extension/AggregationItemExtensionInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationItemExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Extension/AggregationItemExtensionInterface.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationItemExtensionInterface.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultCollectionExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultCollectionExtensionInterface.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultCollectionExtensionInterface.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultItemExtensionInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultItemExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultItemExtensionInterface.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultItemExtensionInterface.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php similarity index 68% rename from src/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php index d0563f5db3b..f3751fabe01 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php @@ -17,6 +17,8 @@ use ApiPlatform\Core\Api\FilterLocatorTrait; use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\FilterInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Psr\Container\ContainerInterface; @@ -37,10 +39,14 @@ final class FilterExtension implements AggregationCollectionExtensionInterface /** * @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection */ - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator) + public function __construct($resourceMetadataFactory, $filterLocator) { $this->setFilterLocator($filterLocator); + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; } @@ -50,7 +56,12 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa public function applyToCollection(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) { $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); + try { + $operation = isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName); + $resourceFilters = $operation->getFilters(); + } catch (OperationNotFoundException $e) { + $resourceFilters = $resourceMetadata->getOperation(null, true)->getFilters(); + } if (empty($resourceFilters)) { return; diff --git a/src/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php similarity index 68% rename from src/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php index 561597a4599..aab68ed1070 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php @@ -16,6 +16,8 @@ use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\Persistence\ManagerRegistry; @@ -38,8 +40,12 @@ final class OrderExtension implements AggregationCollectionExtensionInterface private $resourceMetadataFactory; private $managerRegistry; - public function __construct(string $order = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null, ManagerRegistry $managerRegistry = null) + public function __construct(string $order = null, $resourceMetadataFactory = null, ManagerRegistry $managerRegistry = null) { + if ($resourceMetadataFactory && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->order = $order; $this->managerRegistry = $managerRegistry; @@ -53,12 +59,22 @@ public function applyToCollection(Builder $aggregationBuilder, string $resourceC $classMetaData = $this->getClassMetadata($resourceClass); $identifiers = $classMetaData->getIdentifier(); if (null !== $this->resourceMetadataFactory) { - $defaultOrder = $this->resourceMetadataFactory->create($resourceClass) - ->getCollectionOperationAttribute($operationName, 'order', [], true); - if (empty($defaultOrder)) { + if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + if (isset($context['operation'])) { + $defaultOrder = $context['operation']->getOrder(); + } else { + $metadata = $this->resourceMetadataFactory->create($resourceClass); + try { + $defaultOrder = isset($context['graphql_operation_name']) ? $metadata->getGraphQlOperation($operationName)->getOrder() : $metadata->getOperation($operationName)->getOrder(); + } catch (OperationNotFoundException $e) { + $defaultOrder = $metadata->getOperation(null, true)->getOrder(); + } + } + } else { $defaultOrder = $this->resourceMetadataFactory->create($resourceClass)->getAttribute('order'); } - if (null !== $defaultOrder) { + + if ($defaultOrder) { foreach ($defaultOrder as $field => $order) { if (\is_int($field)) { // Default direction diff --git a/src/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php similarity index 84% rename from src/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php index cb6dae92d66..d12628ff0c9 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php @@ -17,6 +17,8 @@ use ApiPlatform\Core\DataProvider\Pagination; use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; @@ -37,9 +39,14 @@ final class PaginationExtension implements AggregationResultCollectionExtensionI private $resourceMetadataFactory; private $pagination; - public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataFactoryInterface $resourceMetadataFactory, Pagination $pagination) + public function __construct(ManagerRegistry $managerRegistry, $resourceMetadataFactory, Pagination $pagination) { $this->managerRegistry = $managerRegistry; + + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->pagination = $pagination; } @@ -117,7 +124,12 @@ public function getResult(Builder $aggregationBuilder, string $resourceClass, st } $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $resourceMetadata->getCollectionOperationAttribute($operationName, 'doctrine_mongodb', [], true); + try { + $operation = $context['operation'] ?? (isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName)); + $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; + } catch (OperationNotFoundException $e) { + $attribute = $resourceMetadata->getOperation(null, true)->getExtraProperties()['doctrine_mongodb'] ?? []; + } $executeOptions = $attribute['execute_options'] ?? []; return new Paginator($aggregationBuilder->execute($executeOptions), $manager->getUnitOfWork(), $resourceClass, $aggregationBuilder->getPipeline()); diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php similarity index 92% rename from src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php index 8e3ad833508..ec785f96733 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php @@ -13,8 +13,9 @@ namespace ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter; +use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterInterface; use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterTrait; use ApiPlatform\Core\Exception\InvalidArgumentException; @@ -43,16 +44,20 @@ final class SearchFilter extends AbstractFilter implements SearchFilterInterface public const DOCTRINE_INTEGER_TYPE = [MongoDbType::INTEGER, MongoDbType::INT]; - public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, IdentifiersExtractorInterface $identifiersExtractor, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, $iriConverter, IdentifiersExtractorInterface $identifiersExtractor, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); + if ($iriConverter instanceof LegacyIriConverterInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class)); + } + $this->iriConverter = $iriConverter; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->identifiersExtractor = $identifiersExtractor; } - protected function getIriConverter(): IriConverterInterface + protected function getIriConverter() { return $this->iriConverter; } diff --git a/src/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php b/src/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php similarity index 81% rename from src/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php index adf95c289f6..6151356a440 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php @@ -23,6 +23,8 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\Persistence\ManagerRegistry; @@ -45,9 +47,14 @@ final class ItemDataProvider implements DenormalizedIdentifiersAwareItemDataProv /** * @param AggregationItemExtensionInterface[] $itemExtensions */ - public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $itemExtensions = []) + public function __construct(ManagerRegistry $managerRegistry, $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $itemExtensions = []) { $this->managerRegistry = $managerRegistry; + + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; @@ -99,7 +106,13 @@ public function getItem(string $resourceClass, $id, string $operationName = null } $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $resourceMetadata->getItemOperationAttribute($operationName, 'doctrine_mongodb', [], true); + + if ($resourceMetadata instanceof ResourceMetadataCollection) { + $attribute = $resourceMetadata->getOperation($operationName)->getExtraProperties()['doctrine_mongodb'] ?? []; + } else { + $attribute = $resourceMetadata->getItemOperationAttribute($operationName, 'doctrine_mongodb', [], true); + } + $executeOptions = $attribute['execute_options'] ?? []; return $aggregationBuilder->hydrate($resourceClass)->execute($executeOptions)->current() ?: null; diff --git a/src/Bridge/Doctrine/MongoDbOdm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/Paginator.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Paginator.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/Paginator.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/Paginator.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/PropertyHelperTrait.php b/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyHelperTrait.php similarity index 100% rename from src/Bridge/Doctrine/MongoDbOdm/PropertyHelperTrait.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/PropertyHelperTrait.php diff --git a/src/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php b/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php similarity index 99% rename from src/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php index 03d59416c4b..b305ca46fc6 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php @@ -63,7 +63,7 @@ public function getTypes($class, $property, array $context = []) } if ($metadata->hasAssociation($property)) { - /** @var ?string $class */ + /** @var class-string|null */ $class = $metadata->getAssociationTargetClass($property); if (null === $class) { diff --git a/src/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php b/src/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php similarity index 85% rename from src/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php rename to src/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php index 30c41749e7a..b7e70fc1934 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php +++ b/src/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php @@ -25,6 +25,8 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; @@ -52,9 +54,14 @@ final class SubresourceDataProvider implements SubresourceDataProviderInterface * @param AggregationCollectionExtensionInterface[] $collectionExtensions * @param AggregationItemExtensionInterface[] $itemExtensions */ - public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $collectionExtensions = [], iterable $itemExtensions = []) + public function __construct(ManagerRegistry $managerRegistry, $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $collectionExtensions = [], iterable $itemExtensions = []) { $this->managerRegistry = $managerRegistry; + + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; @@ -79,12 +86,23 @@ public function getSubresource(string $resourceClass, array $identifiers, array throw new RuntimeException(sprintf('The repository for "%s" must be an instance of "%s".', $resourceClass, DocumentRepository::class)); } + if (isset($context['identifiers'], $context['operation']) && !isset($context['property'])) { + $context['property'] = $context['operation']->getExtraProperties()['legacy_subresource_property'] ?? null; + $context['collection'] = $context['operation']->isCollection(); + } + if (!isset($context['identifiers'], $context['property'])) { throw new ResourceClassNotSupportedException('The given resource class is not a subresource.'); } $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $resourceMetadata->getSubresourceOperationAttribute($operationName, 'doctrine_mongodb', [], true); + try { + $operation = $context['operation'] ?? (isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName)); + $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; + } catch (OperationNotFoundException $e) { + $attribute = $resourceMetadata->getOperation()->getExtraProperties()['doctrine_mongodb'] ?? []; + } + $executeOptions = $attribute['execute_options'] ?? []; $aggregationBuilder = $this->buildAggregation($identifiers, $context, $executeOptions, $repository->createAggregationBuilder(), \count($context['identifiers'])); diff --git a/src/Bridge/Doctrine/Orm/AbstractPaginator.php b/src/Core/Bridge/Doctrine/Orm/AbstractPaginator.php similarity index 100% rename from src/Bridge/Doctrine/Orm/AbstractPaginator.php rename to src/Core/Bridge/Doctrine/Orm/AbstractPaginator.php diff --git a/src/Bridge/Doctrine/Orm/CollectionDataProvider.php b/src/Core/Bridge/Doctrine/Orm/CollectionDataProvider.php similarity index 100% rename from src/Bridge/Doctrine/Orm/CollectionDataProvider.php rename to src/Core/Bridge/Doctrine/Orm/CollectionDataProvider.php diff --git a/src/Bridge/Doctrine/Orm/Extension/ContextAwareQueryCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryCollectionExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Extension/ContextAwareQueryCollectionExtensionInterface.php rename to src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryCollectionExtensionInterface.php diff --git a/src/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultCollectionExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultCollectionExtensionInterface.php rename to src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultCollectionExtensionInterface.php diff --git a/src/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultItemExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultItemExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultItemExtensionInterface.php rename to src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultItemExtensionInterface.php diff --git a/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php similarity index 83% rename from src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php rename to src/Core/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php index 486cea24b1d..313ea75e691 100644 --- a/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php +++ b/src/Core/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php @@ -24,6 +24,8 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -54,7 +56,7 @@ final class EagerLoadingExtension implements ContextAwareQueryCollectionExtensio /** * @TODO move $fetchPartial after $forceEager (@soyuka) in 3.0 */ - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, int $maxJoins = 30, bool $forceEager = true, RequestStack $requestStack = null, SerializerContextBuilderInterface $serializerContextBuilder = null, bool $fetchPartial = false, ClassMetadataFactoryInterface $classMetadataFactory = null) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, $resourceMetadataFactory, int $maxJoins = 30, bool $forceEager = true, RequestStack $requestStack = null, SerializerContextBuilderInterface $serializerContextBuilder = null, bool $fetchPartial = false, ClassMetadataFactoryInterface $classMetadataFactory = null) { if (null !== $this->requestStack) { @trigger_error(sprintf('Passing an instance of "%s" is deprecated since version 2.2 and will be removed in 3.0. Use the data provider\'s context instead.', RequestStack::class), \E_USER_DEPRECATED); @@ -65,6 +67,11 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; + + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->classMetadataFactory = $classMetadataFactory; $this->maxJoins = $maxJoins; @@ -100,15 +107,37 @@ private function apply(bool $collection, QueryBuilder $queryBuilder, QueryNameGe $options = []; if (null !== $operationName) { + // TODO remove in 3.0 $options[($collection ? 'collection' : 'item').'_operation_name'] = $operationName; } - $forceEager = $this->shouldOperationForceEager($resourceClass, $options); - $fetchPartial = $this->shouldOperationFetchPartial($resourceClass, $options); + $operation = null; + $forceEager = $this->forceEager; + $fetchPartial = $this->fetchPartial; + if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass); + try { + $operation = isset($context['graphql_operation_name']) ? $resourceMetadataCollection->getGraphQlOperation($operationName) : $resourceMetadataCollection->getOperation($operationName); + $forceEager = $operation->getForceEager() ?? $this->forceEager; + $fetchPartial = $operation->getFetchPartial() ?? $this->fetchPartial; + } catch (OperationNotFoundException $e) { + // In some cases the operation may not exist + } + } else { + $forceEager = $this->shouldOperationForceEager($resourceClass, $options); + $fetchPartial = $this->shouldOperationFetchPartial($resourceClass, $options); + } if (!isset($context['groups']) && !isset($context['attributes'])) { $contextType = isset($context['api_denormalize']) ? 'denormalization_context' : 'normalization_context'; - $context += $this->getNormalizationContext($context['resource_class'] ?? $resourceClass, $contextType, $options); + if (null !== $this->requestStack && null !== $this->serializerContextBuilder && null !== $request = $this->requestStack->getCurrentRequest()) { + $context += $this->serializerContextBuilder->createFromRequest($request, 'normalization_context' === $contextType); + } elseif ($operation) { + $context += 'denormalization_context' === $contextType ? $operation->getDenormalizationContext() : $operation->getNormalizationContext(); + } elseif (!$this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + // TODO: remove in 3.0 + $context += $this->getNormalizationContext($context['resource_class'] ?? $resourceClass, $contextType, $options); + } } if (empty($context[AbstractNormalizer::GROUPS]) && !isset($context[AbstractNormalizer::ATTRIBUTES])) { @@ -119,6 +148,11 @@ private function apply(bool $collection, QueryBuilder $queryBuilder, QueryNameGe $options['serializer_groups'] = (array) $context[AbstractNormalizer::GROUPS]; } + if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + $options['normalization_groups'] = $operation ? ($operation->getNormalizationContext()['groups'] ?? null) : null; + $options['denormalization_groups'] = $operation ? ($operation->getDenormalizationContext()['groups'] ?? null) : null; + } + $this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $fetchPartial, $queryBuilder->getRootAliases()[0], $options, $context); } diff --git a/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php similarity index 79% rename from src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php rename to src/Core/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php index 32e144f4aad..6ae3bb4c9dd 100644 --- a/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php +++ b/src/Core/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php @@ -19,6 +19,10 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -32,8 +36,12 @@ final class FilterEagerLoadingExtension implements ContextAwareQueryCollectionEx private $resourceClassResolver; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, bool $forceEager = true, ResourceClassResolverInterface $resourceClassResolver = null) + public function __construct($resourceMetadataFactory, bool $forceEager = true, ResourceClassResolverInterface $resourceClassResolver = null) { + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->forceEager = $forceEager; $this->resourceClassResolver = $resourceClassResolver; @@ -50,9 +58,27 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $em = $queryBuilder->getEntityManager(); $classMetadata = $em->getClassMetadata($resourceClass); + /** @var Operation|GraphQlOperation|null */ + $operation = null; + $forceEager = $this->forceEager; + + if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass); + try { + $operation = isset($context['graphql_operation_name']) ? $resourceMetadataCollection->getGraphQlOperation($operationName) : $resourceMetadataCollection->getOperation($operationName); + $forceEager = $operation->getForceEager() ?? $this->forceEager; + } catch (OperationNotFoundException $e) { + // In some cases the operation may not exist + } - if (!$this->shouldOperationForceEager($resourceClass, ['collection_operation_name' => $operationName]) && !$this->hasFetchEagerAssociation($em, $classMetadata)) { - return; + if (!$forceEager && !$this->hasFetchEagerAssociation($em, $classMetadata)) { + return; + } + } else { + // TODO: remove in 3.0 + if (!$this->shouldOperationForceEager($resourceClass, ['collection_operation_name' => $operationName]) && !$this->hasFetchEagerAssociation($em, $classMetadata)) { + return; + } } //If no where part, nothing to do diff --git a/src/Bridge/Doctrine/Orm/Extension/FilterExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/FilterExtension.php similarity index 65% rename from src/Bridge/Doctrine/Orm/Extension/FilterExtension.php rename to src/Core/Bridge/Doctrine/Orm/Extension/FilterExtension.php index 152bcc0e188..fe4400c0f87 100644 --- a/src/Bridge/Doctrine/Orm/Extension/FilterExtension.php +++ b/src/Core/Bridge/Doctrine/Orm/Extension/FilterExtension.php @@ -20,6 +20,10 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Doctrine\ORM\QueryBuilder; use Psr\Container\ContainerInterface; @@ -38,10 +42,14 @@ final class FilterExtension implements ContextAwareQueryCollectionExtensionInter /** * @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection */ - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator) + public function __construct($resourceMetadataFactory, $filterLocator) { $this->setFilterLocator($filterLocator); + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; } @@ -54,8 +62,22 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); } + /** @var ResourceMetadata|ResourceMetadataCollection */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); + + if ($resourceMetadata instanceof ResourceMetadata) { + $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); + } else { + try { + $operation = $context['operation'] ?? (isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName)); + $resourceFilters = $operation->getFilters(); + } catch (OperationNotFoundException $e) { + // In some cases the operation may not exist + if (isset($context['graphql_operation_name'])) { + $resourceFilters = $resourceMetadata->getOperation(null, true)->getFilters(); + } + } + } if (empty($resourceFilters)) { return; diff --git a/src/Bridge/Doctrine/Orm/Extension/OrderExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/OrderExtension.php similarity index 66% rename from src/Bridge/Doctrine/Orm/Extension/OrderExtension.php rename to src/Core/Bridge/Doctrine/Orm/Extension/OrderExtension.php index b9486857dea..34959643a77 100644 --- a/src/Bridge/Doctrine/Orm/Extension/OrderExtension.php +++ b/src/Core/Bridge/Doctrine/Orm/Extension/OrderExtension.php @@ -17,6 +17,8 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ORM\QueryBuilder; /** @@ -31,9 +33,17 @@ final class OrderExtension implements ContextAwareQueryCollectionExtensionInterf private $order; private $resourceMetadataFactory; - public function __construct(string $order = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null) + /** + * @param ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory + */ + public function __construct(string $order = null, $resourceMetadataFactory = null) { $this->resourceMetadataFactory = $resourceMetadataFactory; + + if ($this->resourceMetadataFactory && $this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->order = $order; } @@ -51,12 +61,21 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $classMetaData = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass); $identifiers = $classMetaData->getIdentifier(); if (null !== $this->resourceMetadataFactory) { - $defaultOrder = $this->resourceMetadataFactory->create($resourceClass) - ->getCollectionOperationAttribute($operationName, 'order', [], true); - if (empty($defaultOrder)) { - $defaultOrder = $this->resourceMetadataFactory->create($resourceClass)->getAttribute('order'); + if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass); + try { + $defaultOrder = isset($context['graphql_operation_name']) ? $resourceMetadataCollection->getGraphQlOperation($operationName)->getOrder() : $resourceMetadataCollection->getOperation($operationName)->getOrder(); + } catch (OperationNotFoundException $e) { + // In some cases the operation may not exist + $defaultOrder = []; + } + } else { + // TODO: remove in 3.0 + $defaultOrder = $this->resourceMetadataFactory->create($resourceClass)->getCollectionOperationAttribute($operationName, 'order', [], true); } - if (null !== $defaultOrder) { + + // TODO: 3.0 default value is [] not null + if (null !== $defaultOrder && [] !== $defaultOrder) { foreach ($defaultOrder as $field => $order) { if (\is_int($field)) { // Default direction diff --git a/src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/PaginationExtension.php similarity index 68% rename from src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php rename to src/Core/Bridge/Doctrine/Orm/Extension/PaginationExtension.php index 39d88c78c3d..b8f00e99b19 100644 --- a/src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php +++ b/src/Core/Bridge/Doctrine/Orm/Extension/PaginationExtension.php @@ -21,6 +21,9 @@ use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Tools\Pagination\CountWalker; use Doctrine\ORM\Tools\Pagination\Paginator as DoctrineOrmPaginator; @@ -42,7 +45,7 @@ final class PaginationExtension implements ContextAwareQueryResultCollectionExte private $managerRegistry; private $requestStack; /** - * @var ResourceMetadataFactoryInterface + * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface */ private $resourceMetadataFactory; private $enabled; @@ -65,7 +68,7 @@ final class PaginationExtension implements ContextAwareQueryResultCollectionExte * @param ResourceMetadataFactoryInterface|RequestStack $resourceMetadataFactory * @param Pagination|ResourceMetadataFactoryInterface $pagination */ - public function __construct(ManagerRegistry $managerRegistry, /* ResourceMetadataFactoryInterface */ $resourceMetadataFactory, /* Pagination */ $pagination) + public function __construct(ManagerRegistry $managerRegistry, /* ResourceMetadataCollectionFactoryInterface */ $resourceMetadataFactory, /* Pagination */ $pagination) { if ($resourceMetadataFactory instanceof RequestStack && $pagination instanceof ResourceMetadataFactoryInterface) { @trigger_error(sprintf('Passing an instance of "%s" as second argument of "%s" is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "%s" instead.', RequestStack::class, self::class, ResourceMetadataFactoryInterface::class), \E_USER_DEPRECATED); @@ -105,7 +108,7 @@ public function __construct(ManagerRegistry $managerRegistry, /* ResourceMetadat $this->{$arg['arg_name']} = $value; } - } elseif (!$resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { + } elseif (!$resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { throw new InvalidArgumentException(sprintf('The "$resourceMetadataFactory" argument is expected to be an implementation of the "%s" interface.', ResourceMetadataFactoryInterface::class)); } elseif (!$pagination instanceof Pagination) { throw new InvalidArgumentException(sprintf('The "$pagination" argument is expected to be an instance of the "%s" class.', Pagination::class)); @@ -209,28 +212,53 @@ private function getPagination(QueryBuilder $queryBuilder, string $resourceClass return \array_slice($this->pagination->getPagination($resourceClass, $operationName, $context), 1); } + /** + * @var ResourceMetadata|ResourceMetadataCollection + */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); if (!$this->isPaginationEnabled($request, $resourceMetadata, $operationName)) { return null; } - $itemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_items_per_page', $this->itemsPerPage, true); - if ($request->attributes->getBoolean('_graphql', false)) { - $collectionArgs = $request->attributes->get('_graphql_collections_args', []); - $itemsPerPage = $collectionArgs[$resourceClass]['first'] ?? $itemsPerPage; - } + $itemsPerPage = $this->itemsPerPage; + $maxItemsPerPage = $this->maximumItemPerPage; - if ($resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $this->clientItemsPerPage, true)) { - $maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', null, true); - - if (null !== $maxItemsPerPage) { - @trigger_error('The "maximum_items_per_page" option has been deprecated since API Platform 2.5 in favor of "pagination_maximum_items_per_page" and will be removed in API Platform 3.', \E_USER_DEPRECATED); + // TODO: remove in 3.0 + if ($resourceMetadata instanceof ResourceMetadata) { + $itemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_items_per_page', $this->itemsPerPage, true); + if ($request->attributes->getBoolean('_graphql', false)) { + $collectionArgs = $request->attributes->get('_graphql_collections_args', []); + $itemsPerPage = $collectionArgs[$resourceClass]['first'] ?? $itemsPerPage; } - $maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_maximum_items_per_page', $maxItemsPerPage ?? $this->maximumItemPerPage, true); + if ($resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $this->clientItemsPerPage, true)) { + $maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'maximum_items_per_page', null, true); + + if (null !== $maxItemsPerPage) { + @trigger_error('The "maximum_items_per_page" option has been deprecated since API Platform 2.5 in favor of "pagination_maximum_items_per_page" and will be removed in API Platform 3.', \E_USER_DEPRECATED); + } + + $maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_maximum_items_per_page', $maxItemsPerPage ?? $this->maximumItemPerPage, true); + $itemsPerPage = (int) $this->getPaginationParameter($request, $this->itemsPerPageParameterName, $itemsPerPage); + $itemsPerPage = (null !== $maxItemsPerPage && $itemsPerPage >= $maxItemsPerPage ? $maxItemsPerPage : $itemsPerPage); + } + } elseif ($resourceMetadata instanceof ResourceMetadataCollection) { + try { + $operation = isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName); + $itemsPerPage = $operation->getPaginationItemsPerPage(); + if ($operation->getPaginationClientItemsPerPage()) { + $maxItemsPerPage = $operation->getPaginationMaximumItemsPerPage() ?? $this->maximumItemPerPage; + $itemsPerPage = (int) $this->getPaginationParameter($request, $this->itemsPerPageParameterName, $itemsPerPage); + $itemsPerPage = (null !== $maxItemsPerPage && $itemsPerPage >= $maxItemsPerPage ? $maxItemsPerPage : $itemsPerPage); + } + } catch (OperationNotFoundException $e) { + // In some cases the operation may not exist + } - $itemsPerPage = (int) $this->getPaginationParameter($request, $this->itemsPerPageParameterName, $itemsPerPage); - $itemsPerPage = (null !== $maxItemsPerPage && $itemsPerPage >= $maxItemsPerPage ? $maxItemsPerPage : $itemsPerPage); + if ($request->attributes->getBoolean('_graphql', false)) { + $collectionArgs = $request->attributes->get('_graphql_collections_args', []); + $itemsPerPage = $collectionArgs[$resourceClass]['first'] ?? $itemsPerPage; + } } if (0 > $itemsPerPage) { @@ -260,17 +288,24 @@ private function getPagination(QueryBuilder $queryBuilder, string $resourceClass return [$firstResult, $itemsPerPage]; } - private function isPartialPaginationEnabled(Request $request = null, ResourceMetadata $resourceMetadata = null, string $operationName = null): bool + /** + * @param ResourceMetadata|ResourceMetadataCollection $resourceMetadata + */ + private function isPartialPaginationEnabled(Request $request = null, $resourceMetadata = null, string $operationName = null): bool { $enabled = $this->partial; $clientEnabled = $this->clientPartial; - if ($resourceMetadata) { + if ($resourceMetadata instanceof ResourceMetadata) { $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_partial', $enabled, true); if ($request) { $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_partial', $clientEnabled, true); } + } elseif ($resourceMetadata instanceof ResourceMetadataCollection) { + $operation = $resourceMetadata->getOperation($operationName); + $enabled = $operation->getPaginationPartial() ?? $enabled; + $clientEnabled = $operation->getPaginationClientPartial() ?? $clientEnabled; } if ($clientEnabled && $request) { @@ -280,10 +315,21 @@ private function isPartialPaginationEnabled(Request $request = null, ResourceMet return $enabled; } - private function isPaginationEnabled(Request $request, ResourceMetadata $resourceMetadata, string $operationName = null): bool + /** + * @param ResourceMetadata|ResourceMetadataCollection $resourceMetadata + */ + private function isPaginationEnabled(Request $request, $resourceMetadata, string $operationName = null): bool { - $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_enabled', $this->enabled, true); - $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_enabled', $this->clientEnabled, true); + $clientEnabled = false; + $enabled = false; + if ($resourceMetadata instanceof ResourceMetadata) { + $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_enabled', $this->enabled, true); + $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_enabled', $this->clientEnabled, true); + } elseif ($resourceMetadata instanceof ResourceMetadataCollection) { + $operation = $resourceMetadata->getOperation($operationName); + $enabled = $operation->getPaginationEnabled(); + $clientEnabled = $operation->getPaginationClientEnabled(); + } if ($clientEnabled) { $enabled = filter_var($this->getPaginationParameter($request, $this->enabledParameterName, $enabled), \FILTER_VALIDATE_BOOLEAN); @@ -320,13 +366,32 @@ private function addCountToContext(QueryBuilder $queryBuilder, array $context): private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = []): bool { if (null !== $resourceClass) { + /** + * @var ResourceMetadata|ResourceMetadataCollection + */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (isset($context['collection_operation_name']) && null !== $fetchJoinCollection = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_fetch_join_collection', null, true)) { + if ($resourceMetadata instanceof ResourceMetadata) { + $fetchJoinCollection = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_fetch_join_collection', null, true); + } elseif ($resourceMetadata instanceof ResourceMetadataCollection) { + try { + $operation = isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName); + $fetchJoinCollection = $operation->getPaginationFetchJoinCollection(); + } catch (OperationNotFoundException $e) { + // In some cases the operation may not exist + $fetchJoinCollection = null; + } + } + + if ((isset($context['collection_operation_name']) || isset($context['operation_name'])) && isset($fetchJoinCollection)) { return $fetchJoinCollection; } - if (isset($context['graphql_operation_name']) && null !== $fetchJoinCollection = $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_fetch_join_collection', null, true)) { + if ($resourceMetadata instanceof ResourceMetadata) { + $fetchJoinCollection = $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_fetch_join_collection', null, true); + } + + if (isset($context['graphql_operation_name']) && isset($fetchJoinCollection)) { return $fetchJoinCollection; } } @@ -355,13 +420,31 @@ private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryB private function shouldDoctrinePaginatorUseOutputWalkers(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = []): bool { if (null !== $resourceClass) { + /** + * @var ResourceMetadata|ResourceMetadataCollection + */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (isset($context['collection_operation_name']) && null !== $useOutputWalkers = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_use_output_walkers', null, true)) { + if ($resourceMetadata instanceof ResourceMetadata) { + $useOutputWalkers = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_use_output_walkers', null, true); + } elseif ($resourceMetadata instanceof ResourceMetadataCollection) { + try { + $operation = isset($context['graphql_operation_name']) ? $resourceMetadata->getGraphQlOperation($operationName) : $resourceMetadata->getOperation($operationName); + $useOutputWalkers = $operation->getPaginationUseOutputWalkers(); + } catch (OperationNotFoundException $e) { + // In some cases the operation may not exist + } + } + + if ((isset($context['collection_operation_name']) || isset($context['operation_name'])) && isset($useOutputWalkers)) { return $useOutputWalkers; } - if (isset($context['graphql_operation_name']) && null !== $useOutputWalkers = $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_use_output_walkers', null, true)) { + if ($resourceMetadata instanceof ResourceMetadata) { + $useOutputWalkers = $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_use_output_walkers', null, true); + } + + if (isset($context['graphql_operation_name']) && isset($useOutputWalkers)) { return $useOutputWalkers; } } diff --git a/src/Bridge/Doctrine/Orm/Extension/QueryCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/QueryCollectionExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Extension/QueryCollectionExtensionInterface.php rename to src/Core/Bridge/Doctrine/Orm/Extension/QueryCollectionExtensionInterface.php diff --git a/src/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php rename to src/Core/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php diff --git a/src/Bridge/Doctrine/Orm/Extension/QueryResultCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/QueryResultCollectionExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Extension/QueryResultCollectionExtensionInterface.php rename to src/Core/Bridge/Doctrine/Orm/Extension/QueryResultCollectionExtensionInterface.php diff --git a/src/Bridge/Doctrine/Orm/Extension/QueryResultItemExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/QueryResultItemExtensionInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Extension/QueryResultItemExtensionInterface.php rename to src/Core/Bridge/Doctrine/Orm/Extension/QueryResultItemExtensionInterface.php diff --git a/src/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php diff --git a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/AbstractFilter.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/AbstractFilter.php diff --git a/src/Bridge/Doctrine/Orm/Filter/BooleanFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/BooleanFilter.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/BooleanFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/BooleanFilter.php diff --git a/src/Bridge/Doctrine/Orm/Filter/ContextAwareFilterInterface.php b/src/Core/Bridge/Doctrine/Orm/Filter/ContextAwareFilterInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/ContextAwareFilterInterface.php rename to src/Core/Bridge/Doctrine/Orm/Filter/ContextAwareFilterInterface.php diff --git a/src/Bridge/Doctrine/Orm/Filter/DateFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/DateFilter.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/DateFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/DateFilter.php diff --git a/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/ExistsFilter.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/ExistsFilter.php diff --git a/src/Bridge/Doctrine/Orm/Filter/FilterInterface.php b/src/Core/Bridge/Doctrine/Orm/Filter/FilterInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/FilterInterface.php rename to src/Core/Bridge/Doctrine/Orm/Filter/FilterInterface.php diff --git a/src/Bridge/Doctrine/Orm/Filter/NumericFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/NumericFilter.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/NumericFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/NumericFilter.php diff --git a/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/OrderFilter.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/OrderFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/OrderFilter.php diff --git a/src/Bridge/Doctrine/Orm/Filter/RangeFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Filter/RangeFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php diff --git a/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/SearchFilter.php similarity index 93% rename from src/Bridge/Doctrine/Orm/Filter/SearchFilter.php rename to src/Core/Bridge/Doctrine/Orm/Filter/SearchFilter.php index 92a02ae8d55..d89bbf9eac2 100644 --- a/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php +++ b/src/Core/Bridge/Doctrine/Orm/Filter/SearchFilter.php @@ -13,8 +13,9 @@ namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Filter; +use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterInterface; use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterTrait; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper; @@ -42,7 +43,7 @@ class SearchFilter extends AbstractContextAwareFilter implements SearchFilterInt public const DOCTRINE_INTEGER_TYPE = DBALType::INTEGER; - public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, IdentifiersExtractorInterface $identifiersExtractor = null, NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack, $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, IdentifiersExtractorInterface $identifiersExtractor = null, NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter); @@ -50,12 +51,16 @@ public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $req @trigger_error('Not injecting ItemIdentifiersExtractor is deprecated since API Platform 2.5 and can lead to unexpected behaviors, it will not be possible anymore in API Platform 3.0.', \E_USER_DEPRECATED); } + if ($iriConverter instanceof LegacyIriConverterInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class)); + } + $this->iriConverter = $iriConverter; $this->identifiersExtractor = $identifiersExtractor; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } - protected function getIriConverter(): IriConverterInterface + protected function getIriConverter() { return $this->iriConverter; } diff --git a/src/Bridge/Doctrine/Orm/ItemDataProvider.php b/src/Core/Bridge/Doctrine/Orm/ItemDataProvider.php similarity index 89% rename from src/Bridge/Doctrine/Orm/ItemDataProvider.php rename to src/Core/Bridge/Doctrine/Orm/ItemDataProvider.php index e36cd8c79c8..d96bfa34ff6 100644 --- a/src/Bridge/Doctrine/Orm/ItemDataProvider.php +++ b/src/Core/Bridge/Doctrine/Orm/ItemDataProvider.php @@ -24,6 +24,7 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; @@ -44,13 +45,19 @@ class ItemDataProvider implements DenormalizedIdentifiersAwareItemDataProviderIn private $itemExtensions; /** - * @param QueryItemExtensionInterface[] $itemExtensions + * @param QueryItemExtensionInterface[] $itemExtensions + * @param ResourceMetadataCollectionFactoryInterface|null $resourceMetadataFactory */ - public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $itemExtensions = [], ResourceMetadataFactoryInterface $resourceMetadataFactory = null) + public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $itemExtensions = [], $resourceMetadataFactory = null) { $this->managerRegistry = $managerRegistry; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; + + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->itemExtensions = $itemExtensions; } diff --git a/src/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php b/src/Core/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php rename to src/Core/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php diff --git a/src/Bridge/Doctrine/Orm/Paginator.php b/src/Core/Bridge/Doctrine/Orm/Paginator.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Paginator.php rename to src/Core/Bridge/Doctrine/Orm/Paginator.php diff --git a/src/Bridge/Doctrine/Orm/PropertyHelperTrait.php b/src/Core/Bridge/Doctrine/Orm/PropertyHelperTrait.php similarity index 100% rename from src/Bridge/Doctrine/Orm/PropertyHelperTrait.php rename to src/Core/Bridge/Doctrine/Orm/PropertyHelperTrait.php diff --git a/src/Bridge/Doctrine/Orm/QueryAwareInterface.php b/src/Core/Bridge/Doctrine/Orm/QueryAwareInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/QueryAwareInterface.php rename to src/Core/Bridge/Doctrine/Orm/QueryAwareInterface.php diff --git a/src/Bridge/Doctrine/Orm/SubresourceDataProvider.php b/src/Core/Bridge/Doctrine/Orm/SubresourceDataProvider.php similarity index 92% rename from src/Bridge/Doctrine/Orm/SubresourceDataProvider.php rename to src/Core/Bridge/Doctrine/Orm/SubresourceDataProvider.php index 264cf65d84c..bf211d007a9 100644 --- a/src/Bridge/Doctrine/Orm/SubresourceDataProvider.php +++ b/src/Core/Bridge/Doctrine/Orm/SubresourceDataProvider.php @@ -27,6 +27,7 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\QueryBuilder; @@ -46,14 +47,20 @@ final class SubresourceDataProvider implements SubresourceDataProviderInterface private $itemExtensions; /** - * @param QueryCollectionExtensionInterface[] $collectionExtensions - * @param QueryItemExtensionInterface[] $itemExtensions + * @param QueryCollectionExtensionInterface[] $collectionExtensions + * @param QueryItemExtensionInterface[] $itemExtensions + * @param ResourceMetadataCollectionFactoryInterface|null $resourceMetadataFactory */ - public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $collectionExtensions = [], iterable $itemExtensions = [], ResourceMetadataFactoryInterface $resourceMetadataFactory = null) + public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $collectionExtensions = [], iterable $itemExtensions = [], $resourceMetadataFactory = null) { $this->managerRegistry = $managerRegistry; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; + + if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } + $this->resourceMetadataFactory = $resourceMetadataFactory; $this->collectionExtensions = $collectionExtensions; $this->itemExtensions = $itemExtensions; @@ -76,6 +83,11 @@ public function getSubresource(string $resourceClass, array $identifiers, array throw new RuntimeException('The repository class must have a "createQueryBuilder" method.'); } + if (isset($context['identifiers'], $context['operation']) && !isset($context['property'])) { + $context['property'] = $context['operation']->getExtraProperties()['legacy_subresource_property'] ?? null; + $context['collection'] = $context['operation']->isCollection(); + } + if (!isset($context['identifiers'], $context['property'])) { throw new ResourceClassNotSupportedException('The given resource class is not a subresource.'); } @@ -160,7 +172,6 @@ private function buildQuery(array $identifiers, array $context, QueryNameGenerat $normalizedIdentifiers = []; if (isset($identifiers[$identifier])) { - // if it's an array it's already normalized, the IdentifierManagerTrait is deprecated if ($context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] ?? false) { $normalizedIdentifiers = $identifiers[$identifier]; } else { diff --git a/src/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php b/src/Core/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php similarity index 80% rename from src/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php rename to src/Core/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php index 5c93c1fae03..dfa78f30eb0 100644 --- a/src/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php +++ b/src/Core/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php @@ -13,18 +13,27 @@ namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Util; +use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; /** * @author Antoine Bluchet * + * TODO: remove in 3.0. + * * @internal */ trait EagerLoadingTrait { private $forceEager; private $fetchPartial; + + /** + * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface + */ private $resourceMetadataFactory; /** @@ -32,6 +41,10 @@ trait EagerLoadingTrait */ private function shouldOperationForceEager(string $resourceClass, array $options): bool { + if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + throw new RuntimeException('Method should not be called.'); + } + return $this->getBooleanOperationAttribute($resourceClass, $options, 'force_eager', $this->forceEager); } @@ -40,6 +53,10 @@ private function shouldOperationForceEager(string $resourceClass, array $options */ private function shouldOperationFetchPartial(string $resourceClass, array $options): bool { + if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + throw new RuntimeException('Method should not be called.'); + } + return $this->getBooleanOperationAttribute($resourceClass, $options, 'fetch_partial', $this->fetchPartial); } diff --git a/src/Bridge/Doctrine/Orm/Util/QueryBuilderHelper.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryBuilderHelper.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Util/QueryBuilderHelper.php rename to src/Core/Bridge/Doctrine/Orm/Util/QueryBuilderHelper.php diff --git a/src/Bridge/Doctrine/Orm/Util/QueryChecker.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryChecker.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Util/QueryChecker.php rename to src/Core/Bridge/Doctrine/Orm/Util/QueryChecker.php diff --git a/src/Bridge/Doctrine/Orm/Util/QueryJoinParser.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryJoinParser.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Util/QueryJoinParser.php rename to src/Core/Bridge/Doctrine/Orm/Util/QueryJoinParser.php diff --git a/src/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php rename to src/Core/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php diff --git a/src/Bridge/Doctrine/Orm/Util/QueryNameGeneratorInterface.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryNameGeneratorInterface.php similarity index 100% rename from src/Bridge/Doctrine/Orm/Util/QueryNameGeneratorInterface.php rename to src/Core/Bridge/Doctrine/Orm/Util/QueryNameGeneratorInterface.php diff --git a/src/Bridge/Elasticsearch/Api/IdentifierExtractor.php b/src/Core/Bridge/Elasticsearch/Api/IdentifierExtractor.php similarity index 100% rename from src/Bridge/Elasticsearch/Api/IdentifierExtractor.php rename to src/Core/Bridge/Elasticsearch/Api/IdentifierExtractor.php diff --git a/src/Bridge/Elasticsearch/Api/IdentifierExtractorInterface.php b/src/Core/Bridge/Elasticsearch/Api/IdentifierExtractorInterface.php similarity index 100% rename from src/Bridge/Elasticsearch/Api/IdentifierExtractorInterface.php rename to src/Core/Bridge/Elasticsearch/Api/IdentifierExtractorInterface.php diff --git a/src/Bridge/Elasticsearch/DataProvider/CollectionDataProvider.php b/src/Core/Bridge/Elasticsearch/DataProvider/CollectionDataProvider.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/CollectionDataProvider.php rename to src/Core/Bridge/Elasticsearch/DataProvider/CollectionDataProvider.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Extension/RequestBodySearchCollectionExtensionInterface.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/RequestBodySearchCollectionExtensionInterface.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Extension/RequestBodySearchCollectionExtensionInterface.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Extension/RequestBodySearchCollectionExtensionInterface.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php similarity index 99% rename from src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php index 6596dd6eeba..6fe29b2d262 100644 --- a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php +++ b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter; -use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/ConstantScoreFilterInterface.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/ConstantScoreFilterInterface.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Filter/ConstantScoreFilterInterface.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Filter/ConstantScoreFilterInterface.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/MatchFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/MatchFilter.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Filter/MatchFilter.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Filter/MatchFilter.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/SortFilterInterface.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/SortFilterInterface.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Filter/SortFilterInterface.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Filter/SortFilterInterface.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/TermFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/TermFilter.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Filter/TermFilter.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Filter/TermFilter.php diff --git a/src/Bridge/Elasticsearch/DataProvider/ItemDataProvider.php b/src/Core/Bridge/Elasticsearch/DataProvider/ItemDataProvider.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/ItemDataProvider.php rename to src/Core/Bridge/Elasticsearch/DataProvider/ItemDataProvider.php diff --git a/src/Bridge/Elasticsearch/DataProvider/Paginator.php b/src/Core/Bridge/Elasticsearch/DataProvider/Paginator.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/Paginator.php rename to src/Core/Bridge/Elasticsearch/DataProvider/Paginator.php diff --git a/src/Bridge/Elasticsearch/DataProvider/SubresourceDataProvider.php b/src/Core/Bridge/Elasticsearch/DataProvider/SubresourceDataProvider.php similarity index 100% rename from src/Bridge/Elasticsearch/DataProvider/SubresourceDataProvider.php rename to src/Core/Bridge/Elasticsearch/DataProvider/SubresourceDataProvider.php diff --git a/src/Bridge/Elasticsearch/Exception/IndexNotFoundException.php b/src/Core/Bridge/Elasticsearch/Exception/IndexNotFoundException.php similarity index 100% rename from src/Bridge/Elasticsearch/Exception/IndexNotFoundException.php rename to src/Core/Bridge/Elasticsearch/Exception/IndexNotFoundException.php diff --git a/src/Bridge/Elasticsearch/Exception/NonUniqueIdentifierException.php b/src/Core/Bridge/Elasticsearch/Exception/NonUniqueIdentifierException.php similarity index 100% rename from src/Bridge/Elasticsearch/Exception/NonUniqueIdentifierException.php rename to src/Core/Bridge/Elasticsearch/Exception/NonUniqueIdentifierException.php diff --git a/src/Bridge/Elasticsearch/Metadata/Document/DocumentMetadata.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/DocumentMetadata.php similarity index 100% rename from src/Bridge/Elasticsearch/Metadata/Document/DocumentMetadata.php rename to src/Core/Bridge/Elasticsearch/Metadata/Document/DocumentMetadata.php diff --git a/src/Bridge/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php similarity index 100% rename from src/Bridge/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php rename to src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php diff --git a/src/Bridge/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php similarity index 100% rename from src/Bridge/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php rename to src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php diff --git a/src/Bridge/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php similarity index 100% rename from src/Bridge/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php rename to src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php diff --git a/src/Bridge/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php similarity index 100% rename from src/Bridge/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php rename to src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php diff --git a/src/Bridge/Elasticsearch/Metadata/Document/Factory/DocumentMetadataFactoryInterface.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/DocumentMetadataFactoryInterface.php similarity index 100% rename from src/Bridge/Elasticsearch/Metadata/Document/Factory/DocumentMetadataFactoryInterface.php rename to src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/DocumentMetadataFactoryInterface.php diff --git a/src/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactory.php similarity index 100% rename from src/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactory.php rename to src/Core/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactory.php diff --git a/src/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php b/src/Core/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php similarity index 100% rename from src/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php rename to src/Core/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php diff --git a/src/Bridge/Elasticsearch/Serializer/ItemNormalizer.php b/src/Core/Bridge/Elasticsearch/Serializer/ItemNormalizer.php similarity index 100% rename from src/Bridge/Elasticsearch/Serializer/ItemNormalizer.php rename to src/Core/Bridge/Elasticsearch/Serializer/ItemNormalizer.php diff --git a/src/Bridge/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php b/src/Core/Bridge/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php similarity index 100% rename from src/Bridge/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php rename to src/Core/Bridge/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php diff --git a/src/Bridge/Elasticsearch/Util/FieldDatatypeTrait.php b/src/Core/Bridge/Elasticsearch/Util/FieldDatatypeTrait.php similarity index 100% rename from src/Bridge/Elasticsearch/Util/FieldDatatypeTrait.php rename to src/Core/Bridge/Elasticsearch/Util/FieldDatatypeTrait.php diff --git a/src/Bridge/FosUser/EventListener.php b/src/Core/Bridge/FosUser/EventListener.php similarity index 100% rename from src/Bridge/FosUser/EventListener.php rename to src/Core/Bridge/FosUser/EventListener.php diff --git a/src/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php b/src/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php new file mode 100644 index 00000000000..ede9ae48dfd --- /dev/null +++ b/src/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider; + +use ApiPlatform\Core\Api\FilterCollection; +use ApiPlatform\Core\Api\FilterLocatorTrait; +use ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser; +use ApiPlatform\Core\Bridge\Symfony\Routing\OperationMethodResolverInterface; +use ApiPlatform\Core\Documentation\Documentation; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use Nelmio\ApiDocBundle\Annotation\ApiDoc; +use Nelmio\ApiDocBundle\Extractor\AnnotationsProviderInterface; +use Psr\Container\ContainerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +if (interface_exists(AnnotationsProviderInterface::class)) { + /** + * Creates Nelmio ApiDoc annotations for the api platform. + * + * @author Kévin Dunglas + * @author Teoh Han Hui + * + * @deprecated since version 2.2, to be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. + */ + final class ApiPlatformProvider implements AnnotationsProviderInterface + { + use FilterLocatorTrait; + + private $resourceNameCollectionFactory; + private $documentationNormalizer; + private $resourceMetadataFactory; + private $operationMethodResolver; + + /** + * @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection + */ + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, NormalizerInterface $documentationNormalizer, ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator, OperationMethodResolverInterface $operationMethodResolver) + { + @trigger_error('The '.__CLASS__.' class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.', \E_USER_DEPRECATED); + + $this->setFilterLocator($filterLocator); + + $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; + $this->documentationNormalizer = $documentationNormalizer; + $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->operationMethodResolver = $operationMethodResolver; + } + + /** + * {@inheritdoc} + */ + public function getAnnotations(): array + { + $resourceNameCollection = $this->resourceNameCollectionFactory->create(); + + $hydraDoc = $this->documentationNormalizer->normalize(new Documentation($resourceNameCollection)); + if (!\is_array($hydraDoc)) { + throw new \UnexpectedValueException('Expected data to be an array'); + } + + if (empty($hydraDoc)) { + return []; + } + + $entrypointHydraDoc = $this->getResourceHydraDoc($hydraDoc, '#Entrypoint'); + if (null === $entrypointHydraDoc) { + return []; + } + + $annotations = []; + foreach ($resourceNameCollection as $resourceClass) { + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + + $prefixedShortName = ($iri = $resourceMetadata->getIri()) ? $iri : '#'.$resourceMetadata->getShortName(); + $resourceHydraDoc = $this->getResourceHydraDoc($hydraDoc, $prefixedShortName); + if (null === $resourceHydraDoc) { + continue; + } + + if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) { + foreach ($collectionOperations as $operationName => $operation) { + $annotations[] = $this->getApiDoc(true, $resourceClass, $resourceMetadata, $operationName, $resourceHydraDoc, $entrypointHydraDoc); + } + } + + if (null !== $itemOperations = $resourceMetadata->getItemOperations()) { + foreach ($itemOperations as $operationName => $operation) { + $annotations[] = $this->getApiDoc(false, $resourceClass, $resourceMetadata, $operationName, $resourceHydraDoc); + } + } + } + + return $annotations; + } + + /** + * Builds ApiDoc annotation from ApiPlatform data. + */ + private function getApiDoc(bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $resourceHydraDoc, array $entrypointHydraDoc = []): ApiDoc + { + if ($collection) { + $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); + $route = $this->operationMethodResolver->getCollectionOperationRoute($resourceClass, $operationName); + $operationHydraDoc = $this->getCollectionOperationHydraDoc($resourceMetadata->getShortName(), $method, $entrypointHydraDoc); + } else { + $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); + $route = $this->operationMethodResolver->getItemOperationRoute($resourceClass, $operationName); + $operationHydraDoc = $this->getOperationHydraDoc($method, $resourceHydraDoc); + } + + $data = [ + 'resource' => $route->getPath(), + 'description' => $operationHydraDoc['hydra:title'] ?? '', + 'resourceDescription' => $resourceHydraDoc['hydra:title'] ?? '', + 'section' => $resourceHydraDoc['hydra:title'] ?? '', + ]; + + if (isset($operationHydraDoc['expects']) && 'owl:Nothing' !== $operationHydraDoc['expects']) { + $data['input'] = sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, $resourceClass, $operationName); + } + + if (isset($operationHydraDoc['returns']) && 'owl:Nothing' !== $operationHydraDoc['returns']) { + $data['output'] = sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, $resourceClass, $operationName); + } + + if ($collection && 'GET' === $method) { + $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); + + $data['filters'] = []; + foreach ($resourceFilters as $filterId) { + if ($filter = $this->getFilter($filterId)) { + foreach ($filter->getDescription($resourceClass) as $name => $definition) { + $data['filters'][] = ['name' => $name] + $definition; + } + } + } + } + + $apiDoc = new ApiDoc($data); + $apiDoc->setRoute($route); + + return $apiDoc; + } + + /** + * Gets Hydra documentation for the given resource. + */ + private function getResourceHydraDoc(array $hydraApiDoc, string $prefixedShortName): ?array + { + if (!isset($hydraApiDoc['hydra:supportedClass']) || !\is_array($hydraApiDoc['hydra:supportedClass'])) { + return null; + } + + foreach ($hydraApiDoc['hydra:supportedClass'] as $supportedClass) { + if (isset($supportedClass['@id']) && $supportedClass['@id'] === $prefixedShortName) { + return $supportedClass; + } + } + + return null; + } + + /** + * Gets the Hydra documentation of a given operation. + */ + private function getOperationHydraDoc(string $method, array $hydraDoc): array + { + if (!isset($hydraDoc['hydra:supportedOperation']) || !\is_array($hydraDoc['hydra:supportedOperation'])) { + return []; + } + + foreach ($hydraDoc['hydra:supportedOperation'] as $supportedOperation) { + if ($supportedOperation['hydra:method'] === $method) { + return $supportedOperation; + } + } + + return []; + } + + /** + * Gets the Hydra documentation for the collection operation. + */ + private function getCollectionOperationHydraDoc(string $shortName, string $method, array $hydraEntrypointDoc): array + { + if (!isset($hydraEntrypointDoc['hydra:supportedProperty']) || !\is_array($hydraEntrypointDoc['hydra:supportedProperty'])) { + return []; + } + + $propertyName = '#Entrypoint/'.lcfirst($shortName); + + foreach ($hydraEntrypointDoc['hydra:supportedProperty'] as $supportedProperty) { + if (isset($supportedProperty['hydra:property']['@id']) + && $supportedProperty['hydra:property']['@id'] === $propertyName) { + return $this->getOperationHydraDoc($method, $supportedProperty['hydra:property']); + } + } + + return []; + } + } +} diff --git a/src/Core/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php b/src/Core/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php new file mode 100644 index 00000000000..fbfc02bfa7d --- /dev/null +++ b/src/Core/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Bridge\NelmioApiDoc\Parser; + +use ApiPlatform\Core\Exception\ResourceClassNotFoundException; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Property\PropertyMetadata; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use Nelmio\ApiDocBundle\DataTypes; +use Nelmio\ApiDocBundle\Parser\ParserInterface; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + +if (interface_exists(ParserInterface::class)) { + /** + * Extract input and output information for the NelmioApiDocBundle. + * + * @author Kévin Dunglas + * @author Teoh Han Hui + * + * @deprecated since version 2.2, to be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. + */ + final class ApiPlatformParser implements ParserInterface + { + public const IN_PREFIX = 'api_platform_in'; + public const OUT_PREFIX = 'api_platform_out'; + public const TYPE_IRI = 'IRI'; + public const TYPE_MAP = [ + Type::BUILTIN_TYPE_BOOL => DataTypes::BOOLEAN, + Type::BUILTIN_TYPE_FLOAT => DataTypes::FLOAT, + Type::BUILTIN_TYPE_INT => DataTypes::INTEGER, + Type::BUILTIN_TYPE_STRING => DataTypes::STRING, + ]; + + private $resourceMetadataFactory; + private $propertyNameCollectionFactory; + private $propertyMetadataFactory; + private $nameConverter; + + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, NameConverterInterface $nameConverter = null) + { + @trigger_error('The '.__CLASS__.' class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.', \E_USER_DEPRECATED); + + $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; + $this->propertyMetadataFactory = $propertyMetadataFactory; + $this->nameConverter = $nameConverter; + } + + /** + * {@inheritdoc} + */ + public function supports(array $item) + { + $data = explode(':', $item['class'], 3); + if (!\in_array($data[0], [self::IN_PREFIX, self::OUT_PREFIX], true)) { + return false; + } + if (!isset($data[1])) { + return false; + } + + try { + $this->resourceMetadataFactory->create($data[1]); + + return true; + } catch (ResourceClassNotFoundException $e) { + // return false + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function parse(array $item): array + { + [$io, $resourceClass, $operationName] = explode(':', $item['class'], 3); + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + + $classOperations = $this->getGroupsForItemAndCollectionOperation($resourceMetadata, $operationName, $io); + + if (!empty($classOperations['serializer_groups'])) { + return $this->getPropertyMetadata($resourceMetadata, $resourceClass, $io, [], $classOperations); + } + + return $this->parseResource($resourceMetadata, $resourceClass, $io); + } + + /** + * Parses a class. + * + * @param string[] $visited + */ + private function parseResource(ResourceMetadata $resourceMetadata, string $resourceClass, string $io, array $visited = []): array + { + $visited[] = $resourceClass; + + $options = []; + $attributes = $resourceMetadata->getAttributes(); + + if (isset($attributes['normalization_context'][AbstractNormalizer::GROUPS])) { + $options['serializer_groups'] = (array) $attributes['normalization_context'][AbstractNormalizer::GROUPS]; + } + + if (isset($attributes['denormalization_context'][AbstractNormalizer::GROUPS])) { + if (isset($options['serializer_groups'])) { + $options['serializer_groups'] += $attributes['denormalization_context'][AbstractNormalizer::GROUPS]; + } else { + $options['serializer_groups'] = (array) $attributes['denormalization_context'][AbstractNormalizer::GROUPS]; + } + } + + return $this->getPropertyMetadata($resourceMetadata, $resourceClass, $io, $visited, $options); + } + + private function getGroupsContext(ResourceMetadata $resourceMetadata, string $operationName, bool $isNormalization): array + { + $groupsContext = $isNormalization ? 'normalization_context' : 'denormalization_context'; + $itemOperationAttribute = $resourceMetadata->getItemOperationAttribute($operationName, $groupsContext, [AbstractNormalizer::GROUPS => []], true)[AbstractNormalizer::GROUPS]; + $collectionOperationAttribute = $resourceMetadata->getCollectionOperationAttribute($operationName, $groupsContext, [AbstractNormalizer::GROUPS => []], true)[AbstractNormalizer::GROUPS]; + + return [ + $groupsContext => [ + AbstractNormalizer::GROUPS => array_merge((array) ($itemOperationAttribute ?? []), (array) ($collectionOperationAttribute ?? [])), + ], + ]; + } + + /** + * Returns groups of item & collection. + */ + private function getGroupsForItemAndCollectionOperation(ResourceMetadata $resourceMetadata, string $operationName, string $io): array + { + $operation = $this->getGroupsContext($resourceMetadata, $operationName, true); + $operation += $this->getGroupsContext($resourceMetadata, $operationName, false); + + if (self::OUT_PREFIX === $io) { + return [ + 'serializer_groups' => !empty($operation['normalization_context']) ? $operation['normalization_context'][AbstractNormalizer::GROUPS] : [], + ]; + } + + if (self::IN_PREFIX === $io) { + return [ + 'serializer_groups' => !empty($operation['denormalization_context']) ? $operation['denormalization_context'][AbstractNormalizer::GROUPS] : [], + ]; + } + + return []; + } + + /** + * Returns a property metadata. + * + * @param string[] $visited + * @param string[] $options + */ + private function getPropertyMetadata(ResourceMetadata $resourceMetadata, string $resourceClass, string $io, array $visited, array $options): array + { + $data = []; + + foreach ($this->propertyNameCollectionFactory->create($resourceClass, $options) as $propertyName) { + $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); + if ( + ($propertyMetadata->isReadable() && self::OUT_PREFIX === $io) || + ($propertyMetadata->isWritable() && self::IN_PREFIX === $io) + ) { + $normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName, $resourceClass) : $propertyName; + $data[$normalizedPropertyName] = $this->parseProperty($resourceMetadata, $propertyMetadata, $io, null, $visited); + } + } + + return $data; + } + + /** + * Parses a property. + * + * @param string $io + * @param string[] $visited + */ + private function parseProperty(ResourceMetadata $resourceMetadata, PropertyMetadata $propertyMetadata, $io, Type $type = null, array $visited = []): array + { + $data = [ + 'dataType' => null, + 'required' => $propertyMetadata->isRequired(), + 'description' => $propertyMetadata->getDescription(), + 'readonly' => !$propertyMetadata->isWritable(), + ]; + + if (null === $type && null === $type = $propertyMetadata->getType()) { + // Default to string + $data['dataType'] = DataTypes::STRING; + + return $data; + } + + if ($type->isCollection()) { + $data['actualType'] = DataTypes::COLLECTION; + + if ($collectionType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType()) { + $subProperty = $this->parseProperty($resourceMetadata, $propertyMetadata, $io, $collectionType, $visited); + if (self::TYPE_IRI === $subProperty['dataType']) { + $data['dataType'] = 'array of IRIs'; + $data['subType'] = DataTypes::STRING; + + return $data; + } + + $data['subType'] = $subProperty['subType'] ?? null; + if (isset($subProperty['children'])) { + $data['children'] = $subProperty['children']; + } + } + + return $data; + } + + $builtinType = $type->getBuiltinType(); + if ('object' === $builtinType) { + $className = $type->getClassName(); + + if (is_a($className, \DateTimeInterface::class, true)) { + $data['dataType'] = DataTypes::DATETIME; + $data['format'] = sprintf('{DateTime %s}', \DateTime::RFC3339); + + return $data; + } + + try { + $this->resourceMetadataFactory->create($className); + } catch (ResourceClassNotFoundException $e) { + $data['actualType'] = DataTypes::MODEL; + $data['subType'] = $className; + + return $data; + } + + if ( + (self::OUT_PREFIX === $io && true !== $propertyMetadata->isReadableLink()) || + (self::IN_PREFIX === $io && true !== $propertyMetadata->isWritableLink()) + ) { + $data['dataType'] = self::TYPE_IRI; + $data['actualType'] = DataTypes::STRING; + + return $data; + } + + $data['actualType'] = DataTypes::MODEL; + $data['subType'] = $className; + $data['children'] = \in_array($className, $visited, true) ? [] : $this->parseResource($resourceMetadata, $className, $io, $visited); + + return $data; + } + + $data['dataType'] = self::TYPE_MAP[$builtinType] ?? DataTypes::STRING; + + return $data; + } + } +} diff --git a/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php b/src/Core/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php similarity index 100% rename from src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php rename to src/Core/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php diff --git a/src/Bridge/RamseyUuid/Serializer/UuidDenormalizer.php b/src/Core/Bridge/RamseyUuid/Serializer/UuidDenormalizer.php similarity index 100% rename from src/Bridge/RamseyUuid/Serializer/UuidDenormalizer.php rename to src/Core/Bridge/RamseyUuid/Serializer/UuidDenormalizer.php diff --git a/src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php b/src/Core/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php similarity index 100% rename from src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php rename to src/Core/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php diff --git a/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php b/src/Core/Bridge/Symfony/Bundle/ApiPlatformBundle.php similarity index 96% rename from src/Bridge/Symfony/Bundle/ApiPlatformBundle.php rename to src/Core/Bridge/Symfony/Bundle/ApiPlatformBundle.php index da038d8324b..9d9c8803da5 100644 --- a/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php +++ b/src/Core/Bridge/Symfony/Bundle/ApiPlatformBundle.php @@ -25,7 +25,6 @@ use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass; use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; -use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/src/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php b/src/Core/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php similarity index 75% rename from src/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php rename to src/Core/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php index 423577837e4..5cf63dc4d21 100644 --- a/src/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php +++ b/src/Core/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php @@ -13,25 +13,24 @@ namespace ApiPlatform\Core\Bridge\Symfony\Bundle\ArgumentResolver; -use ApiPlatform\Core\EventListener\ToggleableDeserializationTrait; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Util\OperationRequestInitiatorTrait; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; final class PayloadArgumentResolver implements ArgumentValueResolverInterface { - use ToggleableDeserializationTrait; - + use OperationRequestInitiatorTrait; private $serializationContextBuilder; public function __construct( - ResourceMetadataFactoryInterface $resourceMetadataFactory, + ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, SerializerContextBuilderInterface $serializationContextBuilder ) { - $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->serializationContextBuilder = $serializationContextBuilder; } @@ -67,13 +66,12 @@ public function resolve(Request $request, ArgumentMetadata $argument): \Generato private function getExpectedInputClass(Request $request): ?string { - $attributes = RequestAttributesExtractor::extractAttributes($request); - - if (!$this->isRequestToDeserialize($request, $attributes)) { + $operation = $this->initializeOperation($request); + if (Request::METHOD_DELETE === $request->getMethod() || $request->isMethodSafe() || !$operation->canDeserialize()) { return null; } - $context = $this->serializationContextBuilder->createFromRequest($request, false, $attributes); + $context = $this->serializationContextBuilder->createFromRequest($request, false, RequestAttributesExtractor::extractAttributes($request)); return $context['input'] ?? $context['resource_class']; } diff --git a/src/Bridge/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/src/Core/Bridge/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php similarity index 100% rename from src/Bridge/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php rename to src/Core/Bridge/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php diff --git a/src/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php b/src/Core/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php similarity index 97% rename from src/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php rename to src/Core/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php index 2b2402fc54a..8f478733db5 100644 --- a/src/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php +++ b/src/Core/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Command; -use ApiPlatform\Core\GraphQl\Type\SchemaBuilderInterface; +use ApiPlatform\GraphQl\Type\SchemaBuilderInterface; use GraphQL\Utils\SchemaPrinter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; diff --git a/src/Bridge/Symfony/Bundle/Command/OpenApiCommand.php b/src/Core/Bridge/Symfony/Bundle/Command/OpenApiCommand.php similarity index 100% rename from src/Bridge/Symfony/Bundle/Command/OpenApiCommand.php rename to src/Core/Bridge/Symfony/Bundle/Command/OpenApiCommand.php diff --git a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php b/src/Core/Bridge/Symfony/Bundle/Command/SwaggerCommand.php similarity index 100% rename from src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php rename to src/Core/Bridge/Symfony/Bundle/Command/SwaggerCommand.php diff --git a/src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php b/src/Core/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php similarity index 84% rename from src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php rename to src/Core/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php index 2b7c993cf5d..1f8ddc8d34a 100644 --- a/src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php +++ b/src/Core/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php @@ -21,8 +21,11 @@ use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; +use ApiPlatform\Core\Metadata\Resource\ApiResourceToLegacyResourceMetadataTrait; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use PackageVersions\Versions; use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -35,6 +38,11 @@ */ final class RequestDataCollector extends DataCollector { + use ApiResourceToLegacyResourceMetadataTrait; + + /** + * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface + */ private $metadataFactory; private $filterLocator; private $collectionDataProvider; @@ -42,7 +50,7 @@ final class RequestDataCollector extends DataCollector private $subresourceDataProvider; private $dataPersister; - public function __construct(ResourceMetadataFactoryInterface $metadataFactory, ContainerInterface $filterLocator, CollectionDataProviderInterface $collectionDataProvider = null, ItemDataProviderInterface $itemDataProvider = null, SubresourceDataProviderInterface $subresourceDataProvider = null, DataPersisterInterface $dataPersister = null) + public function __construct($metadataFactory, ContainerInterface $filterLocator, CollectionDataProviderInterface $collectionDataProvider = null, ItemDataProviderInterface $itemDataProvider = null, SubresourceDataProviderInterface $subresourceDataProvider = null, DataPersisterInterface $dataPersister = null) { $this->metadataFactory = $metadataFactory; $this->filterLocator = $filterLocator; @@ -50,6 +58,10 @@ public function __construct(ResourceMetadataFactoryInterface $metadataFactory, C $this->itemDataProvider = $itemDataProvider; $this->subresourceDataProvider = $subresourceDataProvider; $this->dataPersister = $dataPersister; + + if (!$metadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); + } } /** @@ -61,6 +73,10 @@ public function collect(Request $request, Response $response, \Throwable $except $resourceClass = $request->attributes->get('_api_resource_class'); $resourceMetadata = $resourceClass ? $this->metadataFactory->create($resourceClass) : null; + if ($resourceMetadata instanceof ResourceMetadataCollection) { + $resourceMetadata = $this->transformResourceToResourceMetadata($resourceMetadata[0]); + } + $filters = []; foreach ($resourceMetadata ? $resourceMetadata->getAttribute('filters', []) : [] as $id) { if ($this->filterLocator->has($id)) { diff --git a/src/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersister.php b/src/Core/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersister.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersister.php rename to src/Core/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersister.php diff --git a/src/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php b/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php similarity index 93% rename from src/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php rename to src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php index 14fd37c1ba1..549fc5681b9 100644 --- a/src/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php +++ b/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php @@ -16,15 +16,17 @@ use ApiPlatform\Core\DataProvider\ChainCollectionDataProvider; use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; +use ApiPlatform\Core\DataProvider\RestrictDataProviderTrait; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; /** * @author Anthony GRASSIOT */ -final class TraceableChainCollectionDataProvider implements ContextAwareCollectionDataProviderInterface +final class TraceableChainCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface { - private $dataProviders = []; + use RestrictDataProviderTrait; + private $context = []; private $providersResponse = []; diff --git a/src/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php b/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php similarity index 95% rename from src/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php rename to src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php index caa3d69ab13..982ca74d616 100644 --- a/src/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php +++ b/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php @@ -16,15 +16,17 @@ use ApiPlatform\Core\DataProvider\ChainItemDataProvider; use ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; +use ApiPlatform\Core\DataProvider\RestrictDataProviderTrait; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; /** * @author Anthony GRASSIOT */ -final class TraceableChainItemDataProvider implements ItemDataProviderInterface +final class TraceableChainItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface { - private $dataProviders = []; + use RestrictDataProviderTrait; + private $context = []; private $providersResponse = []; diff --git a/src/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataProvider.php b/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataProvider.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataProvider.php rename to src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataProvider.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php similarity index 97% rename from src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index e8bec677f08..273d5201417 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -33,11 +33,11 @@ use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface; use ApiPlatform\Core\DataTransformer\DataTransformerInterface; -use ApiPlatform\Core\GraphQl\Error\ErrorHandlerInterface; -use ApiPlatform\Core\GraphQl\Resolver\MutationResolverInterface; -use ApiPlatform\Core\GraphQl\Resolver\QueryCollectionResolverInterface; -use ApiPlatform\Core\GraphQl\Resolver\QueryItemResolverInterface; -use ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface as GraphQlTypeInterface; +use ApiPlatform\GraphQl\Error\ErrorHandlerInterface; +use ApiPlatform\GraphQl\Resolver\MutationResolverInterface; +use ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface; +use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface; +use ApiPlatform\GraphQl\Type\Definition\TypeInterface as GraphQlTypeInterface; use Doctrine\Common\Annotations\Annotation; use phpDocumentor\Reflection\DocBlockFactoryInterface; use Ramsey\Uuid\Uuid; @@ -129,6 +129,7 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerSecurityConfiguration($container, $loader); $this->registerMakerConfiguration($container, $config, $loader); $this->registerArgumentResolverConfiguration($container, $loader); + $this->registerLegacyServices($container, $config); $container->registerForAutoconfiguration(DataPersisterInterface::class) ->addTag('api_platform.data_persister'); @@ -147,6 +148,7 @@ private function registerCommonConfiguration(ContainerBuilder $container, array $loader->load('api.xml'); $loader->load('data_persister.xml'); $loader->load('data_provider.xml'); + $loader->load('state.xml'); $loader->load('filter.xml'); $container->getDefinition('api_platform.operation_method_resolver') @@ -262,6 +264,7 @@ private function normalizeDefaults(array $defaults): array private function registerMetadataConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void { $loader->load('metadata/metadata.xml'); + $loader->load('metadata/resource_collection.xml'); $loader->load('metadata/xml.xml'); [$xmlResources, $yamlResources] = $this->getResourcesToWatch($container, $config); @@ -297,7 +300,6 @@ private function getBundlesResourcesPaths(ContainerBuilder $container, array $co $dirname = $bundle['path']; foreach (['.yaml', '.yml', '.xml', ''] as $extension) { $paths[] = "$dirname/Resources/config/api_resources$extension"; - $paths[] = "$dirname/config/api_resources$extension"; } if ($this->isConfigEnabled($container, $config['doctrine'])) { $paths[] = "$dirname/Entity"; @@ -759,6 +761,22 @@ private function registerArgumentResolverConfiguration(ContainerBuilder $contain $loader->load('argument_resolver.xml'); } + private function registerLegacyServices(ContainerBuilder $container, array $config): void + { + if (!$config['metadata_backward_compatibility_layer']) { + return; + } + + $container->removeDefinition('api_platform.identifiers_extractor'); + $container->setAlias('api_platform.identifiers_extractor', 'api_platform.identifiers_extractor.legacy'); + + $container->removeDefinition('api_platform.iri_converter'); + $container->setAlias('api_platform.iri_converter', 'api_platform.iri_converter.legacy'); + + $container->removeDefinition('api_platform.openapi.factory'); + $container->setAlias('api_platform.openapi.factory', 'api_platform.openapi.factory.legacy'); + } + private function buildDeprecationArgs(string $version, string $message): array { return method_exists(Definition::class, 'getDeprecation') diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php similarity index 100% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php similarity index 99% rename from src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php rename to src/Core/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index 1b1e2b5a48b..549e4cf6370 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -88,6 +88,7 @@ public function getConfigTreeBuilder() ->defaultValue('0.0.0') ->end() ->booleanNode('show_webby')->defaultTrue()->info('If true, show Webby on the documentation page')->end() + ->booleanNode('metadata_backward_compatibility_layer')->defaultTrue()->info('If true, declared services are using legacy interfaces for the following services: "api_platform.iri_converter", "api_platform.openapi.factory", "api_platform.identifiers_extractor".')->end() ->scalarNode('default_operation_path_resolver') ->defaultValue('api_platform.operation_path_resolver.underscore') ->setDeprecated(...$this->buildDeprecationArgs('2.1', 'The use of the `default_operation_path_resolver` has been deprecated in 2.1 and will be removed in 3.0. Use `path_segment_name_generator` instead.')) diff --git a/src/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php b/src/Core/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php similarity index 100% rename from src/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php rename to src/Core/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/api.xml similarity index 88% rename from src/Bridge/Symfony/Bundle/Resources/config/api.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/api.xml index d1caa9cd508..c38c24f3cbb 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -32,7 +32,7 @@ - + %api_platform.formats% @@ -48,14 +48,15 @@ - + %api_platform.url_generation_strategy% - + + @@ -68,7 +69,17 @@ - + + + + + + + + + + + @@ -81,12 +92,12 @@ - + - + @@ -115,7 +126,7 @@ %api_platform.allow_plain_identifiers% null - + @@ -157,7 +168,7 @@ - + %api_platform.formats% @@ -165,20 +176,20 @@ - - - + + null + null - + - + - + @@ -187,21 +198,25 @@ + + - + + - + + @@ -254,19 +269,20 @@ %api_platform.error_formats% %api_platform.exception_to_status% - + - + + - + @@ -274,6 +290,15 @@ + + + + + + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/argument_resolver.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/argument_resolver.xml similarity index 94% rename from src/Bridge/Symfony/Bundle/Resources/config/argument_resolver.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/argument_resolver.xml index 4af1443fe5c..944bd1e7738 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/argument_resolver.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/argument_resolver.xml @@ -6,7 +6,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/data_collector.xml similarity index 96% rename from src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/data_collector.xml index 29691335016..428abfaa6c8 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/data_collector.xml @@ -6,7 +6,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/data_persister.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/data_persister.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/data_provider.xml similarity index 99% rename from src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/data_provider.xml index 33a513bfad4..2869d5e4cbf 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/data_provider.xml @@ -48,7 +48,6 @@ %api_platform.collection.pagination.partial_parameter_name% - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/debug.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/debug.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/debug.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/debug.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml similarity index 93% rename from src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml index a8f68402635..872c23eabf3 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml @@ -24,13 +24,13 @@ - + - + @@ -38,7 +38,7 @@ - + @@ -130,7 +130,7 @@ - + @@ -140,7 +140,7 @@ - + @@ -149,12 +149,20 @@ %api_platform.collection.order% - + + + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm_mercure_publisher.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm_mercure_publisher.xml similarity index 97% rename from src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm_mercure_publisher.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm_mercure_publisher.xml index 8d7c9672b12..cda213f11f8 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm_mercure_publisher.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm_mercure_publisher.xml @@ -11,7 +11,7 @@ - + %api_platform.formats% diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml similarity index 94% rename from src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml index 86d1d6b31a8..875ca150538 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml @@ -25,7 +25,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -125,7 +125,7 @@ - + %api_platform.eager_loading.max_joins% %api_platform.eager_loading.force_eager% null @@ -140,7 +140,7 @@ - + @@ -150,7 +150,7 @@ - + %api_platform.eager_loading.force_eager% @@ -160,7 +160,7 @@ - + @@ -169,11 +169,16 @@ %api_platform.collection.order% - + + + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_http_cache_purger.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_http_cache_purger.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_http_cache_purger.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_http_cache_purger.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml similarity index 97% rename from src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml index fbd3d138618..80a507533ee 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml @@ -11,7 +11,7 @@ - + %api_platform.formats% diff --git a/src/Bridge/Symfony/Bundle/Resources/config/elasticsearch.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/elasticsearch.xml similarity index 99% rename from src/Bridge/Symfony/Bundle/Resources/config/elasticsearch.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/elasticsearch.xml index a36b677db62..47f76aa28a0 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/elasticsearch.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/elasticsearch.xml @@ -40,7 +40,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/filter.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/filter.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/filter.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/filter.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/fos_user.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/fos_user.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/fos_user.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/fos_user.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/graphql.xml similarity index 82% rename from src/Bridge/Symfony/Bundle/Resources/config/graphql.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/graphql.xml index 4e23af26195..c055bbdf732 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/graphql.xml @@ -5,30 +5,30 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + - + - + - + - + - + @@ -37,22 +37,22 @@ - + - + - + - - + + @@ -60,41 +60,41 @@ %api_platform.graphql.nesting_separator% - - + + - - + + - - + + - - + + - - + + - - + + - + @@ -108,11 +108,11 @@ - + - + @@ -120,29 +120,29 @@ - + - + - + - + - + - + - + @@ -163,9 +163,9 @@ - + - + @@ -173,7 +173,7 @@ - + @@ -186,14 +186,14 @@ %api_platform.graphql.default_ide% - + %api_platform.graphql.graphiql.enabled% %api_platform.title% - + %api_platform.graphql.graphql_playground.enabled% @@ -202,11 +202,11 @@ - + - + @@ -220,14 +220,14 @@ %api_platform.allow_plain_identifiers% null - + - + @@ -236,40 +236,40 @@ - + - + %api_platform.exception_to_status% - + - + - - + + - + - + - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/graphql_mercure.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/graphql_mercure.xml similarity index 83% rename from src/Bridge/Symfony/Bundle/Resources/config/graphql_mercure.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/graphql_mercure.xml index 8307be7cb87..f682291900e 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/graphql_mercure.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/graphql_mercure.xml @@ -6,7 +6,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/hal.xml similarity index 95% rename from src/Bridge/Symfony/Bundle/Resources/config/hal.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/hal.xml index ac713781345..5095e6b41ab 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/hal.xml @@ -12,7 +12,7 @@ - + @@ -22,7 +22,7 @@ %api_platform.collection.pagination.page_parameter_name% - + @@ -40,7 +40,7 @@ false - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/http_cache.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/http_cache.xml similarity index 90% rename from src/Bridge/Symfony/Bundle/Resources/config/http_cache.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/http_cache.xml index e761cd827fc..c54ecaf4633 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/http_cache.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/http_cache.xml @@ -11,7 +11,7 @@ %api_platform.http_cache.shared_max_age% %api_platform.http_cache.vary% %api_platform.http_cache.public% - + null diff --git a/src/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml similarity index 89% rename from src/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml index 0e2224f1ba8..b0d86044207 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/http_cache_tags.xml @@ -10,6 +10,7 @@ + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/hydra.xml similarity index 96% rename from src/Bridge/Symfony/Bundle/Resources/config/hydra.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/hydra.xml index f8a7967e42d..455afb31734 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/hydra.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/hydra.xml @@ -6,7 +6,7 @@ - + @@ -38,7 +38,7 @@ - + @@ -65,13 +65,13 @@ %api_platform.collection.pagination.page_parameter_name% %api_platform.collection.pagination.enabled_parameter_name% - + - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/json_schema.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/json_schema.xml similarity index 98% rename from src/Bridge/Symfony/Bundle/Resources/config/json_schema.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/json_schema.xml index 315efe73bb0..d22801ab5ce 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/json_schema.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/json_schema.xml @@ -17,7 +17,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml similarity index 96% rename from src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml index 2c2203cbad5..0e76f46de91 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml @@ -16,7 +16,7 @@ - + @@ -26,7 +26,7 @@ %api_platform.collection.pagination.page_parameter_name% - + @@ -39,7 +39,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -83,7 +83,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/jsonld.xml similarity index 96% rename from src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/jsonld.xml index 7c1715f9e67..cf2aa871212 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/jsonld.xml @@ -7,7 +7,7 @@ - + @@ -16,7 +16,7 @@ - + @@ -53,7 +53,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/maker.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/maker.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/maker.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/maker.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/mercure.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/mercure.xml similarity index 95% rename from src/Bridge/Symfony/Bundle/Resources/config/mercure.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/mercure.xml index 508ce9600fb..f604706a805 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/mercure.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/mercure.xml @@ -8,7 +8,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/messenger.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/messenger.xml similarity index 93% rename from src/Bridge/Symfony/Bundle/Resources/config/messenger.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/messenger.xml index d50ec0e3d69..13c7e0b9a98 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/messenger.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/messenger.xml @@ -8,14 +8,14 @@ - + - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml similarity index 99% rename from src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml index 3efa9dd56b2..003d1bcd11f 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml @@ -14,6 +14,7 @@ + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/php_doc.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/php_doc.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/metadata/php_doc.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/php_doc.xml diff --git a/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/resource_collection.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/resource_collection.xml new file mode 100644 index 00000000000..094603eecc6 --- /dev/null +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/resource_collection.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + null + %api_platform.defaults% + + + + + + + %api_platform.defaults% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %api_platform.formats% + %api_platform.patch_formats% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/xml.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/openapi.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/openapi.xml similarity index 80% rename from src/Bridge/Symfony/Bundle/Resources/config/openapi.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/openapi.xml index ec63780de2c..1aaf62d1f84 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/openapi.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/openapi.xml @@ -50,7 +50,22 @@ - + + + + + + + + + + %api_platform.formats% + + + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/problem.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/problem.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/problem.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/problem.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/routing/api.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/routing/api.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/routing/api.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/docs.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/routing/docs.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/routing/docs.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/routing/docs.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphiql.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphiql.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphiql.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphiql.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphql.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphql.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphql.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphql.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphql_playground.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphql_playground.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphql_playground.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/routing/graphql/graphql_playground.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/routing/jsonld.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/security.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/security.xml similarity index 97% rename from src/Bridge/Symfony/Bundle/Resources/config/security.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/security.xml index 0fdff8300f1..ff7e70ca3cf 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/security.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/security.xml @@ -17,7 +17,7 @@ - + diff --git a/src/Core/Bridge/Symfony/Bundle/Resources/config/state.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/state.xml new file mode 100644 index 00000000000..2d7e927c10a --- /dev/null +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/state.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/swagger.xml similarity index 98% rename from src/Bridge/Symfony/Bundle/Resources/config/swagger.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/swagger.xml index f41fb55c44e..9e768f6a41f 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/swagger.xml @@ -7,7 +7,7 @@ - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/symfony_uid.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/symfony_uid.xml similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/config/symfony_uid.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/symfony_uid.xml diff --git a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/validator.xml similarity index 98% rename from src/Bridge/Symfony/Bundle/Resources/config/validator.xml rename to src/Core/Bridge/Symfony/Bundle/Resources/config/validator.xml index 8e0a2c3f22b..2ea59e8eea8 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml +++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/validator.xml @@ -73,7 +73,7 @@ - + @@ -83,7 +83,7 @@ - + %api_platform.validator.query_parameter_validation% diff --git a/src/Bridge/Symfony/Bundle/Resources/public/es6-promise/es6-promise.auto.min.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/es6-promise/es6-promise.auto.min.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/es6-promise/es6-promise.auto.min.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/es6-promise/es6-promise.auto.min.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fetch/fetch.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/fetch/fetch.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fetch/fetch.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fetch/fetch.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/400.css b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/400.css similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/400.css rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/400.css diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/700.css b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/700.css similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/700.css rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/700.css diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-400-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-400-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-400-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-400-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-700-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-700-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-700-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-700-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-ext-400-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-ext-400-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-ext-400-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-ext-400-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-ext-700-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-ext-700-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-ext-700-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-cyrillic-ext-700-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-400-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-400-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-400-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-400-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-700-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-700-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-700-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-700-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-ext-400-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-ext-400-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-ext-400-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-ext-400-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-ext-700-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-ext-700-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-ext-700-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-greek-ext-700-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-400-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-400-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-400-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-400-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-700-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-700-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-700-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-700-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-ext-400-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-ext-400-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-ext-400-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-ext-400-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-ext-700-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-ext-700-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-ext-700-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-latin-ext-700-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-vietnamese-400-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-vietnamese-400-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-vietnamese-400-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-vietnamese-400-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-vietnamese-700-normal.woff2 b/src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-vietnamese-700-normal.woff2 similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-vietnamese-700-normal.woff2 rename to src/Core/Bridge/Symfony/Bundle/Resources/public/fonts/open-sans/files/open-sans-vietnamese-700-normal.woff2 diff --git a/src/Bridge/Symfony/Bundle/Resources/public/graphiql-style.css b/src/Core/Bridge/Symfony/Bundle/Resources/public/graphiql-style.css similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/graphiql-style.css rename to src/Core/Bridge/Symfony/Bundle/Resources/public/graphiql-style.css diff --git a/src/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.css b/src/Core/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.css similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.css rename to src/Core/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.css diff --git a/src/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.min.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.min.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.min.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/graphiql/graphiql.min.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/graphql-playground/index.css b/src/Core/Bridge/Symfony/Bundle/Resources/public/graphql-playground/index.css similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/graphql-playground/index.css rename to src/Core/Bridge/Symfony/Bundle/Resources/public/graphql-playground/index.css diff --git a/src/Bridge/Symfony/Bundle/Resources/public/graphql-playground/middleware.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/graphql-playground/middleware.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/graphql-playground/middleware.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/graphql-playground/middleware.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/init-graphiql.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/init-graphiql.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/init-graphiql.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/init-graphiql.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/init-graphql-playground.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/init-graphql-playground.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/init-graphql-playground.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/init-graphql-playground.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/init-redoc-ui.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/init-redoc-ui.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/init-redoc-ui.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/init-redoc-ui.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/init-swagger-ui.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/init-swagger-ui.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/init-swagger-ui.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/init-swagger-ui.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/logo-header.svg b/src/Core/Bridge/Symfony/Bundle/Resources/public/logo-header.svg similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/logo-header.svg rename to src/Core/Bridge/Symfony/Bundle/Resources/public/logo-header.svg diff --git a/src/Bridge/Symfony/Bundle/Resources/public/react/react-dom.production.min.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/react/react-dom.production.min.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/react/react-dom.production.min.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/react/react-dom.production.min.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/react/react.production.min.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/react/react.production.min.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/react/react.production.min.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/react/react.production.min.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/redoc/redoc.standalone.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/redoc/redoc.standalone.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/redoc/redoc.standalone.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/redoc/redoc.standalone.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/style.css b/src/Core/Bridge/Symfony/Bundle/Resources/public/style.css similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/style.css rename to src/Core/Bridge/Symfony/Bundle/Resources/public/style.css diff --git a/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/oauth2-redirect.html b/src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/oauth2-redirect.html similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/oauth2-redirect.html rename to src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/oauth2-redirect.html diff --git a/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-bundle.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-bundle.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-bundle.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-bundle.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-bundle.js.map b/src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-bundle.js.map similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-bundle.js.map rename to src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-bundle.js.map diff --git a/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-standalone-preset.js b/src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-standalone-preset.js similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-standalone-preset.js rename to src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-standalone-preset.js diff --git a/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-standalone-preset.js.map b/src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-standalone-preset.js.map similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-standalone-preset.js.map rename to src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui-standalone-preset.js.map diff --git a/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui.css b/src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui.css similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui.css rename to src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui.css diff --git a/src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui.css.map b/src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui.css.map similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui.css.map rename to src/Core/Bridge/Symfony/Bundle/Resources/public/swagger-ui/swagger-ui.css.map diff --git a/src/Bridge/Symfony/Bundle/Resources/public/web.png b/src/Core/Bridge/Symfony/Bundle/Resources/public/web.png similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/web.png rename to src/Core/Bridge/Symfony/Bundle/Resources/public/web.png diff --git a/src/Bridge/Symfony/Bundle/Resources/public/webby.png b/src/Core/Bridge/Symfony/Bundle/Resources/public/webby.png similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/public/webby.png rename to src/Core/Bridge/Symfony/Bundle/Resources/public/webby.png diff --git a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform-icon.svg b/src/Core/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform-icon.svg similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform-icon.svg rename to src/Core/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform-icon.svg diff --git a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform.svg b/src/Core/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform.svg similarity index 100% rename from src/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform.svg rename to src/Core/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform.svg diff --git a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig b/src/Core/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig similarity index 98% rename from src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig rename to src/Core/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig index 13bfe8939e2..786b0f90d1f 100644 --- a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig +++ b/src/Core/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig @@ -128,8 +128,8 @@

Resource Metadata

Short name: "{{ collector.resourceMetadata.shortName }}"

- {{ apiPlatform.operationTable(collector.resourceMetadata.itemOperations, 'item', collector.requestAttributes.item_operation_name|default('')) }} - {{ apiPlatform.operationTable(collector.resourceMetadata.collectionOperations, 'collection', collector.requestAttributes.collection_operation_name|default('')) }} + {{ apiPlatform.operationTable(collector.resourceMetadata.itemOperations, 'item', collector.requestAttributes.operation_name|default('')) }} + {{ apiPlatform.operationTable(collector.resourceMetadata.collectionOperations, 'collection', collector.requestAttributes.operation_name|default('')) }} @@ -198,3 +198,4 @@ {% endif %} {% endblock %} + diff --git a/src/Bridge/Symfony/Bundle/Resources/views/GraphQlPlayground/index.html.twig b/src/Core/Bridge/Symfony/Bundle/Resources/views/GraphQlPlayground/index.html.twig similarity index 99% rename from src/Bridge/Symfony/Bundle/Resources/views/GraphQlPlayground/index.html.twig rename to src/Core/Bridge/Symfony/Bundle/Resources/views/GraphQlPlayground/index.html.twig index 7f275121896..106b125a7b1 100644 --- a/src/Bridge/Symfony/Bundle/Resources/views/GraphQlPlayground/index.html.twig +++ b/src/Core/Bridge/Symfony/Bundle/Resources/views/GraphQlPlayground/index.html.twig @@ -482,7 +482,7 @@ }
-