diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a0199050ce..1e9700c08cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,11 +98,6 @@ jobs: strategy: matrix: php: - - '7.1' - - '7.2' - - '7.3' - - '7.4' - - '8.0' - '8.1' include: - php: '8.1' @@ -131,16 +126,9 @@ jobs: - name: Enable code coverage if: matrix.coverage run: echo "COVERAGE=1" >> $GITHUB_ENV - - name: Remove Doctrine MongoDB ODM - if: (startsWith(matrix.php, '7.1')) - run: | - composer remove --dev --no-interaction --no-progress --no-update --ansi \ - doctrine/mongodb-odm \ - doctrine/mongodb-odm-bundle - name: Update project dependencies run: composer update --no-interaction --no-progress --ansi - name: Require Symfony components - if: (!startsWith(matrix.php, '7.1')) run: composer require symfony/uid --dev --no-interaction --no-progress --ansi - name: Install PHPUnit run: vendor/bin/simple-phpunit --version @@ -154,9 +142,6 @@ jobs: else vendor/bin/simple-phpunit --log-junit build/logs/phpunit/junit.xml fi - - name: Run PHPUnit 8.1+ tests - if: (startsWith(matrix.php, '8.1')) - run: ./vendor/bin/simple-phpunit --stop-on-failure tests/Metadata/Resource/Factory/ - name: Upload test artifacts if: always() uses: actions/upload-artifact@v1 @@ -189,11 +174,6 @@ jobs: strategy: matrix: php: - - '7.1' - - '7.2' - - '7.3' - - '7.4' - - '8.0' - '8.1' include: - php: '8.1' @@ -222,40 +202,15 @@ jobs: - name: Enable code coverage if: matrix.coverage run: echo "COVERAGE=1" >> $GITHUB_ENV - - name: Remove Doctrine MongoDB ODM - if: startsWith(matrix.php, '7.1') - run: | - composer remove --dev --no-interaction --no-progress --no-update --ansi \ - doctrine/mongodb-odm \ - doctrine/mongodb-odm-bundle - name: Update project dependencies run: composer update --no-interaction --no-progress --ansi - name: Require Symfony components - if: (!startsWith(matrix.php, '7.1')) run: composer require symfony/uid --dev --no-interaction --no-progress --ansi - name: Install PHPUnit run: vendor/bin/simple-phpunit --version - name: Clear test app cache - if: (!startsWith(matrix.php, '8.')) run: tests/Fixtures/app/console cache:clear --ansi - - name: Clear test app cache (php 8.0) - if: (startsWith(matrix.php, '8.')) - run: rm -Rf tests/Fixtures/app/var/cache/* - - name: Run Behat tests (PHP < 8) - if: (!startsWith(matrix.php, '8.')) - 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 --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&&~@php8' - else - 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 (PHP 8) - if: (startsWith(matrix.php, '8.')) run: | mkdir -p build/logs/behat if [ "$COVERAGE" = '1' ]; then @@ -320,87 +275,6 @@ jobs: path: build/out/openapi continue-on-error: true - phpunit-lowest: - name: PHPUnit (PHP ${{ matrix.php }} lowest) - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - php: - - '7.4' - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: pecl, composer - extensions: intl, bcmath, curl, openssl, mbstring - coverage: none - ini-values: memory_limit=-1 - - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Require Symfony components - run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi --no-update - - name: Update project dependencies - run: composer update --no-interaction --no-progress --ansi --prefer-lowest - - name: Clear test app cache - run: tests/Fixtures/app/console cache:clear --ansi - - name: Install PHPUnit - run: vendor/bin/simple-phpunit --version - - name: Run PHPUnit tests - run: vendor/bin/simple-phpunit - - behat-lowest: - name: Behat (PHP ${{ matrix.php }} lowest) - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - php: - - '7.4' - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: pecl, composer - extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite - coverage: none - ini-values: memory_limit=-1 - - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Require Symfony components - run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi --no-update - - name: Update project dependencies - run: composer update --no-interaction --no-progress --ansi --prefer-lowest - - name: Install PHPUnit - run: vendor/bin/simple-phpunit --version - - name: Clear test app cache - 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&&~php8' - postgresql: name: Behat (PHP ${{ matrix.php }}) (PostgreSQL) runs-on: ubuntu-latest @@ -408,7 +282,7 @@ jobs: strategy: matrix: php: - - '7.4' + - '8.1' fail-fast: false env: APP_ENV: postgres @@ -459,7 +333,7 @@ jobs: strategy: matrix: php: - - '7.4' + - '8.1' fail-fast: false services: mysql: @@ -510,7 +384,7 @@ jobs: strategy: matrix: php: - - '8' + - '8.1' fail-fast: false env: APP_ENV: mongodb @@ -596,8 +470,6 @@ jobs: strategy: matrix: php: - - '7.4' - - '8.0' - '8.1' fail-fast: false env: @@ -650,7 +522,7 @@ jobs: strategy: matrix: php: - - '7.4' + - '8.1' fail-fast: false env: SYMFONY_DEPRECATIONS_HELPER: max[total]=0 @@ -779,148 +651,6 @@ jobs: - name: Run Behat tests run: vendor/bin/behat --out=std --format=progress --profile=default --no-interaction - behat-rector-upgrade: - name: Behat (PHP ${{ matrix.php }}) (upgrade script) - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - php: - - '8.1' - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: pecl, composer - extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite - ini-values: memory_limit=-1 - - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Update project dependencies - run: composer update --no-interaction --no-progress --ansi - - name: Require Symfony components - run: composer require symfony/uid --dev --no-interaction --no-progress --ansi - - name: Install PHPUnit - run: vendor/bin/simple-phpunit --version - - name: Clear test app cache - run: rm -Rf tests/Fixtures/app/var/cache/* - - name: Convert metadata to API Platform 3 - run: | - tests/Fixtures/app/console api:upgrade-resource -f - - name: Apply behat features patch (IRIs update) - run: | - git apply .github/patch/v3_features.patch - - name: clear test app cache - run: rm -rf tests/Fixtures/app/var/cache/* - - name: Run Behat tests - run: | - mkdir -p build/logs/behat - vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction - - name: Upload test artifacts - if: always() - uses: actions/upload-artifact@v1 - with: - name: behat-logs-php${{ matrix.php }} - path: build/logs/behat - continue-on-error: true - - name: Export OpenAPI documents - run: | - mkdir -p build/out/openapi - tests/Fixtures/app/console api:openapi:export --spec-version=2 -o build/out/openapi/swagger_v2.json - tests/Fixtures/app/console api:openapi:export --spec-version=2 --yaml -o build/out/openapi/swagger_v2.yaml - tests/Fixtures/app/console api:openapi:export --spec-version=3 -o build/out/openapi/openapi_v3.json - tests/Fixtures/app/console api:openapi:export --spec-version=3 --yaml -o build/out/openapi/openapi_v3.yaml - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: '14' - - name: Validate OpenAPI documents - run: | - npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/swagger_v2.json - npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/swagger_v2.yaml - npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/openapi_v3.json - npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/openapi_v3.yaml - - name: Upload OpenAPI artifacts - if: always() - uses: actions/upload-artifact@v1 - with: - name: openapi-docs-php${{ matrix.php }} - path: build/out/openapi - continue-on-error: true - - behat-rector-upgrade-mongodb: - name: Behat (PHP ${{ matrix.php }}) (upgrade script / MongoDB) - runs-on: ubuntu-latest - env: - APP_ENV: mongodb - MONGODB_URL: mongodb://localhost:27017 - timeout-minutes: 20 - strategy: - matrix: - php: - - '8.1' - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Check - run: | - sudo systemctl start mongod.service - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: pecl, composer - extensions: intl, bcmath, curl, openssl, mbstring, mongodb - ini-values: memory_limit=-1 - - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Update project dependencies - run: composer update --no-interaction --no-progress --ansi - - name: Require Symfony components - run: composer require symfony/uid --dev --no-interaction --no-progress --ansi - - name: Install PHPUnit - run: vendor/bin/simple-phpunit --version - - name: Clear test app cache - run: rm -Rf tests/Fixtures/app/var/cache/* - - name: Convert metadata to API Platform 3 - run: | - tests/Fixtures/app/console api:upgrade-resource -f - - name: Apply behat features patch (IRIs update) - run: | - git apply .github/patch/v3_features.patch - - name: Clear test app cache - run: rm -Rf tests/Fixtures/app/var/cache/* - - name: Run Behat tests - run: | - mkdir -p build/logs/behat - vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=mongodb --no-interaction - - name: Upload test artifacts - if: always() - uses: actions/upload-artifact@v1 - with: - name: behat-logs-php${{ matrix.php }} - path: build/logs/behat - continue-on-error: true - windows-phpunit: name: Windows PHPUnit (PHP ${{ matrix.php }}) (SQLite) runs-on: windows-latest @@ -928,8 +658,6 @@ jobs: strategy: matrix: php: - - '7.4' - - '8.0' - '8.1' fail-fast: false env: @@ -955,10 +683,6 @@ jobs: path: ${{ steps.composercache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - - name: Set Composer platform config - if: (startsWith(matrix.php, '8.')) - run: | - composer config platform.php 7.4.99 - name: Update project dependencies run: composer update --no-interaction --no-progress --ansi - name: Require Symfony components @@ -977,8 +701,6 @@ jobs: strategy: matrix: php: - - '7.4' - - '8.0' - '8.1' fail-fast: false env: @@ -1004,10 +726,6 @@ jobs: path: ${{ steps.composercache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - - name: Set Composer platform config - if: (startsWith(matrix.php, '8.')) - run: | - composer config platform.php 7.4.99 - name: Update project dependencies run: composer update --no-interaction --no-progress --ansi - name: Require Symfony components @@ -1017,119 +735,4 @@ jobs: - name: Clear test app cache run: tests/Fixtures/app/console cache:clear --ansi - name: Run Behat tests - 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 - } - - phpunit_legacy_metadata: - name: PHPUnit (PHP ${{ matrix.php }}) 2.7 BC layer - env: - METADATA_BACKWARD_COMPATIBILITY_LAYER: 1 - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - php: - - '7.4' - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: pecl, composer - extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite - coverage: pcov - ini-values: memory_limit=-1 - - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Update project dependencies - run: composer update --no-interaction --no-progress --ansi - - name: Require Symfony components - run: composer require symfony/uid --dev --no-interaction --no-progress --ansi - - name: Install PHPUnit - run: vendor/bin/simple-phpunit --version - - name: Clear test app cache - run: tests/Fixtures/app/console cache:clear --ansi - - name: Run PHPUnit tests - run: | - mkdir -p build/logs/phpunit - vendor/bin/simple-phpunit --log-junit build/logs/phpunit/junit.xml - - behat_legacy_metadata: - name: Behat (PHP ${{ matrix.php }}) 2.7 BC layer - env: - METADATA_BACKWARD_COMPATIBILITY_LAYER: 1 - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - php: - - '7.4' - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: pecl, composer - extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite - coverage: pcov - ini-values: memory_limit=-1 - - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache dependencies - uses: actions/cache@v2 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Update project dependencies - run: composer update --no-interaction --no-progress --ansi - - name: Require Symfony components - run: composer require symfony/uid --dev --no-interaction --no-progress --ansi - - name: Install PHPUnit - run: vendor/bin/simple-phpunit --version - - name: Clear test app cache - run: tests/Fixtures/app/console cache:clear --ansi - - name: Run Behat tests - run: | - mkdir -p build/logs/behat - vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction --tags='~@php8' --tags='~@v3' - - name: Run Behat tests (PHP 8) - if: (startsWith(matrix.php, '8.')) - run: | - mkdir -p build/logs/behat - vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction - - name: Export OpenAPI documents - run: | - mkdir -p build/out/openapi - tests/Fixtures/app/console api:openapi:export --spec-version=2 -o build/out/openapi/swagger_v2.json - tests/Fixtures/app/console api:openapi:export --spec-version=2 --yaml -o build/out/openapi/swagger_v2.yaml - tests/Fixtures/app/console api:openapi:export --spec-version=3 -o build/out/openapi/openapi_v3.json - tests/Fixtures/app/console api:openapi:export --spec-version=3 --yaml -o build/out/openapi/openapi_v3.yaml - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: '14' - - name: Validate OpenAPI documents - run: | - npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/swagger_v2.json - npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/swagger_v2.yaml - npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/openapi_v3.json - npx git+https://github.com/soyuka/swagger-cli#master validate build/out/openapi/openapi_v3.yaml + run: vendor/bin/behat --out=std --format=progress --profile=default --no-interaction diff --git a/CHANGELOG.md b/CHANGELOG.md index 6433177c068..1ad27dcd57a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,41 +1,5 @@ # Changelog -## 2.7.0-beta - -Json-Ld: property metadata types and iris (#4769) -Symfony: write listener uri variables converter (#4774) -Metadata: extra properties operation inheritance (#4773) - -**BC** - -Doctrine: new interfaces for Filters and Extensions ready, switch to the `ApiPlatform\Doctrine` namespace after fixing your deprecations: (#4779) - - `ApiPlatform\Core\Bridge\Doctrine\Orm\Extension` interfaces have an `Operation` instead of the `$operationName`, the new namespace is `ApiPlatform\Doctrine\Orm\Extension` - - `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension` interfaces have an `Operation` instead of the `$operationName`, the new namespace is `ApiPlatform\Doctrine\Odm\Extension` - -## 2.7.0-alpha.7 - -Metadata: defaults deprecation (#4772) - -## 2.7.0-alpha.6 - -GraphQl: output creates its own type in TypeBuilder (#4766) -Metadata: clear missing metadata cache pools (#4770) -Metadata: property override when value is set (#4767) -Metadata: add read and write to extractor (#4760) -JsonSchema: factory backward compatibility layer (#4758) -Metadata: defaults properly overrides metadata (#4759) -Metadata: Add missing processor and provider to extractor (#4754) - -## 2.7.0-alpha.5 - -* Backward compatibility: fix upgrade script for subresources (#4747) -* Backward compatibility: fix dependency injection (#4748) - -## 2.7.0-alpha.4 - -* Backward compatibility: fix dependency injection (#4744) -* Metadata: allow extra keys within defaults (#4743) - ## 2.7.0-alpha.3 * Implements Skolem IRIs instead of blank nodes, can be disabled using `iri: false` (#4731) diff --git a/behat.yml.dist b/behat.yml.dist index 2ecdb71e7c6..7675928087b 100644 --- a/behat.yml.dist +++ b/behat.yml.dist @@ -2,17 +2,17 @@ default: suites: default: contexts: - - 'ApiPlatform\Core\Tests\Behat\CommandContext' - - 'ApiPlatform\Core\Tests\Behat\DoctrineContext' - - 'ApiPlatform\Core\Tests\Behat\GraphqlContext' - - 'ApiPlatform\Core\Tests\Behat\JsonContext' - - 'ApiPlatform\Core\Tests\Behat\HydraContext' - - 'ApiPlatform\Core\Tests\Behat\OpenApiContext' - - 'ApiPlatform\Core\Tests\Behat\HttpCacheContext' - - 'ApiPlatform\Core\Tests\Behat\JsonApiContext' - - 'ApiPlatform\Core\Tests\Behat\JsonHalContext' - - 'ApiPlatform\Core\Tests\Behat\MercureContext' - - 'ApiPlatform\Core\Tests\Behat\XmlContext' + - 'ApiPlatform\Tests\Behat\CommandContext' + - 'ApiPlatform\Tests\Behat\DoctrineContext' + - 'ApiPlatform\Tests\Behat\GraphqlContext' + - 'ApiPlatform\Tests\Behat\JsonContext' + - 'ApiPlatform\Tests\Behat\HydraContext' + - 'ApiPlatform\Tests\Behat\OpenApiContext' + - 'ApiPlatform\Tests\Behat\HttpCacheContext' + - 'ApiPlatform\Tests\Behat\JsonApiContext' + - 'ApiPlatform\Tests\Behat\JsonHalContext' + - 'ApiPlatform\Tests\Behat\MercureContext' + - 'ApiPlatform\Tests\Behat\XmlContext' - 'Behat\MinkExtension\Context\MinkContext' - 'behatch:context:rest' filters: @@ -38,17 +38,17 @@ postgres: default: false postgres: &postgres-suite contexts: - - 'ApiPlatform\Core\Tests\Behat\CommandContext' - - 'ApiPlatform\Core\Tests\Behat\DoctrineContext' - - 'ApiPlatform\Core\Tests\Behat\GraphqlContext' - - 'ApiPlatform\Core\Tests\Behat\JsonContext' - - 'ApiPlatform\Core\Tests\Behat\HydraContext' - - 'ApiPlatform\Core\Tests\Behat\OpenApiContext' - - 'ApiPlatform\Core\Tests\Behat\HttpCacheContext' - - 'ApiPlatform\Core\Tests\Behat\JsonApiContext' - - 'ApiPlatform\Core\Tests\Behat\JsonHalContext' - - 'ApiPlatform\Core\Tests\Behat\MercureContext' - - 'ApiPlatform\Core\Tests\Behat\XmlContext' + - 'ApiPlatform\Tests\Behat\CommandContext' + - 'ApiPlatform\Tests\Behat\DoctrineContext' + - 'ApiPlatform\Tests\Behat\GraphqlContext' + - 'ApiPlatform\Tests\Behat\JsonContext' + - 'ApiPlatform\Tests\Behat\HydraContext' + - 'ApiPlatform\Tests\Behat\OpenApiContext' + - 'ApiPlatform\Tests\Behat\HttpCacheContext' + - 'ApiPlatform\Tests\Behat\JsonApiContext' + - 'ApiPlatform\Tests\Behat\JsonHalContext' + - 'ApiPlatform\Tests\Behat\MercureContext' + - 'ApiPlatform\Tests\Behat\XmlContext' - 'Behat\MinkExtension\Context\MinkContext' - 'behatch:context:rest' filters: @@ -59,17 +59,17 @@ mongodb: default: false mongodb: &mongodb-suite contexts: - - 'ApiPlatform\Core\Tests\Behat\CommandContext' - - 'ApiPlatform\Core\Tests\Behat\DoctrineContext' - - 'ApiPlatform\Core\Tests\Behat\GraphqlContext' - - 'ApiPlatform\Core\Tests\Behat\JsonContext' - - 'ApiPlatform\Core\Tests\Behat\HydraContext' - - 'ApiPlatform\Core\Tests\Behat\OpenApiContext' - - 'ApiPlatform\Core\Tests\Behat\HttpCacheContext' - - 'ApiPlatform\Core\Tests\Behat\JsonApiContext' - - 'ApiPlatform\Core\Tests\Behat\JsonHalContext' - - 'ApiPlatform\Core\Tests\Behat\MercureContext' - - 'ApiPlatform\Core\Tests\Behat\XmlContext' + - 'ApiPlatform\Tests\Behat\CommandContext' + - 'ApiPlatform\Tests\Behat\DoctrineContext' + - 'ApiPlatform\Tests\Behat\GraphqlContext' + - 'ApiPlatform\Tests\Behat\JsonContext' + - 'ApiPlatform\Tests\Behat\HydraContext' + - 'ApiPlatform\Tests\Behat\OpenApiContext' + - 'ApiPlatform\Tests\Behat\HttpCacheContext' + - 'ApiPlatform\Tests\Behat\JsonApiContext' + - 'ApiPlatform\Tests\Behat\JsonHalContext' + - 'ApiPlatform\Tests\Behat\MercureContext' + - 'ApiPlatform\Tests\Behat\XmlContext' - 'Behat\MinkExtension\Context\MinkContext' - 'behatch:context:rest' filters: @@ -82,9 +82,9 @@ elasticsearch: paths: - '%paths.base%/features/elasticsearch' contexts: - - 'ApiPlatform\Core\Tests\Behat\CommandContext' - - 'ApiPlatform\Core\Tests\Behat\ElasticsearchContext' - - 'ApiPlatform\Core\Tests\Behat\JsonContext' + - 'ApiPlatform\Tests\Behat\CommandContext' + - 'ApiPlatform\Tests\Behat\ElasticsearchContext' + - 'ApiPlatform\Tests\Behat\JsonContext' - 'Behat\MinkExtension\Context\MinkContext' - 'behatch:context:rest' filters: @@ -94,18 +94,18 @@ default-coverage: suites: default: &default-coverage-suite contexts: - - 'ApiPlatform\Core\Tests\Behat\CommandContext' - - 'ApiPlatform\Core\Tests\Behat\DoctrineContext' - - 'ApiPlatform\Core\Tests\Behat\GraphqlContext' - - 'ApiPlatform\Core\Tests\Behat\JsonContext' - - 'ApiPlatform\Core\Tests\Behat\HydraContext' - - 'ApiPlatform\Core\Tests\Behat\OpenApiContext' - - 'ApiPlatform\Core\Tests\Behat\HttpCacheContext' - - 'ApiPlatform\Core\Tests\Behat\JsonApiContext' - - 'ApiPlatform\Core\Tests\Behat\JsonHalContext' - - 'ApiPlatform\Core\Tests\Behat\MercureContext' - - 'ApiPlatform\Core\Tests\Behat\CoverageContext' - - 'ApiPlatform\Core\Tests\Behat\XmlContext' + - 'ApiPlatform\Tests\Behat\CommandContext' + - 'ApiPlatform\Tests\Behat\DoctrineContext' + - 'ApiPlatform\Tests\Behat\GraphqlContext' + - 'ApiPlatform\Tests\Behat\JsonContext' + - 'ApiPlatform\Tests\Behat\HydraContext' + - 'ApiPlatform\Tests\Behat\OpenApiContext' + - 'ApiPlatform\Tests\Behat\HttpCacheContext' + - 'ApiPlatform\Tests\Behat\JsonApiContext' + - 'ApiPlatform\Tests\Behat\JsonHalContext' + - 'ApiPlatform\Tests\Behat\MercureContext' + - 'ApiPlatform\Tests\Behat\CoverageContext' + - 'ApiPlatform\Tests\Behat\XmlContext' - 'Behat\MinkExtension\Context\MinkContext' - 'behatch:context:rest' @@ -115,18 +115,18 @@ mongodb-coverage: mongodb: &mongodb-coverage-suite <<: *mongodb-suite contexts: - - 'ApiPlatform\Core\Tests\Behat\CommandContext' - - 'ApiPlatform\Core\Tests\Behat\DoctrineContext' - - 'ApiPlatform\Core\Tests\Behat\GraphqlContext' - - 'ApiPlatform\Core\Tests\Behat\JsonContext' - - 'ApiPlatform\Core\Tests\Behat\HydraContext' - - 'ApiPlatform\Core\Tests\Behat\OpenApiContext' - - 'ApiPlatform\Core\Tests\Behat\HttpCacheContext' - - 'ApiPlatform\Core\Tests\Behat\JsonApiContext' - - 'ApiPlatform\Core\Tests\Behat\JsonHalContext' - - 'ApiPlatform\Core\Tests\Behat\MercureContext' - - 'ApiPlatform\Core\Tests\Behat\CoverageContext' - - 'ApiPlatform\Core\Tests\Behat\XmlContext' + - 'ApiPlatform\Tests\Behat\CommandContext' + - 'ApiPlatform\Tests\Behat\DoctrineContext' + - 'ApiPlatform\Tests\Behat\GraphqlContext' + - 'ApiPlatform\Tests\Behat\JsonContext' + - 'ApiPlatform\Tests\Behat\HydraContext' + - 'ApiPlatform\Tests\Behat\OpenApiContext' + - 'ApiPlatform\Tests\Behat\HttpCacheContext' + - 'ApiPlatform\Tests\Behat\JsonApiContext' + - 'ApiPlatform\Tests\Behat\JsonHalContext' + - 'ApiPlatform\Tests\Behat\MercureContext' + - 'ApiPlatform\Tests\Behat\CoverageContext' + - 'ApiPlatform\Tests\Behat\XmlContext' - 'Behat\MinkExtension\Context\MinkContext' - 'behatch:context:rest' @@ -136,9 +136,9 @@ elasticsearch-coverage: elasticsearch: &elasticsearch-coverage-suite <<: *elasticsearch-suite contexts: - - 'ApiPlatform\Core\Tests\Behat\CommandContext' - - 'ApiPlatform\Core\Tests\Behat\ElasticsearchContext' - - 'ApiPlatform\Core\Tests\Behat\JsonContext' - - 'ApiPlatform\Core\Tests\Behat\CoverageContext' + - 'ApiPlatform\Tests\Behat\CommandContext' + - 'ApiPlatform\Tests\Behat\ElasticsearchContext' + - 'ApiPlatform\Tests\Behat\JsonContext' + - 'ApiPlatform\Tests\Behat\CoverageContext' - 'Behat\MinkExtension\Context\MinkContext' - 'behatch:context:rest' diff --git a/composer.json b/composer.json index 5826a3a7400..d262f4f25b7 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ } ], "require": { - "php": ">=7.1", + "php": ">=8.1", "doctrine/inflector": "^1.0 || ^2.0", "fig/link-util": "^1.0", "psr/cache": "^1.0 || ^2.0 || ^3.0", @@ -29,7 +29,6 @@ "require-dev": { "behat/behat": "^3.1", "behat/mink": "^1.9@dev", - "doctrine/annotations": "^1.7", "doctrine/cache": "^1.11 || ^2.1", "doctrine/common": "^2.11 || ^3.0", "doctrine/data-fixtures": "^1.2.2", @@ -115,14 +114,10 @@ "autoload": { "psr-4": { "ApiPlatform\\": "src/" - }, - "files": [ - "src/deprecation.php" - ] + } }, "autoload-dev": { "psr-4": { - "ApiPlatform\\Core\\Tests\\": "tests/Core/", "ApiPlatform\\Tests\\": "tests/", "App\\": "tests/Fixtures/app/var/tmp/src/" } @@ -139,7 +134,7 @@ }, "extra": { "branch-alias": { - "dev-main": "2.7.x-dev" + "dev-main": "3.0.x-dev" }, "symfony": { "require": "^3.4 || ^4.4 || ^5.1 || ^6.0" diff --git a/features/graphql/input_output.feature b/features/graphql/input_output.feature index 6dad98666d4..1a5bcb166b5 100644 --- a/features/graphql/input_output.feature +++ b/features/graphql/input_output.feature @@ -27,14 +27,12 @@ Feature: GraphQL DTO input and output "bat": "OutputDto/bat", "relatedDummies": "OutputDto/relatedDummies" }, - "@type": "DummyDtoInputOutput", - "@id": "/dummy_dto_input_outputs/1", + "@type": "OutputDto", "id": 1, "baz": 1, "bat": "test", "relatedDummies": [ { - "@context": "/contexts/RelatedDummy", "@id": "/related_dummies/1", "@type": "https://schema.org/Product", "name": "RelatedDummy with friends", diff --git a/features/graphql/introspection.feature b/features/graphql/introspection.feature index e60211419c6..22934bbbb31 100644 --- a/features/graphql/introspection.feature +++ b/features/graphql/introspection.feature @@ -565,4 +565,4 @@ Feature: GraphQL introspection support And the header "Content-Type" should be equal to "application/json" And the JSON node "errors[0].debugMessage" should be equal to 'Type with id "VoDummyInspectionCursorConnection" is not present in the types container' And the JSON node "data.typeNotAvailable" should be null - And the JSON node "data.typeOwner.fields[3].type.name" should be equal to "VoDummyInspectionCursorConnection" + And the JSON node "data.typeOwner.fields[1].type.name" should be equal to "VoDummyInspectionCursorConnection" diff --git a/features/graphql/mutation.feature b/features/graphql/mutation.feature index 4dadbdd19d9..01b38f97e17 100644 --- a/features/graphql/mutation.feature +++ b/features/graphql/mutation.feature @@ -819,26 +819,6 @@ Feature: GraphQL mutation support And the JSON node "data.testCustomArgumentsDummyCustomMutation.dummyCustomMutation.result" should be equal to "18" And the JSON node "data.testCustomArgumentsDummyCustomMutation.clientMutationId" should be equal to "myId" - Scenario: Execute a custom mutation with output - When I send the following GraphQL request: - """ - mutation { - testOutputDummyCustomMutation(input: {id: "/dummy_custom_mutations/1", operandA: 9, clientMutationId: "myId"}) { - dummyCustomMutation { - baz - bat - } - clientMutationId - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON node "data.testOutputDummyCustomMutation.dummyCustomMutation.baz" should be equal to "98" - And the JSON node "data.testOutputDummyCustomMutation.dummyCustomMutation.bat" should be equal to "9" - And the JSON node "data.testOutputDummyCustomMutation.clientMutationId" should be equal to "myId" - Scenario: Uploading a file with a custom mutation Given I have the following file for a GraphQL request: | name | file | diff --git a/features/graphql/query.feature b/features/graphql/query.feature index 4a7a0464acd..4a893973062 100644 --- a/features/graphql/query.feature +++ b/features/graphql/query.feature @@ -432,32 +432,6 @@ Feature: GraphQL query support } """ - Scenario: Custom item query with output - Given there are 2 dummyCustomQuery objects - When I send the following GraphQL request: - """ - { - testItemOutputDummyCustomQuery(id: "/dummy_custom_queries/1",) { - baz - bat - } - } - """ - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json" - And the JSON should be equal to: - """ - { - "data": { - "testItemOutputDummyCustomQuery": { - "baz": 46, - "bat": "Success!" - } - } - } - """ - @createSchema Scenario: Retrieve an item with different serialization groups for item_query and collection_query Given there are 1 dummy with different GraphQL serialization groups objects diff --git a/features/hal/input_output.feature b/features/hal/input_output.feature index 531c349c280..c7acbe989f0 100644 --- a/features/hal/input_output.feature +++ b/features/hal/input_output.feature @@ -2,7 +2,6 @@ Feature: HAL DTO input and output In order to use a hypermedia API As a client software developer I need to be able to use DTOs on my resources as Input or Output objects. - TODO: in 3.0 we should change the generated IRIs, /dummy_dto_custom_output/1 should retrieve /dummy_dto_custom_output/1 not /dumy_dto_customs/1 for the collection we can search for an Operation with the same Output class as the given one for the collection Background: @@ -18,11 +17,6 @@ Feature: HAL DTO input and output And the JSON should be a superset of: """ { - "_links": { - "self": { - "href": "/dummy_dto_customs/1" - } - }, "foo": "test", "bar": 1 } @@ -41,20 +35,10 @@ Feature: HAL DTO input and output "_embedded": { "item": [ { - "_links": { - "self": { - "href": "/dummy_dto_customs/1" - } - }, "foo": "test", "bar": 1 }, { - "_links": { - "self": { - "href": "/dummy_dto_customs/2" - } - }, "foo": "test", "bar": 2 } diff --git a/features/jsonapi/input_output.feature b/features/jsonapi/input_output.feature index 71fc0d377da..1fb6771081d 100644 --- a/features/jsonapi/input_output.feature +++ b/features/jsonapi/input_output.feature @@ -19,8 +19,7 @@ Feature: JSON API DTO input and output """ { "data": { - "id": "/dummy_dto_customs/1", - "type": "DummyDtoCustom", + "type": "CustomOutputDto", "attributes": { "foo": "test", "bar": 1 @@ -42,16 +41,14 @@ Feature: JSON API DTO input and output { "data": [ { - "id": "/dummy_dto_customs/1", - "type": "DummyDtoCustom", + "type": "CustomOutputDto", "attributes": { "foo": "test", "bar": 1 } }, { - "id": "/dummy_dto_customs/2", - "type": "DummyDtoCustom", + "type": "CustomOutputDto", "attributes": { "foo": "test", "bar": 2 diff --git a/features/jsonld/context.feature b/features/jsonld/context.feature index 45f324a742f..2c7b71e6ac5 100644 --- a/features/jsonld/context.feature +++ b/features/jsonld/context.feature @@ -79,7 +79,7 @@ Feature: JSON-LD contexts generation "@vocab": "http://example.com/docs.jsonld#", "hydra": "http://www.w3.org/ns/hydra/core#", "person": { - "@id": "http://example.com/id", + "@id": "https://example.com/id", "@type": "@id", "foo": "bar" } diff --git a/features/jsonld/input_output.feature b/features/jsonld/input_output.feature index ad7ee0344e8..eb5b65514c0 100644 --- a/features/jsonld/input_output.feature +++ b/features/jsonld/input_output.feature @@ -38,7 +38,7 @@ Feature: JSON-LD DTO input and output Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: + And the JSON should be a superset of: """ { "@context": { @@ -47,8 +47,7 @@ Feature: JSON-LD DTO input and output "foo": "CustomOutputDto/foo", "bar": "CustomOutputDto/bar" }, - "@type": "DummyDtoCustom", - "@id": "/dummy_dto_customs/1", + "@type": "CustomOutputDto", "foo": "test", "bar": 1 } @@ -61,22 +60,20 @@ Feature: JSON-LD DTO input and output Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: + And the JSON should be a superset of: """ { "@context": "/contexts/DummyDtoCustom", - "@id": "/dummy_dto_customs", + "@id": "/dummy_dto_custom_output", "@type": "hydra:Collection", "hydra:member": [ { - "@type": "DummyDtoCustom", - "@id": "/dummy_dto_customs/1", + "@type": "CustomOutputDto", "foo": "test", "bar": 1 }, { - "@type": "DummyDtoCustom", - "@id": "/dummy_dto_customs/2", + "@type": "CustomOutputDto", "foo": "test", "bar": 2 } @@ -85,44 +82,6 @@ Feature: JSON-LD DTO input and output } """ - @createSchema - Scenario: Get an item with same class as custom output - Given there is a DummyDtoOutputSameClass - When I send a "GET" request to "/dummy_dto_output_same_classes/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/DummyDtoOutputSameClass", - "@id": "/dummy_dto_output_same_classes/1", - "@type": "DummyDtoOutputSameClass", - "lorem": "test", - "ipsum": "modified", - "id": 1 - } - """ - - @createSchema - Scenario: Get an item with a data transformer that will return the original class as a fallback - Given there is a DummyDtoOutputFallbackToSameClass - When I send a "GET" request to "/dummy_dto_output_fallback_to_same_classes/1" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: - """ - { - "@context": "/contexts/DummyDtoOutputFallbackToSameClass", - "@id": "/dummy_dto_output_fallback_to_same_classes/1", - "@type": "DummyDtoOutputFallbackToSameClass", - "lorem": "test", - "ipsum": "modified", - "id": 1 - } - """ - @createSchema Scenario: Create a DummyDtoCustom object without output When I send a "POST" request to "/dummy_dto_custom_post_without_output" with body: @@ -145,7 +104,7 @@ Feature: JSON-LD DTO input and output } """ Then the response status code should be 201 - And the JSON should be equal to: + And the JSON should be a superset of: """ { "@context": { @@ -156,8 +115,7 @@ Feature: JSON-LD DTO input and output "bat": "OutputDto/bat", "relatedDummies": "OutputDto/relatedDummies" }, - "@type": "DummyDtoInputOutput", - "@id": "/dummy_dto_input_outputs/1", + "@type": "OutputDto", "id": 1, "baz": 1, "bat": "test", @@ -174,7 +132,7 @@ Feature: JSON-LD DTO input and output } """ Then the response status code should be 200 - And the JSON should be equal to: + And the JSON should be a superset of: """ { "@context": { @@ -185,8 +143,7 @@ Feature: JSON-LD DTO input and output "bat": "OutputDto/bat", "relatedDummies": "OutputDto/relatedDummies" }, - "@type": "DummyDtoInputOutput", - "@id": "/dummy_dto_input_outputs/1", + "@type": "OutputDto", "id": 1, "baz": 2, "bat": "test", @@ -215,7 +172,7 @@ Feature: JSON-LD DTO input and output } """ Then the response status code should be 200 - And the JSON should be equal to: + And the JSON should be a superset of: """ { "@context": { @@ -223,8 +180,7 @@ Feature: JSON-LD DTO input and output "hydra": "http://www.w3.org/ns/hydra/core#", "dummy": "RecoverPasswordOutput/dummy" }, - "@type": "User", - "@id": "/users/1", + "@type": "RecoverPasswordOutput", "dummy": "/dummies/1" } """ @@ -235,7 +191,7 @@ Feature: JSON-LD DTO input and output Then the response status code should be 201 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: + And the JSON should be a superset of: """ { "@context": { @@ -246,8 +202,7 @@ Feature: JSON-LD DTO input and output "bat": "OutputDto/bat", "relatedDummies": "OutputDto/relatedDummies" }, - "@type": "DummyDtoNoInput", - "@id": "/dummy_dto_no_inputs/1", + "@type": "OutputDto", "id": 1, "baz": 1, "bat": "test", @@ -260,7 +215,7 @@ Feature: JSON-LD DTO input and output Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" - And the JSON should be equal to: + And the JSON should be a superset of: """ { "@context": { @@ -271,8 +226,7 @@ Feature: JSON-LD DTO input and output "bat": "OutputDto/bat", "relatedDummies": "OutputDto/relatedDummies" }, - "@type": "DummyDtoNoInput", - "@id": "/dummy_dto_no_inputs/1", + "@type": "OutputDto", "id": 1, "baz": 1, "bat": "testtest", diff --git a/features/main/custom_identifier_with_subresource.feature b/features/main/custom_identifier_with_subresource.feature index cf18a15a3c9..773dd872866 100644 --- a/features/main/custom_identifier_with_subresource.feature +++ b/features/main/custom_identifier_with_subresource.feature @@ -84,7 +84,7 @@ Feature: Using custom parent identifier for subresources """ { "@context": "/contexts/SlugParentDummy", - "@id": "/slug_parent_dummies/parent-dummy", + "@id": "/slug_child_dummies/child-dummy/parent_dummy", "@type": "SlugParentDummy", "id": 1, "slug": "parent-dummy", diff --git a/features/main/custom_operation.feature b/features/main/custom_operation.feature index 6b8e4a7bc82..9fa829ec5c7 100644 --- a/features/main/custom_operation.feature +++ b/features/main/custom_operation.feature @@ -64,7 +64,7 @@ Feature: Custom operation """ { "@context": "/contexts/CustomActionDummy", - "@id": "/custom_action_dummies/1", + "@id": "/custom_action_collection_dummies/1", "@type": "CustomActionDummy", "id": 1, "foo": "custom!" diff --git a/features/main/default_order.feature b/features/main/default_order.feature index 1bc97ab37c1..91211014315 100644 --- a/features/main/default_order.feature +++ b/features/main/default_order.feature @@ -127,7 +127,7 @@ Feature: Default order """ { "@context": "/contexts/Foo", - "@id": "/foos", + "@id": "/custom_collection_asc_foos", "@type": "hydra:Collection", "hydra:member": [ { @@ -183,7 +183,7 @@ Feature: Default order """ { "@context": "/contexts/Foo", - "@id": "/foos", + "@id": "/custom_collection_desc_foos", "@type": "hydra:Collection", "hydra:member": [ { diff --git a/features/main/operation.feature b/features/main/operation.feature index 24dda9b8746..8786f6b04ef 100644 --- a/features/main/operation.feature +++ b/features/main/operation.feature @@ -47,7 +47,7 @@ Feature: Operation support """ { "@context": "/contexts/EmbeddedDummy", - "@id": "/embedded_dummies/1", + "@id": "/embedded_dummies_groups/1", "@type": "EmbeddedDummy", "name": "Dummy #1", "embeddedDummy": { @@ -76,7 +76,7 @@ Feature: Operation support """ { "@context": "/contexts/Book", - "@id": "/books/1", + "@id": "/books/by_isbn/9780451524935", "@type": "Book", "name": "1984", "isbn": "9780451524935", diff --git a/features/main/operation_resource.feature b/features/main/operation_resource.feature index 42794174264..a30054294fb 100644 --- a/features/main/operation_resource.feature +++ b/features/main/operation_resource.feature @@ -28,6 +28,7 @@ Feature: Resource operations """ {"name": "Patched"} """ + Then print last JSON response Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" diff --git a/features/main/overridden_operation.feature b/features/main/overridden_operation.feature index 4d57a700a03..d07181de8bd 100644 --- a/features/main/overridden_operation.feature +++ b/features/main/overridden_operation.feature @@ -153,4 +153,4 @@ Feature: Create-Retrieve-Update-Delete with a Overridden Operation context """ Then the response status code should be 200 And the JSON node "success" should be equal to "YES" - And the JSON node "@type" should be equal to "RPC" + And the JSON node "@type" should be equal to "RPCOutput" diff --git a/features/main/subresource.feature b/features/main/subresource.feature index ae4969a5abf..2fd278ea02e 100644 --- a/features/main/subresource.feature +++ b/features/main/subresource.feature @@ -13,7 +13,7 @@ Feature: Subresource support """ { "@context": "/contexts/Answer", - "@id": "/answers/1", + "@id": "/questions/1/answer", "@type": "Answer", "id": 1, "content": "42", @@ -234,7 +234,7 @@ Feature: Subresource support """ { "@context": "/contexts/RelatedDummy", - "@id": "/related_dummies/2", + "@id": "/dummies/1/related_dummies/2", "@type": "https://schema.org/Product", "id": 2, "name": null, @@ -274,7 +274,7 @@ Feature: Subresource support """ { "@context": "/contexts/ThirdLevel", - "@id": "/third_levels/1", + "@id": "/dummies/1/related_dummies/1/third_level", "@type": "ThirdLevel", "fourthLevel": "/fourth_levels/1", "badFourthLevel": null, @@ -293,7 +293,7 @@ Feature: Subresource support """ { "@context": "/contexts/FourthLevel", - "@id": "/fourth_levels/1", + "@id": "/dummies/1/related_dummies/1/third_level/fourth_level", "@type": "FourthLevel", "badThirdLevel": [], "id": 1, @@ -411,7 +411,7 @@ Feature: Subresource support """ { "@context": "/contexts/Dummy", - "@id": "/dummies/1", + "@id": "/related_owned_dummies/1/owning_dummy", "@type": "Dummy", "description": null, "dummy": null, @@ -444,7 +444,7 @@ Feature: Subresource support """ { "@context": "/contexts/Dummy", - "@id": "/dummies/1", + "@id": "/related_owning_dummies/1/owned_dummy", "@type": "Dummy", "description": null, "dummy": null, diff --git a/features/openapi/docs.feature b/features/openapi/docs.feature index 624e18632d7..3e9fda188bc 100644 --- a/features/openapi/docs.feature +++ b/features/openapi/docs.feature @@ -5,7 +5,7 @@ Feature: Documentation support @createSchema Scenario: Retrieve the OpenAPI documentation - Given I send a "GET" request to "/docs.json?spec_version=3" + Given I send a "GET" request to "/docs.json" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/json; charset=utf-8" @@ -219,112 +219,15 @@ Feature: Documentation support } """ - @createSchema - Scenario: Retrieve the Swagger documentation - Given I send a "GET" request to "/docs.json?spec_version=2" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json; charset=utf-8" - # Context - And the JSON node "swagger" should be equal to "2.0" - # Root properties - And the JSON node "info.title" should be equal to "My Dummy API" - And the JSON node "info.description" should contain "This is a test API." - And the JSON node "info.description" should contain "Made with love" - # Supported classes - And the Swagger class "AbstractDummy" exists - And the Swagger class "CircularReference" exists - And the Swagger class "CircularReference-circular" exists - And the Swagger class "CompositeItem" exists - And the Swagger class "CompositeLabel" exists - And the Swagger class "ConcreteDummy" exists - And the Swagger class "CustomIdentifierDummy" exists - And the Swagger class "CustomNormalizedDummy-input" exists - And the Swagger class "CustomNormalizedDummy-output" exists - And the Swagger class "CustomWritableIdentifierDummy" exists - And the Swagger class "Dummy" exists - And the Swagger class "DummyBoolean" exists - And the Swagger class "RelatedDummy" exists - And the Swagger class "DummyTableInheritance" exists - And the Swagger class "DummyTableInheritanceChild" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_get" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_put" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_read" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_write" exists - And the Swagger class "RelatedDummy" exists - And the Swagger class "NoCollectionDummy" exists - And the Swagger class "RelatedToDummyFriend" exists - And the Swagger class "RelatedToDummyFriend-fakemanytomany" exists - And the Swagger class "DummyFriend" exists - And the Swagger class "RelationEmbedder-barcelona" exists - And the Swagger class "RelationEmbedder-chicago" exists - And the Swagger class "User-user_user-read" exists - And the Swagger class "User-user_user-write" exists - And the Swagger class "UuidIdentifierDummy" exists - And the Swagger class "ThirdLevel" exists - And the Swagger class "ParentDummy" doesn't exist - And the Swagger class "UnknownDummy" doesn't exist - And the Swagger path "/relation_embedders/{id}/custom" exists - And the Swagger path "/override/swagger" exists - And the Swagger path "/api/custom-call/{id}" exists - And the JSON node "paths./api/custom-call/{id}.get" should exist - And the JSON node "paths./api/custom-call/{id}.put" should exist - # Properties - And the "id" property exists for the Swagger class "Dummy" - And the "name" property is required for the Swagger class "Dummy" - # Enable these tests when SF 4.4 / PHP 7.1 support is dropped - #And the "isDummyBoolean" property exists for the Swagger class "DummyBoolean" - #And the "isDummyBoolean" property is not read only for the Swagger class "DummyBoolean" - # Filters - And the JSON node "paths./dummies.get.parameters[0].name" should be equal to "dummyBoolean" - And the JSON node "paths./dummies.get.parameters[0].in" should be equal to "query" - And the JSON node "paths./dummies.get.parameters[0].required" should be false - - # Subcollection - check filter on subResource - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].name" should be equal to "id" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].in" should be equal to "path" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].required" should be true - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].name" should be equal to "name" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].required" should be false - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].name" should be equal to "description" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].required" should be false - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[3].name" should be equal to "page" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[3].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[3].required" should be false - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[4].name" should be equal to "itemsPerPage" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[4].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[4].required" should be false - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[5].name" should be equal to "pagination" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[5].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[5].required" should be false - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters" should have 6 elements - - # Deprecations - And the JSON node "paths./dummies.get.deprecated" should not exist - And the JSON node "paths./deprecated_resources.get.deprecated" should be true - And the JSON node "paths./deprecated_resources.post.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.get.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.delete.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.put.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.patch.deprecated" should be true - Scenario: OpenAPI UI is enabled for docs endpoint Given I add "Accept" header equal to "text/html" - And I send a "GET" request to "/docs?spec_version=3" + And I send a "GET" request to "/docs" Then the response status code should be 200 And I should see text matching "My Dummy API" And I should see text matching "openapi" Scenario: OpenAPI extension properties is enabled in JSON docs - Given I send a "GET" request to "/docs.json?spec_version=3" + Given I send a "GET" request to "/docs.json" Then the response status code should be 200 And the response should be in JSON And the header "Content-Type" should be equal to "application/json; charset=utf-8" @@ -332,6 +235,16 @@ Feature: Documentation support Scenario: OpenAPI UI is enabled for an arbitrary endpoint Given I add "Accept" header equal to "text/html" - And I send a "GET" request to "/dummies?spec_version=3" + And I send a "GET" request to "/dummies" Then the response status code should be 200 And I should see text matching "openapi" + + Scenario: Retrieve the OpenAPI documentation with API Gateway compatibility + Given I send a "GET" request to "/docs.json?api_gateway=true" + Then the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/json; charset=utf-8" + And the JSON node "basePath" should be equal to "/" + And the JSON node "components.schemas.RamseyUuidDummy.properties.id.description" should be equal to "The dummy id." + And the JSON node "components.schemas.RelatedDummy-barcelona" should not exist + And the JSON node "components.schemas.RelatedDummybarcelona" should exist diff --git a/features/swagger/docs.feature b/features/swagger/docs.feature deleted file mode 100644 index 9b14c8353c6..00000000000 --- a/features/swagger/docs.feature +++ /dev/null @@ -1,137 +0,0 @@ -Feature: Documentation support - In order to build an auto-discoverable API - As a client software developer - I need to know Swagger specifications of objects I send and receive - - @createSchema - Scenario: Retrieve the Swagger/OpenAPI documentation - Given I send a "GET" request to "/docs.json" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json; charset=utf-8" - # OverrideDocumentationNormalizer - And the JSON node "definitions.RamseyUuidDummy.properties.id.description" should be equal to "The dummy id" - And the JSON node "definitions.RelatedDummy-barcelona" should exist - And the JSON node "definitions.RelatedDummybarcelona" should not exist - # Context - And the JSON node "swagger" should be equal to "2.0" - # Root properties - And the JSON node "info.title" should be equal to "My Dummy API" - And the JSON node "info.description" should contain "This is a test API." - And the JSON node "info.description" should contain "Made with love" - # Supported classes - And the Swagger class "AbstractDummy" exists - And the Swagger class "CircularReference" exists - And the Swagger class "CircularReference-circular" exists - And the Swagger class "CompositeItem" exists - And the Swagger class "CompositeLabel" exists - And the Swagger class "ConcreteDummy" exists - And the Swagger class "CustomIdentifierDummy" exists - And the Swagger class "CustomNormalizedDummy-input" exists - And the Swagger class "CustomNormalizedDummy-output" exists - And the Swagger class "CustomWritableIdentifierDummy" exists - And the Swagger class "Dummy" exists - And the Swagger class "RelatedDummy" exists - And the Swagger class "DummyTableInheritance" exists - And the Swagger class "DummyTableInheritanceChild" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_get" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_put" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_read" exists - And the Swagger class "OverriddenOperationDummy-overridden_operation_dummy_write" exists - And the Swagger class "RelatedDummy" exists - And the Swagger class "NoCollectionDummy" exists - And the Swagger class "RelatedToDummyFriend" exists - And the Swagger class "RelatedToDummyFriend-fakemanytomany" exists - And the Swagger class "DummyFriend" exists - And the Swagger class "RelationEmbedder-barcelona" exists - And the Swagger class "RelationEmbedder-chicago" exists - And the Swagger class "User-user_user-read" exists - And the Swagger class "User-user_user-write" exists - And the Swagger class "UuidIdentifierDummy" exists - And the Swagger class "ThirdLevel" exists - And the Swagger class "ParentDummy" doesn't exist - And the Swagger class "UnknownDummy" doesn't exist - And the Swagger path "/relation_embedders/{id}/custom" exists - And the Swagger path "/override/swagger" exists - And the Swagger path "/api/custom-call/{id}" exists - And the JSON node "paths./api/custom-call/{id}.get" should exist - And the JSON node "paths./api/custom-call/{id}.put" should exist - # Properties - And the "id" property exists for the Swagger class "Dummy" - And the "name" property is required for the Swagger class "Dummy" - # Filters - And the JSON node "paths./dummies.get.parameters[0].name" should be equal to "dummyBoolean" - And the JSON node "paths./dummies.get.parameters[0].in" should be equal to "query" - And the JSON node "paths./dummies.get.parameters[0].required" should be false - And the JSON node "paths./dummies.get.parameters[0].type" should be equal to "boolean" - - # Subcollection - check filter on subResource - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].name" should be equal to "id" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].in" should be equal to "path" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].required" should be true - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[0].type" should be equal to "string" - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].name" should be equal to "name" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].required" should be false - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[1].type" should be equal to "string" - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].name" should be equal to "description" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].required" should be false - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[2].type" should be equal to "string" - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[3].name" should be equal to "page" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[3].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[3].required" should be false - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[3].type" should be equal to "integer" - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[4].name" should be equal to "itemsPerPage" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[4].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[4].required" should be false - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[4].type" should be equal to "integer" - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[5].name" should be equal to "pagination" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[5].in" should be equal to "query" - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[5].required" should be false - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters[5].type" should be equal to "boolean" - - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters" should have 6 elements - - # Subcollection - check schema - And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.responses.200.schema.items.$ref" should be equal to "#/definitions/RelatedToDummyFriend-fakemanytomany" - - # Deprecations - And the JSON node "paths./dummies.get.deprecated" should not exist - And the JSON node "paths./deprecated_resources.get.deprecated" should be true - And the JSON node "paths./deprecated_resources.post.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.get.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.delete.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.put.deprecated" should be true - And the JSON node "paths./deprecated_resources/{id}.patch.deprecated" should be true - - Scenario: Swagger UI is enabled for docs endpoint - Given I add "Accept" header equal to "text/html" - And I send a "GET" request to "/docs" - Then the response status code should be 200 - And I should see text matching "My Dummy API" - And I should see text matching "swagger" - And I should see text matching "2.0" - - Scenario: Swagger UI is enabled for an arbitrary endpoint - Given I add "Accept" header equal to "text/html" - And I send a "GET" request to "/dummies" - Then the response status code should be 200 - And I should see text matching "My Dummy API" - And I should see text matching "swagger" - And I should see text matching "2.0" - - Scenario: Retrieve the Swagger/OpenAPI documentation with API Gateway compatibility - Given I send a "GET" request to "/docs.json?api_gateway=true" - Then the response status code should be 200 - And the response should be in JSON - And the header "Content-Type" should be equal to "application/json; charset=utf-8" - And the JSON node "basePath" should be equal to "/" - And the JSON node "definitions.RamseyUuidDummy.properties.id.description" should be equal to "The dummy id" - And the JSON node "definitions.RelatedDummy-barcelona" should not exist - And the JSON node "definitions.RelatedDummybarcelona" should exist diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c49003c29d1..799cd8d7b21 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -16,235 +16,10 @@ parameters: - src/Symfony/Bundle/Test/Constraint/ArraySubset.php - tests/Fixtures/app/AppKernel.php excludePaths: - - src/deprecation.php - - src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php - # Class ... extends final class cannot be ignored... - - src/Core/Action/EntrypointAction.php - - src/Core/Action/ExceptionAction.php - - src/Core/Action/NotFoundAction.php - - src/Core/Action/PlaceholderAction.php - - src/Core/Api/Entrypoint.php - - src/Core/Api/FormatMatcher.php - - src/Core/Api/ResourceClassResolver.php - - src/Core/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php - - src/Core/Bridge/Doctrine/EventListener/WriteListener.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Extension/FilterEagerLoadingExtension.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php - - src/Core/Bridge/Doctrine/MongoDbOdm/Paginator.php - - src/Core/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php - - src/Core/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php - - src/Core/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php - - src/Core/Bridge/Doctrine/Orm/Extension/FilterExtension.php - - src/Core/Bridge/Doctrine/Orm/Extension/OrderExtension.php - - src/Core/Bridge/Doctrine/Orm/Extension/PaginationExtension.php - - src/Core/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php - - src/Core/Bridge/Doctrine/Orm/Filter/AbstractFilter.php - - src/Core/Bridge/Doctrine/Orm/Filter/BooleanFilter.php - - src/Core/Bridge/Doctrine/Orm/Filter/DateFilter.php - - src/Core/Bridge/Doctrine/Orm/Filter/ExistsFilter.php - - src/Core/Bridge/Doctrine/Orm/Filter/NumericFilter.php - - src/Core/Bridge/Doctrine/Orm/Filter/OrderFilter.php - - src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php - - src/Core/Bridge/Doctrine/Orm/Paginator.php - - src/Core/Bridge/Doctrine/Orm/Util/QueryBuilderHelper.php - - src/Core/Bridge/Doctrine/Orm/Util/QueryChecker.php - - src/Core/Bridge/Doctrine/Orm/Util/QueryJoinParser.php - - src/Core/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php - - src/Core/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php - - src/Core/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php - - src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php - - src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php - - src/Core/Bridge/Elasticsearch/DataProvider/Paginator.php - - src/Core/Bridge/Elasticsearch/Exception/IndexNotFoundException.php - - src/Core/Bridge/Elasticsearch/Exception/NonUniqueIdentifierException.php - - src/Core/Bridge/Elasticsearch/Metadata/Document/DocumentMetadata.php - - src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php - - src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php - - src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php - - src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php - - src/Core/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php - - src/Core/Bridge/Elasticsearch/Serializer/ItemNormalizer.php - - src/Core/Bridge/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php - - src/Core/Bridge/RamseyUuid/Serializer/UuidDenormalizer.php - - src/Core/Bridge/Symfony/Bundle/ApiPlatformBundle.php - - src/Core/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php - - src/Core/Bridge/Symfony/Bundle/Command/OpenApiCommand.php - - src/Core/Bridge/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php - - src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php - - src/Core/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php - - src/Core/Bridge/Symfony/Bundle/SwaggerUi/SwaggerUiAction.php - - src/Core/Bridge/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php - - src/Core/Bridge/Symfony/Bundle/Test/Client.php - - src/Core/Bridge/Symfony/Bundle/Test/Response.php - - src/Core/Bridge/Symfony/Identifier/Normalizer/UuidNormalizer.php - - src/Core/Bridge/Symfony/Messenger/ContextStamp.php - - src/Core/Bridge/Symfony/Messenger/RemoveStamp.php - - src/Core/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyNameCollectionFactory.php - - src/Core/Bridge/Symfony/Routing/ApiLoader.php - - src/Core/Bridge/Symfony/Routing/Router.php - - src/Core/Bridge/Symfony/Validator/EventListener/ValidationExceptionListener.php - - src/Core/Bridge/Symfony/Validator/Exception/ValidationException.php - - src/Core/Bridge/Symfony/Validator/Validator.php - - src/Core/DataProvider/ArrayPaginator.php - - src/Core/DataProvider/PaginationOptions.php - - src/Core/DataProvider/TraversablePaginator.php - - src/Core/Documentation/Action/DocumentationAction.php - - src/Core/Documentation/Documentation.php - - src/Core/EventListener/AddFormatListener.php - - src/Core/EventListener/DeserializeListener.php - - src/Core/EventListener/EventPriorities.php - - src/Core/EventListener/ExceptionListener.php - - src/Core/EventListener/QueryParameterValidateListener.php - - src/Core/EventListener/RespondListener.php - - src/Core/EventListener/SerializeListener.php - - src/Core/Exception/FilterValidationException.php - - src/Core/Exception/InvalidIdentifierException.php - - src/Core/Filter/Validator/ArrayItems.php - - src/Core/Filter/Validator/Bounds.php - - src/Core/Filter/Validator/Enum.php - - src/Core/Filter/Validator/Length.php - - src/Core/Filter/Validator/MultipleOf.php - - src/Core/Filter/Validator/Pattern.php - - src/Core/Filter/Validator/Required.php - - src/Core/GraphQl/Action/EntrypointAction.php - - src/Core/GraphQl/Action/GraphQlPlaygroundAction.php - - src/Core/GraphQl/Action/GraphiQlAction.php - - src/Core/GraphQl/Error/ErrorHandler.php - - src/Core/GraphQl/Executor.php - - src/Core/GraphQl/Type/Definition/IterableType.php - - src/Core/GraphQl/Type/Definition/UploadType.php - - src/Core/GraphQl/Type/TypeNotFoundException.php - - src/Core/GraphQl/Type/TypesContainer.php - - src/Core/GraphQl/Type/TypesFactory.php - - src/Core/Hal/JsonSchema/SchemaFactory.php - - src/Core/Hal/Serializer/CollectionNormalizer.php - - src/Core/Hal/Serializer/EntrypointNormalizer.php - - src/Core/Hal/Serializer/ItemNormalizer.php - - src/Core/Hal/Serializer/ObjectNormalizer.php - - src/Core/HttpCache/EventListener/AddHeadersListener.php - - src/Core/HttpCache/EventListener/AddTagsListener.php - - src/Core/HttpCache/VarnishPurger.php - - src/Core/HttpCache/VarnishXKeyPurger.php - - src/Core/Hydra/EventListener/AddLinkHeaderListener.php - - src/Core/Hydra/JsonSchema/SchemaFactory.php - - src/Core/Hydra/Serializer/CollectionFiltersNormalizer.php - - src/Core/Hydra/Serializer/CollectionNormalizer.php - - src/Core/Hydra/Serializer/ConstraintViolationListNormalizer.php - - src/Core/Hydra/Serializer/DocumentationNormalizer.php - - src/Core/Hydra/Serializer/EntrypointNormalizer.php - - src/Core/Hydra/Serializer/ErrorNormalizer.php - - src/Core/Hydra/Serializer/PartialCollectionViewNormalizer.php - - src/Core/JsonApi/EventListener/TransformFieldsetsParametersListener.php - - src/Core/JsonApi/EventListener/TransformFilteringParametersListener.php - - src/Core/JsonApi/EventListener/TransformPaginationParametersListener.php - - src/Core/JsonApi/EventListener/TransformSortingParametersListener.php - - src/Core/JsonApi/Serializer/CollectionNormalizer.php - - src/Core/JsonApi/Serializer/ConstraintViolationListNormalizer.php - - src/Core/JsonApi/Serializer/EntrypointNormalizer.php - - src/Core/JsonApi/Serializer/ErrorNormalizer.php - - src/Core/JsonApi/Serializer/ItemNormalizer.php - - src/Core/JsonApi/Serializer/ObjectNormalizer.php - - src/Core/JsonApi/Serializer/ReservedAttributeNameConverter.php - - src/Core/JsonLd/Action/ContextAction.php - - src/Core/JsonLd/ContextBuilder.php - - src/Core/JsonLd/Serializer/ItemNormalizer.php - - src/Core/JsonLd/Serializer/ObjectNormalizer.php - - src/Core/JsonSchema/Command/JsonSchemaGenerateCommand.php - - src/Core/JsonSchema/Schema.php - - src/Core/JsonSchema/SchemaFactory.php - - src/Core/JsonSchema/TypeFactory.php - - src/Core/Mercure/EventListener/AddLinkHeaderListener.php - - src/Core/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php - - src/Core/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php - - src/Core/Metadata/Property/PropertyNameCollection.php - - src/Core/OpenApi/Model/Components.php - - src/Core/OpenApi/Model/Contact.php - - src/Core/OpenApi/Model/Encoding.php - - src/Core/OpenApi/Model/ExternalDocumentation.php - - src/Core/OpenApi/Model/Info.php - - src/Core/OpenApi/Model/License.php - - src/Core/OpenApi/Model/Link.php - - src/Core/OpenApi/Model/MediaType.php - - src/Core/OpenApi/Model/OAuthFlow.php - - src/Core/OpenApi/Model/OAuthFlows.php - - src/Core/OpenApi/Model/Operation.php - - src/Core/OpenApi/Model/Parameter.php - - src/Core/OpenApi/Model/PathItem.php - - src/Core/OpenApi/Model/Paths.php - - src/Core/OpenApi/Model/RequestBody.php - - src/Core/OpenApi/Model/Response.php - - src/Core/OpenApi/Model/Schema.php - - src/Core/OpenApi/Model/SecurityScheme.php - - src/Core/OpenApi/Model/Server.php - - src/Core/OpenApi/OpenApi.php - - src/Core/OpenApi/Options.php - - src/Core/OpenApi/Serializer/OpenApiNormalizer.php - - src/Core/Operation/DashPathSegmentNameGenerator.php - - src/Core/Operation/UnderscorePathSegmentNameGenerator.php - - src/Core/PathResolver/CustomOperationPathResolver.php - - src/Core/PathResolver/OperationPathResolver.php - - src/Core/Problem/Serializer/ConstraintViolationListNormalizer.php - - src/Core/Problem/Serializer/ErrorNormalizer.php - - src/Core/Security/Core/Authorization/ExpressionLanguageProvider.php - - src/Core/Security/EventListener/DenyAccessListener.php - - src/Core/Security/ResourceAccessChecker.php - - src/Core/Serializer/AbstractItemNormalizer.php - - src/Core/Serializer/AbstractCollectionNormalizer.php - - src/Core/Serializer/AbstractConstraintViolationListNormalizer.php - - src/Core/Serializer/Filter/GroupFilter.php - - src/Core/Serializer/Filter/PropertyFilter.php - - src/Core/Serializer/JsonEncoder.php - - src/Core/Serializer/Mapping/Factory/ClassMetadataFactory.php - - src/Core/Serializer/SerializerContextBuilder.php - - src/Core/Serializer/SerializerFilterContextBuilder.php - - src/Core/Swagger/Serializer/ApiGatewayNormalizer.php - - src/Core/Swagger/Serializer/DocumentationNormalizer.php - - src/Core/Test/DoctrineMongoDbOdmFilterTestCase.php - - src/Core/Test/DoctrineOrmFilterTestCase.php - - src/Core/Upgrade/SubresourceTransformer.php - - src/Core/Upgrade/UpgradeApiResourceVisitor.php - - src/Core/Upgrade/UpgradeApiSubresourceVisitor.php - - src/Core/Util/AttributesExtractor.php - - src/Core/Util/ErrorFormatGuesser.php - - src/Core/Util/Inflector.php - - src/Core/Util/IriHelper.php - - src/Core/Util/Reflection.php - - src/Core/Util/ReflectionClassRecursiveIterator.php - - src/Core/Util/RequestAttributesExtractor.php - - src/Core/Util/RequestParser.php - - src/Core/Validator/EventListener/ValidateListener.php # Symfony cache - tests/Fixtures/app/var/cache - # Deprecated integrations (will be removed in API Platform 3) - - src/Core/Bridge/NelmioApiDoc/* - - tests/Core/Bridge/NelmioApiDoc/* - - src/Core/Bridge/FosUser/* # BC layer - src/deprecated_interfaces.php - - tests/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPassTest.php - - tests/Core/Annotation/ApiResourceTest.php - - tests/Core/Annotation/ApiPropertyTest.php - - tests/Core/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php - tests/Fixtures/TestBundle/BrowserKit/Client.php # The Symfony Configuration API isn't good enough to be analysed - src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -253,17 +28,9 @@ parameters: # Imported code (temporary) - src/Symfony/Bundle/Test/BrowserKitAssertionsTrait.php - tests/Symfony/Bundle/Test/WebTestCaseTest.php - - tests/Core/ProphecyTrait.php - - tests/Core/Behat/CoverageContext.php - - tests/Core/Behat/DoctrineContext.php - tests/Fixtures/TestBundle/Security/AbstractSecurityUser.php # Templates for Maker - - src/Core/Bridge/Symfony/Maker/Resources/skeleton - src/Bridge/Symfony/Maker/Resources/skeleton - # Rector because new API Platform 3.0 classes don't exist yet - - src/Core/Bridge/Rector - - src/Core/Bridge/Symfony/Bundle/Command/RectorCommand.php - - tests/Core/Bridge/Rector/Rules earlyTerminatingMethodCalls: PHPUnit\Framework\Constraint\Constraint: - fail @@ -276,23 +43,31 @@ parameters: paths: - tests/Fixtures/TestBundle/Document/ - tests/Fixtures/TestBundle/Entity/ - - src/Core/Identifier/ - src/OpenApi/Factory/OpenApiFactory.php - message: '#is never written, only read.#' paths: - tests/Fixtures/TestBundle/Document/ - tests/Fixtures/TestBundle/Entity/ - - '#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/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php + message: '#Strict comparison using !== between .+ and .+ will always evaluate to false\.#' + paths: + - src/Doctrine/Common/PropertyHelperTrait.php + - '#Access to an undefined property Prophecy\\Prophecy\\ObjectProphecy<(\\?[a-zA-Z0-9_]+)+>::\$[a-zA-Z0-9_]+#' # https://github.com/willdurand/Negotiation/issues/89#issuecomment-513283286 - message: '#Call to an undefined method Negotiation\\AcceptHeader::getType\(\)\.#' path: src/Symfony/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\\Doctrine\\Orm\\Extension\\PaginationExtension constructor expects (ApiPlatform\\Core\\Metadata\\Resource\\Factory\\ResourceMetadataFactoryInterface\|Symfony\\Component\\HttpFoundation\\RequestStack|ApiPlatform\\Core\\Metadata\\Resource\\Factory\\ResourceMetadataFactoryInterface\|ApiPlatform\\State\\Pagination\\Pagination), stdClass given\.#' + - + message: '#Parameter \#[0-9] \$filterLocator of class .+ constructor expects ApiPlatform\\Core\\Api\\FilterCollection|Psr\\Container\\ContainerInterface, ArrayObject given\.#' + paths: + - tests/Doctrine/Orm/Extension/FilterExtensionTest.php + - tests/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 @@ -313,6 +88,9 @@ parameters: - '#Constructor of class ApiPlatform\\Core\\Annotation\\ApiProperty has an unused parameter#' # Expected, due to optional interfaces + - '#Method ApiPlatform\\Doctrine\\Orm\\Extension\\QueryCollectionExtensionInterface::applyToCollection\(\) invoked with 5 parameters, 3-4 required\.#' + - '#Method ApiPlatform\\\Doctrine\\Orm\\Extension\\QueryResult(Item|Collection)ExtensionInterface::getResult\(\) invoked with 4 parameters, 1 required\.#' + - '#Method ApiPlatform\\Doctrine\\Orm\\Extension\\QueryResult(Item|Collection)ExtensionInterface::supportsResult\(\) invoked with 3 parameters, 1-2 required\.#' - '#Method ApiPlatform\\Core\\Bridge\\Symfony\\Routing\\RouteNameResolverInterface::getRouteName\(\) invoked with 3 parameters, 2 required\.#' - '#Method ApiPlatform\\Core\\DataPersister\\DataPersisterInterface::persist\(\) invoked with 2 parameters, 1 required\.#' - '#Method ApiPlatform\\Core\\DataPersister\\DataPersisterInterface::remove\(\) invoked with 2 parameters, 1 required\.#' @@ -323,10 +101,9 @@ parameters: - '#Method Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface::supportsNormalization\(\) invoked with 3 parameters, 1-2 required\.#' # Expected, due to deprecations + - '#Method ApiPlatform\\Doctrine\\Orm\\Filter\\(Abstract|Exists|Order)Filter::filterProperty\(\) invoked with 7 parameters, 5-6 required\.#' + - '#Method ApiPlatform\\Doctrine\\Orm\\Filter\\(AbstractFilter|FilterInterface)::apply\(\) invoked with 5 parameters, 3-4 required\.#' - '#Method ApiPlatform\\PathResolver\\OperationPathResolverInterface::resolveOperationPath\(\) invoked with 4 parameters, 3 required\.#' - - - message: '#If condition is always false.#' - path: src/Core # Expected, due to backward compatibility - @@ -350,12 +127,9 @@ parameters: message: '#Call to an undefined method Symfony\\Component\\PropertyInfo\\Type::getCollectionKeyType\(\)#' path: src # Skipped tests, we do this on purpose - - + - message: "#^Unreachable statement - code above always terminates.$#" path: tests - - - message: "#^Unreachable statement - code above always terminates.$#" - path: src/Core/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php - message: "#Access to an undefined static property static\\([^)]+\\)::\\$container.$#" path: tests diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b687076cfda..b73ea695c19 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,8 +16,6 @@ tests - tests/Metadata/Resource/Factory - tests/Metadata/Resource/Factory @@ -29,9 +27,6 @@ features tests vendor - src/Core/Bridge/NelmioApiDoc - src/Core/Bridge/FosUser - src/Core/Bridge/Symfony/Maker/Resources/skeleton .php-cs-fixer.dist.php src/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php diff --git a/src/Action/EntrypointAction.php b/src/Action/EntrypointAction.php index 1771db220d3..c256865f35a 100644 --- a/src/Action/EntrypointAction.php +++ b/src/Action/EntrypointAction.php @@ -35,5 +35,3 @@ public function __invoke(): Entrypoint return new Entrypoint($this->resourceNameCollectionFactory->create()); } } - -class_alias(EntrypointAction::class, \ApiPlatform\Core\Action\EntrypointAction::class); diff --git a/src/Action/ExceptionAction.php b/src/Action/ExceptionAction.php index b5fd5f63ef9..fe1e39061d8 100644 --- a/src/Action/ExceptionAction.php +++ b/src/Action/ExceptionAction.php @@ -13,7 +13,8 @@ namespace ApiPlatform\Action; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\ErrorFormatGuesser; use ApiPlatform\Util\OperationRequestInitiatorTrait; @@ -37,28 +38,17 @@ final class ExceptionAction 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 mixed|null $resourceMetadataFactory + * @param array $errorFormats A list of enabled error formats + * @param array $exceptionToStatus A list of exceptions mapped to their HTTP status code */ - public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = [], $resourceMetadataFactory = null) + public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = [], ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = 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; - } + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } /** @@ -98,23 +88,19 @@ private function getOperationExceptionToStatus(Request $request): array { $attributes = RequestAttributesExtractor::extractAttributes($request); - if ([] === $attributes || null === $this->resourceMetadataFactory) { + if ([] === $attributes) { return []; } - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - $operationExceptionToStatus = $resourceMetadata->getOperationAttribute($attributes, 'exception_to_status', [], false); - $resourceExceptionToStatus = $resourceMetadata->getAttribute('exception_to_status', []); - - if (!\is_array($operationExceptionToStatus) || !\is_array($resourceExceptionToStatus)) { - throw new \LogicException('"exception_to_status" attribute should be an array.'); + $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($attributes['resource_class']); + /** @var HttpOperation $operation */ + $operation = $resourceMetadataCollection->getOperation($attributes['operation_name'] ?? null); + $exceptionToStatus = $operation->getExceptionToStatus() ?: []; + foreach ($resourceMetadataCollection as $resourceMetadata) { + /** @var ApiResource $resourceMetadata */ + $exceptionToStatus = array_merge($exceptionToStatus, $resourceMetadata->getExceptionToStatus() ?: []); } - return array_merge( - $resourceExceptionToStatus, - $operationExceptionToStatus - ); + return $exceptionToStatus; } } - -class_alias(ExceptionAction::class, \ApiPlatform\Core\Action\ExceptionAction::class); diff --git a/src/Action/NotFoundAction.php b/src/Action/NotFoundAction.php index 2e5e1d2cf95..546811fee66 100644 --- a/src/Action/NotFoundAction.php +++ b/src/Action/NotFoundAction.php @@ -25,5 +25,3 @@ public function __invoke() throw new NotFoundHttpException(); } } - -class_alias(NotFoundAction::class, \ApiPlatform\Core\Action\NotFoundAction::class); diff --git a/src/Action/PlaceholderAction.php b/src/Action/PlaceholderAction.php index 5ed07426db2..79cc83f8371 100644 --- a/src/Action/PlaceholderAction.php +++ b/src/Action/PlaceholderAction.php @@ -30,5 +30,3 @@ public function __invoke($data) return $data; } } - -class_alias(PlaceholderAction::class, \ApiPlatform\Core\Action\PlaceholderAction::class); diff --git a/src/Core/Identifier/CompositeIdentifierParser.php b/src/Api/CompositeIdentifierParser.php similarity index 97% rename from src/Core/Identifier/CompositeIdentifierParser.php rename to src/Api/CompositeIdentifierParser.php index f95c1191f07..aed4bc8c094 100644 --- a/src/Core/Identifier/CompositeIdentifierParser.php +++ b/src/Api/CompositeIdentifierParser.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Identifier; +namespace ApiPlatform\Api; /** * Normalizes a composite identifier. diff --git a/src/Api/Entrypoint.php b/src/Api/Entrypoint.php index e3289f62559..ed654c01cdb 100644 --- a/src/Api/Entrypoint.php +++ b/src/Api/Entrypoint.php @@ -34,5 +34,3 @@ public function getResourceNameCollection(): ResourceNameCollection return $this->resourceNameCollection; } } - -class_alias(Entrypoint::class, \ApiPlatform\Core\Api\Entrypoint::class); diff --git a/src/Api/FilterLocatorTrait.php b/src/Api/FilterLocatorTrait.php index 6384e6926d2..005494fa9a2 100644 --- a/src/Api/FilterLocatorTrait.php +++ b/src/Api/FilterLocatorTrait.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Api; +use ApiPlatform\Exception\InvalidArgumentException; use Psr\Container\ContainerInterface; /** @@ -24,17 +25,24 @@ */ trait FilterLocatorTrait { - /** @var ContainerInterface */ private $filterLocator; /** * Sets a filter locator with a backward compatibility. * - * @param ContainerInterface|null $filterLocator + * @param ContainerInterface|FilterCollection|null $filterLocator */ private function setFilterLocator($filterLocator, bool $allowNull = false): void { - $this->filterLocator = $filterLocator; + if ($filterLocator instanceof ContainerInterface || $filterLocator instanceof FilterCollection || (null === $filterLocator && $allowNull)) { + if ($filterLocator instanceof FilterCollection) { + @trigger_error(sprintf('The %s class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of %s instead.', FilterCollection::class, ContainerInterface::class), \E_USER_DEPRECATED); + } + + $this->filterLocator = $filterLocator; + } else { + throw new InvalidArgumentException(sprintf('The "$filterLocator" argument is expected to be an implementation of the "%s" interface%s.', ContainerInterface::class, $allowNull ? ' or null' : '')); + } } /** @@ -46,6 +54,10 @@ private function getFilter(string $filterId): ?FilterInterface return $this->filterLocator->get($filterId); } + if ($this->filterLocator instanceof FilterCollection && $this->filterLocator->offsetExists($filterId)) { + return $this->filterLocator->offsetGet($filterId); + } + return null; } } diff --git a/src/Api/FormatMatcher.php b/src/Api/FormatMatcher.php index a5aed0185ab..c86270c10de 100644 --- a/src/Api/FormatMatcher.php +++ b/src/Api/FormatMatcher.php @@ -62,5 +62,3 @@ public function getFormat(string $mimeType): ?string return null; } } - -class_alias(FormatMatcher::class, \ApiPlatform\Core\Api\FormatMatcher::class); diff --git a/src/Api/IdentifiersExtractor.php b/src/Api/IdentifiersExtractor.php index eddfb87ebb1..532d5572197 100644 --- a/src/Api/IdentifiersExtractor.php +++ b/src/Api/IdentifiersExtractor.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Api; -use ApiPlatform\Core\Identifier\CompositeIdentifierParser; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; use ApiPlatform\Metadata\HttpOperation; @@ -56,6 +55,11 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource public function getIdentifiersFromItem($item, Operation $operation = null, array $context = []): array { $identifiers = []; + + if (!$this->isResourceClass($this->getObjectClass($item))) { + return ['id' => $this->propertyAccessor->getValue($item, 'id')]; + } + $resourceClass = $this->getResourceClass($item, true); $operation = $operation ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation(null, false, true); @@ -72,7 +76,7 @@ public function getIdentifiersFromItem($item, Operation $operation = null, array $compositeIdentifiers[$identifier] = $this->getIdentifierValue($item, $link->getFromClass() ?? $resourceClass, $identifier, $link->getParameterName()); } - $identifiers[($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) ? 'id' : $link->getParameterName()] = CompositeIdentifierParser::stringify($compositeIdentifiers); + $identifiers[$link->getParameterName()] = CompositeIdentifierParser::stringify($compositeIdentifiers); continue; } diff --git a/src/Api/QueryParameterValidator/QueryParameterValidator.php b/src/Api/QueryParameterValidator/QueryParameterValidator.php index 67f3c725209..53c0d8c73d0 100644 --- a/src/Api/QueryParameterValidator/QueryParameterValidator.php +++ b/src/Api/QueryParameterValidator/QueryParameterValidator.php @@ -64,5 +64,3 @@ public function validateFilters(string $resourceClass, array $resourceFilters, a } } } - -class_alias(QueryParameterValidator::class, \ApiPlatform\Core\Filter\QueryParameterValidator::class); diff --git a/src/Api/QueryParameterValidator/Validator/ArrayItems.php b/src/Api/QueryParameterValidator/Validator/ArrayItems.php index c2e65758de1..af8851377ef 100644 --- a/src/Api/QueryParameterValidator/Validator/ArrayItems.php +++ b/src/Api/QueryParameterValidator/Validator/ArrayItems.php @@ -84,5 +84,3 @@ private static function getSeparator(string $collectionFormat): string } } } - -class_alias(ArrayItems::class, \ApiPlatform\Core\Filter\Validator\ArrayItems::class); diff --git a/src/Api/QueryParameterValidator/Validator/Bounds.php b/src/Api/QueryParameterValidator/Validator/Bounds.php index 1cef0ba9ad4..eebc40b3eca 100644 --- a/src/Api/QueryParameterValidator/Validator/Bounds.php +++ b/src/Api/QueryParameterValidator/Validator/Bounds.php @@ -49,5 +49,3 @@ public function validate(string $name, array $filterDescription, array $queryPar return $errorList; } } - -class_alias(Bounds::class, \ApiPlatform\Core\Filter\Validator\Bounds::class); diff --git a/src/Api/QueryParameterValidator/Validator/Enum.php b/src/Api/QueryParameterValidator/Validator/Enum.php index 72ad2056268..2e0bf5c277b 100644 --- a/src/Api/QueryParameterValidator/Validator/Enum.php +++ b/src/Api/QueryParameterValidator/Validator/Enum.php @@ -36,5 +36,3 @@ public function validate(string $name, array $filterDescription, array $queryPar return []; } } - -class_alias(Enum::class, \ApiPlatform\Core\Filter\Validator\Enum::class); diff --git a/src/Api/QueryParameterValidator/Validator/Length.php b/src/Api/QueryParameterValidator/Validator/Length.php index 4d658898bdd..0b0c67e5595 100644 --- a/src/Api/QueryParameterValidator/Validator/Length.php +++ b/src/Api/QueryParameterValidator/Validator/Length.php @@ -41,5 +41,3 @@ public function validate(string $name, array $filterDescription, array $queryPar return $errorList; } } - -class_alias(Length::class, \ApiPlatform\Core\Filter\Validator\Length::class); diff --git a/src/Api/QueryParameterValidator/Validator/MultipleOf.php b/src/Api/QueryParameterValidator/Validator/MultipleOf.php index 197d081bcd0..6da33972688 100644 --- a/src/Api/QueryParameterValidator/Validator/MultipleOf.php +++ b/src/Api/QueryParameterValidator/Validator/MultipleOf.php @@ -36,5 +36,3 @@ public function validate(string $name, array $filterDescription, array $queryPar return []; } } - -class_alias(MultipleOf::class, \ApiPlatform\Core\Filter\Validator\MultipleOf::class); diff --git a/src/Api/QueryParameterValidator/Validator/Pattern.php b/src/Api/QueryParameterValidator/Validator/Pattern.php index 414b7cc8da8..a585fd8b321 100644 --- a/src/Api/QueryParameterValidator/Validator/Pattern.php +++ b/src/Api/QueryParameterValidator/Validator/Pattern.php @@ -36,5 +36,3 @@ public function validate(string $name, array $filterDescription, array $queryPar return []; } } - -class_alias(Pattern::class, \ApiPlatform\Core\Filter\Validator\Pattern::class); diff --git a/src/Api/QueryParameterValidator/Validator/Required.php b/src/Api/QueryParameterValidator/Validator/Required.php index 308bdbf354d..0045b0513b4 100644 --- a/src/Api/QueryParameterValidator/Validator/Required.php +++ b/src/Api/QueryParameterValidator/Validator/Required.php @@ -100,5 +100,3 @@ private function requestGetQueryParameter(array $queryParameters, string $name) return $queryParameters[(string) $rootName]; } } - -class_alias(Required::class, \ApiPlatform\Core\Filter\Validator\Required::class); diff --git a/src/Api/ResourceClassResolver.php b/src/Api/ResourceClassResolver.php index e0f9243458c..b1bec160917 100644 --- a/src/Api/ResourceClassResolver.php +++ b/src/Api/ResourceClassResolver.php @@ -109,5 +109,3 @@ public function isResourceClass(string $type): bool return $this->localIsResourceClassCache[$type] = false; } } - -class_alias(ResourceClassResolver::class, \ApiPlatform\Core\Api\ResourceClassResolver::class); diff --git a/src/Core/Action/EntrypointAction.php b/src/Core/Action/EntrypointAction.php deleted file mode 100644 index 7a82b6cfc98..00000000000 --- a/src/Core/Action/EntrypointAction.php +++ /dev/null @@ -1,22 +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\Action; - -class_exists(\ApiPlatform\Action\EntrypointAction::class); - -if (false) { - final class EntrypointAction extends \ApiPlatform\Action\EntrypointAction - { - } -} diff --git a/src/Core/Action/ExceptionAction.php b/src/Core/Action/ExceptionAction.php deleted file mode 100644 index 7272c7d1cb2..00000000000 --- a/src/Core/Action/ExceptionAction.php +++ /dev/null @@ -1,22 +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\Action; - -class_exists(\ApiPlatform\Action\ExceptionAction::class); - -if (false) { - final class ExceptionAction extends \ApiPlatform\Action\ExceptionAction - { - } -} diff --git a/src/Core/Action/NotFoundAction.php b/src/Core/Action/NotFoundAction.php deleted file mode 100644 index cb413a942b9..00000000000 --- a/src/Core/Action/NotFoundAction.php +++ /dev/null @@ -1,22 +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\Action; - -class_exists(\ApiPlatform\Action\NotFoundAction::class); - -if (false) { - final class NotFoundAction extends \ApiPlatform\Action\NotFoundAction - { - } -} diff --git a/src/Core/Action/PlaceholderAction.php b/src/Core/Action/PlaceholderAction.php deleted file mode 100644 index 830ad04323d..00000000000 --- a/src/Core/Action/PlaceholderAction.php +++ /dev/null @@ -1,22 +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\Action; - -class_exists(\ApiPlatform\Action\PlaceholderAction::class); - -if (false) { - final class PlaceholderAction extends \ApiPlatform\Action\PlaceholderAction - { - } -} diff --git a/src/Core/Annotation/ApiFilter.php b/src/Core/Annotation/ApiFilter.php deleted file mode 100644 index c759e8e0526..00000000000 --- a/src/Core/Annotation/ApiFilter.php +++ /dev/null @@ -1,96 +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\Annotation; - -use ApiPlatform\Api\FilterInterface; -use ApiPlatform\Exception\InvalidArgumentException; - -/** - * Filter annotation. - * - * @author Antoine Bluchet - * - * @Annotation - * @Target({"PROPERTY", "CLASS"}) - */ -#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] -final class ApiFilter -{ - /** - * @var string - */ - public $id; - - /** - * @var string - */ - public $strategy; - - /** - * @var string|FilterInterface - */ - public $filterClass; - - /** - * @var array - */ - public $properties = []; - - /** - * @var array raw arguments for the filter - */ - public $arguments = []; - - /** - * @param string $filterClass - * @param string $id - * @param string $strategy - */ - public function __construct( - $filterClass, - ?string $id = null, - ?string $strategy = null, - array $properties = [], - array $arguments = [] - ) { - if (\is_array($filterClass)) { /** @phpstan-ignore-line Doctrine annotations */ - $options = $filterClass; - $this->filterClass = $options['value'] ?? null; /* @phpstan-ignore-line Doctrine annotations */ - unset($options['value']); - - foreach ($options as $key => $value) { - if (!property_exists($this, $key)) { - throw new InvalidArgumentException(sprintf('Property "%s" does not exist on the ApiFilter annotation.', $key)); - } - - $this->{$key} = $value; - } - } else { - // PHP attribute - $this->filterClass = $filterClass; - $this->id = $id; - $this->strategy = $strategy; - $this->properties = $properties; - $this->arguments = $arguments; - } - - if (!\is_string($this->filterClass)) { - throw new InvalidArgumentException('This annotation needs a value representing the filter class.'); - } - - if (!is_a($this->filterClass, FilterInterface::class, true)) { - throw new InvalidArgumentException(sprintf('The filter class "%s" does not implement "%s". Did you forget a use statement?', $this->filterClass, FilterInterface::class)); - } - } -} diff --git a/src/Core/Annotation/ApiProperty.php b/src/Core/Annotation/ApiProperty.php deleted file mode 100644 index 7070b81a175..00000000000 --- a/src/Core/Annotation/ApiProperty.php +++ /dev/null @@ -1,165 +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\Annotation; - -use ApiPlatform\Exception\InvalidArgumentException; - -/** - * ApiProperty annotation. - * - * @author Kévin Dunglas - * - * @Annotation - * @Target({"METHOD", "PROPERTY"}) - * @Attributes( - * @Attribute("deprecationReason", type="string"), - * @Attribute("fetchable", type="bool"), - * @Attribute("fetchEager", type="bool"), - * @Attribute("openapiContext", type="array"), - * @Attribute("jsonldContext", type="array"), - * @Attribute("push", type="bool"), - * @Attribute("security", type="string"), - * @Attribute("securityPostDenormalize", type="string"), - * @Attribute("swaggerContext", type="array") - * ) - */ -#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::TARGET_PARAMETER)] -final class ApiProperty -{ - use AttributesHydratorTrait; - - /** - * @var array - */ - private static $deprecatedAttributes = []; - - /** - * @var string - */ - public $description; - - /** - * @var bool - */ - public $readable; - - /** - * @var bool - */ - public $writable; - - /** - * @var bool - */ - public $readableLink; - - /** - * @var bool - */ - public $writableLink; - - /** - * @var bool - */ - public $required; - - /** - * @var string - */ - public $iri; - - /** - * @var bool - */ - public $identifier; - - /** - * @var string|int|float|bool|array|null - */ - public $default; - - /** - * @var string|int|float|bool|array|null - */ - public $example; - - public $types; - public $builtinTypes; - - /** - * @param string $description - * @param bool $readable - * @param bool $writable - * @param bool $readableLink - * @param bool $writableLink - * @param bool $required - * @param string $iri - * @param bool $identifier - * @param string|int|float|bool|array $default - * @param string|int|float|bool|array|null $example - * @param string $deprecationReason - * @param bool $fetchable - * @param bool $fetchEager - * @param array $jsonldContext - * @param array $openapiContext - * @param bool $push - * @param string $security - * @param array $swaggerContext - * @param string $securityPostDenormalize - * - * @throws InvalidArgumentException - */ - public function __construct( - $description = null, - ?bool $readable = null, - ?bool $writable = null, - ?bool $readableLink = null, - ?bool $writableLink = null, - ?bool $required = null, - ?string $iri = null, - ?bool $identifier = null, - $default = null, - $example = null, - - // attributes - ?array $attributes = null, - ?string $deprecationReason = null, - ?bool $fetchable = null, - ?bool $fetchEager = null, - ?array $jsonldContext = null, - ?array $openapiContext = null, - ?bool $push = null, - ?string $security = null, - ?array $swaggerContext = null, - ?string $securityPostDenormalize = null, - - ?array $types = [], - ?array $builtinTypes = [] - ) { - if (!\is_array($description)) { // @phpstan-ignore-line Doctrine annotations support - [$publicProperties, $configurableAttributes] = self::getConfigMetadata(); - - foreach ($publicProperties as $prop => $_) { - $this->{$prop} = ${$prop}; - } - - $description = []; - foreach ($configurableAttributes as $attribute => $_) { - $description[$attribute] = ${$attribute}; - } - } - - $this->hydrateAttributes($description); - } -} diff --git a/src/Core/Annotation/ApiResource.php b/src/Core/Annotation/ApiResource.php deleted file mode 100644 index 8f3a9c69887..00000000000 --- a/src/Core/Annotation/ApiResource.php +++ /dev/null @@ -1,251 +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\Annotation; - -use ApiPlatform\Exception\InvalidArgumentException; - -/** - * ApiResource annotation. - * - * @author Kévin Dunglas - * - * @Annotation - * @Target({"CLASS"}) - * @Attributes( - * @Attribute("accessControl", type="string"), - * @Attribute("accessControlMessage", type="string"), - * @Attribute("attributes", type="array"), - * @Attribute("cacheHeaders", type="array"), - * @Attribute("collectionOperations", type="array"), - * @Attribute("compositeIdentifier", type="bool"), - * @Attribute("denormalizationContext", type="array"), - * @Attribute("deprecationReason", type="string"), - * @Attribute("description", type="string"), - * @Attribute("elasticsearch", type="bool"), - * @Attribute("fetchPartial", type="bool"), - * @Attribute("forceEager", type="bool"), - * @Attribute("formats", type="array"), - * @Attribute("filters", type="string[]"), - * @Attribute("graphql", type="array"), - * @Attribute("hydraContext", type="array"), - * @Attribute("input", type="mixed"), - * @Attribute("iri", type="string"), - * @Attribute("itemOperations", type="array"), - * @Attribute("mercure", type="mixed"), - * @Attribute("messenger", type="mixed"), - * @Attribute("normalizationContext", type="array"), - * @Attribute("openapiContext", type="array"), - * @Attribute("order", type="array"), - * @Attribute("output", type="mixed"), - * @Attribute("paginationClientEnabled", type="bool"), - * @Attribute("paginationClientItemsPerPage", type="bool"), - * @Attribute("paginationClientPartial", type="bool"), - * @Attribute("paginationEnabled", type="bool"), - * @Attribute("paginationFetchJoinCollection", type="bool"), - * @Attribute("paginationItemsPerPage", type="int"), - * @Attribute("maximumItemsPerPage", type="int"), - * @Attribute("paginationMaximumItemsPerPage", type="int"), - * @Attribute("paginationPartial", type="bool"), - * @Attribute("paginationViaCursor", type="array"), - * @Attribute("routePrefix", type="string"), - * @Attribute("security", type="string"), - * @Attribute("securityMessage", type="string"), - * @Attribute("securityPostDenormalize", type="string"), - * @Attribute("securityPostDenormalizeMessage", type="string"), - * @Attribute("securityPostValidation", type="string"), - * @Attribute("securityPostValidationMessage", type="string"), - * @Attribute("shortName", type="string"), - * @Attribute("stateless", type="bool"), - * @Attribute("subresourceOperations", type="array"), - * @Attribute("sunset", type="string"), - * @Attribute("swaggerContext", type="array"), - * @Attribute("urlGenerationStrategy", type="int"), - * @Attribute("validationGroups", type="mixed"), - * @Attribute("exceptionToStatus", type="array"), - * @Attribute("queryParameterValidationEnabled", type="bool") - * ) - */ -#[\Attribute(\Attribute::TARGET_CLASS)] -final class ApiResource -{ - use AttributesHydratorTrait; - - /** - * @var array - */ - private static $deprecatedAttributes = [ - 'accessControl' => ['security', '2.5'], - 'accessControlMessage' => ['securityMessage', '2.5'], - 'maximumItemsPerPage' => ['paginationMaximumItemsPerPage', '2.6'], - ]; - - /** - * @see https://api-platform.com/docs/core/operations - * - * @var array - */ - public $collectionOperations; - - /** - * @var string - */ - public $description; - - /** - * @see https://api-platform.com/docs/core/graphql - * - * @var array - */ - public $graphql; - - /** - * @var string - */ - public $iri; - - /** - * @see https://api-platform.com/docs/core/operations - * - * @var array - */ - public $itemOperations; - - /** - * @var string - */ - public $shortName; - - /** - * @see https://api-platform.com/docs/core/subresources - * - * @var array - */ - public $subresourceOperations; - - /** - * @param string $description - * @param array $collectionOperations https://api-platform.com/docs/core/operations - * @param array $graphql https://api-platform.com/docs/core/graphql - * @param array $itemOperations https://api-platform.com/docs/core/operations - * @param array $subresourceOperations https://api-platform.com/docs/core/subresources - * @param array $cacheHeaders https://api-platform.com/docs/core/performance/#setting-custom-http-cache-headers - * @param array $denormalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups - * @param string $deprecationReason https://api-platform.com/docs/core/deprecations/#deprecating-resource-classes-operations-and-properties - * @param bool $elasticsearch https://api-platform.com/docs/core/elasticsearch/ - * @param bool $fetchPartial https://api-platform.com/docs/core/performance/#fetch-partial - * @param bool $forceEager https://api-platform.com/docs/core/performance/#force-eager - * @param array $formats https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation - * @param string[] $filters https://api-platform.com/docs/core/filters/#doctrine-orm-and-mongodb-odm-filters - * @param string[] $hydraContext https://api-platform.com/docs/core/extending-jsonld-context/#hydra - * @param string|false $input https://api-platform.com/docs/core/dto/#specifying-an-input-or-an-output-data-representation - * @param bool|array $mercure https://api-platform.com/docs/core/mercure - * @param bool $messenger https://api-platform.com/docs/core/messenger/#dispatching-a-resource-through-the-message-bus - * @param array $normalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups - * @param array $openapiContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts - * @param array $order https://api-platform.com/docs/core/default-order/#overriding-default-order - * @param string|false $output https://api-platform.com/docs/core/dto/#specifying-an-input-or-an-output-data-representation - * @param bool $paginationClientEnabled https://api-platform.com/docs/core/pagination/#for-a-specific-resource-1 - * @param bool $paginationClientItemsPerPage https://api-platform.com/docs/core/pagination/#for-a-specific-resource-3 - * @param bool $paginationClientPartial https://api-platform.com/docs/core/pagination/#for-a-specific-resource-6 - * @param array $paginationViaCursor https://api-platform.com/docs/core/pagination/#cursor-based-pagination - * @param bool $paginationEnabled https://api-platform.com/docs/core/pagination/#for-a-specific-resource - * @param bool $paginationFetchJoinCollection https://api-platform.com/docs/core/pagination/#controlling-the-behavior-of-the-doctrine-orm-paginator - * @param int $paginationItemsPerPage https://api-platform.com/docs/core/pagination/#changing-the-number-of-items-per-page - * @param int $paginationMaximumItemsPerPage https://api-platform.com/docs/core/pagination/#changing-maximum-items-per-page - * @param bool $paginationPartial https://api-platform.com/docs/core/performance/#partial-pagination - * @param string $routePrefix https://api-platform.com/docs/core/operations/#prefixing-all-routes-of-all-operations - * @param string $security https://api-platform.com/docs/core/security - * @param string $securityMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message - * @param string $securityPostDenormalize https://api-platform.com/docs/core/security/#executing-access-control-rules-after-denormalization - * @param string $securityPostDenormalizeMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message - * @param string $securityPostValidation https://api-platform.com/docs/core/security/#executing-access-control-rules-after-validation - * @param string $securityPostValidationMessage https://api-platform.com/docs/core/security/#configuring-the-access-control-error-message - * @param bool $stateless - * @param string $sunset https://api-platform.com/docs/core/deprecations/#setting-the-sunset-http-header-to-indicate-when-a-resource-or-an-operation-will-be-removed - * @param array $swaggerContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts - * @param array $validationGroups https://api-platform.com/docs/core/validation/#using-validation-groups - * @param int $urlGenerationStrategy - * @param array $exceptionToStatus https://api-platform.com/docs/core/errors/#fine-grained-configuration - * @param bool $queryParameterValidationEnabled - * - * @throws InvalidArgumentException - */ - public function __construct( - $description = null, - ?array $collectionOperations = null, - ?array $graphql = null, - ?string $iri = null, - ?array $itemOperations = null, - ?string $shortName = null, - ?array $subresourceOperations = null, - - // attributes - ?array $attributes = null, - ?array $cacheHeaders = null, - ?array $denormalizationContext = null, - ?string $deprecationReason = null, - ?bool $elasticsearch = null, - ?bool $fetchPartial = null, - ?bool $forceEager = null, - ?array $formats = null, - ?array $filters = null, - ?array $hydraContext = null, - $input = null, - $mercure = null, - $messenger = null, - ?array $normalizationContext = null, - ?array $openapiContext = null, - ?array $order = null, - $output = null, - ?bool $paginationClientEnabled = null, - ?bool $paginationClientItemsPerPage = null, - ?bool $paginationClientPartial = null, - ?array $paginationViaCursor = null, - ?bool $paginationEnabled = null, - ?bool $paginationFetchJoinCollection = null, - ?int $paginationItemsPerPage = null, - ?int $paginationMaximumItemsPerPage = null, - ?bool $paginationPartial = null, - ?string $routePrefix = null, - ?string $security = null, - ?string $securityMessage = null, - ?string $securityPostDenormalize = null, - ?string $securityPostDenormalizeMessage = null, - ?string $securityPostValidation = null, - ?string $securityPostValidationMessage = null, - ?bool $stateless = null, - ?string $sunset = null, - ?array $swaggerContext = null, - ?array $validationGroups = null, - ?int $urlGenerationStrategy = null, - ?bool $compositeIdentifier = null, - ?array $exceptionToStatus = null, - ?bool $queryParameterValidationEnabled = null - ) { - if (!\is_array($description)) { // @phpstan-ignore-line Doctrine annotations support - [$publicProperties, $configurableAttributes] = self::getConfigMetadata(); - - foreach ($publicProperties as $prop => $_) { - $this->{$prop} = ${$prop}; - } - - $description = []; - foreach ($configurableAttributes as $attribute => $_) { - $description[$attribute] = ${$attribute}; - } - } - - $this->hydrateAttributes($description ?? []); // @phpstan-ignore-line - } -} diff --git a/src/Core/Annotation/ApiSubresource.php b/src/Core/Annotation/ApiSubresource.php deleted file mode 100644 index 27e321505f6..00000000000 --- a/src/Core/Annotation/ApiSubresource.php +++ /dev/null @@ -1,48 +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\Annotation; - -/** - * ApiSubresource annotation. - * - * @author Antoine Bluchet - * - * @Annotation - * @Target({"METHOD", "PROPERTY"}) - * @Attributes( - * @Attribute("maxDepth", type="int"), - * ) - */ -#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)] -final class ApiSubresource -{ - /** - * @var int - */ - public $maxDepth; - - /** - * @param int $maxDepth - */ - public function __construct($maxDepth = null) - { - if (\is_array($maxDepth)) { // @phpstan-ignore-line - $this->maxDepth = $maxDepth['maxDepth'] ?? null; // @phpstan-ignore-line - - return; - } - - $this->maxDepth = $maxDepth; - } -} diff --git a/src/Core/Annotation/AttributesHydratorTrait.php b/src/Core/Annotation/AttributesHydratorTrait.php deleted file mode 100644 index 00fe7481994..00000000000 --- a/src/Core/Annotation/AttributesHydratorTrait.php +++ /dev/null @@ -1,99 +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\Annotation; - -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Util\Inflector; - -/** - * Hydrates attributes from annotation's parameters. - * - * @internal - * - * @author Baptiste Meyer - * @author Kévin Dunglas - */ -trait AttributesHydratorTrait -{ - private static $configMetadata; - - /** - * @internal - */ - public static function getConfigMetadata(): array - { - if (null !== self::$configMetadata) { - return self::$configMetadata; - } - - $rc = new \ReflectionClass(self::class); - - $publicProperties = []; - foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflectionProperty) { - $publicProperties[$reflectionProperty->getName()] = true; - } - - $configurableAttributes = []; - foreach ($rc->getConstructor()->getParameters() as $param) { - if (!isset($publicProperties[$name = $param->getName()])) { - $configurableAttributes[$name] = true; - } - } - - return [$publicProperties, $configurableAttributes]; - } - - /** - * @var array - */ - public $attributes = null; - - /** - * @throws InvalidArgumentException - */ - private function hydrateAttributes(array $values): void - { - if (isset($values['attributes'])) { - $this->attributes = $values['attributes']; - unset($values['attributes']); - } - - foreach (self::$deprecatedAttributes as $deprecatedAttribute => $options) { - if (\array_key_exists($deprecatedAttribute, $values)) { - $values[$options[0]] = $values[$deprecatedAttribute]; - @trigger_error(sprintf('Attribute "%s" is deprecated in annotation since API Platform %s, prefer using "%s" attribute instead', $deprecatedAttribute, $options[1], $options[0]), \E_USER_DEPRECATED); - unset($values[$deprecatedAttribute]); - } - } - - [$publicProperties, $configurableAttributes] = self::getConfigMetadata(); - foreach ($values as $key => $value) { - $key = (string) $key; - if (!isset($publicProperties[$key]) && !isset($configurableAttributes[$key]) && !isset(self::$deprecatedAttributes[$key])) { - throw new InvalidArgumentException(sprintf('Unknown property "%s" on annotation "%s".', $key, self::class)); - } - - if (isset($publicProperties[$key])) { - $this->{$key} = $value; - continue; - } - - if (!\is_array($this->attributes)) { - $this->attributes = []; - } - - $this->attributes += [Inflector::tableize($key) => $value]; - } - } -} diff --git a/src/Core/Annotation/TemporaryApiResource.php b/src/Core/Annotation/TemporaryApiResource.php deleted file mode 100644 index e54a5442888..00000000000 --- a/src/Core/Annotation/TemporaryApiResource.php +++ /dev/null @@ -1,21 +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\Annotation; - -final class TemporaryApiResource -{ - public function __construct() - { - } -} diff --git a/src/Core/Api/CachedIdentifiersExtractor.php b/src/Core/Api/CachedIdentifiersExtractor.php deleted file mode 100644 index 13d772a4c25..00000000000 --- a/src/Core/Api/CachedIdentifiersExtractor.php +++ /dev/null @@ -1,132 +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\Api; - -use ApiPlatform\Util\ResourceClassInfoTrait; -use Psr\Cache\CacheException; -use Psr\Cache\CacheItemPoolInterface; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; - -/** - * {@inheritdoc} - * - * @author Antoine Bluchet - */ -final class CachedIdentifiersExtractor implements IdentifiersExtractorInterface -{ - use ResourceClassInfoTrait; - - public const CACHE_KEY_PREFIX = 'iri_identifiers'; - - private $cacheItemPool; - private $propertyAccessor; - private $decorated; - private $localCache = []; - private $localResourceCache = []; - - public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersExtractorInterface $decorated, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null) - { - $this->cacheItemPool = $cacheItemPool; - $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); - $this->decorated = $decorated; - $this->resourceClassResolver = $resourceClassResolver; - - if (null === $this->resourceClassResolver) { - @trigger_error(sprintf('Not injecting %s in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), \E_USER_DEPRECATED); - } - } - - /** - * {@inheritdoc} - */ - public function getIdentifiersFromResourceClass(string $resourceClass): array - { - if (isset($this->localResourceCache[$resourceClass])) { - return $this->localResourceCache[$resourceClass]; - } - - return $this->localResourceCache[$resourceClass] = $this->decorated->getIdentifiersFromResourceClass($resourceClass); - } - - /** - * {@inheritdoc} - */ - public function getIdentifiersFromItem($item): array - { - $keys = $this->getKeys($item, function ($item) use (&$identifiers) { - return $identifiers = $this->decorated->getIdentifiersFromItem($item); - }); - - if (null !== $identifiers) { - return $identifiers; - } - - $identifiers = []; - foreach ($keys as $propertyName) { - $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); - - if (!\is_object($identifiers[$propertyName])) { - continue; - } - - if (null === $relatedResourceClass = $this->getResourceClass($identifiers[$propertyName])) { - continue; - } - - if (!$relatedIdentifiers = $this->localCache[$relatedResourceClass] ?? false) { - $relatedCacheKey = self::CACHE_KEY_PREFIX.md5($relatedResourceClass); - try { - $relatedCacheItem = $this->cacheItemPool->getItem($relatedCacheKey); - if (!$relatedCacheItem->isHit()) { - return $this->decorated->getIdentifiersFromItem($item); - } - } catch (CacheException $e) { - return $this->decorated->getIdentifiersFromItem($item); - } - - $relatedIdentifiers = $relatedCacheItem->get(); - } - - $identifiers[$propertyName] = $this->propertyAccessor->getValue($identifiers[$propertyName], $relatedIdentifiers[0]); - } - - return $identifiers; - } - - private function getKeys($item, callable $retriever): array - { - $resourceClass = $this->getObjectClass($item); - if (isset($this->localCache[$resourceClass])) { - return $this->localCache[$resourceClass]; - } - - try { - $cacheItem = $this->cacheItemPool->getItem(self::CACHE_KEY_PREFIX.md5($resourceClass)); - } catch (CacheException $e) { - return $this->localCache[$resourceClass] = array_keys($retriever($item)); - } - - if ($cacheItem->isHit()) { - return $this->localCache[$resourceClass] = $cacheItem->get(); - } - - $keys = array_keys($retriever($item)); - - $cacheItem->set($keys); - $this->cacheItemPool->save($cacheItem); - - return $this->localCache[$resourceClass] = $keys; - } -} diff --git a/src/Core/Api/Entrypoint.php b/src/Core/Api/Entrypoint.php deleted file mode 100644 index af2588bfb28..00000000000 --- a/src/Core/Api/Entrypoint.php +++ /dev/null @@ -1,22 +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\Api; - -class_exists(\ApiPlatform\Api\Entrypoint::class); - -if (false) { - final class Entrypoint extends \ApiPlatform\Api\Entrypoint - { - } -} diff --git a/src/Core/Api/FilterCollection.php b/src/Core/Api/FilterCollection.php deleted file mode 100644 index b1bcc1c3584..00000000000 --- a/src/Core/Api/FilterCollection.php +++ /dev/null @@ -1,33 +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\Api; - -use Psr\Container\ContainerInterface; - -/** - * A list of filters. - * - * @author Kévin Dunglas - * - * @deprecated since version 2.1, to be removed in 3.0. Use a service locator {@see \Psr\Container\ContainerInterface}. - */ -final class FilterCollection extends \ArrayObject -{ - public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator') - { - @trigger_error(sprintf('The %s class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of %s instead.', self::class, ContainerInterface::class), \E_USER_DEPRECATED); - - parent::__construct($input, $flags, $iterator_class); - } -} diff --git a/src/Core/Api/FilterCollectionFactory.php b/src/Core/Api/FilterCollectionFactory.php deleted file mode 100644 index 20da8ee17cf..00000000000 --- a/src/Core/Api/FilterCollectionFactory.php +++ /dev/null @@ -1,54 +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\Api; - -use Psr\Container\ContainerInterface; - -/** - * Filter collection factory. - * - * @author Baptiste Meyer - * - * @deprecated see FilterCollection - * - * @internal - */ -class FilterCollectionFactory -{ - private $filtersIds; - - /** - * @param string[] $filtersIds - */ - public function __construct(array $filtersIds) - { - $this->filtersIds = $filtersIds; - } - - /** - * Creates a filter collection from a filter locator. - */ - public function createFilterCollectionFromLocator(ContainerInterface $filterLocator): FilterCollection - { - $filters = []; - - foreach ($this->filtersIds as $filterId) { - if ($filterLocator->has($filterId)) { - $filters[$filterId] = $filterLocator->get($filterId); - } - } - - return new FilterCollection($filters); - } -} diff --git a/src/Core/Api/FilterLocatorTrait.php b/src/Core/Api/FilterLocatorTrait.php deleted file mode 100644 index 590e2f063e7..00000000000 --- a/src/Core/Api/FilterLocatorTrait.php +++ /dev/null @@ -1,63 +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\Api; - -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Psr\Container\ContainerInterface; - -/** - * Manipulates filters with a backward compatibility between the new filter locator and the deprecated filter collection. - * - * @author Baptiste Meyer - * - * @internal - */ -trait FilterLocatorTrait -{ - private $filterLocator; - - /** - * Sets a filter locator with a backward compatibility. - * - * @param ContainerInterface|FilterCollection|null $filterLocator - */ - private function setFilterLocator($filterLocator, bool $allowNull = false): void - { - if ($filterLocator instanceof ContainerInterface || $filterLocator instanceof FilterCollection || (null === $filterLocator && $allowNull)) { - if ($filterLocator instanceof FilterCollection) { - @trigger_error(sprintf('The %s class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of %s instead.', FilterCollection::class, ContainerInterface::class), \E_USER_DEPRECATED); - } - - $this->filterLocator = $filterLocator; - } else { - throw new InvalidArgumentException(sprintf('The "$filterLocator" argument is expected to be an implementation of the "%s" interface%s.', ContainerInterface::class, $allowNull ? ' or null' : '')); - } - } - - /** - * Gets a filter with a backward compatibility. - */ - private function getFilter(string $filterId): ?FilterInterface - { - if ($this->filterLocator instanceof ContainerInterface && $this->filterLocator->has($filterId)) { - return $this->filterLocator->get($filterId); - } - - if ($this->filterLocator instanceof FilterCollection && $this->filterLocator->offsetExists($filterId)) { - return $this->filterLocator->offsetGet($filterId); - } - - return null; - } -} diff --git a/src/Core/Api/FormatMatcher.php b/src/Core/Api/FormatMatcher.php deleted file mode 100644 index 38cc12cf6df..00000000000 --- a/src/Core/Api/FormatMatcher.php +++ /dev/null @@ -1,22 +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\Api; - -class_exists(\ApiPlatform\Api\FormatMatcher::class); - -if (false) { - final class FormatMatcher extends \ApiPlatform\Api\FormatMatcher - { - } -} diff --git a/src/Core/Api/FormatsProvider.php b/src/Core/Api/FormatsProvider.php deleted file mode 100644 index 2c6ae254c73..00000000000 --- a/src/Core/Api/FormatsProvider.php +++ /dev/null @@ -1,109 +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\Api; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\InvalidArgumentException; - -/** - * {@inheritdoc} - * - * @author Anthony GRASSIOT - * - * @deprecated since API Platform 2.5, use the "formats" attribute instead - */ -final class FormatsProvider implements FormatsProviderInterface, OperationAwareFormatsProviderInterface -{ - private $configuredFormats; - private $resourceMetadataFactory; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $configuredFormats) - { - @trigger_error(sprintf('The "%s" class is deprecated since API Platform 2.5, use the "formats" attribute instead.', __CLASS__), \E_USER_DEPRECATED); - - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->configuredFormats = $configuredFormats; - } - - /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - */ - public function getFormatsFromAttributes(array $attributes): array - { - if (!$attributes || !isset($attributes['resource_class'])) { - return $this->configuredFormats; - } - - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - - if (!$formats = $resourceMetadata->getOperationAttribute($attributes, 'formats', [], true)) { - return $this->configuredFormats; - } - - if (!\is_array($formats)) { - throw new InvalidArgumentException(sprintf("The 'formats' attributes must be an array, %s given for resource class '%s'.", \gettype($formats), $attributes['resource_class'])); - } - - return $this->getOperationFormats($formats); - } - - /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - */ - public function getFormatsFromOperation(string $resourceClass, string $operationName, string $operationType): array - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if (!$formats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'formats', [], true)) { - return $this->configuredFormats; - } - - if (!\is_array($formats)) { - throw new InvalidArgumentException(sprintf("The 'formats' attributes must be an array, %s given for resource class '%s'.", \gettype($formats), $resourceClass)); - } - - return $this->getOperationFormats($formats); - } - - /** - * Filter and populate the acceptable formats. - * - * @throws InvalidArgumentException - */ - private function getOperationFormats(array $annotationFormats): array - { - $resourceFormats = []; - foreach ($annotationFormats as $format => $value) { - if (!is_numeric($format)) { - $resourceFormats[$format] = (array) $value; - continue; - } - if (!\is_string($value)) { - throw new InvalidArgumentException(sprintf("The 'formats' attributes value must be a string when trying to include an already configured format, %s given.", \gettype($value))); - } - if (\array_key_exists($value, $this->configuredFormats)) { - $resourceFormats[$value] = $this->configuredFormats[$value]; - continue; - } - - throw new InvalidArgumentException(sprintf("You either need to add the format '%s' to your project configuration or declare a mime type for it in your annotation.", $value)); - } - - return $resourceFormats; - } -} diff --git a/src/Core/Api/FormatsProviderInterface.php b/src/Core/Api/FormatsProviderInterface.php deleted file mode 100644 index ea979141a28..00000000000 --- a/src/Core/Api/FormatsProviderInterface.php +++ /dev/null @@ -1,29 +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\Api; - -/** - * Extracts formats for a given operation according to the retrieved Metadata. - * - * @author Anthony GRASSIOT - * - * @deprecated since API Platform 2.5, use the "formats" attribute instead - */ -interface FormatsProviderInterface -{ - /** - * Finds formats for an operation. - */ - public function getFormatsFromAttributes(array $attributes): array; -} diff --git a/src/Core/Api/IdentifiersExtractor.php b/src/Core/Api/IdentifiersExtractor.php deleted file mode 100644 index f6bbf3f43fb..00000000000 --- a/src/Core/Api/IdentifiersExtractor.php +++ /dev/null @@ -1,121 +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\Api; - -use ApiPlatform\Api\IdentifiersExtractor as NewIdentifiersExtractor; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\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, bool $metadataBackwardCompatibilityLayer = null) - { - $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); - } - - if ($metadataBackwardCompatibilityLayer) { - trigger_deprecation('api-platform/core', '2.7', sprintf('The service "%s" is deprecated, use %s instead.', self::class, NewIdentifiersExtractor::class)); - } - } - - /** - * {@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 deleted file mode 100644 index 2a964d489ad..00000000000 --- a/src/Core/Api/IdentifiersExtractorInterface.php +++ /dev/null @@ -1,38 +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\Api; - -use ApiPlatform\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 deleted file mode 100644 index 85af6b3024f..00000000000 --- a/src/Core/Api/IriConverterInterface.php +++ /dev/null @@ -1,68 +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\Api; - -use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Exception\ItemNotFoundException; -use ApiPlatform\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/Core/Api/OperationAwareFormatsProviderInterface.php b/src/Core/Api/OperationAwareFormatsProviderInterface.php deleted file mode 100644 index 7860aa1ff0f..00000000000 --- a/src/Core/Api/OperationAwareFormatsProviderInterface.php +++ /dev/null @@ -1,29 +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\Api; - -/** - * Extracts formats for a given operation according to the retrieved Metadata. - * - * @author Anthony GRASSIOT - * - * @deprecated since API Platform 2.5, use the "formats" attribute instead - */ -interface OperationAwareFormatsProviderInterface extends FormatsProviderInterface -{ - /** - * Finds formats for an operation. - */ - public function getFormatsFromOperation(string $resourceClass, string $operationName, string $operationType): array; -} diff --git a/src/Core/Api/OperationMethodResolverInterface.php b/src/Core/Api/OperationMethodResolverInterface.php deleted file mode 100644 index a9f95c9706e..00000000000 --- a/src/Core/Api/OperationMethodResolverInterface.php +++ /dev/null @@ -1,40 +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\Api; - -use ApiPlatform\Exception\RuntimeException; - -/** - * Resolves the uppercased HTTP method associated with an operation. - * - * @author Kévin Dunglas - * - * @deprecated since API Platform 2.5, use the "method" attribute instead - */ -interface OperationMethodResolverInterface -{ - /** - * Resolves the uppercased HTTP method associated with a collection operation. - * - * @throws RuntimeException - */ - public function getCollectionOperationMethod(string $resourceClass, string $operationName): string; - - /** - * Resolves the uppercased HTTP method associated with an item operation. - * - * @throws RuntimeException - */ - public function getItemOperationMethod(string $resourceClass, string $operationName): string; -} diff --git a/src/Core/Api/OperationType.php b/src/Core/Api/OperationType.php deleted file mode 100644 index f1daa8b016b..00000000000 --- a/src/Core/Api/OperationType.php +++ /dev/null @@ -1,22 +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\Api; - -final class OperationType -{ - public const ITEM = 'item'; - public const COLLECTION = 'collection'; - public const SUBRESOURCE = 'subresource'; - public const TYPES = [self::ITEM, self::COLLECTION, self::SUBRESOURCE]; -} diff --git a/src/Core/Api/OperationTypeDeprecationHelper.php b/src/Core/Api/OperationTypeDeprecationHelper.php deleted file mode 100644 index 4d8c35e873e..00000000000 --- a/src/Core/Api/OperationTypeDeprecationHelper.php +++ /dev/null @@ -1,44 +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\Api; - -/** - * Class OperationTypeDeprecationHelper - * Before API Platform 2.1, the operation type was one of: - * - "collection" (true) - * - "item" (false). - * - * Because we introduced a third type in API Platform 2.1, we're using a string with OperationType constants: - * - OperationType::ITEM - * - OperationType::COLLECTION - * - OperationType::SUBRESOURCE - * - * @internal - */ -final class OperationTypeDeprecationHelper -{ - /** - * @param string|bool $operationType - */ - public static function getOperationType($operationType): string - { - if (\is_bool($operationType)) { - @trigger_error('Using a boolean for the Operation Type is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3', \E_USER_DEPRECATED); - - $operationType = $operationType ? OperationType::COLLECTION : OperationType::ITEM; - } - - return $operationType; - } -} diff --git a/src/Core/Api/ResourceClassResolver.php b/src/Core/Api/ResourceClassResolver.php deleted file mode 100644 index 9eff13927b7..00000000000 --- a/src/Core/Api/ResourceClassResolver.php +++ /dev/null @@ -1,22 +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\Api; - -class_exists(\ApiPlatform\Api\ResourceClassResolver::class); - -if (false) { - final class ResourceClassResolver extends \ApiPlatform\Api\ResourceClassResolver - { - } -} diff --git a/src/Core/Bridge/Doctrine/Common/DataPersister.php b/src/Core/Bridge/Doctrine/Common/DataPersister.php deleted file mode 100644 index a13d92fc5f7..00000000000 --- a/src/Core/Bridge/Doctrine/Common/DataPersister.php +++ /dev/null @@ -1,105 +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\Doctrine\Common; - -use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; -use ApiPlatform\Util\ClassInfoTrait; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectManager as DoctrineObjectManager; - -/** - * Data persister for Doctrine. - * - * @author Baptiste Meyer - * - * @deprecated - */ -final class DataPersister implements ContextAwareDataPersisterInterface -{ - use ClassInfoTrait; - - private $managerRegistry; - - public function __construct(ManagerRegistry $managerRegistry) - { - $this->managerRegistry = $managerRegistry; - } - - /** - * {@inheritdoc} - */ - public function supports($data, array $context = []): bool - { - return null !== $this->getManager($data); - } - - /** - * {@inheritdoc} - */ - public function persist($data, array $context = []) - { - if (!$manager = $this->getManager($data)) { - return $data; - } - - if (!$manager->contains($data) || $this->isDeferredExplicit($manager, $data)) { - $manager->persist($data); - } - - $manager->flush(); - $manager->refresh($data); - - return $data; - } - - /** - * {@inheritdoc} - */ - public function remove($data, array $context = []) - { - if (!$manager = $this->getManager($data)) { - return; - } - - $manager->remove($data); - $manager->flush(); - } - - /** - * Gets the Doctrine object manager associated with given data. - * - * @param mixed $data - */ - private function getManager($data): ?DoctrineObjectManager - { - return \is_object($data) ? $this->managerRegistry->getManagerForClass($this->getObjectClass($data)) : null; - } - - /** - * Checks if doctrine does not manage data automatically. - * - * @param mixed $data - */ - private function isDeferredExplicit(DoctrineObjectManager $manager, $data): bool - { - $classMetadata = $manager->getClassMetadata($this->getObjectClass($data)); - if (($classMetadata instanceof ClassMetadataInfo || $classMetadata instanceof ClassMetadata) && method_exists($classMetadata, 'isChangeTrackingDeferredExplicit')) { - return $classMetadata->isChangeTrackingDeferredExplicit(); - } - - return false; - } -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php deleted file mode 100644 index 549b27901df..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php +++ /dev/null @@ -1,99 +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\Doctrine\Common\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Psr\Log\LoggerInterface; - -/** - * Trait for filtering the collection by boolean values. - * - * Filters collection on equality of boolean properties. The value is specified - * as one of ( "true" | "false" | "1" | "0" ) in the query. - * - * For each property passed, if the resource does not have such property or if - * the value is not one of ( "true" | "false" | "1" | "0" ) the property is ignored. - * - * @author Amrouche Hamza - * @author Teoh Han Hui - * @author Alan Poulain - */ -trait BooleanFilterTrait -{ - use PropertyHelperTrait; - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - $properties = $this->getProperties(); - if (null === $properties) { - $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null); - } - - foreach ($properties as $property => $unused) { - if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isBooleanField($property, $resourceClass)) { - continue; - } - $propertyName = $this->normalizePropertyName($property); - $description[$propertyName] = [ - 'property' => $propertyName, - 'type' => 'bool', - 'required' => false, - ]; - } - - return $description; - } - - abstract protected function getProperties(): ?array; - - abstract protected function getLogger(): LoggerInterface; - - abstract protected function normalizePropertyName($property); - - /** - * Determines whether the given property refers to a boolean field. - */ - protected function isBooleanField(string $property, string $resourceClass): bool - { - return isset(self::DOCTRINE_BOOLEAN_TYPES[(string) $this->getDoctrineFieldType($property, $resourceClass)]); - } - - private function normalizeValue($value, string $property): ?bool - { - if (\in_array($value, [true, 'true', '1'], true)) { - return true; - } - - if (\in_array($value, [false, 'false', '0'], true)) { - return false; - } - - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Invalid boolean value for "%s" property, expected one of ( "%s" )', $property, implode('" | "', [ - 'true', - 'false', - '1', - '0', - ]))), - ]); - - return null; - } -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/DateFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/DateFilterInterface.php deleted file mode 100644 index 8ec6ebf5b77..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/DateFilterInterface.php +++ /dev/null @@ -1,33 +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\Doctrine\Common\Filter; - -/** - * Interface for filtering the collection by date intervals. - * - * @author Kévin Dunglas - * @author Théo FIDRY - * @author Alan Poulain - */ -interface DateFilterInterface -{ - public const PARAMETER_BEFORE = 'before'; - public const PARAMETER_STRICTLY_BEFORE = 'strictly_before'; - public const PARAMETER_AFTER = 'after'; - public const PARAMETER_STRICTLY_AFTER = 'strictly_after'; - public const EXCLUDE_NULL = 'exclude_null'; - public const INCLUDE_NULL_BEFORE = 'include_null_before'; - public const INCLUDE_NULL_AFTER = 'include_null_after'; - public const INCLUDE_NULL_BEFORE_AND_AFTER = 'include_null_before_and_after'; -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/DateFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/DateFilterTrait.php deleted file mode 100644 index 82e5db681f4..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/DateFilterTrait.php +++ /dev/null @@ -1,82 +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\Doctrine\Common\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; - -/** - * Trait for filtering the collection by date intervals. - * - * @author Kévin Dunglas - * @author Théo FIDRY - * @author Alan Poulain - */ -trait DateFilterTrait -{ - use PropertyHelperTrait; - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - $properties = $this->getProperties(); - if (null === $properties) { - $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null); - } - - foreach ($properties as $property => $nullManagement) { - if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isDateField($property, $resourceClass)) { - continue; - } - - $description += $this->getFilterDescription($property, self::PARAMETER_BEFORE); - $description += $this->getFilterDescription($property, self::PARAMETER_STRICTLY_BEFORE); - $description += $this->getFilterDescription($property, self::PARAMETER_AFTER); - $description += $this->getFilterDescription($property, self::PARAMETER_STRICTLY_AFTER); - } - - return $description; - } - - abstract protected function getProperties(): ?array; - - abstract protected function normalizePropertyName($property); - - /** - * Determines whether the given property refers to a date field. - */ - protected function isDateField(string $property, string $resourceClass): bool - { - return isset(self::DOCTRINE_DATE_TYPES[(string) $this->getDoctrineFieldType($property, $resourceClass)]); - } - - /** - * Gets filter description. - */ - protected function getFilterDescription(string $property, string $period): array - { - $propertyName = $this->normalizePropertyName($property); - - return [ - sprintf('%s[%s]', $propertyName, $period) => [ - 'property' => $propertyName, - 'type' => \DateTimeInterface::class, - 'required' => false, - ], - ]; - } -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterInterface.php deleted file mode 100644 index 4194f4fb9e1..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterInterface.php +++ /dev/null @@ -1,25 +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\Doctrine\Common\Filter; - -/** - * Interface for filtering the collection by whether a property value exists or not. - * - * @author Teoh Han Hui - * @author Alan Poulain - */ -interface ExistsFilterInterface -{ - public const QUERY_PARAMETER_KEY = 'exists'; -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php deleted file mode 100644 index cd07e388fe4..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php +++ /dev/null @@ -1,102 +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\Doctrine\Common\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Psr\Log\LoggerInterface; - -/** - * Trait for filtering the collection by whether a property value exists or not. - * - * @author Teoh Han Hui - * @author Alan Poulain - */ -trait ExistsFilterTrait -{ - use PropertyHelperTrait; - - /** - * @var string Keyword used to retrieve the value - */ - private $existsParameterName; - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - $properties = $this->getProperties(); - if (null === $properties) { - $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null); - } - - foreach ($properties as $property => $unused) { - if (!$this->isPropertyMapped($property, $resourceClass, true) || !$this->isNullableField($property, $resourceClass)) { - continue; - } - $propertyName = $this->normalizePropertyName($property); - $description[sprintf('%s[%s]', $this->existsParameterName, $propertyName)] = [ - 'property' => $propertyName, - 'type' => 'bool', - 'required' => false, - ]; - } - - return $description; - } - - /** - * Determines whether the given property refers to a nullable field. - */ - abstract protected function isNullableField(string $property, string $resourceClass): bool; - - abstract protected function getProperties(): ?array; - - abstract protected function getLogger(): LoggerInterface; - - abstract protected function normalizePropertyName($property); - - private function normalizeValue($value, string $property): ?bool - { - if (\is_array($value) && isset($value[self::QUERY_PARAMETER_KEY])) { - @trigger_error( - sprintf('The ExistsFilter syntax "%s[exists]=true/false" is deprecated since 2.5. Use the syntax "%s[%s]=true/false" instead.', $property, $this->existsParameterName, $property), - \E_USER_DEPRECATED - ); - $value = $value[self::QUERY_PARAMETER_KEY]; - } - - if (\in_array($value, [true, 'true', '1', '', null], true)) { - return true; - } - - if (\in_array($value, [false, 'false', '0'], true)) { - return false; - } - - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Invalid value for "%s[%s]", expected one of ( "%s" )', $this->existsParameterName, $property, implode('" | "', [ - 'true', - 'false', - '1', - '0', - ]))), - ]); - - return null; - } -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php deleted file mode 100644 index 46fd6fcebd1..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php +++ /dev/null @@ -1,124 +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\Doctrine\Common\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Psr\Log\LoggerInterface; - -/** - * Trait for filtering the collection by numeric values. - * - * @author Amrouche Hamza - * @author Teoh Han Hui - * @author Alan Poulain - */ -trait NumericFilterTrait -{ - use PropertyHelperTrait; - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - $properties = $this->getProperties(); - if (null === $properties) { - $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null); - } - - foreach ($properties as $property => $unused) { - if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isNumericField($property, $resourceClass)) { - continue; - } - - $propertyName = $this->normalizePropertyName($property); - $filterParameterNames = [$propertyName, $propertyName.'[]']; - foreach ($filterParameterNames as $filterParameterName) { - $description[$filterParameterName] = [ - 'property' => $propertyName, - 'type' => $this->getType((string) $this->getDoctrineFieldType($property, $resourceClass)), - 'required' => false, - 'is_collection' => '[]' === substr((string) $filterParameterName, -2), - ]; - } - } - - return $description; - } - - /** - * Gets the PHP type corresponding to this Doctrine type. - */ - abstract protected function getType(string $doctrineType = null): string; - - abstract protected function getProperties(): ?array; - - abstract protected function getLogger(): LoggerInterface; - - abstract protected function normalizePropertyName($property); - - /** - * Determines whether the given property refers to a numeric field. - */ - protected function isNumericField(string $property, string $resourceClass): bool - { - return isset(self::DOCTRINE_NUMERIC_TYPES[(string) $this->getDoctrineFieldType($property, $resourceClass)]); - } - - protected function normalizeValues($value, string $property): ?array - { - if (!is_numeric($value) && (!\is_array($value) || !$this->isNumericArray($value))) { - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Invalid numeric value for "%s" property', $property)), - ]); - - return null; - } - - $values = (array) $value; - - foreach ($values as $key => $val) { - if (!\is_int($key)) { - unset($values[$key]); - - continue; - } - $values[$key] = $val + 0; // coerce $val to the right type. - } - - if (empty($values)) { - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('At least one value is required, multiple values should be in "%1$s[]=firstvalue&%1$s[]=secondvalue" format', $property)), - ]); - - return null; - } - - return array_values($values); - } - - protected function isNumericArray(array $values): bool - { - foreach ($values as $value) { - if (!is_numeric($value)) { - return false; - } - } - - return true; - } -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/OrderFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/OrderFilterInterface.php deleted file mode 100644 index 4edbeaad129..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/OrderFilterInterface.php +++ /dev/null @@ -1,39 +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\Doctrine\Common\Filter; - -/** - * Interface for ordering the collection by given properties. - * - * @author Kévin Dunglas - * @author Théo FIDRY - * @author Alan Poulain - */ -interface OrderFilterInterface -{ - public const DIRECTION_ASC = 'ASC'; - public const DIRECTION_DESC = 'DESC'; - public const NULLS_SMALLEST = 'nulls_smallest'; - public const NULLS_LARGEST = 'nulls_largest'; - public const NULLS_DIRECTION_MAP = [ - self::NULLS_SMALLEST => [ - 'ASC' => 'ASC', - 'DESC' => 'DESC', - ], - self::NULLS_LARGEST => [ - 'ASC' => 'DESC', - 'DESC' => 'ASC', - ], - ]; -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php deleted file mode 100644 index 207f36e5e1c..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php +++ /dev/null @@ -1,86 +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\Doctrine\Common\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; - -/** - * Trait for ordering the collection by given properties. - * - * @author Kévin Dunglas - * @author Théo FIDRY - * @author Alan Poulain - */ -trait OrderFilterTrait -{ - use PropertyHelperTrait; - - /** - * @var string Keyword used to retrieve the value - */ - protected $orderParameterName; - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - $properties = $this->getProperties(); - if (null === $properties) { - $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null); - } - - foreach ($properties as $property => $propertyOptions) { - if (!$this->isPropertyMapped($property, $resourceClass)) { - continue; - } - $propertyName = $this->normalizePropertyName($property); - $description[sprintf('%s[%s]', $this->orderParameterName, $propertyName)] = [ - 'property' => $propertyName, - 'type' => 'string', - 'required' => false, - 'schema' => [ - 'type' => 'string', - 'enum' => [ - strtolower(OrderFilterInterface::DIRECTION_ASC), - strtolower(OrderFilterInterface::DIRECTION_DESC), - ], - ], - ]; - } - - return $description; - } - - abstract protected function getProperties(): ?array; - - abstract protected function normalizePropertyName($property); - - private function normalizeValue($value, string $property): ?string - { - if (empty($value) && null !== $defaultDirection = $this->getProperties()[$property]['default_direction'] ?? null) { - // fallback to default direction - $value = $defaultDirection; - } - - $value = strtoupper($value); - if (!\in_array($value, [self::DIRECTION_ASC, self::DIRECTION_DESC], true)) { - return null; - } - - return $value; - } -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php deleted file mode 100644 index 3d789fff16d..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterInterface.php +++ /dev/null @@ -1,29 +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\Doctrine\Common\Filter; - -/** - * Interface for filtering the collection by range. - * - * @author Lee Siong Chan - * @author Alan Poulain - */ -interface RangeFilterInterface -{ - public const PARAMETER_BETWEEN = 'between'; - public const PARAMETER_GREATER_THAN = 'gt'; - public const PARAMETER_GREATER_THAN_OR_EQUAL = 'gte'; - public const PARAMETER_LESS_THAN = 'lt'; - public const PARAMETER_LESS_THAN_OR_EQUAL = 'lte'; -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php deleted file mode 100644 index 97148b8cacc..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php +++ /dev/null @@ -1,141 +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\Doctrine\Common\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Psr\Log\LoggerInterface; - -/** - * Trait for filtering the collection by range. - * - * @author Lee Siong Chan - * @author Alan Poulain - */ -trait RangeFilterTrait -{ - use PropertyHelperTrait; - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - $properties = $this->getProperties(); - if (null === $properties) { - $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null); - } - - foreach ($properties as $property => $unused) { - if (!$this->isPropertyMapped($property, $resourceClass)) { - continue; - } - - $description += $this->getFilterDescription($property, self::PARAMETER_BETWEEN); - $description += $this->getFilterDescription($property, self::PARAMETER_GREATER_THAN); - $description += $this->getFilterDescription($property, self::PARAMETER_GREATER_THAN_OR_EQUAL); - $description += $this->getFilterDescription($property, self::PARAMETER_LESS_THAN); - $description += $this->getFilterDescription($property, self::PARAMETER_LESS_THAN_OR_EQUAL); - } - - return $description; - } - - abstract protected function getProperties(): ?array; - - abstract protected function getLogger(): LoggerInterface; - - abstract protected function normalizePropertyName($property); - - /** - * Gets filter description. - */ - protected function getFilterDescription(string $fieldName, string $operator): array - { - $propertyName = $this->normalizePropertyName($fieldName); - - return [ - sprintf('%s[%s]', $propertyName, $operator) => [ - 'property' => $propertyName, - 'type' => 'string', - 'required' => false, - ], - ]; - } - - private function normalizeValues(array $values, string $property): ?array - { - $operators = [self::PARAMETER_BETWEEN, self::PARAMETER_GREATER_THAN, self::PARAMETER_GREATER_THAN_OR_EQUAL, self::PARAMETER_LESS_THAN, self::PARAMETER_LESS_THAN_OR_EQUAL]; - - foreach ($values as $operator => $value) { - if (!\in_array($operator, $operators, true)) { - unset($values[$operator]); - } - } - - if (empty($values)) { - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('At least one valid operator ("%s") is required for "%s" property', implode('", "', $operators), $property)), - ]); - - return null; - } - - return $values; - } - - /** - * Normalize the values array for between operator. - */ - private function normalizeBetweenValues(array $values): ?array - { - if (2 !== \count($values)) { - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Invalid format for "[%s]", expected ".."', self::PARAMETER_BETWEEN)), - ]); - - return null; - } - - if (!is_numeric($values[0]) || !is_numeric($values[1])) { - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Invalid values for "[%s]" range, expected numbers', self::PARAMETER_BETWEEN)), - ]); - - return null; - } - - return [$values[0] + 0, $values[1] + 0]; // coerce to the right types. - } - - /** - * Normalize the value. - * - * @return int|float|null - */ - private function normalizeValue(string $value, string $operator) - { - if (!is_numeric($value)) { - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Invalid value for "[%s]", expected number', $operator)), - ]); - - return null; - } - - return $value + 0; // coerce $value to the right type. - } -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterInterface.php b/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterInterface.php deleted file mode 100644 index e80e3dc1a96..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterInterface.php +++ /dev/null @@ -1,48 +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\Doctrine\Common\Filter; - -/** - * Interface for filtering the collection by given properties. - * - * @author Kévin Dunglas - * @author Alan Poulain - */ -interface SearchFilterInterface -{ - /** - * @var string Exact matching - */ - public const STRATEGY_EXACT = 'exact'; - - /** - * @var string The value must be contained in the field - */ - public const STRATEGY_PARTIAL = 'partial'; - - /** - * @var string Finds fields that are starting with the value - */ - public const STRATEGY_START = 'start'; - - /** - * @var string Finds fields that are ending with the value - */ - public const STRATEGY_END = 'end'; - - /** - * @var string Finds fields that are starting with the word - */ - public const STRATEGY_WORD_START = 'word_start'; -} diff --git a/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php b/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php deleted file mode 100644 index 8169b2fc719..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php +++ /dev/null @@ -1,170 +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\Doctrine\Common\Filter; - -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Psr\Log\LoggerInterface; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; - -/** - * Trait for filtering the collection by given properties. - * - * @author Kévin Dunglas - * @author Alan Poulain - */ -trait SearchFilterTrait -{ - use PropertyHelperTrait; - - protected $iriConverter; - protected $propertyAccessor; - protected $identifiersExtractor; - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - $properties = $this->getProperties(); - if (null === $properties) { - $properties = array_fill_keys($this->getClassMetadata($resourceClass)->getFieldNames(), null); - } - - foreach ($properties as $property => $strategy) { - if (!$this->isPropertyMapped($property, $resourceClass, true)) { - continue; - } - - if ($this->isPropertyNested($property, $resourceClass)) { - $propertyParts = $this->splitPropertyParts($property, $resourceClass); - $field = $propertyParts['field']; - $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']); - } else { - $field = $property; - $metadata = $this->getClassMetadata($resourceClass); - } - - $propertyName = $this->normalizePropertyName($property); - if ($metadata->hasField($field)) { - $typeOfField = $this->getType($metadata->getTypeOfField($field)); - $strategy = $this->getProperties()[$property] ?? self::STRATEGY_EXACT; - $filterParameterNames = [$propertyName]; - - if (self::STRATEGY_EXACT === $strategy) { - $filterParameterNames[] = $propertyName.'[]'; - } - - foreach ($filterParameterNames as $filterParameterName) { - $description[$filterParameterName] = [ - 'property' => $propertyName, - 'type' => $typeOfField, - 'required' => false, - 'strategy' => $strategy, - 'is_collection' => '[]' === substr((string) $filterParameterName, -2), - ]; - } - } elseif ($metadata->hasAssociation($field)) { - $filterParameterNames = [ - $propertyName, - $propertyName.'[]', - ]; - - foreach ($filterParameterNames as $filterParameterName) { - $description[$filterParameterName] = [ - 'property' => $propertyName, - 'type' => 'string', - 'required' => false, - 'strategy' => self::STRATEGY_EXACT, - 'is_collection' => '[]' === substr((string) $filterParameterName, -2), - ]; - } - } - } - - return $description; - } - - /** - * Converts a Doctrine type in PHP type. - */ - abstract protected function getType(string $doctrineType): string; - - abstract protected function getProperties(): ?array; - - abstract protected function getLogger(): LoggerInterface; - - abstract protected function getIriConverter(): IriConverterInterface; - - abstract protected function getPropertyAccessor(): PropertyAccessorInterface; - - abstract protected function normalizePropertyName($property); - - /** - * Gets the ID from an IRI or a raw ID. - */ - protected function getIdFromValue(string $value) - { - try { - $item = $this->getIriConverter()->getItemFromIri($value, ['fetch_data' => false]); - - return $this->getPropertyAccessor()->getValue($item, 'id'); - } catch (InvalidArgumentException $e) { - // Do nothing, return the raw value - } - - return $value; - } - - /** - * Normalize the values array. - */ - protected function normalizeValues(array $values, string $property): ?array - { - foreach ($values as $key => $value) { - if (!\is_int($key) || !(\is_string($value) || \is_int($value))) { - unset($values[$key]); - } - } - - if (empty($values)) { - $this->getLogger()->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('At least one value is required, multiple values should be in "%1$s[]=firstvalue&%1$s[]=secondvalue" format', $property)), - ]); - - return null; - } - - return array_values($values); - } - - /** - * When the field should be an integer, check that the given value is a valid one. - * - * @param mixed|null $type - */ - protected function hasValidValues(array $values, $type = null): bool - { - foreach ($values as $key => $value) { - if (null !== $value && \in_array($type, (array) self::DOCTRINE_INTEGER_TYPE, true) && false === filter_var($value, \FILTER_VALIDATE_INT)) { - return false; - } - } - - return true; - } -} diff --git a/src/Core/Bridge/Doctrine/Common/PropertyHelperTrait.php b/src/Core/Bridge/Doctrine/Common/PropertyHelperTrait.php deleted file mode 100644 index 23ea9abdd78..00000000000 --- a/src/Core/Bridge/Doctrine/Common/PropertyHelperTrait.php +++ /dev/null @@ -1,171 +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\Doctrine\Common; - -use Doctrine\DBAL\Types\Type; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\Mapping\ClassMetadata; - -/** - * Helper trait for getting information regarding a property using the resource metadata. - * - * @author Kévin Dunglas - * @author Théo FIDRY - * @author Alan Poulain - */ -trait PropertyHelperTrait -{ - abstract protected function getManagerRegistry(): ManagerRegistry; - - /** - * Determines whether the given property is mapped. - */ - protected function isPropertyMapped(string $property, string $resourceClass, bool $allowAssociation = false): bool - { - if ($this->isPropertyNested($property, $resourceClass)) { - $propertyParts = $this->splitPropertyParts($property, $resourceClass); - $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']); - $property = $propertyParts['field']; - } else { - $metadata = $this->getClassMetadata($resourceClass); - } - - return $metadata->hasField($property) || ($allowAssociation && $metadata->hasAssociation($property)); - } - - /** - * Determines whether the given property is nested. - */ - protected function isPropertyNested(string $property/* , string $resourceClass */): bool - { - if (\func_num_args() > 1) { - $resourceClass = (string) func_get_arg(1); - } else { - if (__CLASS__ !== static::class) { // @phpstan-ignore-line - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a second `$resourceClass` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.1.', __FUNCTION__), \E_USER_DEPRECATED); - } - } - $resourceClass = null; - } - - $pos = strpos($property, '.'); - if (false === $pos) { - return false; - } - - return null !== $resourceClass && $this->getClassMetadata($resourceClass)->hasAssociation(substr($property, 0, $pos)); - } - - /** - * Determines whether the given property is embedded. - */ - protected function isPropertyEmbedded(string $property, string $resourceClass): bool - { - return false !== strpos($property, '.') && $this->getClassMetadata($resourceClass)->hasField($property); - } - - /** - * Splits the given property into parts. - * - * Returns an array with the following keys: - * - associations: array of associations according to nesting order - * - field: string holding the actual field (leaf node) - */ - protected function splitPropertyParts(string $property/* , string $resourceClass */): array - { - $resourceClass = null; - $parts = explode('.', $property); - - if (\func_num_args() > 1) { - $resourceClass = func_get_arg(1); - } elseif (__CLASS__ !== static::class) { // @phpstan-ignore-line - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a second `$resourceClass` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.1.', __FUNCTION__), \E_USER_DEPRECATED); - } - } - - if (null === $resourceClass) { - return [ - 'associations' => \array_slice($parts, 0, -1), - 'field' => end($parts), - ]; - } - - $metadata = $this->getClassMetadata($resourceClass); - $slice = 0; - - foreach ($parts as $part) { - if ($metadata->hasAssociation($part)) { - $metadata = $this->getClassMetadata($metadata->getAssociationTargetClass($part)); - ++$slice; - } - } - - if (\count($parts) === $slice) { - --$slice; - } - - return [ - 'associations' => \array_slice($parts, 0, $slice), - 'field' => implode('.', \array_slice($parts, $slice)), - ]; - } - - /** - * Gets the Doctrine Type of a given property/resourceClass. - * - * @return Type|string|null - */ - protected function getDoctrineFieldType(string $property, string $resourceClass) - { - $propertyParts = $this->splitPropertyParts($property, $resourceClass); - $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']); - - return $metadata->getTypeOfField($propertyParts['field']); - } - - /** - * Gets nested class metadata for the given resource. - * - * @param string[] $associations - */ - protected function getNestedMetadata(string $resourceClass, array $associations): ClassMetadata - { - $metadata = $this->getClassMetadata($resourceClass); - - foreach ($associations as $association) { - if ($metadata->hasAssociation($association)) { - $associationClass = $metadata->getAssociationTargetClass($association); - - $metadata = $this->getClassMetadata($associationClass); - } - } - - return $metadata; - } - - /** - * Gets class metadata for the given resource. - */ - protected function getClassMetadata(string $resourceClass): ClassMetadata - { - return $this - ->getManagerRegistry() - ->getManagerForClass($resourceClass) - ->getClassMetadata($resourceClass); - } -} diff --git a/src/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php b/src/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php deleted file mode 100644 index 3ecb1f9dfff..00000000000 --- a/src/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php +++ /dev/null @@ -1,113 +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\Doctrine\Common\Util; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\InvalidIdentifierException; -use ApiPlatform\Exception\PropertyNotFoundException; -use Doctrine\DBAL\Types\ConversionException; -use Doctrine\DBAL\Types\Type as DBALType; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\Persistence\ObjectManager; - -/** - * @internal - */ -trait IdentifierManagerTrait -{ - private $propertyNameCollectionFactory; - private $propertyMetadataFactory; - private $resourceMetadataFactory; - - /** - * Transform and check the identifier, composite or not. - * - * @param int|string $id - * - * @throws PropertyNotFoundException - * @throws InvalidIdentifierException - */ - private function normalizeIdentifiers($id, ObjectManager $manager, string $resourceClass): array - { - $identifierValues = [$id]; - $doctrineClassMetadata = $manager->getClassMetadata($resourceClass); - $doctrineIdentifierFields = $doctrineClassMetadata->getIdentifier(); - $isOrm = $manager instanceof EntityManagerInterface; - $isOdm = $manager instanceof DocumentManager; - $platform = $isOrm ? $manager->getConnection()->getDatabasePlatform() : null; - $identifiersMap = null; - - if (\count($doctrineIdentifierFields) > 1) { - $identifiersMap = []; - - // first transform identifiers to a proper key/value array - foreach (explode(';', (string) $id) as $identifier) { - if (!$identifier) { - continue; - } - - $identifierPair = explode('=', $identifier); - $identifiersMap[$identifierPair[0]] = $identifierPair[1]; - } - } - - $identifiers = []; - $i = 0; - - foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); - - if (!$propertyMetadata->isIdentifier()) { - continue; - } - - $identifier = null === $identifiersMap ? $identifierValues[$i] ?? null : $identifiersMap[$propertyName] ?? null; - if (null === $identifier) { - $exceptionMessage = sprintf('Invalid identifier "%s", "%s" was not found', $id, $propertyName); - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $exceptionMessage .= sprintf(' for resource "%s"', $resourceMetadata->getShortName()); - } - - throw new PropertyNotFoundException($exceptionMessage.'.'); - } - - $doctrineTypeName = $doctrineClassMetadata->getTypeOfField($propertyName); - - try { - if ($isOrm && null !== $doctrineTypeName && DBALType::hasType($doctrineTypeName)) { - $identifier = DBALType::getType($doctrineTypeName)->convertToPHPValue($identifier, $platform); - } - if ($isOdm && null !== $doctrineTypeName && MongoDbType::hasType($doctrineTypeName)) { - $identifier = MongoDbType::getType($doctrineTypeName)->convertToPHPValue($identifier); - } - } catch (ConversionException $e) { - $exceptionMessage = sprintf('Invalid value "%s" provided for an identifier', $propertyName); - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $exceptionMessage .= sprintf(' for resource "%s"', $resourceMetadata->getShortName()); - } - - throw new InvalidIdentifierException($exceptionMessage.'.', $e->getCode(), $e); - } - - $identifiers[$propertyName] = $identifier; - ++$i; - } - - return $identifiers; - } -} diff --git a/src/Core/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php b/src/Core/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php deleted file mode 100644 index 2cf6b56b204..00000000000 --- a/src/Core/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php +++ /dev/null @@ -1,285 +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\Doctrine\EventListener; - -use ApiPlatform\Core\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 Doctrine\Common\EventArgs; -use Doctrine\ODM\MongoDB\Event\OnFlushEventArgs as MongoDbOdmOnFlushEventArgs; -use Doctrine\ORM\Event\OnFlushEventArgs as OrmOnFlushEventArgs; -use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\Mercure\HubRegistry; -use Symfony\Component\Mercure\Update; -use Symfony\Component\Messenger\MessageBusInterface; -use Symfony\Component\Serializer\SerializerInterface; - -/** - * Publishes resources updates to the Mercure hub. - * - * @author Kévin Dunglas - * - * @experimental - */ -final class PublishMercureUpdatesListener -{ - use DispatchTrait; - use ResourceClassInfoTrait; - - private const ALLOWED_KEYS = [ - 'topics' => true, - 'data' => true, - 'private' => true, - 'id' => true, - 'type' => true, - 'retry' => true, - 'normalization_context' => true, - 'hub' => true, - 'enable_async_update' => true, - ]; - - private $iriConverter; - private $serializer; - private $hubRegistry; - private $expressionLanguage; - private $createdObjects; - private $updatedObjects; - private $deletedObjects; - private $formats; - private $graphQlSubscriptionManager; - private $graphQlMercureSubscriptionIriGenerator; - - /** - * @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) - { - if (null === $messageBus && null === $hubRegistry) { - throw new InvalidArgumentException('A message bus or a hub registry must be provided.'); - } - - $this->resourceClassResolver = $resourceClassResolver; - $this->iriConverter = $iriConverter; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->serializer = $serializer; - $this->formats = $formats; - $this->messageBus = $messageBus; - $this->hubRegistry = $hubRegistry; - $this->expressionLanguage = $expressionLanguage ?? (class_exists(ExpressionLanguage::class) ? new ExpressionLanguage() : null); - $this->graphQlSubscriptionManager = $graphQlSubscriptionManager; - $this->graphQlMercureSubscriptionIriGenerator = $graphQlMercureSubscriptionIriGenerator; - $this->reset(); - } - - /** - * Collects created, updated and deleted objects. - */ - public function onFlush(EventArgs $eventArgs): void - { - if ($eventArgs instanceof OrmOnFlushEventArgs) { - $uow = $eventArgs->getEntityManager()->getUnitOfWork(); - } elseif ($eventArgs instanceof MongoDbOdmOnFlushEventArgs) { - $uow = $eventArgs->getDocumentManager()->getUnitOfWork(); - } else { - return; - } - - $methodName = $eventArgs instanceof OrmOnFlushEventArgs ? 'getScheduledEntityInsertions' : 'getScheduledDocumentInsertions'; - foreach ($uow->{$methodName}() as $object) { - $this->storeObjectToPublish($object, 'createdObjects'); - } - - $methodName = $eventArgs instanceof OrmOnFlushEventArgs ? 'getScheduledEntityUpdates' : 'getScheduledDocumentUpdates'; - foreach ($uow->{$methodName}() as $object) { - $this->storeObjectToPublish($object, 'updatedObjects'); - } - - $methodName = $eventArgs instanceof OrmOnFlushEventArgs ? 'getScheduledEntityDeletions' : 'getScheduledDocumentDeletions'; - foreach ($uow->{$methodName}() as $object) { - $this->storeObjectToPublish($object, 'deletedObjects'); - } - } - - /** - * Publishes updates for changes collected on flush, and resets the store. - */ - public function postFlush(): void - { - try { - foreach ($this->createdObjects as $object) { - $this->publishUpdate($object, $this->createdObjects[$object], 'create'); - } - - foreach ($this->updatedObjects as $object) { - $this->publishUpdate($object, $this->updatedObjects[$object], 'update'); - } - - foreach ($this->deletedObjects as $object) { - $this->publishUpdate($object, $this->deletedObjects[$object], 'delete'); - } - } finally { - $this->reset(); - } - } - - private function reset(): void - { - $this->createdObjects = new \SplObjectStorage(); - $this->updatedObjects = new \SplObjectStorage(); - $this->deletedObjects = new \SplObjectStorage(); - } - - /** - * @param object $object - */ - private function storeObjectToPublish($object, string $property): void - { - if (null === $resourceClass = $this->getResourceClass($object)) { - return; - } - - $options = $this->resourceMetadataFactory->create($resourceClass)->getAttribute('mercure', false); - - if (\is_string($options)) { - if (null === $this->expressionLanguage) { - throw new RuntimeException('The Expression Language component is not installed. Try running "composer require symfony/expression-language".'); - } - - $options = $this->expressionLanguage->evaluate($options, ['object' => $object]); - } - - if (false === $options) { - return; - } - - if (true === $options) { - $options = []; - } - - if (!\is_array($options)) { - throw new InvalidArgumentException(sprintf('The value of the "mercure" attribute of the "%s" resource class must be a boolean, an array of options or an expression returning this array, "%s" given.', $resourceClass, \gettype($options))); - } - - foreach ($options as $key => $value) { - if (0 === $key) { - if (method_exists(Update::class, 'isPrivate')) { - throw new \InvalidArgumentException('Targets do not exist anymore since Mercure 0.10. Mark the update as private instead or downgrade the Mercure Component to version 0.3'); - } - - @trigger_error('Targets do not exist anymore since Mercure 0.10. Mark the update as private instead.', \E_USER_DEPRECATED); - break; - } - - if (!isset(self::ALLOWED_KEYS[$key])) { - throw new InvalidArgumentException(sprintf('The option "%s" set in the "mercure" attribute of the "%s" resource does not exist. Existing options: "%s"', $key, $resourceClass, implode('", "', self::ALLOWED_KEYS))); - } - - if ('hub' === $key && !$this->hubRegistry instanceof HubRegistry) { - throw new InvalidArgumentException(sprintf('The option "hub" of the "mercure" attribute cannot be set on the "%s" resource . Try running "composer require symfony/mercure:^0.5".', $resourceClass)); - } - } - - $options['enable_async_update'] = $options['enable_async_update'] ?? true; - - if ('deletedObjects' === $property) { - $this->deletedObjects[(object) [ - 'id' => $this->iriConverter->getIriFromItem($object), - 'iri' => $this->iriConverter->getIriFromItem($object, UrlGeneratorInterface::ABS_URL), - ]] = $options; - - return; - } - - $this->{$property}[$object] = $options; - } - - /** - * @param object $object - */ - private function publishUpdate($object, array $options, string $type): void - { - if ($object instanceof \stdClass) { - // By convention, if the object has been deleted, we send only its IRI. - // This may change in the feature, because it's not JSON Merge Patch compliant, - // and I'm not a fond of this approach. - $iri = $options['topics'] ?? $object->iri; - /** @var string $data */ - $data = json_encode(['@id' => $object->id]); - } else { - $resourceClass = $this->getObjectClass($object); - $context = $options['normalization_context'] ?? $this->resourceMetadataFactory->create($resourceClass)->getAttribute('normalization_context', []); - - $iri = $options['topics'] ?? $this->iriConverter->getIriFromItem($object, UrlGeneratorInterface::ABS_URL); - $data = $options['data'] ?? $this->serializer->serialize($object, key($this->formats), $context); - } - - $updates = array_merge([$this->buildUpdate($iri, $data, $options)], $this->getGraphQlSubscriptionUpdates($object, $options, $type)); - - foreach ($updates as $update) { - if ($options['enable_async_update'] && $this->messageBus) { - $this->dispatch($update); - continue; - } - - $this->hubRegistry instanceof HubRegistry ? $this->hubRegistry->getHub($options['hub'] ?? null)->publish($update) : ($this->hubRegistry)($update); - } - } - - /** - * @param object $object - * - * @return Update[] - */ - private function getGraphQlSubscriptionUpdates($object, array $options, string $type): array - { - if ('update' !== $type || !$this->graphQlSubscriptionManager || !$this->graphQlMercureSubscriptionIriGenerator) { - return []; - } - - $payloads = $this->graphQlSubscriptionManager->getPushPayloads($object); - - $updates = []; - foreach ($payloads as [$subscriptionId, $data]) { - $updates[] = $this->buildUpdate( - $this->graphQlMercureSubscriptionIriGenerator->generateTopicIri($subscriptionId), - (string) (new JsonResponse($data))->getContent(), - $options - ); - } - - return $updates; - } - - /** - * @param string|string[] $iri - */ - private function buildUpdate($iri, string $data, array $options): Update - { - if (method_exists(Update::class, 'isPrivate')) { - return new Update($iri, $data, $options['private'] ?? false, $options['id'] ?? null, $options['type'] ?? null, $options['retry'] ?? null); - } - - // Mercure Component < 0.4. - return new Update($iri, $data, $options); // @phpstan-ignore-line - } -} diff --git a/src/Core/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php b/src/Core/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php deleted file mode 100644 index 32c6875e788..00000000000 --- a/src/Core/Bridge/Doctrine/EventListener/PurgeHttpCacheListener.php +++ /dev/null @@ -1,22 +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\Doctrine\EventListener; - -class_exists(\ApiPlatform\Doctrine\EventListener\PurgeHttpCacheListener::class); - -if (false) { - final class PurgeHttpCacheListener extends \ApiPlatform\Doctrine\EventListener\PurgeHttpCacheListener - { - } -} diff --git a/src/Core/Bridge/Doctrine/EventListener/WriteListener.php b/src/Core/Bridge/Doctrine/EventListener/WriteListener.php deleted file mode 100644 index 68ac5b04e9f..00000000000 --- a/src/Core/Bridge/Doctrine/EventListener/WriteListener.php +++ /dev/null @@ -1,22 +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\Doctrine\EventListener; - -class_exists(\ApiPlatform\Doctrine\EventListener\WriteListener::class); - -if (false) { - final class WriteListener extends \ApiPlatform\Doctrine\EventListener\WriteListener - { - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php b/src/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php deleted file mode 100644 index 7900a86caa2..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProvider.php +++ /dev/null @@ -1,118 +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\Doctrine\MongoDbOdm; - -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface as LegacyAggregationCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface as LegacyAggregationResultCollectionExtensionInterface; -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; -use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Repository\DocumentRepository; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectRepository; - -/** - * Collection data provider for the Doctrine MongoDB ODM. - * - * @experimental - * - * @author Alan Poulain - */ -final class CollectionDataProvider implements CollectionDataProviderInterface, RestrictedDataProviderInterface -{ - private $managerRegistry; - private $resourceMetadataFactory; - private $collectionExtensions; - - /** - * @param LegacyAggregationCollectionExtensionInterface[]|AggregationCollectionExtensionInterface[] $collectionExtensions - * @param ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface $resourceMetadataFactory - */ - public function __construct(ManagerRegistry $managerRegistry, $resourceMetadataFactory, iterable $collectionExtensions = []) - { - $this->managerRegistry = $managerRegistry; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->collectionExtensions = $collectionExtensions; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return $this->managerRegistry->getManagerForClass($resourceClass) instanceof DocumentManager; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException - */ - public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable - { - /** @var DocumentManager $manager */ - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - - /** @var ObjectRepository $repository */ - $repository = $manager->getRepository($resourceClass); - if (!$repository instanceof DocumentRepository) { - throw new RuntimeException(sprintf('The repository for "%s" must be an instance of "%s".', $resourceClass, DocumentRepository::class)); - } - - $aggregationBuilder = $repository->createAggregationBuilder(); - foreach ($this->collectionExtensions as $extension) { - if ($extension instanceof LegacyAggregationCollectionExtensionInterface) { - $extension->applyToCollection($aggregationBuilder, $resourceClass, $operationName, $context); - } elseif ($extension instanceof AggregationCollectionExtensionInterface) { - $extension->applyToCollection($aggregationBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - - if ($extension instanceof LegacyAggregationResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context); - } - - if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $context['operation'] ?? null, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - } - - $attribute = []; - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - try { - $operation = $context['operation'] ?? $resourceMetadata->getOperation($operationName); - $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; - } catch (OperationNotFoundException $e) { - $attribute = $resourceMetadata->getOperation()->getExtraProperties()['doctrine_mongodb'] ?? []; - } - } else { - /** @var ResourceMetadata */ - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $resourceMetadata->getSubresourceOperationAttribute($operationName, 'doctrine_mongodb', [], true); - } - - $executeOptions = $attribute['execute_options'] ?? []; - $builder = $aggregationBuilder->hydrate($resourceClass); - - if (method_exists($builder, 'getAggregation')) { - return $builder->getAggregation($executeOptions)->getIterator(); - } - - return $builder->execute($executeOptions); - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationCollectionExtensionInterface.php deleted file mode 100644 index bb8cb9d8799..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationCollectionExtensionInterface.php +++ /dev/null @@ -1,28 +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\Doctrine\MongoDbOdm\Extension; - -use Doctrine\ODM\MongoDB\Aggregation\Builder; - -/** - * Interface of Doctrine MongoDB ODM aggregation extensions for collection aggregations. - * - * @experimental - * - * @author Alan Poulain - */ -interface AggregationCollectionExtensionInterface -{ - public function applyToCollection(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []); -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationItemExtensionInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationItemExtensionInterface.php deleted file mode 100644 index 8e9b6fa9ef1..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationItemExtensionInterface.php +++ /dev/null @@ -1,28 +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\Doctrine\MongoDbOdm\Extension; - -use Doctrine\ODM\MongoDB\Aggregation\Builder; - -/** - * Interface of Doctrine MongoDB ODM aggregation extensions for item aggregations. - * - * @experimental - * - * @author Alan Poulain - */ -interface AggregationItemExtensionInterface -{ - public function applyToItem(Builder $aggregationBuilder, string $resourceClass, array $identifiers, string $operationName = null, array &$context = []); -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultCollectionExtensionInterface.php deleted file mode 100644 index f186a2d02b4..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultCollectionExtensionInterface.php +++ /dev/null @@ -1,31 +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\Doctrine\MongoDbOdm\Extension; - -use Doctrine\ODM\MongoDB\Aggregation\Builder; - -/** - * Interface of Doctrine MongoDB ODM aggregation extensions that supports result production - * for specific cases such as pagination. - * - * @experimental - * - * @author Alan Poulain - */ -interface AggregationResultCollectionExtensionInterface extends AggregationCollectionExtensionInterface -{ - public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool; - - public function getResult(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array $context = []); -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultItemExtensionInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultItemExtensionInterface.php deleted file mode 100644 index cdef83af46b..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/AggregationResultItemExtensionInterface.php +++ /dev/null @@ -1,31 +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\Doctrine\MongoDbOdm\Extension; - -use Doctrine\ODM\MongoDB\Aggregation\Builder; - -/** - * Interface of Doctrine MongoDB ODM aggregation extensions that supports result production - * for specific cases such as Aggregation alteration. - * - * @experimental - * - * @author Alan Poulain - */ -interface AggregationResultItemExtensionInterface extends AggregationItemExtensionInterface -{ - public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool; - - public function getResult(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array $context = []); -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php deleted file mode 100644 index d0563f5db3b..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/FilterExtension.php +++ /dev/null @@ -1,67 +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\Doctrine\MongoDbOdm\Extension; - -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\FilterLocatorTrait; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\FilterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Psr\Container\ContainerInterface; - -/** - * Applies filters on a resource aggregation. - * - * @experimental - * - * @author Kévin Dunglas - * @author Samuel ROZE - */ -final class FilterExtension implements AggregationCollectionExtensionInterface -{ - use FilterLocatorTrait; - - private $resourceMetadataFactory; - - /** - * @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection - */ - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator) - { - $this->setFilterLocator($filterLocator); - - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - /** - * {@inheritdoc} - */ - public function applyToCollection(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); - - if (empty($resourceFilters)) { - return; - } - - foreach ($resourceFilters as $filterId) { - $filter = $this->getFilter($filterId); - if ($filter instanceof FilterInterface) { - $context['filters'] = $context['filters'] ?? []; - $filter->apply($aggregationBuilder, $resourceClass, $operationName, $context); - } - } - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php deleted file mode 100644 index 17f6a32ccbe..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/OrderExtension.php +++ /dev/null @@ -1,124 +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\Doctrine\MongoDbOdm\Extension; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Aggregation\Stage\Sort; -use Doctrine\Persistence\ManagerRegistry; -use OutOfRangeException; - -/** - * Applies selected ordering while querying resource collection. - * - * @experimental - * - * @author Kévin Dunglas - * @author Samuel ROZE - * @author Vincent Chalamon - * @author Alan Poulain - */ -final class OrderExtension implements AggregationCollectionExtensionInterface -{ - use MongoDbOdmPropertyHelperTrait; - use PropertyHelperTrait; - - private $order; - private $resourceMetadataFactory; - private $managerRegistry; - - public function __construct(string $order = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null, ManagerRegistry $managerRegistry = null) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->order = $order; - $this->managerRegistry = $managerRegistry; - } - - /** - * {@inheritdoc} - */ - public function applyToCollection(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - // Do not apply order if already defined on $aggregationBuilder - if ($this->hasSortStage($aggregationBuilder)) { - return; - } - - $classMetaData = $this->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 (null !== $defaultOrder) { - foreach ($defaultOrder as $field => $order) { - if (\is_int($field)) { - // Default direction - $field = $order; - $order = 'ASC'; - } - - if ($this->isPropertyNested($field, $resourceClass)) { - [$field] = $this->addLookupsForNestedProperty($field, $aggregationBuilder, $resourceClass); - } - $aggregationBuilder->sort( - $context['mongodb_odm_sort_fields'] = ($context['mongodb_odm_sort_fields'] ?? []) + [$field => $order] - ); - } - - return; - } - } - - if (null !== $this->order) { - foreach ($identifiers as $identifier) { - $aggregationBuilder->sort( - $context['mongodb_odm_sort_fields'] = ($context['mongodb_odm_sort_fields'] ?? []) + [$identifier => $this->order] - ); - } - } - } - - protected function getManagerRegistry(): ManagerRegistry - { - return $this->managerRegistry; - } - - private function hasSortStage(Builder $aggregationBuilder): bool - { - $shouldStop = false; - $index = 0; - - do { - try { - if ($aggregationBuilder->getStage($index) instanceof Sort) { - // If at least one stage is sort, then it has sorting - return true; - } - } catch (OutOfRangeException $outOfRangeException) { - // There is no more stages on the aggregation builder - $shouldStop = true; - } - - ++$index; - } while (!$shouldStop); - - // No stage was sort, and we iterated through all stages - return false; - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php deleted file mode 100644 index ab7f08699a1..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php +++ /dev/null @@ -1,137 +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\Doctrine\MongoDbOdm\Extension; - -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Paginator; -use ApiPlatform\Core\DataProvider\Pagination; -use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Repository\DocumentRepository; -use Doctrine\Persistence\ManagerRegistry; - -/** - * Applies pagination on the Doctrine aggregation for resource collection when enabled. - * - * @experimental - * - * @author Kévin Dunglas - * @author Samuel ROZE - * @author Alan Poulain - */ -final class PaginationExtension implements AggregationResultCollectionExtensionInterface -{ - private $managerRegistry; - private $resourceMetadataFactory; - private $pagination; - - public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataFactoryInterface $resourceMetadataFactory, Pagination $pagination) - { - $this->managerRegistry = $managerRegistry; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->pagination = $pagination; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException - */ - public function applyToCollection(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if (!$this->pagination->isEnabled($resourceClass, $operationName, $context)) { - return; - } - - if (($context['graphql_operation_name'] ?? false) && !$this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) { - return; - } - - $context = $this->addCountToContext(clone $aggregationBuilder, $context); - - [, $offset, $limit] = $this->pagination->getPagination($resourceClass, $operationName, $context); - - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - if (!$manager instanceof DocumentManager) { - throw new RuntimeException(sprintf('The manager for "%s" must be an instance of "%s".', $resourceClass, DocumentManager::class)); - } - - /** - * @var DocumentRepository - */ - $repository = $manager->getRepository($resourceClass); - $resultsAggregationBuilder = $repository->createAggregationBuilder()->skip($offset); - if ($limit > 0) { - $resultsAggregationBuilder->limit($limit); - } else { - // Results have to be 0 but MongoDB does not support a limit equal to 0. - $resultsAggregationBuilder->match()->field(Paginator::LIMIT_ZERO_MARKER_FIELD)->equals(Paginator::LIMIT_ZERO_MARKER); - } - - $aggregationBuilder - ->facet() - ->field('results')->pipeline( - $resultsAggregationBuilder - ) - ->field('count')->pipeline( - $repository->createAggregationBuilder() - ->count('count') - ); - } - - /** - * {@inheritdoc} - */ - public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool - { - if ($context['graphql_operation_name'] ?? false) { - return $this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context); - } - - return $this->pagination->isEnabled($resourceClass, $operationName, $context); - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException - */ - public function getResult(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array $context = []) - { - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - if (!$manager instanceof DocumentManager) { - throw new RuntimeException(sprintf('The manager for "%s" must be an instance of "%s".', $resourceClass, DocumentManager::class)); - } - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $resourceMetadata->getCollectionOperationAttribute($operationName, 'doctrine_mongodb', [], true); - $executeOptions = $attribute['execute_options'] ?? []; - - return new Paginator($aggregationBuilder->execute($executeOptions), $manager->getUnitOfWork(), $resourceClass, $aggregationBuilder->getPipeline()); - } - - private function addCountToContext(Builder $aggregationBuilder, array $context): array - { - if (!($context['graphql_operation_name'] ?? false)) { - return $context; - } - - if (isset($context['filters']['last']) && !isset($context['filters']['before'])) { - $context['count'] = $aggregationBuilder->count('count')->execute()->toArray()[0]['count']; - } - - return $context; - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php deleted file mode 100644 index 483ae408715..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php +++ /dev/null @@ -1,113 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * {@inheritdoc} - * - * Abstract class for easing the implementation of a filter. - * - * @experimental - * - * @author Alan Poulain - */ -abstract class AbstractFilter implements FilterInterface -{ - use MongoDbOdmPropertyHelperTrait; - use PropertyHelperTrait; - - protected $managerRegistry; - protected $logger; - protected $properties; - protected $nameConverter; - - public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) - { - $this->managerRegistry = $managerRegistry; - $this->logger = $logger ?? new NullLogger(); - $this->properties = $properties; - $this->nameConverter = $nameConverter; - } - - /** - * {@inheritdoc} - */ - public function apply(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - foreach ($context['filters'] as $property => $value) { - $this->filterProperty($this->denormalizePropertyName($property), $value, $aggregationBuilder, $resourceClass, $operationName, $context); - } - } - - /** - * Passes a property through the filter. - * - * @param mixed $value - */ - abstract protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []); - - protected function getManagerRegistry(): ManagerRegistry - { - return $this->managerRegistry; - } - - protected function getProperties(): ?array - { - return $this->properties; - } - - protected function getLogger(): LoggerInterface - { - return $this->logger; - } - - /** - * Determines whether the given property is enabled. - */ - protected function isPropertyEnabled(string $property, string $resourceClass): bool - { - if (null === $this->properties) { - // to ensure sanity, nested properties must still be explicitly enabled - return !$this->isPropertyNested($property, $resourceClass); - } - - return \array_key_exists($property, $this->properties); - } - - protected function denormalizePropertyName($property) - { - if (!$this->nameConverter instanceof NameConverterInterface) { - return $property; - } - - return implode('.', array_map([$this->nameConverter, 'denormalize'], explode('.', (string) $property))); - } - - protected function normalizePropertyName($property) - { - if (!$this->nameConverter instanceof NameConverterInterface) { - return $property; - } - - return implode('.', array_map([$this->nameConverter, 'normalize'], explode('.', (string) $property))); - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php deleted file mode 100644 index 7b72bbfaec9..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php +++ /dev/null @@ -1,70 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\BooleanFilterTrait; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; - -/** - * Filters the collection by boolean values. - * - * Filters collection on equality of boolean properties. The value is specified - * as one of ( "true" | "false" | "1" | "0" ) in the query. - * - * For each property passed, if the resource does not have such property or if - * the value is not one of ( "true" | "false" | "1" | "0" ) the property is ignored. - * - * @experimental - * - * @author Amrouche Hamza - * @author Teoh Han Hui - * @author Alan Poulain - */ -final class BooleanFilter extends AbstractFilter -{ - use BooleanFilterTrait; - - public const DOCTRINE_BOOLEAN_TYPES = [ - MongoDbType::BOOL => true, - MongoDbType::BOOLEAN => true, - ]; - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if ( - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass) || - !$this->isBooleanField($property, $resourceClass) - ) { - return; - } - - $value = $this->normalizeValue($value, $property); - if (null === $value) { - return; - } - - $matchField = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$matchField] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass); - } - - $aggregationBuilder->match()->field($matchField)->equals($value); - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php deleted file mode 100644 index 8904bf570b7..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php +++ /dev/null @@ -1,145 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\DateFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\DateFilterTrait; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; - -/** - * Filters the collection by date intervals. - * - * @experimental - * - * @author Kévin Dunglas - * @author Théo FIDRY - * @author Alan Poulain - */ -class DateFilter extends AbstractFilter implements DateFilterInterface -{ - use DateFilterTrait; - - public const DOCTRINE_DATE_TYPES = [ - MongoDbType::DATE => true, - MongoDbType::DATE_IMMUTABLE => true, - ]; - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $values, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - // Expect $values to be an array having the period as keys and the date value as values - if ( - !\is_array($values) || - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass) || - !$this->isDateField($property, $resourceClass) - ) { - return; - } - - $matchField = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$matchField] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass); - } - - $nullManagement = $this->properties[$property] ?? null; - - if (self::EXCLUDE_NULL === $nullManagement) { - $aggregationBuilder->match()->field($matchField)->notEqual(null); - } - - if (isset($values[self::PARAMETER_BEFORE])) { - $this->addMatch( - $aggregationBuilder, - $matchField, - self::PARAMETER_BEFORE, - $values[self::PARAMETER_BEFORE], - $nullManagement - ); - } - - if (isset($values[self::PARAMETER_STRICTLY_BEFORE])) { - $this->addMatch( - $aggregationBuilder, - $matchField, - self::PARAMETER_STRICTLY_BEFORE, - $values[self::PARAMETER_STRICTLY_BEFORE], - $nullManagement - ); - } - - if (isset($values[self::PARAMETER_AFTER])) { - $this->addMatch( - $aggregationBuilder, - $matchField, - self::PARAMETER_AFTER, - $values[self::PARAMETER_AFTER], - $nullManagement - ); - } - - if (isset($values[self::PARAMETER_STRICTLY_AFTER])) { - $this->addMatch( - $aggregationBuilder, - $matchField, - self::PARAMETER_STRICTLY_AFTER, - $values[self::PARAMETER_STRICTLY_AFTER], - $nullManagement - ); - } - } - - /** - * Adds the match stage according to the chosen null management. - */ - private function addMatch(Builder $aggregationBuilder, string $field, string $operator, string $value, string $nullManagement = null): void - { - try { - $value = new \DateTime($value); - } catch (\Exception $e) { - // Silently ignore this filter if it can not be transformed to a \DateTime - $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)), - ]); - - return; - } - - $operatorValue = [ - self::PARAMETER_BEFORE => '$lte', - self::PARAMETER_STRICTLY_BEFORE => '$lt', - self::PARAMETER_AFTER => '$gte', - self::PARAMETER_STRICTLY_AFTER => '$gt', - ]; - - if ((self::INCLUDE_NULL_BEFORE === $nullManagement && \in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true)) || - (self::INCLUDE_NULL_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true)) || - (self::INCLUDE_NULL_BEFORE_AND_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER, self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true)) - ) { - $aggregationBuilder->match()->addOr( - $aggregationBuilder->matchExpr()->field($field)->operator($operatorValue[$operator], $value), - $aggregationBuilder->matchExpr()->field($field)->equals(null) - ); - - return; - } - - $aggregationBuilder->match()->addAnd($aggregationBuilder->matchExpr()->field($field)->operator($operatorValue[$operator], $value)); - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php deleted file mode 100644 index d3624f8dcfe..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php +++ /dev/null @@ -1,107 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterTrait; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Log\LoggerInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Filters the collection by whether a property value exists or not. - * - * For each property passed, if the resource does not have such property or if - * the value is not one of ( "true" | "false" | "1" | "0" ) the property is ignored. - * - * A query parameter with key but no value is treated as `true`, e.g.: - * Request: GET /products?exists[brand] - * Interpretation: filter products which have a brand - * - * @experimental - * - * @author Teoh Han Hui - * @author Alan Poulain - */ -final class ExistsFilter extends AbstractFilter implements ExistsFilterInterface -{ - use ExistsFilterTrait; - - public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, NameConverterInterface $nameConverter = null) - { - parent::__construct($managerRegistry, $logger, $properties, $nameConverter); - - $this->existsParameterName = $existsParameterName; - } - - /** - * {@inheritdoc} - */ - public function apply(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if (!\is_array($context['filters'][$this->existsParameterName] ?? null)) { - $context['exists_deprecated_syntax'] = true; - parent::apply($aggregationBuilder, $resourceClass, $operationName, $context); - - return; - } - - foreach ($context['filters'][$this->existsParameterName] as $property => $value) { - $this->filterProperty($this->denormalizePropertyName($property), $value, $aggregationBuilder, $resourceClass, $operationName, $context); - } - } - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if ( - (($context['exists_deprecated_syntax'] ?? false) && !isset($value[self::QUERY_PARAMETER_KEY])) || - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass, true) || - !$this->isNullableField($property, $resourceClass) - ) { - return; - } - - $value = $this->normalizeValue($value, $property); - if (null === $value) { - return; - } - - $matchField = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$matchField] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass); - } - - $aggregationBuilder->match()->field($matchField)->{$value ? 'notEqual' : 'equals'}(null); - } - - /** - * {@inheritdoc} - */ - protected function isNullableField(string $property, string $resourceClass): bool - { - $propertyParts = $this->splitPropertyParts($property, $resourceClass); - $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']); - - $field = $propertyParts['field']; - - return $metadata instanceof ClassMetadata && $metadata->hasField($field) ? $metadata->isNullable($field) : false; - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php deleted file mode 100644 index c5f7546e843..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php +++ /dev/null @@ -1,32 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Api\FilterInterface as BaseFilterInterface; -use Doctrine\ODM\MongoDB\Aggregation\Builder; - -/** - * Doctrine MongoDB ODM filter interface. - * - * @experimental - * - * @author Alan Poulain - */ -interface FilterInterface extends BaseFilterInterface -{ - /** - * Applies the filter. - */ - public function apply(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []); -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php deleted file mode 100644 index a8effa1c7de..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php +++ /dev/null @@ -1,93 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\NumericFilterTrait; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; - -/** - * Filters the collection by numeric values. - * - * Filters collection by equality of numeric properties. - * - * For each property passed, if the resource does not have such property or if - * the value is not numeric, the property is ignored. - * - * @experimental - * - * @author Amrouche Hamza - * @author Teoh Han Hui - * @author Alan Poulain - */ -final class NumericFilter extends AbstractFilter -{ - use NumericFilterTrait; - - /** - * Type of numeric in Doctrine. - */ - public const DOCTRINE_NUMERIC_TYPES = [ - MongoDbType::INT => true, - MongoDbType::INTEGER => true, - MongoDbType::FLOAT => true, - ]; - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if ( - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass) || - !$this->isNumericField($property, $resourceClass) - ) { - return; - } - - $values = $this->normalizeValues($value, $property); - if (null === $values) { - return; - } - - $matchField = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$matchField] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass); - } - - if (1 === \count($values)) { - $aggregationBuilder->match()->field($matchField)->equals($values[0]); - } else { - $aggregationBuilder->match()->field($matchField)->in($values); - } - } - - /** - * {@inheritdoc} - */ - protected function getType(string $doctrineType = null): string - { - if (null === $doctrineType) { - return 'string'; - } - - if (MongoDbType::FLOAT === $doctrineType) { - return 'float'; - } - - return 'int'; - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php deleted file mode 100644 index 54b18a777f9..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php +++ /dev/null @@ -1,107 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterTrait; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Log\LoggerInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Order the collection by given properties. - * - * The ordering is done in the same sequence as they are specified in the query, - * and for each property a direction value can be specified. - * - * For each property passed, if the resource does not have such property or if the - * direction value is different from "asc" or "desc" (case insensitive), the property - * is ignored. - * - * @experimental - * - * @author Kévin Dunglas - * @author Théo FIDRY - * @author Alan Poulain - */ -final class OrderFilter extends AbstractFilter implements OrderFilterInterface -{ - use OrderFilterTrait; - - public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) - { - if (null !== $properties) { - $properties = array_map(static function ($propertyOptions) { - // shorthand for default direction - if (\is_string($propertyOptions)) { - $propertyOptions = [ - 'default_direction' => $propertyOptions, - ]; - } - - return $propertyOptions; - }, $properties); - } - - parent::__construct($managerRegistry, $logger, $properties, $nameConverter); - - $this->orderParameterName = $orderParameterName; - } - - /** - * {@inheritdoc} - */ - public function apply(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if (isset($context['filters']) && !isset($context['filters'][$this->orderParameterName])) { - return; - } - - if (!isset($context['filters'][$this->orderParameterName]) || !\is_array($context['filters'][$this->orderParameterName])) { - parent::apply($aggregationBuilder, $resourceClass, $operationName, $context); - - return; - } - - foreach ($context['filters'][$this->orderParameterName] as $property => $value) { - $this->filterProperty($this->denormalizePropertyName($property), $value, $aggregationBuilder, $resourceClass, $operationName, $context); - } - } - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $direction, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)) { - return; - } - - $direction = $this->normalizeValue($direction, $property); - if (null === $direction) { - return; - } - - $matchField = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$matchField] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass); - } - - $aggregationBuilder->sort( - $context['mongodb_odm_sort_fields'] = ($context['mongodb_odm_sort_fields'] ?? []) + [$matchField => $direction] - ); - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php deleted file mode 100644 index 394bd284f79..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php +++ /dev/null @@ -1,128 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\RangeFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\RangeFilterTrait; -use Doctrine\ODM\MongoDB\Aggregation\Builder; - -/** - * Filters the collection by range. - * - * @experimental - * - * @author Lee Siong Chan - * @author Alan Poulain - */ -final class RangeFilter extends AbstractFilter implements RangeFilterInterface -{ - use RangeFilterTrait; - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $values, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if ( - !\is_array($values) || - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass) - ) { - return; - } - - $values = $this->normalizeValues($values, $property); - if (null === $values) { - return; - } - - $matchField = $field = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$matchField] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass); - } - - foreach ($values as $operator => $value) { - $this->addMatch( - $aggregationBuilder, - $field, - $matchField, - $operator, - $value - ); - } - } - - /** - * Adds the match stage according to the operator. - */ - protected function addMatch(Builder $aggregationBuilder, string $field, string $matchField, string $operator, string $value) - { - switch ($operator) { - case self::PARAMETER_BETWEEN: - $rangeValue = explode('..', $value); - - $rangeValue = $this->normalizeBetweenValues($rangeValue); - if (null === $rangeValue) { - return; - } - - if ($rangeValue[0] === $rangeValue[1]) { - $aggregationBuilder->match()->field($matchField)->equals($rangeValue[0]); - - return; - } - - $aggregationBuilder->match()->field($matchField)->gte($rangeValue[0])->lte($rangeValue[1]); - - break; - case self::PARAMETER_GREATER_THAN: - $value = $this->normalizeValue($value, $operator); - if (null === $value) { - return; - } - - $aggregationBuilder->match()->field($matchField)->gt($value); - - break; - case self::PARAMETER_GREATER_THAN_OR_EQUAL: - $value = $this->normalizeValue($value, $operator); - if (null === $value) { - return; - } - - $aggregationBuilder->match()->field($matchField)->gte($value); - - break; - case self::PARAMETER_LESS_THAN: - $value = $this->normalizeValue($value, $operator); - if (null === $value) { - return; - } - - $aggregationBuilder->match()->field($matchField)->lt($value); - - break; - case self::PARAMETER_LESS_THAN_OR_EQUAL: - $value = $this->normalizeValue($value, $operator); - if (null === $value) { - return; - } - - $aggregationBuilder->match()->field($matchField)->lte($value); - - break; - } - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php deleted file mode 100644 index bb9e83a31d7..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php +++ /dev/null @@ -1,223 +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\Doctrine\MongoDbOdm\Filter; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterTrait; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as MongoDBClassMetadata; -use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\Mapping\ClassMetadata; -use MongoDB\BSON\Regex; -use Psr\Log\LoggerInterface; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Filter the collection by given properties. - * - * @experimental - * - * @author Kévin Dunglas - * @author Alan Poulain - */ -final class SearchFilter extends AbstractFilter implements SearchFilterInterface -{ - use SearchFilterTrait; - - 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) - { - parent::__construct($managerRegistry, $logger, $properties, $nameConverter); - - $this->iriConverter = $iriConverter; - $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); - $this->identifiersExtractor = $identifiersExtractor; - } - - protected function getIriConverter(): IriConverterInterface - { - return $this->iriConverter; - } - - protected function getPropertyAccessor(): PropertyAccessorInterface - { - return $this->propertyAccessor; - } - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) - { - if ( - null === $value || - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass, true) - ) { - return; - } - - $matchField = $field = $property; - - $values = $this->normalizeValues((array) $value, $property); - if (null === $values) { - return; - } - - $associations = []; - if ($this->isPropertyNested($property, $resourceClass)) { - [$matchField, $field, $associations] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass); - } - - $caseSensitive = true; - $strategy = $this->properties[$property] ?? self::STRATEGY_EXACT; - - // prefixing the strategy with i makes it case insensitive - if (0 === strpos($strategy, 'i')) { - $strategy = substr($strategy, 1); - $caseSensitive = false; - } - - /** @var MongoDBClassMetadata */ - $metadata = $this->getNestedMetadata($resourceClass, $associations); - - if ($metadata->hasField($field) && !$metadata->hasAssociation($field)) { - if ('id' === $field) { - $values = array_map([$this, 'getIdFromValue'], $values); - } - - if (!$this->hasValidValues($values, $this->getDoctrineFieldType($property, $resourceClass))) { - $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), - ]); - - return; - } - - $this->addEqualityMatchStrategy($strategy, $aggregationBuilder, $field, $matchField, $values, $caseSensitive, $metadata); - - return; - } - - // metadata doesn't have the field, nor an association on the field - if (!$metadata->hasAssociation($field)) { - return; - } - - $values = array_map([$this, 'getIdFromValue'], $values); - $doctrineTypeField = $this->getDoctrineFieldType($property, $resourceClass); - - if (null !== $this->identifiersExtractor) { - $associationResourceClass = $metadata->getAssociationTargetClass($field); - $associationFieldIdentifier = $this->identifiersExtractor->getIdentifiersFromResourceClass($associationResourceClass)[0]; - $doctrineTypeField = $this->getDoctrineFieldType($associationFieldIdentifier, $associationResourceClass); - } - - if (!$this->hasValidValues($values, $doctrineTypeField)) { - $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $property)), - ]); - - return; - } - - $this->addEqualityMatchStrategy($strategy, $aggregationBuilder, $field, $matchField, $values, $caseSensitive, $metadata); - } - - /** - * Add equality match stage according to the strategy. - * - * @param mixed $values - */ - private function addEqualityMatchStrategy(string $strategy, Builder $aggregationBuilder, string $field, string $matchField, $values, bool $caseSensitive, ClassMetadata $metadata): void - { - $inValues = []; - foreach ($values as $inValue) { - $inValues[] = $this->getEqualityMatchStrategyValue($strategy, $field, $inValue, $caseSensitive, $metadata); - } - - $aggregationBuilder - ->match() - ->field($matchField) - ->in($inValues); - } - - /** - * Get equality match value according to the strategy. - * - * @param mixed $value - * - * @throws InvalidArgumentException If strategy does not exist - * - * @return Regex|string - */ - private function getEqualityMatchStrategyValue(string $strategy, string $field, $value, bool $caseSensitive, ClassMetadata $metadata) - { - $type = $metadata->getTypeOfField($field); - - if (!MongoDbType::hasType($type)) { - return $value; - } - if (MongoDbType::STRING !== $type) { - return MongoDbType::getType($type)->convertToDatabaseValue($value); - } - - $quotedValue = preg_quote($value); - - switch ($strategy) { - case null: - case self::STRATEGY_EXACT: - return $caseSensitive ? $value : new Regex("^$quotedValue$", 'i'); - case self::STRATEGY_PARTIAL: - return new Regex($quotedValue, $caseSensitive ? '' : 'i'); - case self::STRATEGY_START: - return new Regex("^$quotedValue", $caseSensitive ? '' : 'i'); - case self::STRATEGY_END: - return new Regex("$quotedValue$", $caseSensitive ? '' : 'i'); - case self::STRATEGY_WORD_START: - return new Regex("(^$quotedValue.*|.*\s$quotedValue.*)", $caseSensitive ? '' : 'i'); - default: - throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy)); - } - } - - /** - * {@inheritdoc} - */ - protected function getType(string $doctrineType): string - { - switch ($doctrineType) { - case MongoDbType::INT: - case MongoDbType::INTEGER: - return 'int'; - case MongoDbType::BOOL: - case MongoDbType::BOOLEAN: - return 'bool'; - case MongoDbType::DATE: - case MongoDbType::DATE_IMMUTABLE: - return \DateTimeInterface::class; - case MongoDbType::FLOAT: - return 'float'; - } - - return 'string'; - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php b/src/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php deleted file mode 100644 index 28ad06dc02b..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProvider.php +++ /dev/null @@ -1,141 +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\Doctrine\MongoDbOdm; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Util\IdentifierManagerTrait; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface as LegacyAggregationItemExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface as LegacyAggregationResultItemExtensionInterface; -use ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -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\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; -use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Repository\DocumentRepository; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectRepository; - -/** - * Item data provider for the Doctrine MongoDB ODM. - * - * @experimental - * - * @author Alan Poulain - */ -final class ItemDataProvider implements DenormalizedIdentifiersAwareItemDataProviderInterface, RestrictedDataProviderInterface -{ - use IdentifierManagerTrait; - - private $managerRegistry; - private $resourceMetadataFactory; - private $itemExtensions; - - /** - * @param LegacyAggregationItemExtensionInterface[]|AggregationItemExtensionInterface[] $itemExtensions - * @param ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface $resourceMetadataFactory - */ - 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; - $this->itemExtensions = $itemExtensions; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return $this->managerRegistry->getManagerForClass($resourceClass) instanceof DocumentManager; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException - */ - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - /** @var DocumentManager $manager */ - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - - if (!\is_array($id) && !($context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] ?? false)) { - $id = $this->normalizeIdentifiers($id, $manager, $resourceClass); - } - - $id = (array) $id; - - if (!($context['fetch_data'] ?? true)) { - return $manager->getReference($resourceClass, reset($id)); - } - - $repository = $manager->getRepository($resourceClass); - /** @var ObjectRepository $repository */ - if (!$repository instanceof DocumentRepository) { - throw new RuntimeException(sprintf('The repository for "%s" must be an instance of "%s".', $resourceClass, DocumentRepository::class)); - } - - $aggregationBuilder = $repository->createAggregationBuilder(); - - foreach ($id as $propertyName => $value) { - $aggregationBuilder->match()->field($propertyName)->equals($value); - } - - foreach ($this->itemExtensions as $extension) { - if ($extension instanceof LegacyAggregationItemExtensionInterface) { - $extension->applyToItem($aggregationBuilder, $resourceClass, $id, $operationName, $context); - } elseif ($extension instanceof AggregationItemExtensionInterface) { - $extension->applyToItem($aggregationBuilder, $resourceClass, $id, $context['operation'] ?? null, $context); - } - - if ($extension instanceof LegacyAggregationResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context); - } - - if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($resourceClass, $context['operation'] ?? null, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - } - - $attribute = []; - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - try { - $operation = $context['operation'] ?? $resourceMetadata->getOperation($operationName); - $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; - } catch (OperationNotFoundException $e) { - $attribute = $resourceMetadata->getOperation()->getExtraProperties()['doctrine_mongodb'] ?? []; - } - } else { - /** @var ResourceMetadata */ - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $resourceMetadata->getItemOperationAttribute($operationName, 'doctrine_mongodb', [], true); - } - - $executeOptions = $attribute['execute_options'] ?? []; - - return $aggregationBuilder->hydrate($resourceClass)->execute($executeOptions)->current() ?: null; - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php deleted file mode 100644 index 24b0c1cc081..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php +++ /dev/null @@ -1,79 +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\Doctrine\MongoDbOdm\Metadata\Property; - -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\Persistence\ManagerRegistry; - -/** - * Use Doctrine metadata to populate the identifier property. - * - * @experimental - * - * @author Kévin Dunglas - * @author Alan Poulain - */ -final class DoctrineMongoDbOdmPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - private $decorated; - private $managerRegistry; - - public function __construct(ManagerRegistry $managerRegistry, PropertyMetadataFactoryInterface $decorated) - { - $this->managerRegistry = $managerRegistry; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); - - if (null !== $propertyMetadata->isIdentifier()) { - return $propertyMetadata; - } - - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - if (!$manager instanceof DocumentManager) { - return $propertyMetadata; - } - - $doctrineClassMetadata = $manager->getClassMetadata($resourceClass); - $identifiers = $doctrineClassMetadata->getIdentifier(); - - foreach ($identifiers as $identifier) { - if ($identifier === $property) { - $propertyMetadata = $propertyMetadata->withIdentifier(true); - - if (null !== $propertyMetadata->isWritable()) { - break; - } - - $propertyMetadata = $propertyMetadata->withWritable(false); - - break; - } - } - - if (null === $propertyMetadata->isIdentifier()) { - $propertyMetadata = $propertyMetadata->withIdentifier(false); - } - - return $propertyMetadata; - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/Paginator.php b/src/Core/Bridge/Doctrine/MongoDbOdm/Paginator.php deleted file mode 100644 index b963af12c6f..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/Paginator.php +++ /dev/null @@ -1,22 +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\Doctrine\MongoDbOdm; - -class_exists(\ApiPlatform\Doctrine\Odm\Paginator::class); - -if (false) { - final class Paginator extends \ApiPlatform\Doctrine\Odm\Paginator - { - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyHelperTrait.php b/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyHelperTrait.php deleted file mode 100644 index 70060d6faa4..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyHelperTrait.php +++ /dev/null @@ -1,107 +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\Doctrine\MongoDbOdm; - -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as MongoDbOdmClassMetadata; -use Doctrine\ODM\MongoDB\Mapping\MappingException; -use Doctrine\Persistence\Mapping\ClassMetadata; - -/** - * Helper trait regarding a property in a MongoDB document using the resource metadata. - * - * @experimental - * - * @author Alan Poulain - */ -trait PropertyHelperTrait -{ - /** - * Splits the given property into parts. - */ - abstract protected function splitPropertyParts(string $property/* , string $resourceClass */): array; - - /** - * Gets class metadata for the given resource. - */ - abstract protected function getClassMetadata(string $resourceClass): ClassMetadata; - - /** - * Adds the necessary lookups for a nested property. - * - * @throws InvalidArgumentException If property is not nested - * @throws MappingException - * - * @return array An array where the first element is the $alias of the lookup, - * the second element is the $field name - * the third element is the $associations array - */ - protected function addLookupsForNestedProperty(string $property, Builder $aggregationBuilder, string $resourceClass): array - { - $propertyParts = $this->splitPropertyParts($property, $resourceClass); - $alias = ''; - - foreach ($propertyParts['associations'] as $association) { - $classMetadata = $this->getClassMetadata($resourceClass); - - if (!$classMetadata instanceof MongoDbOdmClassMetadata) { - break; - } - - if ($classMetadata->hasReference($association)) { - $propertyAlias = "${association}_lkup"; - // previous_association_lkup.association - $localField = "$alias$association"; - // previous_association_lkup.association_lkup - $alias .= $propertyAlias; - $referenceMapping = $classMetadata->getFieldMapping($association); - - if (($isOwningSide = $referenceMapping['isOwningSide']) && MongoDbOdmClassMetadata::REFERENCE_STORE_AS_ID !== $referenceMapping['storeAs']) { - throw MappingException::cannotLookupDbRefReference($classMetadata->getReflectionClass()->getShortName(), $association); - } - if (!$isOwningSide) { - if (isset($referenceMapping['repositoryMethod']) || !isset($referenceMapping['mappedBy'])) { - throw MappingException::repositoryMethodLookupNotAllowed($classMetadata->getReflectionClass()->getShortName(), $association); - } - - $targetClassMetadata = $this->getClassMetadata($referenceMapping['targetDocument']); - if ($targetClassMetadata instanceof MongoDbOdmClassMetadata && MongoDbOdmClassMetadata::REFERENCE_STORE_AS_ID !== $targetClassMetadata->getFieldMapping($referenceMapping['mappedBy'])['storeAs']) { - throw MappingException::cannotLookupDbRefReference($classMetadata->getReflectionClass()->getShortName(), $association); - } - } - - $aggregationBuilder->lookup($classMetadata->getAssociationTargetClass($association)) - ->localField($isOwningSide ? $localField : '_id') - ->foreignField($isOwningSide ? '_id' : $referenceMapping['mappedBy']) - ->alias($alias); - $aggregationBuilder->unwind("\$$alias"); - - // association.property => association_lkup.property - $property = substr_replace($property, $propertyAlias, strpos($property, $association), \strlen($association)); - $resourceClass = $classMetadata->getAssociationTargetClass($association); - $alias .= '.'; - } elseif ($classMetadata->hasEmbed($association)) { - $alias = "$association."; - $resourceClass = $classMetadata->getAssociationTargetClass($association); - } - } - - if ('' === $alias) { - throw new InvalidArgumentException(sprintf('Cannot add lookups for property "%s" - property is not nested.', $property)); - } - - return [$property, $propertyParts['field'], $propertyParts['associations']]; - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php b/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php deleted file mode 100644 index f216aa2b1a8..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/PropertyInfo/DoctrineExtractor.php +++ /dev/null @@ -1,22 +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\Doctrine\MongoDbOdm\PropertyInfo; - -class_exists(\ApiPlatform\Doctrine\Odm\PropertyInfo\DoctrineExtractor::class); - -if (false) { - final class DoctrineExtractor extends \ApiPlatform\Doctrine\Odm\PropertyInfo\DoctrineExtractor - { - } -} diff --git a/src/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php b/src/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php deleted file mode 100644 index dd8222fe3a8..00000000000 --- a/src/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProvider.php +++ /dev/null @@ -1,235 +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\Doctrine\MongoDbOdm; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Util\IdentifierManagerTrait; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface as LegacyAggregationCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface as LegacyAggregationItemExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface as LegacyAggregationResultCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface as LegacyAggregationResultItemExtensionInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -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\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; -use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; -use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; -use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ODM\MongoDB\Repository\DocumentRepository; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectRepository; - -/** - * Subresource data provider for the Doctrine MongoDB ODM. - * - * @experimental - * - * @author Antoine Bluchet - * @author Alan Poulain - */ -final class SubresourceDataProvider implements SubresourceDataProviderInterface -{ - use IdentifierManagerTrait; - - private $managerRegistry; - private $resourceMetadataFactory; - private $collectionExtensions; - private $itemExtensions; - - /** - * @param LegacyAggregationCollectionExtensionInterface[]|AggregationCollectionExtensionInterface[] $collectionExtensions - * @param LegacyAggregationItemExtensionInterface[]|AggregationItemExtensionInterface[] $itemExtensions - * @param ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface $resourceMetadataFactory - */ - 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; - $this->collectionExtensions = $collectionExtensions; - $this->itemExtensions = $itemExtensions; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException - */ - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - if (!$manager instanceof DocumentManager) { - throw new ResourceClassNotSupportedException(sprintf('The manager for "%s" must be an instance of "%s".', $resourceClass, DocumentManager::class)); - } - - $repository = $manager->getRepository($resourceClass); - /** @var ObjectRepository $repository */ - if (!$repository instanceof DocumentRepository) { - 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.'); - } - - $attribute = []; - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - try { - $operation = $context['operation'] ?? $resourceMetadata->getOperation($operationName); - $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; - } catch (OperationNotFoundException $e) { - $attribute = $resourceMetadata->getOperation()->getExtraProperties()['doctrine_mongodb'] ?? []; - } - } else { - /** @var ResourceMetadata */ - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $attribute = $resourceMetadata->getSubresourceOperationAttribute($operationName, 'doctrine_mongodb', [], true); - } - - $executeOptions = $attribute['execute_options'] ?? []; - - $aggregationBuilder = $this->buildAggregation($identifiers, $context, $executeOptions, $repository->createAggregationBuilder(), \count($context['identifiers'])); - - if (true === $context['collection']) { - foreach ($this->collectionExtensions as $extension) { - if ($extension instanceof LegacyAggregationCollectionExtensionInterface) { - $extension->applyToCollection($aggregationBuilder, $resourceClass, $operationName, $context); - } elseif ($extension instanceof AggregationCollectionExtensionInterface) { - $extension->applyToCollection($aggregationBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - - if ($extension instanceof LegacyAggregationResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context); - } - - if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $context['operation'] ?? null, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - } - } else { - foreach ($this->itemExtensions as $extension) { - if ($extension instanceof LegacyAggregationItemExtensionInterface) { - $extension->applyToItem($aggregationBuilder, $resourceClass, $identifiers, $operationName, $context); - } elseif ($extension instanceof AggregationItemExtensionInterface) { - $extension->applyToItem($aggregationBuilder, $resourceClass, $identifiers, $context['operation'] ?? null, $context); - } - - if ($extension instanceof LegacyAggregationResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $operationName, $context); - } - - if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($resourceClass, $context['operation'] ?? null, $context)) { - return $extension->getResult($aggregationBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - } - } - - $iterator = $aggregationBuilder->hydrate($resourceClass)->execute($executeOptions); - - return $context['collection'] ? $iterator->toArray() : ($iterator->current() ?: null); - } - - /** - * @throws RuntimeException - */ - private function buildAggregation(array $identifiers, array $context, array $executeOptions, Builder $previousAggregationBuilder, int $remainingIdentifiers, Builder $topAggregationBuilder = null): Builder - { - if ($remainingIdentifiers <= 0) { - return $previousAggregationBuilder; - } - - $topAggregationBuilder = $topAggregationBuilder ?? $previousAggregationBuilder; - - if (\is_string(key($context['identifiers']))) { - $contextIdentifiers = array_keys($context['identifiers']); - $identifier = $contextIdentifiers[$remainingIdentifiers - 1]; - $identifierResourceClass = $context['identifiers'][$identifier][0]; - $previousAssociationProperty = $contextIdentifiers[$remainingIdentifiers] ?? $context['property']; - } else { - @trigger_error('Identifiers should match the convention introduced in ADR 0001-resource-identifiers, this behavior will be removed in 3.0.', \E_USER_DEPRECATED); - [$identifier, $identifierResourceClass] = $context['identifiers'][$remainingIdentifiers - 1]; - $previousAssociationProperty = $context['identifiers'][$remainingIdentifiers][0] ?? $context['property']; - } - - $manager = $this->managerRegistry->getManagerForClass($identifierResourceClass); - if (!$manager instanceof DocumentManager) { - throw new RuntimeException(sprintf('The manager for "%s" must be an instance of "%s".', $identifierResourceClass, DocumentManager::class)); - } - - $classMetadata = $manager->getClassMetadata($identifierResourceClass); - - if (!$classMetadata instanceof ClassMetadata) { - throw new RuntimeException(sprintf('The class metadata for "%s" must be an instance of "%s".', $identifierResourceClass, ClassMetadata::class)); - } - - $aggregation = $manager->createAggregationBuilder($identifierResourceClass); - $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 { - $normalizedIdentifiers = $this->normalizeIdentifiers($identifiers[$identifier], $manager, $identifierResourceClass); - } - } - - if ($classMetadata->hasAssociation($previousAssociationProperty)) { - $aggregation->lookup($previousAssociationProperty)->alias($previousAssociationProperty); - foreach ($normalizedIdentifiers as $key => $value) { - $aggregation->match()->field($key)->equals($value); - } - } elseif ($classMetadata->isIdentifier($previousAssociationProperty)) { - foreach ($normalizedIdentifiers as $key => $value) { - $aggregation->match()->field($key)->equals($value); - } - - return $aggregation; - } - - // Recurse aggregations - $aggregation = $this->buildAggregation($identifiers, $context, $executeOptions, $aggregation, --$remainingIdentifiers, $topAggregationBuilder); - - $results = $aggregation->execute($executeOptions)->toArray(); - $in = array_reduce($results, static function ($in, $result) use ($previousAssociationProperty) { - return $in + array_map(static function ($result) { - return $result['_id']; - }, $result[$previousAssociationProperty] ?? []); - }, []); - $previousAggregationBuilder->match()->field('_id')->in($in); - - return $previousAggregationBuilder; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/AbstractPaginator.php b/src/Core/Bridge/Doctrine/Orm/AbstractPaginator.php deleted file mode 100644 index 28ceb260039..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/AbstractPaginator.php +++ /dev/null @@ -1,22 +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\Doctrine\Orm; - -class_exists(\ApiPlatform\Doctrine\Orm\AbstractPaginator::class); - -if (false) { - class AbstractPaginator extends \ApiPlatform\Doctrine\Orm\AbstractPaginator - { - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/CollectionDataProvider.php b/src/Core/Bridge/Doctrine/Orm/CollectionDataProvider.php deleted file mode 100644 index 4ccf679247a..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/CollectionDataProvider.php +++ /dev/null @@ -1,88 +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\Doctrine\Orm; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface as LegacyQueryCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface as LegacyQueryResultCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator; -use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; -use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; -use ApiPlatform\Exception\RuntimeException; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\Persistence\ManagerRegistry; - -/** - * Collection data provider for the Doctrine ORM. - * - * @author Kévin Dunglas - * @author Samuel ROZE - * @final - */ -class CollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface -{ - private $managerRegistry; - private $collectionExtensions; - - /** - * @param LegacyQueryCollectionExtensionInterface[]|QueryCollectionExtensionInterface[] $collectionExtensions - */ - public function __construct(ManagerRegistry $managerRegistry, iterable $collectionExtensions = []) - { - $this->managerRegistry = $managerRegistry; - $this->collectionExtensions = $collectionExtensions; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return $this->managerRegistry->getManagerForClass($resourceClass) instanceof EntityManagerInterface; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException - */ - public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable - { - /** @var EntityManagerInterface $manager */ - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - - $repository = $manager->getRepository($resourceClass); - if (!method_exists($repository, 'createQueryBuilder')) { - throw new RuntimeException('The repository class must have a "createQueryBuilder" method.'); - } - - $queryBuilder = $repository->createQueryBuilder('o'); - $queryNameGenerator = new QueryNameGenerator(); - foreach ($this->collectionExtensions as $extension) { - if ($extension instanceof LegacyQueryCollectionExtensionInterface) { - $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); // @phpstan-ignore-line because of context - } elseif ($extension instanceof QueryCollectionExtensionInterface) { - $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $context['operation'] ?? null, $context); - } - - if ($extension instanceof LegacyQueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { // @phpstan-ignore-line because of context - return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); // @phpstan-ignore-line because of context - } - - if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $context['operation'] ?? null, $context)) { - return $extension->getResult($queryBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - } - - return $queryBuilder->getQuery()->getResult(); - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryCollectionExtensionInterface.php deleted file mode 100644 index 6cc3d9b3da7..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryCollectionExtensionInterface.php +++ /dev/null @@ -1,33 +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\Doctrine\Orm\Extension; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface as LegacyQueryNameGeneratorInterface; -use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\QueryBuilder; - -/** - * Context aware extension. - * - * @author Kévin Dunglas - */ -interface ContextAwareQueryCollectionExtensionInterface extends QueryCollectionExtensionInterface -{ - /** - * {@inheritdoc} - * - * @param LegacyQueryNameGeneratorInterface|QueryNameGeneratorInterface $queryNameGenerator - */ - public function applyToCollection(QueryBuilder $queryBuilder, LegacyQueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultCollectionExtensionInterface.php deleted file mode 100644 index d48069fb1f5..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultCollectionExtensionInterface.php +++ /dev/null @@ -1,34 +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\Doctrine\Orm\Extension; - -use Doctrine\ORM\QueryBuilder; - -/** - * Context aware extension. - * - * @author Kévin Dunglas - */ -interface ContextAwareQueryResultCollectionExtensionInterface extends QueryResultCollectionExtensionInterface -{ - /** - * {@inheritdoc} - */ - public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool; - - /** - * {@inheritdoc} - */ - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = []); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultItemExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultItemExtensionInterface.php deleted file mode 100644 index ec8f24b80cb..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/ContextAwareQueryResultItemExtensionInterface.php +++ /dev/null @@ -1,34 +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\Doctrine\Orm\Extension; - -use Doctrine\ORM\QueryBuilder; - -/** - * Context aware extension. - * - * @author Kévin Dunglas - */ -interface ContextAwareQueryResultItemExtensionInterface extends QueryResultItemExtensionInterface -{ - /** - * {@inheritdoc} - */ - public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool; - - /** - * {@inheritdoc} - */ - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = []); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php deleted file mode 100644 index 59214df5454..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php +++ /dev/null @@ -1,338 +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\Doctrine\Orm\Extension; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\EagerLoadingTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Exception\ResourceClassNotFoundException; -use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\ORM\Query\Expr\Join; -use Doctrine\ORM\Query\Expr\Select; -use Doctrine\ORM\QueryBuilder; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; -use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; - -/** - * Eager loads relations. - * - * @author Charles Sarrazin - * @author Kévin Dunglas - * @author Antoine Bluchet - * @author Baptiste Meyer - */ -final class EagerLoadingExtension implements ContextAwareQueryCollectionExtensionInterface, QueryItemExtensionInterface -{ - use EagerLoadingTrait; - - private $propertyNameCollectionFactory; - private $propertyMetadataFactory; - private $classMetadataFactory; - private $maxJoins; - private $serializerContextBuilder; - private $requestStack; - - 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) - { - 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); - } - if (null !== $this->serializerContextBuilder) { - @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.', SerializerContextBuilderInterface::class), \E_USER_DEPRECATED); - } - - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->classMetadataFactory = $classMetadataFactory; - $this->maxJoins = $maxJoins; - $this->forceEager = $forceEager; - $this->fetchPartial = $fetchPartial; - $this->serializerContextBuilder = $serializerContextBuilder; - $this->requestStack = $requestStack; - } - - /** - * {@inheritdoc} - */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, string $operationName = null, array $context = []) - { - $this->apply(true, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - } - - /** - * {@inheritdoc} - * - * The context may contain serialization groups which helps defining joined entities that are readable. - */ - public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []) - { - $this->apply(false, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - } - - private function apply(bool $collection, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass, ?string $operationName, array $context) - { - if (null === $resourceClass) { - throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); - } - - $options = []; - if (null !== $operationName) { - // TODO remove in 3.0 - $options[($collection ? 'collection' : 'item').'_operation_name'] = $operationName; - } - - $operation = null; - $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'; - if (null !== $this->requestStack && null !== $this->serializerContextBuilder && null !== $request = $this->requestStack->getCurrentRequest()) { - $context += $this->serializerContextBuilder->createFromRequest($request, 'normalization_context' === $contextType); - } else { - $context += $this->getNormalizationContext($context['resource_class'] ?? $resourceClass, $contextType, $options); - } - } - - if (empty($context[AbstractNormalizer::GROUPS]) && !isset($context[AbstractNormalizer::ATTRIBUTES])) { - return; - } - - if (!empty($context[AbstractNormalizer::GROUPS])) { - $options['serializer_groups'] = (array) $context[AbstractNormalizer::GROUPS]; - } - - $this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $fetchPartial, $queryBuilder->getRootAliases()[0], $options, $context); - } - - /** - * Joins relations to eager load. - * - * @param bool $wasLeftJoin if the relation containing the new one had a left join, we have to force the new one to left join too - * @param int $joinCount the number of joins - * @param int $currentDepth the current max depth - * - * @throws RuntimeException when the max number of joins has been reached - */ - private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, bool $forceEager, bool $fetchPartial, string $parentAlias, array $options = [], array $normalizationContext = [], bool $wasLeftJoin = false, int &$joinCount = 0, int $currentDepth = null, string $parentAssociation = null) - { - if ($joinCount > $this->maxJoins) { - throw new RuntimeException('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary with the "api_platform.eager_loading.max_joins" configuration key (https://api-platform.com/docs/core/performance/#eager-loading), or limit the maximum serialization depth using the "enable_max_depth" option of the Symfony serializer (https://symfony.com/doc/current/components/serializer.html#handling-serialization-depth).'); - } - - $currentDepth = $currentDepth > 0 ? $currentDepth - 1 : $currentDepth; - $entityManager = $queryBuilder->getEntityManager(); - $classMetadata = $entityManager->getClassMetadata($resourceClass); - $attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($resourceClass)->getAttributesMetadata() : null; - - foreach ($classMetadata->associationMappings as $association => $mapping) { - // Don't join if max depth is enabled and the current depth limit is reached - if (0 === $currentDepth && ($normalizationContext[AbstractObjectNormalizer::ENABLE_MAX_DEPTH] ?? false)) { - continue; - } - - try { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $association, $options); - } catch (PropertyNotFoundException $propertyNotFoundException) { - // skip properties not found - continue; - // @phpstan-ignore-next-line indeed this can be thrown by the SerializerPropertyMetadataFactory - } catch (ResourceClassNotFoundException $resourceClassNotFoundException) { - // skip associations that are not resource classes - continue; - } - - if ( - // Always skip extra lazy associations - ClassMetadataInfo::FETCH_EXTRA_LAZY === $mapping['fetch'] || - // We don't want to interfere with doctrine on this association - (false === $forceEager && ClassMetadataInfo::FETCH_EAGER !== $mapping['fetch']) - ) { - continue; - } - - // prepare the child context - $childNormalizationContext = $normalizationContext; - if (isset($normalizationContext[AbstractNormalizer::ATTRIBUTES])) { - if ($inAttributes = isset($normalizationContext[AbstractNormalizer::ATTRIBUTES][$association])) { - $childNormalizationContext[AbstractNormalizer::ATTRIBUTES] = $normalizationContext[AbstractNormalizer::ATTRIBUTES][$association]; - } - } else { - $inAttributes = null; - } - - $fetchEager = null; - if ( - (null === $fetchEager = $propertyMetadata->getAttribute('fetch_eager')) && - (null !== $fetchEager = $propertyMetadata->getAttribute('fetchEager')) - ) { - @trigger_error('The "fetchEager" attribute is deprecated since 2.3. Please use "fetch_eager" instead.', \E_USER_DEPRECATED); - } - - if (false === $fetchEager) { - continue; - } - - if (true !== $fetchEager && (false === $propertyMetadata->isReadable() || false === $inAttributes)) { - continue; - } - - // Avoid joining back to the parent that we just came from, but only on *ToOne relations - if ( - null !== $parentAssociation && - isset($mapping['inversedBy']) && - $mapping['inversedBy'] === $parentAssociation && - $mapping['type'] & ClassMetadata::TO_ONE - ) { - continue; - } - - $existingJoin = QueryBuilderHelper::getExistingJoin($queryBuilder, $parentAlias, $association); - - if (null !== $existingJoin) { - $associationAlias = $existingJoin->getAlias(); - $isLeftJoin = Join::LEFT_JOIN === $existingJoin->getJoinType(); - } else { - $isNullable = $mapping['joinColumns'][0]['nullable'] ?? true; - $isLeftJoin = false !== $wasLeftJoin || true === $isNullable; - $method = $isLeftJoin ? 'leftJoin' : 'innerJoin'; - - $associationAlias = $queryNameGenerator->generateJoinAlias($association); - $queryBuilder->{$method}(sprintf('%s.%s', $parentAlias, $association), $associationAlias); - ++$joinCount; - } - - if (true === $fetchPartial) { - try { - $this->addSelect($queryBuilder, $mapping['targetEntity'], $associationAlias, $options); - } catch (ResourceClassNotFoundException $resourceClassNotFoundException) { - continue; - } - } else { - $this->addSelectOnce($queryBuilder, $associationAlias); - } - - // Avoid recursive joins for self-referencing relations - if ($mapping['targetEntity'] === $resourceClass) { - continue; - } - - // Only join the relation's relations recursively if it's a readableLink - if (true !== $fetchEager && (true !== $propertyMetadata->isReadableLink())) { - continue; - } - - if (isset($attributesMetadata[$association])) { - $maxDepth = $attributesMetadata[$association]->getMaxDepth(); - - // The current depth is the lowest max depth available in the ancestor tree. - if (null !== $maxDepth && (null === $currentDepth || $maxDepth < $currentDepth)) { - $currentDepth = $maxDepth; - } - } - - $this->joinRelations($queryBuilder, $queryNameGenerator, $mapping['targetEntity'], $forceEager, $fetchPartial, $associationAlias, $options, $childNormalizationContext, $isLeftJoin, $joinCount, $currentDepth, $association); - } - } - - private function addSelect(QueryBuilder $queryBuilder, string $entity, string $associationAlias, array $propertyMetadataOptions) - { - $select = []; - $entityManager = $queryBuilder->getEntityManager(); - $targetClassMetadata = $entityManager->getClassMetadata($entity); - if (!empty($targetClassMetadata->subClasses)) { - $this->addSelectOnce($queryBuilder, $associationAlias); - - return; - } - - foreach ($this->propertyNameCollectionFactory->create($entity) as $property) { - $propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions); - - if (true === $propertyMetadata->isIdentifier()) { - $select[] = $property; - continue; - } - - // If it's an embedded property see below - if (!\array_key_exists($property, $targetClassMetadata->embeddedClasses)) { - $isFetchable = $propertyMetadata->getAttribute('fetchable'); - // the field test allows to add methods to a Resource which do not reflect real database fields - if ($targetClassMetadata->hasField($property) && (true === $isFetchable || $propertyMetadata->isReadable())) { - $select[] = $property; - } - - continue; - } - - // It's an embedded property, select relevant subfields - foreach ($this->propertyNameCollectionFactory->create($targetClassMetadata->embeddedClasses[$property]['class']) as $embeddedProperty) { - $isFetchable = $propertyMetadata->getAttribute('fetchable'); - $propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions); - $propertyName = "$property.$embeddedProperty"; - if ($targetClassMetadata->hasField($propertyName) && (true === $isFetchable || $propertyMetadata->isReadable())) { - $select[] = $propertyName; - } - } - } - - $queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select))); - } - - private function addSelectOnce(QueryBuilder $queryBuilder, string $alias) - { - $existingSelects = array_reduce($queryBuilder->getDQLPart('select') ?? [], function ($existing, $dqlSelect) { - return ($dqlSelect instanceof Select) ? array_merge($existing, $dqlSelect->getParts()) : $existing; - }, []); - - if (!\in_array($alias, $existingSelects, true)) { - $queryBuilder->addSelect($alias); - } - } - - /** - * Gets the serializer context. - * - * @param string $contextType normalization_context or denormalization_context - * @param array $options represents the operation name so that groups are the one of the specific operation - */ - private function getNormalizationContext(string $resourceClass, string $contextType, array $options): array - { - if (null !== $this->requestStack && null !== $this->serializerContextBuilder && null !== $request = $this->requestStack->getCurrentRequest()) { - return $this->serializerContextBuilder->createFromRequest($request, 'normalization_context' === $contextType); - } - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (isset($options['collection_operation_name'])) { - $context = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], $contextType, null, true); - } elseif (isset($options['item_operation_name'])) { - $context = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], $contextType, null, true); - } else { - $context = $resourceMetadata->getAttribute($contextType); - } - - return $context ?? []; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php deleted file mode 100644 index c3c08598ac1..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php +++ /dev/null @@ -1,179 +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\Doctrine\Orm\Extension; - -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\EagerLoadingTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Doctrine\ORM\Query\Expr\Join; -use Doctrine\ORM\QueryBuilder; - -/** - * Fixes filters on OneToMany associations - * https://github.com/api-platform/core/issues/944. - */ -final class FilterEagerLoadingExtension implements ContextAwareQueryCollectionExtensionInterface -{ - use EagerLoadingTrait; - - private $resourceClassResolver; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, bool $forceEager = true, ResourceClassResolverInterface $resourceClassResolver = null) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->forceEager = $forceEager; - $this->resourceClassResolver = $resourceClassResolver; - } - - /** - * {@inheritdoc} - */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, string $operationName = null, array $context = []) - { - if (null === $resourceClass) { - throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); - } - - $em = $queryBuilder->getEntityManager(); - $classMetadata = $em->getClassMetadata($resourceClass); - - if (!$this->shouldOperationForceEager($resourceClass, ['collection_operation_name' => $operationName]) && !$this->hasFetchEagerAssociation($em, $classMetadata)) { - return; - } - - // If no where part, nothing to do - $wherePart = $queryBuilder->getDQLPart('where'); - - if (!$wherePart) { - return; - } - - $joinParts = $queryBuilder->getDQLPart('join'); - $originAlias = $queryBuilder->getRootAliases()[0]; - - if (!$joinParts || !isset($joinParts[$originAlias])) { - return; - } - - $queryBuilderClone = clone $queryBuilder; - $queryBuilderClone->resetDQLPart('where'); - $changedWhereClause = false; - - if (!$classMetadata->isIdentifierComposite) { - $replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias); - $in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias); - - if ($classMetadata->containsForeignIdentifier) { - $identifier = current($classMetadata->getIdentifier()); - $in->select("IDENTITY($replacementAlias.$identifier)"); - $queryBuilderClone->andWhere($queryBuilderClone->expr()->in("$originAlias.$identifier", $in->getDQL())); - } else { - $in->select($replacementAlias); - $queryBuilderClone->andWhere($queryBuilderClone->expr()->in($originAlias, $in->getDQL())); - } - - $changedWhereClause = true; - } else { - // Because Doctrine doesn't support WHERE ( foo, bar ) IN () (https://github.com/doctrine/doctrine2/issues/5238), we are building as many subqueries as they are identifiers - foreach ($classMetadata->getIdentifier() as $identifier) { - if (!$classMetadata->hasAssociation($identifier)) { - continue; - } - - $replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias); - $in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias); - $in->select("IDENTITY($replacementAlias.$identifier)"); - $queryBuilderClone->andWhere($queryBuilderClone->expr()->in("$originAlias.$identifier", $in->getDQL())); - $changedWhereClause = true; - } - } - - if (false === $changedWhereClause) { - return; - } - - $queryBuilder->resetDQLPart('where'); - $queryBuilder->add('where', $queryBuilderClone->getDQLPart('where')); - } - - /** - * Returns a clone of the given query builder where everything gets re-aliased. - * - * @param string $originAlias the base alias - * @param string $replacement the replacement for the base alias, will change the from alias - */ - private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $originAlias = 'o', string $replacement = 'o_2'): QueryBuilder - { - $queryBuilderClone = clone $queryBuilder; - - $joinParts = $queryBuilder->getDQLPart('join'); - $wherePart = $queryBuilder->getDQLPart('where'); - - // reset parts - $queryBuilderClone->resetDQLPart('join'); - $queryBuilderClone->resetDQLPart('where'); - $queryBuilderClone->resetDQLPart('orderBy'); - $queryBuilderClone->resetDQLPart('groupBy'); - $queryBuilderClone->resetDQLPart('having'); - - // Change from alias - $from = $queryBuilderClone->getDQLPart('from')[0]; - $queryBuilderClone->resetDQLPart('from'); - $queryBuilderClone->from($from->getFrom(), $replacement); - - $aliases = ["$originAlias."]; - $replacements = ["$replacement."]; - - // Change join aliases - foreach ($joinParts[$originAlias] as $joinPart) { - /** @var Join $joinPart */ - $joinString = preg_replace($this->buildReplacePatterns($aliases), $replacements, $joinPart->getJoin()); - $pos = strpos($joinString, '.'); - if (false === $pos) { - if (null !== $joinPart->getCondition() && null !== $this->resourceClassResolver && $this->resourceClassResolver->isResourceClass($joinString)) { - $newAlias = $queryNameGenerator->generateJoinAlias($joinPart->getAlias()); - $aliases[] = "{$joinPart->getAlias()}."; - $replacements[] = "$newAlias."; - $condition = preg_replace($this->buildReplacePatterns($aliases), $replacements, $joinPart->getCondition()); - $join = new Join($joinPart->getJoinType(), $joinPart->getJoin(), $newAlias, $joinPart->getConditionType(), $condition); - /* @phpstan-ignore-next-line */ - $queryBuilderClone->add('join', [$replacement => $join], true); - } - - continue; - } - $alias = substr($joinString, 0, $pos); - $association = substr($joinString, $pos + 1); - $newAlias = $queryNameGenerator->generateJoinAlias($association); - $aliases[] = "{$joinPart->getAlias()}."; - $replacements[] = "$newAlias."; - $condition = preg_replace($this->buildReplacePatterns($aliases), $replacements, $joinPart->getCondition() ?? ''); - QueryBuilderHelper::addJoinOnce($queryBuilderClone, $queryNameGenerator, $alias, $association, $joinPart->getJoinType(), $joinPart->getConditionType(), $condition, $originAlias, $newAlias); - } - - $queryBuilderClone->add('where', preg_replace($this->buildReplacePatterns($aliases), $replacements, (string) $wherePart)); - - return $queryBuilderClone; - } - - private function buildReplacePatterns(array $aliases): array - { - return array_map(static function (string $alias): string { - return '/\b'.preg_quote($alias, '/').'/'; - }, $aliases); - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/FilterExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/FilterExtension.php deleted file mode 100644 index 152bcc0e188..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/FilterExtension.php +++ /dev/null @@ -1,85 +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\Doctrine\Orm\Extension; - -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\FilterLocatorTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\FilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Doctrine\ORM\QueryBuilder; -use Psr\Container\ContainerInterface; - -/** - * Applies filters on a resource query. - * - * @author Kévin Dunglas - * @author Samuel ROZE - */ -final class FilterExtension implements ContextAwareQueryCollectionExtensionInterface -{ - use FilterLocatorTrait; - - private $resourceMetadataFactory; - - /** - * @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection - */ - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, $filterLocator) - { - $this->setFilterLocator($filterLocator); - - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - /** - * {@inheritdoc} - */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, string $operationName = null, array $context = []) - { - if (null === $resourceClass) { - throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); - } - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); - - if (empty($resourceFilters)) { - return; - } - - $orderFilters = []; - - foreach ($resourceFilters as $filterId) { - $filter = $this->getFilter($filterId); - if ($filter instanceof FilterInterface) { - // Apply the OrderFilter after every other filter to avoid an edge case where OrderFilter would do a LEFT JOIN instead of an INNER JOIN - if ($filter instanceof OrderFilter) { - $orderFilters[] = $filter; - continue; - } - - $context['filters'] = $context['filters'] ?? []; - $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - } - } - - foreach ($orderFilters as $orderFilter) { - $context['filters'] = $context['filters'] ?? []; - $orderFilter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - } - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/OrderExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/OrderExtension.php deleted file mode 100644 index 355fe6fd657..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/OrderExtension.php +++ /dev/null @@ -1,99 +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\Doctrine\Orm\Extension; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Doctrine\ORM\QueryBuilder; - -/** - * Applies selected ordering while querying resource collection. - * - * @author Kévin Dunglas - * @author Samuel ROZE - * @author Vincent Chalamon - */ -final class OrderExtension implements ContextAwareQueryCollectionExtensionInterface -{ - private $order; - private $resourceMetadataFactory; - - public function __construct(string $order = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->order = $order; - } - - /** - * {@inheritdoc} - */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, string $operationName = null, array $context = []) - { - if (null === $resourceClass) { - throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); - } - - // Do not apply order if already defined on queryBuilder - $orderByDqlPart = $queryBuilder->getDQLPart('orderBy'); - if (\is_array($orderByDqlPart) && \count($orderByDqlPart) > 0) { - return; - } - - $rootAlias = $queryBuilder->getRootAliases()[0]; - - $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 (null !== $defaultOrder) { - foreach ($defaultOrder as $field => $order) { - if (\is_int($field)) { - // Default direction - $field = $order; - $order = 'ASC'; - } - - $pos = strpos($field, '.'); - if (false === $pos || isset($classMetaData->embeddedClasses[substr($field, 0, $pos)])) { - // Configure default filter with property - $field = "{$rootAlias}.{$field}"; - } else { - $alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $rootAlias, substr($field, 0, $pos)); - $field = sprintf('%s.%s', $alias, substr($field, $pos + 1)); - } - $queryBuilder->addOrderBy($field, $order); - } - - return; - } - } - - if (null !== $this->order) { - // A foreign identifier cannot be used for ordering. - if ($classMetaData->containsForeignIdentifier) { - return; - } - - foreach ($identifiers as $identifier) { - $queryBuilder->addOrderBy("{$rootAlias}.{$identifier}", $this->order); - } - } - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/PaginationExtension.php b/src/Core/Bridge/Doctrine/Orm/Extension/PaginationExtension.php deleted file mode 100644 index d13f2ae8240..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/PaginationExtension.php +++ /dev/null @@ -1,408 +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\Doctrine\Orm\Extension; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\AbstractPaginator; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryChecker; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\DataProvider\Pagination; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use Doctrine\ORM\QueryBuilder; -use Doctrine\ORM\Tools\Pagination\CountWalker; -use Doctrine\ORM\Tools\Pagination\Paginator as DoctrineOrmPaginator; -use Doctrine\Persistence\ManagerRegistry; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; - -// Help opcache.preload discover always-needed symbols -class_exists(AbstractPaginator::class); - -/** - * Applies pagination on the Doctrine query for resource collection when enabled. - * - * @author Kévin Dunglas - * @author Samuel ROZE - */ -final class PaginationExtension implements ContextAwareQueryResultCollectionExtensionInterface -{ - private $managerRegistry; - private $requestStack; - /** - * @var ResourceMetadataFactoryInterface - */ - private $resourceMetadataFactory; - private $enabled; - private $clientEnabled; - private $clientItemsPerPage; - private $itemsPerPage; - private $pageParameterName; - private $enabledParameterName; - private $itemsPerPageParameterName; - private $maximumItemPerPage; - private $partial; - private $clientPartial; - private $partialParameterName; - /** - * @var Pagination|null - */ - private $pagination; - - /** - * @param ResourceMetadataFactoryInterface|RequestStack $resourceMetadataFactory - * @param Pagination|ResourceMetadataFactoryInterface $pagination - */ - public function __construct(ManagerRegistry $managerRegistry, /* ResourceMetadataFactoryInterface */ $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); - @trigger_error(sprintf('Passing an instance of "%s" as third 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.', ResourceMetadataFactoryInterface::class, self::class, Pagination::class), \E_USER_DEPRECATED); - - $this->requestStack = $resourceMetadataFactory; - $resourceMetadataFactory = $pagination; - $pagination = null; - - $args = \array_slice(\func_get_args(), 3); - $legacyPaginationArgs = [ - ['arg_name' => 'enabled', 'type' => 'bool', 'default' => true], - ['arg_name' => 'clientEnabled', 'type' => 'bool', 'default' => false], - ['arg_name' => 'clientItemsPerPage', 'type' => 'bool', 'default' => false], - ['arg_name' => 'itemsPerPage', 'type' => 'int', 'default' => 30], - ['arg_name' => 'pageParameterName', 'type' => 'string', 'default' => 'page'], - ['arg_name' => 'enabledParameterName', 'type' => 'string', 'default' => 'pagination'], - ['arg_name' => 'itemsPerPageParameterName', 'type' => 'string', 'default' => 'itemsPerPage'], - ['arg_name' => 'maximumItemPerPage', 'type' => 'int', 'default' => null], - ['arg_name' => 'partial', 'type' => 'bool', 'default' => false], - ['arg_name' => 'clientPartial', 'type' => 'bool', 'default' => false], - ['arg_name' => 'partialParameterName', 'type' => 'string', 'default' => 'partial'], - ]; - - foreach ($legacyPaginationArgs as $pos => $arg) { - if (\array_key_exists($pos, $args)) { - @trigger_error(sprintf('Passing "$%s" arguments is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Pass an instance of "%s" as third argument instead.', implode('", "$', array_column($legacyPaginationArgs, 'arg_name')), Paginator::class), \E_USER_DEPRECATED); - - if (!((null === $arg['default'] && null === $args[$pos]) || \call_user_func("is_{$arg['type']}", $args[$pos]))) { - throw new InvalidArgumentException(sprintf('The "$%s" argument is expected to be a %s%s.', $arg['arg_name'], $arg['type'], null === $arg['default'] ? ' or null' : '')); - } - - $value = $args[$pos]; - } else { - $value = $arg['default']; - } - - $this->{$arg['arg_name']} = $value; - } - } elseif (!$resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - 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)); - } - - $this->managerRegistry = $managerRegistry; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->pagination = $pagination; - } - - /** - * {@inheritdoc} - */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []) - { - if (null === $pagination = $this->getPagination($queryBuilder, $resourceClass, $operationName, $context)) { - return; - } - - [$offset, $limit] = $pagination; - - $queryBuilder - ->setFirstResult($offset) - ->setMaxResults($limit); - } - - /** - * {@inheritdoc} - */ - public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool - { - if ($context['graphql_operation_name'] ?? false) { - return $this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context); - } - - if (null === $this->requestStack) { - return $this->pagination->isEnabled($resourceClass, $operationName, $context); - } - - if (null === $request = $this->requestStack->getCurrentRequest()) { - return false; - } - - return $this->isPaginationEnabled($request, $this->resourceMetadataFactory->create($resourceClass), $operationName); - } - - /** - * {@inheritdoc} - */ - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = []): iterable - { - $query = $queryBuilder->getQuery(); - - // Only one alias, without joins, disable the DISTINCT on the COUNT - if (1 === \count($queryBuilder->getAllAliases())) { - $query->setHint(CountWalker::HINT_DISTINCT, false); - } - - $doctrineOrmPaginator = new DoctrineOrmPaginator($query, $this->shouldDoctrinePaginatorFetchJoinCollection($queryBuilder, $resourceClass, $operationName, $context)); - $doctrineOrmPaginator->setUseOutputWalkers($this->shouldDoctrinePaginatorUseOutputWalkers($queryBuilder, $resourceClass, $operationName, $context)); - - if (null === $this->requestStack) { - $isPartialEnabled = $this->pagination->isPartialEnabled($resourceClass, $operationName, $context); - } else { - $isPartialEnabled = $this->isPartialPaginationEnabled( - $this->requestStack->getCurrentRequest(), - null === $resourceClass ? null : $this->resourceMetadataFactory->create($resourceClass), - $operationName - ); - } - - if ($isPartialEnabled) { - return new class($doctrineOrmPaginator) extends AbstractPaginator { - }; - } - - return new Paginator($doctrineOrmPaginator); - } - - /** - * @throws InvalidArgumentException - */ - private function getPagination(QueryBuilder $queryBuilder, string $resourceClass, ?string $operationName, array $context): ?array - { - $request = null; - if (null !== $this->requestStack && null === $request = $this->requestStack->getCurrentRequest()) { - return null; - } - - if (null === $request) { - if (!$this->pagination->isEnabled($resourceClass, $operationName, $context)) { - return null; - } - - if (($context['graphql_operation_name'] ?? false) && !$this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) { - return null; - } - - $context = $this->addCountToContext($queryBuilder, $context); - - return \array_slice($this->pagination->getPagination($resourceClass, $operationName, $context), 1); - } - - $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; - } - - 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); - } - - if (0 > $itemsPerPage) { - throw new InvalidArgumentException('Item per page parameter should not be less than 0'); - } - - $page = (int) $this->getPaginationParameter($request, $this->pageParameterName, 1); - - if (1 > $page) { - throw new InvalidArgumentException('Page should not be less than 1'); - } - - if (0 === $itemsPerPage && 1 < $page) { - throw new InvalidArgumentException('Page should not be greater than 1 if itemsPerPage is equal to 0'); - } - - $firstResult = ($page - 1) * $itemsPerPage; - if ($request->attributes->getBoolean('_graphql', false)) { - $collectionArgs = $request->attributes->get('_graphql_collections_args', []); - if (isset($collectionArgs[$resourceClass]['after'])) { - $after = base64_decode($collectionArgs[$resourceClass]['after'], true); - $firstResult = (int) $after; - $firstResult = false === $after ? $firstResult : ++$firstResult; - } - } - - return [$firstResult, $itemsPerPage]; - } - - private function isPartialPaginationEnabled(Request $request = null, ResourceMetadata $resourceMetadata = null, string $operationName = null): bool - { - $enabled = $this->partial; - $clientEnabled = $this->clientPartial; - - if ($resourceMetadata) { - $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_partial', $enabled, true); - - if ($request) { - $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_partial', $clientEnabled, true); - } - } - - if ($clientEnabled && $request) { - $enabled = filter_var($this->getPaginationParameter($request, $this->partialParameterName, $enabled), \FILTER_VALIDATE_BOOLEAN); - } - - return $enabled; - } - - private function isPaginationEnabled(Request $request, ResourceMetadata $resourceMetadata, string $operationName = null): bool - { - $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_enabled', $this->enabled, true); - $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_enabled', $this->clientEnabled, true); - - if ($clientEnabled) { - $enabled = filter_var($this->getPaginationParameter($request, $this->enabledParameterName, $enabled), \FILTER_VALIDATE_BOOLEAN); - } - - return $enabled; - } - - private function getPaginationParameter(Request $request, string $parameterName, $default = null) - { - if (null !== $paginationAttribute = $request->attributes->get('_api_pagination')) { - return \array_key_exists($parameterName, $paginationAttribute) ? $paginationAttribute[$parameterName] : $default; - } - - return $request->query->all()[$parameterName] ?? $default; - } - - private function addCountToContext(QueryBuilder $queryBuilder, array $context): array - { - if (!($context['graphql_operation_name'] ?? false)) { - return $context; - } - - if (isset($context['filters']['last']) && !isset($context['filters']['before'])) { - $context['count'] = (new DoctrineOrmPaginator($queryBuilder))->count(); - } - - return $context; - } - - /** - * Determines the value of the $fetchJoinCollection argument passed to the Doctrine ORM Paginator. - */ - private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = []): bool - { - if (null !== $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if (isset($context['collection_operation_name']) && null !== $fetchJoinCollection = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_fetch_join_collection', null, true)) { - return $fetchJoinCollection; - } - - if (isset($context['graphql_operation_name']) && null !== $fetchJoinCollection = $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_fetch_join_collection', null, true)) { - return $fetchJoinCollection; - } - } - - /* - * "Cannot count query which selects two FROM components, cannot make distinction" - * - * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php#L81 - * @see https://github.com/doctrine/doctrine2/issues/2910 - */ - if (QueryChecker::hasRootEntityWithCompositeIdentifier($queryBuilder, $this->managerRegistry)) { - return false; - } - - if (QueryChecker::hasJoinedToManyAssociation($queryBuilder, $this->managerRegistry)) { - return true; - } - - // disable $fetchJoinCollection by default (performance) - return false; - } - - /** - * Determines whether the Doctrine ORM Paginator should use output walkers. - */ - private function shouldDoctrinePaginatorUseOutputWalkers(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = []): bool - { - if (null !== $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if (isset($context['collection_operation_name']) && null !== $useOutputWalkers = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_use_output_walkers', null, true)) { - return $useOutputWalkers; - } - - if (isset($context['graphql_operation_name']) && null !== $useOutputWalkers = $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_use_output_walkers', null, true)) { - return $useOutputWalkers; - } - } - - /* - * "Cannot count query that uses a HAVING clause. Use the output walkers for pagination" - * - * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php#L56 - */ - if (QueryChecker::hasHavingClause($queryBuilder)) { - return true; - } - - /* - * "Cannot count query which selects two FROM components, cannot make distinction" - * - * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php#L64 - */ - if (QueryChecker::hasRootEntityWithCompositeIdentifier($queryBuilder, $this->managerRegistry)) { - return true; - } - - /* - * "Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator." - * - * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php#L77 - */ - if (QueryChecker::hasRootEntityWithForeignKeyIdentifier($queryBuilder, $this->managerRegistry)) { - return true; - } - - /* - * "Cannot select distinct identifiers from query with LIMIT and ORDER BY on a column from a fetch joined to-many association. Use output walkers." - * - * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php#L150 - */ - if (QueryChecker::hasMaxResults($queryBuilder) && QueryChecker::hasOrderByOnFetchJoinedToManyAssociation($queryBuilder, $this->managerRegistry)) { - return true; - } - - // Disable output walkers by default (performance) - return false; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/QueryCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/QueryCollectionExtensionInterface.php deleted file mode 100644 index f5a286c3160..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/QueryCollectionExtensionInterface.php +++ /dev/null @@ -1,32 +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\Doctrine\Orm\Extension; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface as LegacyQueryNameGeneratorInterface; -use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\QueryBuilder; - -/** - * Interface of Doctrine ORM query extensions for collection queries. - * - * @author Samuel ROZE - * @author Kévin Dunglas - */ -interface QueryCollectionExtensionInterface -{ - /** - * @param QueryNameGeneratorInterface|LegacyQueryNameGeneratorInterface $queryNameGenerator - */ - public function applyToCollection(QueryBuilder $queryBuilder, LegacyQueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php deleted file mode 100644 index 8c699f95c1f..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php +++ /dev/null @@ -1,32 +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\Doctrine\Orm\Extension; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface as LegacyQueryNameGeneratorInterface; -use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\QueryBuilder; - -/** - * Interface of Doctrine ORM query extensions for item queries. - * - * @author Maxime STEINHAUSSER - * @author Kévin Dunglas - */ -interface QueryItemExtensionInterface -{ - /** - * @param QueryNameGeneratorInterface|LegacyQueryNameGeneratorInterface $queryNameGenerator - */ - public function applyToItem(QueryBuilder $queryBuilder, LegacyQueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/QueryResultCollectionExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/QueryResultCollectionExtensionInterface.php deleted file mode 100644 index 9f5733726c1..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/QueryResultCollectionExtensionInterface.php +++ /dev/null @@ -1,33 +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\Doctrine\Orm\Extension; - -use Doctrine\ORM\QueryBuilder; - -/** - * Interface of Doctrine ORM query extensions that supports result production - * for specific cases such as pagination. - * - * @author Samuel ROZE - * @author Kévin Dunglas - */ -interface QueryResultCollectionExtensionInterface extends QueryCollectionExtensionInterface -{ - public function supportsResult(string $resourceClass, string $operationName = null): bool; - - /** - * @return iterable - */ - public function getResult(QueryBuilder $queryBuilder); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Extension/QueryResultItemExtensionInterface.php b/src/Core/Bridge/Doctrine/Orm/Extension/QueryResultItemExtensionInterface.php deleted file mode 100644 index 722627d8a3e..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Extension/QueryResultItemExtensionInterface.php +++ /dev/null @@ -1,32 +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\Doctrine\Orm\Extension; - -use Doctrine\ORM\QueryBuilder; - -/** - * Interface of Doctrine ORM query extensions that supports result production - * for specific cases such as Query alteration. - * - * @author Antoine BLUCHET - */ -interface QueryResultItemExtensionInterface extends QueryItemExtensionInterface -{ - public function supportsResult(string $resourceClass, string $operationName = null): bool; - - /** - * @return object|null - */ - public function getResult(QueryBuilder $queryBuilder); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php deleted file mode 100644 index 312175040cf..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php +++ /dev/null @@ -1,36 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\QueryBuilder; - -abstract class AbstractContextAwareFilter extends AbstractFilter implements ContextAwareFilterInterface -{ - /** - * {@inheritdoc} - */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []) - { - if (!isset($context['filters']) || !\is_array($context['filters'])) { - parent::apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - - return; - } - - foreach ($context['filters'] as $property => $value) { - $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - } - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/AbstractFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/AbstractFilter.php deleted file mode 100644 index 49ec29a8a14..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/AbstractFilter.php +++ /dev/null @@ -1,164 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\PropertyHelperTrait as OrmPropertyHelperTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Util\RequestParser; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * {@inheritdoc} - * - * Abstract class with helpers for easing the implementation of a filter. - * - * @author Kévin Dunglas - * @author Théo FIDRY - */ -abstract class AbstractFilter implements FilterInterface -{ - use OrmPropertyHelperTrait; - use PropertyHelperTrait; - - protected $managerRegistry; - protected $requestStack; - protected $logger; - protected $properties; - protected $nameConverter; - - public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) - { - if (null !== $requestStack) { - @trigger_error(sprintf('Passing an instance of "%s" is deprecated since 2.2. Use "filters" context key instead.', RequestStack::class), \E_USER_DEPRECATED); - } - - $this->managerRegistry = $managerRegistry; - $this->requestStack = $requestStack; - $this->logger = $logger ?? new NullLogger(); - $this->properties = $properties; - $this->nameConverter = $nameConverter; - } - - /** - * {@inheritdoc} - */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/* , array $context = [] */) - { - @trigger_error(sprintf('Using "%s::apply()" is deprecated since 2.2. Use "%s::apply()" with the "filters" context key instead.', __CLASS__, AbstractContextAwareFilter::class), \E_USER_DEPRECATED); - - if (null === $this->requestStack || null === $request = $this->requestStack->getCurrentRequest()) { - return; - } - - foreach ($this->extractProperties($request, $resourceClass) as $property => $value) { - $this->filterProperty($property, $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName); - } - } - - /** - * Passes a property through the filter. - * - * @param mixed $value - */ - abstract protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/* , array $context = [] */); - - protected function getManagerRegistry(): ManagerRegistry - { - return $this->managerRegistry; - } - - protected function getProperties(): ?array - { - return $this->properties; - } - - protected function getLogger(): LoggerInterface - { - return $this->logger; - } - - /** - * Determines whether the given property is enabled. - */ - protected function isPropertyEnabled(string $property/* , string $resourceClass */): bool - { - if (\func_num_args() > 1) { - $resourceClass = func_get_arg(1); - } else { - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a second `$resourceClass` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.1.', __FUNCTION__), \E_USER_DEPRECATED); - } - } - $resourceClass = null; - } - - if (null === $this->properties) { - // to ensure sanity, nested properties must still be explicitly enabled - return !$this->isPropertyNested($property, $resourceClass); - } - - return \array_key_exists($property, $this->properties); - } - - /** - * Extracts properties to filter from the request. - */ - protected function extractProperties(Request $request/* , string $resourceClass */): array - { - @trigger_error(sprintf('The use of "%s::extractProperties()" is deprecated since 2.2. Use the "filters" key of the context instead.', __CLASS__), \E_USER_DEPRECATED); - - $resourceClass = \func_num_args() > 1 ? (string) func_get_arg(1) : null; - $needsFixing = false; - if (null !== $this->properties) { - foreach ($this->properties as $property => $value) { - if (($this->isPropertyNested($property, $resourceClass) || $this->isPropertyEmbedded($property, $resourceClass)) && $request->query->has(str_replace('.', '_', $property))) { - $needsFixing = true; - } - } - } - - if ($needsFixing) { - $request = RequestParser::parseAndDuplicateRequest($request); - } - - return $request->query->all(); - } - - protected function denormalizePropertyName($property) - { - if (!$this->nameConverter instanceof NameConverterInterface) { - return $property; - } - - return implode('.', array_map([$this->nameConverter, 'denormalize'], explode('.', (string) $property))); - } - - protected function normalizePropertyName($property) - { - if (!$this->nameConverter instanceof NameConverterInterface) { - return $property; - } - - return implode('.', array_map([$this->nameConverter, 'normalize'], explode('.', (string) $property))); - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/BooleanFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/BooleanFilter.php deleted file mode 100644 index 1771a4380e5..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/BooleanFilter.php +++ /dev/null @@ -1,72 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\BooleanFilterTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\QueryBuilder; - -/** - * Filters the collection by boolean values. - * - * Filters collection on equality of boolean properties. The value is specified - * as one of ( "true" | "false" | "1" | "0" ) in the query. - * - * For each property passed, if the resource does not have such property or if - * the value is not one of ( "true" | "false" | "1" | "0" ) the property is ignored. - * - * @author Amrouche Hamza - * @author Teoh Han Hui - */ -class BooleanFilter extends AbstractContextAwareFilter -{ - use BooleanFilterTrait; - - public const DOCTRINE_BOOLEAN_TYPES = [ - Types::BOOLEAN => true, - ]; - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) - { - if ( - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass) || - !$this->isBooleanField($property, $resourceClass) - ) { - return; - } - - $value = $this->normalizeValue($value, $property); - if (null === $value) { - return; - } - - $alias = $queryBuilder->getRootAliases()[0]; - $field = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass); - } - - $valueParameter = $queryNameGenerator->generateParameterName($field); - - $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) - ->setParameter($valueParameter, $value); - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/ContextAwareFilterInterface.php b/src/Core/Bridge/Doctrine/Orm/Filter/ContextAwareFilterInterface.php deleted file mode 100644 index a029b5d3d0a..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/ContextAwareFilterInterface.php +++ /dev/null @@ -1,30 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\QueryBuilder; - -/** - * Context aware filter. - * - * @author Kévin Dunglas - */ -interface ContextAwareFilterInterface extends FilterInterface -{ - /** - * {@inheritdoc} - */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/DateFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/DateFilter.php deleted file mode 100644 index b02145a61e7..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/DateFilter.php +++ /dev/null @@ -1,175 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\DateFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\DateFilterTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Doctrine\DBAL\Types\Type as DBALType; -use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\QueryBuilder; - -/** - * Filters the collection by date intervals. - * - * @author Kévin Dunglas - * @author Théo FIDRY - */ -class DateFilter extends AbstractContextAwareFilter implements DateFilterInterface -{ - use DateFilterTrait; - - public const DOCTRINE_DATE_TYPES = [ - Types::DATE_MUTABLE => true, - Types::DATETIME_MUTABLE => true, - Types::DATETIMETZ_MUTABLE => true, - Types::TIME_MUTABLE => true, - Types::DATE_IMMUTABLE => true, - Types::DATETIME_IMMUTABLE => true, - Types::DATETIMETZ_IMMUTABLE => true, - Types::TIME_IMMUTABLE => true, - ]; - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) - { - // Expect $values to be an array having the period as keys and the date value as values - if ( - !\is_array($values) || - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass) || - !$this->isDateField($property, $resourceClass) - ) { - return; - } - - $alias = $queryBuilder->getRootAliases()[0]; - $field = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass); - } - - $nullManagement = $this->properties[$property] ?? null; - $type = (string) $this->getDoctrineFieldType($property, $resourceClass); - - if (self::EXCLUDE_NULL === $nullManagement) { - $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field))); - } - - if (isset($values[self::PARAMETER_BEFORE])) { - $this->addWhere( - $queryBuilder, - $queryNameGenerator, - $alias, - $field, - self::PARAMETER_BEFORE, - $values[self::PARAMETER_BEFORE], - $nullManagement, - $type - ); - } - - if (isset($values[self::PARAMETER_STRICTLY_BEFORE])) { - $this->addWhere( - $queryBuilder, - $queryNameGenerator, - $alias, - $field, - self::PARAMETER_STRICTLY_BEFORE, - $values[self::PARAMETER_STRICTLY_BEFORE], - $nullManagement, - $type - ); - } - - if (isset($values[self::PARAMETER_AFTER])) { - $this->addWhere( - $queryBuilder, - $queryNameGenerator, - $alias, - $field, - self::PARAMETER_AFTER, - $values[self::PARAMETER_AFTER], - $nullManagement, - $type - ); - } - - if (isset($values[self::PARAMETER_STRICTLY_AFTER])) { - $this->addWhere( - $queryBuilder, - $queryNameGenerator, - $alias, - $field, - self::PARAMETER_STRICTLY_AFTER, - $values[self::PARAMETER_STRICTLY_AFTER], - $nullManagement, - $type - ); - } - } - - /** - * Adds the where clause according to the chosen null management. - * - * @param string|DBALType $type - */ - protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, string $value, string $nullManagement = null, $type = null) - { - $type = (string) $type; - try { - $value = false === strpos($type, '_immutable') ? new \DateTime($value) : new \DateTimeImmutable($value); - } catch (\Exception $e) { - // Silently ignore this filter if it can not be transformed to a \DateTime - $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)), - ]); - - return; - } - - $valueParameter = $queryNameGenerator->generateParameterName($field); - $operatorValue = [ - self::PARAMETER_BEFORE => '<=', - self::PARAMETER_STRICTLY_BEFORE => '<', - self::PARAMETER_AFTER => '>=', - self::PARAMETER_STRICTLY_AFTER => '>', - ]; - $baseWhere = sprintf('%s.%s %s :%s', $alias, $field, $operatorValue[$operator], $valueParameter); - - if (null === $nullManagement || self::EXCLUDE_NULL === $nullManagement) { - $queryBuilder->andWhere($baseWhere); - } elseif ( - (self::INCLUDE_NULL_BEFORE === $nullManagement && \in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true)) || - (self::INCLUDE_NULL_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true)) || - (self::INCLUDE_NULL_BEFORE_AND_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER, self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true)) - ) { - $queryBuilder->andWhere($queryBuilder->expr()->orX( - $baseWhere, - $queryBuilder->expr()->isNull(sprintf('%s.%s', $alias, $field)) - )); - } else { - $queryBuilder->andWhere($queryBuilder->expr()->andX( - $baseWhere, - $queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field)) - )); - } - - $queryBuilder->setParameter($valueParameter, $value, $type); - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/ExistsFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/ExistsFilter.php deleted file mode 100644 index 8cf47b9f735..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/ExistsFilter.php +++ /dev/null @@ -1,211 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\ORM\Query\Expr\Join; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Log\LoggerInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Filters the collection by whether a property value exists or not. - * - * For each property passed, if the resource does not have such property or if - * the value is not one of ( "true" | "false" | "1" | "0" ) the property is ignored. - * - * A query parameter with key but no value is treated as `true`, e.g.: - * Request: GET /products?exists[brand] - * Interpretation: filter products which have a brand - * - * @author Teoh Han Hui - */ -class ExistsFilter extends AbstractContextAwareFilter implements ExistsFilterInterface -{ - use ExistsFilterTrait; - - public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, NameConverterInterface $nameConverter = null) - { - parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter); - - $this->existsParameterName = $existsParameterName; - } - - /** - * {@inheritdoc} - */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []) - { - if (!\is_array($context['filters'][$this->existsParameterName] ?? null)) { - $context['exists_deprecated_syntax'] = true; - parent::apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - - return; - } - - foreach ($context['filters'][$this->existsParameterName] as $property => $value) { - $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - } - } - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/* , array $context = [] */) - { - if (\func_num_args() > 6) { - $context = func_get_arg(6); - } else { - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a seventh `$context` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.5.', __FUNCTION__), \E_USER_DEPRECATED); - } - } - $context = []; - } - - if ( - (($context['exists_deprecated_syntax'] ?? false) && !isset($value[self::QUERY_PARAMETER_KEY])) || - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass, true) || - !$this->isNullableField($property, $resourceClass) - ) { - return; - } - - $value = $this->normalizeValue($value, $property); - if (null === $value) { - return; - } - - $alias = $queryBuilder->getRootAliases()[0]; - $field = $property; - - $associations = []; - if ($this->isPropertyNested($property, $resourceClass)) { - [$alias, $field, $associations] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass); - } - $metadata = $this->getNestedMetadata($resourceClass, $associations); - - if ($metadata->hasAssociation($field)) { - if ($metadata->isCollectionValuedAssociation($field)) { - $queryBuilder - ->andWhere(sprintf('%s.%s %s EMPTY', $alias, $field, $value ? 'IS NOT' : 'IS')); - - return; - } - - if ($metadata->isAssociationInverseSide($field)) { - $alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $field, Join::LEFT_JOIN); - - $queryBuilder - ->andWhere(sprintf('%s %s NULL', $alias, $value ? 'IS NOT' : 'IS')); - - return; - } - - $queryBuilder - ->andWhere(sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); - - return; - } - - if ($metadata->hasField($field)) { - $queryBuilder - ->andWhere(sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); - } - } - - /** - * {@inheritdoc} - */ - protected function isNullableField(string $property, string $resourceClass): bool - { - $propertyParts = $this->splitPropertyParts($property, $resourceClass); - $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']); - - $field = $propertyParts['field']; - - if ($metadata->hasAssociation($field)) { - if ($metadata->isSingleValuedAssociation($field)) { - if (!($metadata instanceof ClassMetadataInfo)) { - return false; - } - - $associationMapping = $metadata->getAssociationMapping($field); - - return $this->isAssociationNullable($associationMapping); - } - - return true; - } - - if ($metadata instanceof ClassMetadataInfo && $metadata->hasField($field)) { - return $metadata->isNullable($field); - } - - return false; - } - - /** - * Determines whether an association is nullable. - * - * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 - */ - private function isAssociationNullable(array $associationMapping): bool - { - if (!empty($associationMapping['id'])) { - return false; - } - - if (!isset($associationMapping['joinColumns'])) { - return true; - } - - $joinColumns = $associationMapping['joinColumns']; - foreach ($joinColumns as $joinColumn) { - if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) { - return false; - } - } - - return true; - } - - /** - * {@inheritdoc} - */ - protected function extractProperties(Request $request/* , string $resourceClass */): array - { - if (!$request->query->has($this->existsParameterName)) { - $resourceClass = \func_num_args() > 1 ? (string) func_get_arg(1) : null; - - return parent::extractProperties($request, $resourceClass); - } - - @trigger_error(sprintf('The use of "%s::extractProperties()" is deprecated since 2.2. Use the "filters" key of the context instead.', __CLASS__), \E_USER_DEPRECATED); - - $properties = $request->query->all()[$this->existsParameterName]; - - return \is_array($properties) ? $properties : []; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/FilterInterface.php b/src/Core/Bridge/Doctrine/Orm/Filter/FilterInterface.php deleted file mode 100644 index dfe2da2d041..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/FilterInterface.php +++ /dev/null @@ -1,31 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Api\FilterInterface as BaseFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\QueryBuilder; - -/** - * Doctrine ORM filter interface. - * - * @author Kévin Dunglas - */ -interface FilterInterface extends BaseFilterInterface -{ - /** - * Applies the filter. - */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null); -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/NumericFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/NumericFilter.php deleted file mode 100644 index 5301f7833c1..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/NumericFilter.php +++ /dev/null @@ -1,102 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\NumericFilterTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\QueryBuilder; - -/** - * Filters the collection by numeric values. - * - * Filters collection by equality of numeric properties. - * - * For each property passed, if the resource does not have such property or if - * the value is not numeric, the property is ignored. - * - * @author Amrouche Hamza - * @author Teoh Han Hui - */ -class NumericFilter extends AbstractContextAwareFilter -{ - use NumericFilterTrait; - - /** - * Type of numeric in Doctrine. - * - * @see http://doctrine-orm.readthedocs.org/projects/doctrine-dbal/en/latest/reference/types.html - */ - public const DOCTRINE_NUMERIC_TYPES = [ - Types::BIGINT => true, - Types::DECIMAL => true, - Types::FLOAT => true, - Types::INTEGER => true, - Types::SMALLINT => true, - ]; - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) - { - if ( - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass) || - !$this->isNumericField($property, $resourceClass) - ) { - return; - } - - $values = $this->normalizeValues($value, $property); - if (null === $values) { - return; - } - - $alias = $queryBuilder->getRootAliases()[0]; - $field = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass); - } - - $valueParameter = $queryNameGenerator->generateParameterName($field); - - if (1 === \count($values)) { - $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) - ->setParameter($valueParameter, $values[0], (string) $this->getDoctrineFieldType($property, $resourceClass)); - } else { - $queryBuilder - ->andWhere(sprintf('%s.%s IN (:%s)', $alias, $field, $valueParameter)) - ->setParameter($valueParameter, $values); - } - } - - /** - * {@inheritdoc} - */ - protected function getType(string $doctrineType = null): string - { - if (null === $doctrineType || Types::DECIMAL === $doctrineType) { - return 'string'; - } - - if (Types::FLOAT === $doctrineType) { - return 'float'; - } - - return 'int'; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/OrderFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/OrderFilter.php deleted file mode 100644 index 07b51317bd9..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/OrderFilter.php +++ /dev/null @@ -1,128 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\Query\Expr\Join; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Log\LoggerInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Order the collection by given properties. - * - * The ordering is done in the same sequence as they are specified in the query, - * and for each property a direction value can be specified. - * - * For each property passed, if the resource does not have such property or if the - * direction value is different from "asc" or "desc" (case insensitive), the property - * is ignored. - * - * @author Kévin Dunglas - * @author Théo FIDRY - */ -class OrderFilter extends AbstractContextAwareFilter implements OrderFilterInterface -{ - use OrderFilterTrait; - - public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) - { - if (null !== $properties) { - $properties = array_map(static function ($propertyOptions) { - // shorthand for default direction - if (\is_string($propertyOptions)) { - $propertyOptions = [ - 'default_direction' => $propertyOptions, - ]; - } - - return $propertyOptions; - }, $properties); - } - - parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter); - - $this->orderParameterName = $orderParameterName; - } - - /** - * {@inheritdoc} - */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = []) - { - if (isset($context['filters']) && !isset($context['filters'][$this->orderParameterName])) { - return; - } - - if (!isset($context['filters'][$this->orderParameterName]) || !\is_array($context['filters'][$this->orderParameterName])) { - parent::apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - - return; - } - - foreach ($context['filters'][$this->orderParameterName] as $property => $value) { - $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); - } - } - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) - { - if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)) { - return; - } - - $direction = $this->normalizeValue($direction, $property); - if (null === $direction) { - return; - } - - $alias = $queryBuilder->getRootAliases()[0]; - $field = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::LEFT_JOIN); - } - - if (null !== $nullsComparison = $this->properties[$property]['nulls_comparison'] ?? null) { - $nullsDirection = self::NULLS_DIRECTION_MAP[$nullsComparison][$direction]; - - $nullRankHiddenField = sprintf('_%s_%s_null_rank', $alias, str_replace('.', '_', $field)); - - $queryBuilder->addSelect(sprintf('CASE WHEN %s.%s IS NULL THEN 0 ELSE 1 END AS HIDDEN %s', $alias, $field, $nullRankHiddenField)); - $queryBuilder->addOrderBy($nullRankHiddenField, $nullsDirection); - } - - $queryBuilder->addOrderBy(sprintf('%s.%s', $alias, $field), $direction); - } - - /** - * {@inheritdoc} - */ - protected function extractProperties(Request $request/* , string $resourceClass */): array - { - @trigger_error(sprintf('The use of "%s::extractProperties()" is deprecated since 2.2. Use the "filters" key of the context instead.', __CLASS__), \E_USER_DEPRECATED); - - $properties = $request->query->all()[$this->orderParameterName] ?? null; - - return \is_array($properties) ? $properties : []; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php deleted file mode 100644 index 394c65f9bc1..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/RangeFilter.php +++ /dev/null @@ -1,148 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\RangeFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\RangeFilterTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use Doctrine\ORM\QueryBuilder; - -/** - * Filters the collection by range. - * - * @author Lee Siong Chan - */ -class RangeFilter extends AbstractContextAwareFilter implements RangeFilterInterface -{ - use RangeFilterTrait; - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) - { - if ( - !\is_array($values) || - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass) - ) { - return; - } - - $values = $this->normalizeValues($values, $property); - if (null === $values) { - return; - } - - $alias = $queryBuilder->getRootAliases()[0]; - $field = $property; - - if ($this->isPropertyNested($property, $resourceClass)) { - [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass); - } - - foreach ($values as $operator => $value) { - $this->addWhere( - $queryBuilder, - $queryNameGenerator, - $alias, - $field, - $operator, - $value - ); - } - } - - /** - * Adds the where clause according to the operator. - * - * @param string $alias - * @param string $field - * @param string $operator - * @param string $value - */ - protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, $alias, $field, $operator, $value) - { - $valueParameter = $queryNameGenerator->generateParameterName($field); - - switch ($operator) { - case self::PARAMETER_BETWEEN: - $rangeValue = explode('..', $value); - - $rangeValue = $this->normalizeBetweenValues($rangeValue); - if (null === $rangeValue) { - return; - } - - if ($rangeValue[0] === $rangeValue[1]) { - $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) - ->setParameter($valueParameter, $rangeValue[0]); - - return; - } - - $queryBuilder - ->andWhere(sprintf('%1$s.%2$s BETWEEN :%3$s_1 AND :%3$s_2', $alias, $field, $valueParameter)) - ->setParameter(sprintf('%s_1', $valueParameter), $rangeValue[0]) - ->setParameter(sprintf('%s_2', $valueParameter), $rangeValue[1]); - - break; - case self::PARAMETER_GREATER_THAN: - $value = $this->normalizeValue($value, $operator); - if (null === $value) { - return; - } - - $queryBuilder - ->andWhere(sprintf('%s.%s > :%s', $alias, $field, $valueParameter)) - ->setParameter($valueParameter, $value); - - break; - case self::PARAMETER_GREATER_THAN_OR_EQUAL: - $value = $this->normalizeValue($value, $operator); - if (null === $value) { - return; - } - - $queryBuilder - ->andWhere(sprintf('%s.%s >= :%s', $alias, $field, $valueParameter)) - ->setParameter($valueParameter, $value); - - break; - case self::PARAMETER_LESS_THAN: - $value = $this->normalizeValue($value, $operator); - if (null === $value) { - return; - } - - $queryBuilder - ->andWhere(sprintf('%s.%s < :%s', $alias, $field, $valueParameter)) - ->setParameter($valueParameter, $value); - - break; - case self::PARAMETER_LESS_THAN_OR_EQUAL: - $value = $this->normalizeValue($value, $operator); - if (null === $value) { - return; - } - - $queryBuilder - ->andWhere(sprintf('%s.%s <= :%s', $alias, $field, $valueParameter)) - ->setParameter($valueParameter, $value); - - break; - } - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Filter/SearchFilter.php b/src/Core/Bridge/Doctrine/Orm/Filter/SearchFilter.php deleted file mode 100644 index a42b2ba6053..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Filter/SearchFilter.php +++ /dev/null @@ -1,274 +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\Doctrine\Orm\Filter; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterInterface; -use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Log\LoggerInterface; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Filter the collection by given properties. - * - * @author Kévin Dunglas - */ -class SearchFilter extends AbstractContextAwareFilter implements SearchFilterInterface -{ - use SearchFilterTrait; - - public const DOCTRINE_INTEGER_TYPE = Types::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) - { - parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter); - - if (null === $identifiersExtractor) { - @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); - } - - $this->iriConverter = $iriConverter; - $this->identifiersExtractor = $identifiersExtractor; - $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); - } - - protected function getIriConverter(): IriConverterInterface - { - return $this->iriConverter; - } - - protected function getPropertyAccessor(): PropertyAccessorInterface - { - return $this->propertyAccessor; - } - - /** - * {@inheritdoc} - */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) - { - if ( - null === $value || - !$this->isPropertyEnabled($property, $resourceClass) || - !$this->isPropertyMapped($property, $resourceClass, true) - ) { - return; - } - - $alias = $queryBuilder->getRootAliases()[0]; - $field = $property; - - $values = $this->normalizeValues((array) $value, $property); - if (null === $values) { - return; - } - - $associations = []; - if ($this->isPropertyNested($property, $resourceClass)) { - [$alias, $field, $associations] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass); - } - - $caseSensitive = true; - $strategy = $this->properties[$property] ?? self::STRATEGY_EXACT; - - // prefixing the strategy with i makes it case insensitive - if (0 === strpos($strategy, 'i')) { - $strategy = substr($strategy, 1); - $caseSensitive = false; - } - - $metadata = $this->getNestedMetadata($resourceClass, $associations); - - if ($metadata->hasField($field)) { - if ('id' === $field) { - $values = array_map([$this, 'getIdFromValue'], $values); - } - - if (!$this->hasValidValues($values, $this->getDoctrineFieldType($property, $resourceClass))) { - $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), - ]); - - return; - } - - $this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $values, $caseSensitive); - - return; - } - - // metadata doesn't have the field, nor an association on the field - if (!$metadata->hasAssociation($field)) { - return; - } - - $values = array_map([$this, 'getIdFromValue'], $values); - $associationFieldIdentifier = 'id'; - $doctrineTypeField = $this->getDoctrineFieldType($property, $resourceClass); - - if (null !== $this->identifiersExtractor) { - $associationResourceClass = $metadata->getAssociationTargetClass($field); - $associationFieldIdentifier = $this->identifiersExtractor->getIdentifiersFromResourceClass($associationResourceClass)[0]; - $doctrineTypeField = $this->getDoctrineFieldType($associationFieldIdentifier, $associationResourceClass); - } - - if (!$this->hasValidValues($values, $doctrineTypeField)) { - $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), - ]); - - return; - } - - $associationAlias = $alias; - $associationField = $field; - if ($metadata->isCollectionValuedAssociation($associationField) || $metadata->isAssociationInverseSide($field)) { - $associationAlias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $associationField); - $associationField = $associationFieldIdentifier; - } - - $this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $associationAlias, $associationField, $values, $caseSensitive); - } - - /** - * Adds where clause according to the strategy. - * - * @param mixed $values - * - * @throws InvalidArgumentException If strategy does not exist - */ - protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $values, bool $caseSensitive) - { - if (!\is_array($values)) { - $values = [$values]; - } - - $wrapCase = $this->createWrapCase($caseSensitive); - $valueParameter = ':'.$queryNameGenerator->generateParameterName($field); - $aliasedField = sprintf('%s.%s', $alias, $field); - - if (!$strategy || self::STRATEGY_EXACT === $strategy) { - if (1 === \count($values)) { - $queryBuilder - ->andWhere($queryBuilder->expr()->eq($wrapCase($aliasedField), $wrapCase($valueParameter))) - ->setParameter($valueParameter, $values[0]); - - return; - } - - $queryBuilder - ->andWhere($queryBuilder->expr()->in($wrapCase($aliasedField), $valueParameter)) - ->setParameter($valueParameter, $caseSensitive ? $values : array_map('strtolower', $values)); - - return; - } - - $ors = []; - $parameters = []; - foreach ($values as $key => $value) { - $keyValueParameter = sprintf('%s_%s', $valueParameter, $key); - $parameters[$caseSensitive ? $value : strtolower($value)] = $keyValueParameter; - - switch ($strategy) { - case self::STRATEGY_PARTIAL: - $ors[] = $queryBuilder->expr()->like( - $wrapCase($aliasedField), - $wrapCase((string) $queryBuilder->expr()->concat("'%'", $keyValueParameter, "'%'")) - ); - break; - case self::STRATEGY_START: - $ors[] = $queryBuilder->expr()->like( - $wrapCase($aliasedField), - $wrapCase((string) $queryBuilder->expr()->concat($keyValueParameter, "'%'")) - ); - break; - case self::STRATEGY_END: - $ors[] = $queryBuilder->expr()->like( - $wrapCase($aliasedField), - $wrapCase((string) $queryBuilder->expr()->concat("'%'", $keyValueParameter)) - ); - break; - case self::STRATEGY_WORD_START: - $ors[] = $queryBuilder->expr()->orX( - $queryBuilder->expr()->like($wrapCase($aliasedField), $wrapCase((string) $queryBuilder->expr()->concat($keyValueParameter, "'%'"))), - $queryBuilder->expr()->like($wrapCase($aliasedField), $wrapCase((string) $queryBuilder->expr()->concat("'% '", $keyValueParameter, "'%'"))) - ); - break; - default: - throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy)); - } - } - - $queryBuilder->andWhere($queryBuilder->expr()->orX(...$ors)); - array_walk($parameters, [$queryBuilder, 'setParameter']); - } - - /** - * Creates a function that will wrap a Doctrine expression according to the - * specified case sensitivity. - * - * For example, "o.name" will get wrapped into "LOWER(o.name)" when $caseSensitive - * is false. - */ - protected function createWrapCase(bool $caseSensitive): \Closure - { - return static function (string $expr) use ($caseSensitive): string { - if ($caseSensitive) { - return $expr; - } - - return sprintf('LOWER(%s)', $expr); - }; - } - - /** - * {@inheritdoc} - */ - protected function getType(string $doctrineType): string - { - switch ($doctrineType) { - case Types::ARRAY: - return 'array'; - case Types::BIGINT: - case Types::INTEGER: - case Types::SMALLINT: - return 'int'; - case Types::BOOLEAN: - return 'bool'; - case Types::DATE_MUTABLE: - case Types::TIME_MUTABLE: - case Types::DATETIME_MUTABLE: - case Types::DATETIMETZ_MUTABLE: - case Types::DATE_IMMUTABLE: - case Types::TIME_IMMUTABLE: - case Types::DATETIME_IMMUTABLE: - case Types::DATETIMETZ_IMMUTABLE: - return \DateTimeInterface::class; - case Types::FLOAT: - return 'float'; - } - - return 'string'; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/ItemDataProvider.php b/src/Core/Bridge/Doctrine/Orm/ItemDataProvider.php deleted file mode 100644 index c97519f2260..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/ItemDataProvider.php +++ /dev/null @@ -1,148 +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\Doctrine\Orm; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Util\IdentifierManagerTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface as LegacyQueryItemExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultItemExtensionInterface as LegacyQueryResultItemExtensionInterface; -use ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -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\Doctrine\Orm\Extension\QueryItemExtensionInterface; -use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; -use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\Mapping\ClassMetadata; - -/** - * Item data provider for the Doctrine ORM. - * - * @author Kévin Dunglas - * @author Samuel ROZE - * @final - */ -class ItemDataProvider implements DenormalizedIdentifiersAwareItemDataProviderInterface, RestrictedDataProviderInterface -{ - use IdentifierManagerTrait; - - private $managerRegistry; - private $itemExtensions; - - /** - * @param LegacyQueryItemExtensionInterface[]|QueryItemExtensionInterface[] $itemExtensions - * @param ResourceMetadataCollectionFactoryInterface|null $resourceMetadataFactory - */ - 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; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return $this->managerRegistry->getManagerForClass($resourceClass) instanceof EntityManagerInterface; - } - - /** - * {@inheritdoc} - * - * The context may contain a `fetch_data` key representing whether the value should be fetched by Doctrine or if we should return a reference. - * - * @throws RuntimeException - */ - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - /** @var EntityManagerInterface $manager */ - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - - if ((\is_int($id) || \is_string($id)) && !($context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] ?? false)) { - $id = $this->normalizeIdentifiers($id, $manager, $resourceClass); - } - if (!\is_array($id)) { - throw new \InvalidArgumentException(sprintf('$id must be array when "%s" key is set to true in the $context', IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER)); - } - $identifiers = $id; - - $fetchData = $context['fetch_data'] ?? true; - if (!$fetchData) { - return $manager->getReference($resourceClass, $identifiers); - } - - $repository = $manager->getRepository($resourceClass); - if (!method_exists($repository, 'createQueryBuilder')) { - throw new RuntimeException('The repository class must have a "createQueryBuilder" method.'); - } - - $queryBuilder = $repository->createQueryBuilder('o'); - $queryNameGenerator = new QueryNameGenerator(); - $doctrineClassMetadata = $manager->getClassMetadata($resourceClass); - - $this->addWhereForIdentifiers($identifiers, $queryBuilder, $doctrineClassMetadata, $queryNameGenerator); - - foreach ($this->itemExtensions as $extension) { - if ($extension instanceof LegacyQueryItemExtensionInterface) { - $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName, $context); - } - - if ($extension instanceof QueryItemExtensionInterface) { - $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $context['operation'] ?? null, $context); - } - - if ($extension instanceof LegacyQueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { // @phpstan-ignore-line because of context - return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); // @phpstan-ignore-line because of context - } - - if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $context['operation'] ?? null, $context)) { - return $extension->getResult($queryBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - } - - return $queryBuilder->getQuery()->getOneOrNullResult(); - } - - /** - * Add WHERE conditions to the query for one or more identifiers (simple or composite). - * - * @param mixed $queryNameGenerator - */ - private function addWhereForIdentifiers(array $identifiers, QueryBuilder $queryBuilder, ClassMetadata $classMetadata, $queryNameGenerator) - { - $alias = $queryBuilder->getRootAliases()[0]; - foreach ($identifiers as $identifier => $value) { - $placeholder = $queryNameGenerator->generateParameterName($identifier); - $expression = $queryBuilder->expr()->eq( - "{$alias}.{$identifier}", - ':'.$placeholder - ); - - $queryBuilder->andWhere($expression); - $queryBuilder->setParameter($placeholder, $value, $classMetadata->getTypeOfField($identifier)); - } - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php b/src/Core/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php deleted file mode 100644 index 986c86ecf8e..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php +++ /dev/null @@ -1,87 +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\Doctrine\Orm\Metadata\Property; - -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\Persistence\ManagerRegistry; - -/** - * Use Doctrine metadata to populate the identifier property. - * - * @author Kévin Dunglas - */ -final class DoctrineOrmPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - private $decorated; - private $managerRegistry; - - public function __construct(ManagerRegistry $managerRegistry, PropertyMetadataFactoryInterface $decorated) - { - $this->managerRegistry = $managerRegistry; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); - - if (null !== $propertyMetadata->isIdentifier()) { - return $propertyMetadata; - } - - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - if (!$manager) { - return $propertyMetadata; - } - $doctrineClassMetadata = $manager->getClassMetadata($resourceClass); - - $identifiers = $doctrineClassMetadata->getIdentifier(); - foreach ($identifiers as $identifier) { - if ($identifier === $property) { - $propertyMetadata = $propertyMetadata->withIdentifier(true); - - if (null !== $propertyMetadata->isWritable()) { - break; - } - - if ($doctrineClassMetadata instanceof ClassMetadataInfo) { - $writable = $doctrineClassMetadata->isIdentifierNatural(); - } else { - $writable = false; - } - - $propertyMetadata = $propertyMetadata->withWritable($writable); - - break; - } - } - - if (null === $propertyMetadata->isIdentifier()) { - $propertyMetadata = $propertyMetadata->withIdentifier(false); - } - - if ($doctrineClassMetadata instanceof ClassMetadataInfo && \in_array($property, $doctrineClassMetadata->getFieldNames(), true)) { - /** @var mixed[] */ - $fieldMapping = $doctrineClassMetadata->getFieldMapping($property); - $propertyMetadata = $propertyMetadata->withDefault($fieldMapping['options']['default'] ?? $propertyMetadata->getDefault()); - } - - return $propertyMetadata; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Paginator.php b/src/Core/Bridge/Doctrine/Orm/Paginator.php deleted file mode 100644 index 2777ef9c1ee..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Paginator.php +++ /dev/null @@ -1,22 +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\Doctrine\Orm; - -class_exists(\ApiPlatform\Doctrine\Orm\Paginator::class); - -if (false) { - final class Paginator extends \ApiPlatform\Doctrine\Orm\Paginator - { - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/PropertyHelperTrait.php b/src/Core/Bridge/Doctrine/Orm/PropertyHelperTrait.php deleted file mode 100644 index 873b929b841..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/PropertyHelperTrait.php +++ /dev/null @@ -1,84 +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\Doctrine\Orm; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Doctrine\ORM\QueryBuilder; - -/** - * Helper trait regarding a property in an entity using the resource metadata. - * - * @author Kévin Dunglas - * @author Théo FIDRY - */ -trait PropertyHelperTrait -{ - /** - * Splits the given property into parts. - */ - abstract protected function splitPropertyParts(string $property/* , string $resourceClass */): array; - - /** - * Adds the necessary joins for a nested property. - * - * @throws InvalidArgumentException If property is not nested - * - * @return array An array where the first element is the join $alias of the leaf entity, - * the second element is the $field name - * the third element is the $associations array - */ - protected function addJoinsForNestedProperty(string $property, string $rootAlias, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator/* , string $resourceClass, string $joinType */): array - { - if (\func_num_args() > 4) { - $resourceClass = func_get_arg(4); - } else { - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a fifth `$resourceClass` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.1.', __FUNCTION__), \E_USER_DEPRECATED); - } - } - $resourceClass = null; - } - - if (\func_num_args() > 5) { - $joinType = func_get_arg(5); - } else { - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a sixth `$joinType` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.3.', __FUNCTION__), \E_USER_DEPRECATED); - } - } - $joinType = null; - } - - $propertyParts = $this->splitPropertyParts($property, $resourceClass); - $parentAlias = $rootAlias; - $alias = null; - - foreach ($propertyParts['associations'] as $association) { - $alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $parentAlias, $association, $joinType); - $parentAlias = $alias; - } - - if (null === $alias) { - throw new InvalidArgumentException(sprintf('Cannot add joins for property "%s" - property is not nested.', $property)); - } - - return [$alias, $propertyParts['field'], $propertyParts['associations']]; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/SubresourceDataProvider.php b/src/Core/Bridge/Doctrine/Orm/SubresourceDataProvider.php deleted file mode 100644 index 504a9b0a69e..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/SubresourceDataProvider.php +++ /dev/null @@ -1,262 +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\Doctrine\Orm; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Util\IdentifierManagerTrait; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface as LegacyQueryCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface as LegacyQueryItemExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface as LegacyQueryResultCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultItemExtensionInterface as LegacyQueryResultItemExtensionInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension; -use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; -use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface; -use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; -use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; -use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use ApiPlatform\Exception\RuntimeException; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; - -/** - * Subresource data provider for the Doctrine ORM. - * - * @author Antoine Bluchet - */ -final class SubresourceDataProvider implements SubresourceDataProviderInterface -{ - use IdentifierManagerTrait; - - private $managerRegistry; - private $collectionExtensions; - private $itemExtensions; - - /** - * @param LegacyQueryCollectionExtensionInterface[]|QueryCollectionExtensionInterface[] $collectionExtensions - * @param LegacyQueryItemExtensionInterface[]|QueryItemExtensionInterface[] $itemExtensions - */ - public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $collectionExtensions = [], iterable $itemExtensions = []) - { - $this->managerRegistry = $managerRegistry; - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->collectionExtensions = $collectionExtensions; - $this->itemExtensions = $itemExtensions; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException - */ - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - $manager = $this->managerRegistry->getManagerForClass($resourceClass); - if (null === $manager) { - throw new ResourceClassNotSupportedException(sprintf('The object manager associated with the "%s" resource class cannot be retrieved.', $resourceClass)); - } - - $repository = $manager->getRepository($resourceClass); - if (!method_exists($repository, 'createQueryBuilder')) { - throw new RuntimeException('The repository class must have a "createQueryBuilder" method.'); - } - - if (!isset($context['identifiers'], $context['property'])) { - throw new ResourceClassNotSupportedException('The given resource class is not a subresource.'); - } - - $queryNameGenerator = new QueryNameGenerator(); - - /* - * The following recursively translates to this pseudo-dql: - * - * SELECT thirdLevel WHERE thirdLevel IN ( - * SELECT thirdLevel FROM relatedDummies WHERE relatedDummies = ? AND relatedDummies IN ( - * SELECT relatedDummies FROM Dummy WHERE Dummy = ? - * ) - * ) - * - * By using subqueries, we're forcing the SQL execution plan to go through indexes on doctrine identifiers. - */ - $queryBuilder = $this->buildQuery($identifiers, $context, $queryNameGenerator, $repository->createQueryBuilder($alias = 'o'), $alias, \count($context['identifiers'])); - - if (true === $context['collection']) { - foreach ($this->collectionExtensions as $extension) { - // We don't need this anymore because we already made sub queries to ensure correct results - if ($extension instanceof FilterEagerLoadingExtension) { - continue; - } - - if ($extension instanceof LegacyQueryCollectionExtensionInterface) { - $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); // @phpstan-ignore-line see context aware - } elseif ($extension instanceof QueryCollectionExtensionInterface) { - $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $context['operation'] ?? null, $context); - } - - if ($extension instanceof LegacyQueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { // @phpstan-ignore-line see context aware - return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); // @phpstan-ignore-line see context aware - } - - if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($resourceClass, $context['operation'] ?? null, $context)) { - return $extension->getResult($queryBuilder, $resourceClass, $context['operation'] ?? null, $context); - } - } - } else { - foreach ($this->itemExtensions as $extension) { - if ($extension instanceof LegacyQueryItemExtensionInterface) { - $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName, $context); - } elseif ($extension instanceof QueryItemExtensionInterface) { - $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $context['operation'] ?? null, $context); - } - - if ($extension instanceof LegacyQueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { // @phpstan-ignore-line see context aware - return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); // @phpstan-ignore-line see context aware - } - - if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $context['operation'] ?? null, $context)) { - return $extension->getResult($queryBuilder, $resourceClass, $context['operation'], $context); - } - } - } - - $query = $queryBuilder->getQuery(); - - return $context['collection'] ? $query->getResult() : $query->getOneOrNullResult(); - } - - /** - * @throws RuntimeException - */ - private function buildQuery(array $identifiers, array $context, QueryNameGenerator $queryNameGenerator, QueryBuilder $previousQueryBuilder, string $previousAlias, int $remainingIdentifiers, QueryBuilder $topQueryBuilder = null): QueryBuilder - { - if ($remainingIdentifiers <= 0) { - return $previousQueryBuilder; - } - - $topQueryBuilder = $topQueryBuilder ?? $previousQueryBuilder; - - if (\is_string(key($context['identifiers']))) { - $contextIdentifiers = array_keys($context['identifiers']); - $identifier = $contextIdentifiers[$remainingIdentifiers - 1]; - $identifierResourceClass = $context['identifiers'][$identifier][0]; - $previousAssociationProperty = $contextIdentifiers[$remainingIdentifiers] ?? $context['property']; - } else { - @trigger_error('Identifiers should match the convention introduced in ADR 0001-resource-identifiers, this behavior will be removed in 3.0.', \E_USER_DEPRECATED); - [$identifier, $identifierResourceClass] = $context['identifiers'][$remainingIdentifiers - 1]; - $previousAssociationProperty = $context['identifiers'][$remainingIdentifiers][0] ?? $context['property']; - } - - $manager = $this->managerRegistry->getManagerForClass($identifierResourceClass); - - if (!$manager instanceof EntityManagerInterface) { - throw new RuntimeException("The manager for $identifierResourceClass must be an EntityManager."); - } - - $classMetadata = $manager->getClassMetadata($identifierResourceClass); - - if (!$classMetadata instanceof ClassMetadataInfo) { - throw new RuntimeException("The class metadata for $identifierResourceClass must be an instance of ClassMetadataInfo."); - } - - $qb = $manager->createQueryBuilder(); - $alias = $queryNameGenerator->generateJoinAlias($identifier); - $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 { - $normalizedIdentifiers = $this->normalizeIdentifiers($identifiers[$identifier], $manager, $identifierResourceClass); - } - } - - if ($classMetadata->hasAssociation($previousAssociationProperty)) { - $relationType = $classMetadata->getAssociationMapping($previousAssociationProperty)['type']; - switch ($relationType) { - // MANY_TO_MANY relations need an explicit join so that the identifier part can be retrieved - case ClassMetadataInfo::MANY_TO_MANY: - $joinAlias = $queryNameGenerator->generateJoinAlias($previousAssociationProperty); - - $qb->select($joinAlias) - ->from($identifierResourceClass, $alias) - ->innerJoin("$alias.$previousAssociationProperty", $joinAlias); - break; - case ClassMetadataInfo::ONE_TO_MANY: - $mappedBy = $classMetadata->getAssociationMapping($previousAssociationProperty)['mappedBy']; - $previousAlias = "$previousAlias.$mappedBy"; - - $qb->select($alias) - ->from($identifierResourceClass, $alias); - break; - case ClassMetadataInfo::ONE_TO_ONE: - $association = $classMetadata->getAssociationMapping($previousAssociationProperty); - if (!isset($association['mappedBy'])) { - $qb->select("IDENTITY($alias.$previousAssociationProperty)") - ->from($identifierResourceClass, $alias); - break; - } - $mappedBy = $association['mappedBy']; - $previousAlias = "$previousAlias.$mappedBy"; - - $qb->select($alias) - ->from($identifierResourceClass, $alias); - break; - default: - $qb->select("IDENTITY($alias.$previousAssociationProperty)") - ->from($identifierResourceClass, $alias); - } - } elseif ($classMetadata->isIdentifier($previousAssociationProperty)) { - $qb->select($alias) - ->from($identifierResourceClass, $alias); - } - - $isLeaf = 1 === $remainingIdentifiers; - - // Add where clause for identifiers - foreach ($normalizedIdentifiers as $key => $value) { - $placeholder = $queryNameGenerator->generateParameterName($key); - $topQueryBuilder->setParameter($placeholder, $value, (string) $classMetadata->getTypeOfField($key)); - - // Optimization: add where clause for identifiers, but not via a WHERE ... IN ( ...subquery... ). - // Instead we use a direct identifier equality clause, to speed things up when dealing with large tables. - // We may do so if there is no more recursion levels from here, and if relation allows it. - $association = $classMetadata->hasAssociation($previousAssociationProperty) ? $classMetadata->getAssociationMapping($previousAssociationProperty) : []; - $oneToOneBidirectional = isset($association['inversedBy']) && ClassMetadataInfo::ONE_TO_ONE === $association['type']; - $oneToManyBidirectional = isset($association['mappedBy']) && ClassMetadataInfo::ONE_TO_MANY === $association['type']; - if ($isLeaf && $oneToOneBidirectional) { - $joinAlias = $queryNameGenerator->generateJoinAlias($association['inversedBy']); - - return $previousQueryBuilder->innerJoin("$previousAlias.{$association['inversedBy']}", $joinAlias) - ->andWhere("$joinAlias.$key = :$placeholder"); - } - if ($isLeaf && $oneToManyBidirectional && \in_array($key, $classMetadata->getIdentifier(), true)) { - return $previousQueryBuilder->andWhere("IDENTITY($previousAlias) = :$placeholder"); - } - - $qb->andWhere("$alias.$key = :$placeholder"); - } - - // Recurse queries - $qb = $this->buildQuery($identifiers, $context, $queryNameGenerator, $qb, $alias, --$remainingIdentifiers, $topQueryBuilder); - - return $previousQueryBuilder->andWhere($qb->expr()->in($previousAlias, $qb->getDQL())); - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php b/src/Core/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php deleted file mode 100644 index 5c93c1fae03..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php +++ /dev/null @@ -1,91 +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\Doctrine\Orm\Util; - -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; - -/** - * @author Antoine Bluchet - * - * @internal - */ -trait EagerLoadingTrait -{ - private $forceEager; - private $fetchPartial; - private $resourceMetadataFactory; - - /** - * Checks if an operation has a `force_eager` attribute. - */ - private function shouldOperationForceEager(string $resourceClass, array $options): bool - { - return $this->getBooleanOperationAttribute($resourceClass, $options, 'force_eager', $this->forceEager); - } - - /** - * Checks if an operation has a `fetch_partial` attribute. - */ - private function shouldOperationFetchPartial(string $resourceClass, array $options): bool - { - return $this->getBooleanOperationAttribute($resourceClass, $options, 'fetch_partial', $this->fetchPartial); - } - - /** - * Get the boolean attribute of an operation or the resource metadata. - */ - private function getBooleanOperationAttribute(string $resourceClass, array $options, string $attributeName, bool $default): bool - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if (isset($options['collection_operation_name'])) { - $attribute = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], $attributeName, null, true); - } elseif (isset($options['item_operation_name'])) { - $attribute = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], $attributeName, null, true); - } else { - $attribute = $resourceMetadata->getAttribute($attributeName); - } - - return \is_bool($attribute) ? $attribute : $default; - } - - /** - * Checks if the class has an associationMapping with FETCH=EAGER. - * - * @param array $checked array cache of tested metadata classes - */ - private function hasFetchEagerAssociation(EntityManagerInterface $em, ClassMetadataInfo $classMetadata, array &$checked = []): bool - { - $checked[] = $classMetadata->name; - - foreach ($classMetadata->getAssociationMappings() as $mapping) { - if (ClassMetadataInfo::FETCH_EAGER === $mapping['fetch']) { - return true; - } - - $related = $em->getClassMetadata($mapping['targetEntity']); - - if (\in_array($related->name, $checked, true)) { - continue; - } - - if (true === $this->hasFetchEagerAssociation($em, $related, $checked)) { - return true; - } - } - - return false; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Util/QueryBuilderHelper.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryBuilderHelper.php deleted file mode 100644 index 5f244f5398c..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Util/QueryBuilderHelper.php +++ /dev/null @@ -1,22 +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\Doctrine\Orm\Util; - -class_exists(\ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper::class); - -if (false) { - final class QueryBuilderHelper extends \ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper - { - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Util/QueryChecker.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryChecker.php deleted file mode 100644 index 568a0ca3523..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Util/QueryChecker.php +++ /dev/null @@ -1,208 +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\Doctrine\Orm\Util; - -use ApiPlatform\Doctrine\Orm\Util\QueryChecker as NewQueryChecker; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Query\Expr\Join; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; - -/** - * Utility functions for working with Doctrine ORM query. - * - * @author Teoh Han Hui - * @author Vincent Chalamon - * - * @internal - */ -final class QueryChecker -{ - private function __construct() - { - } - - /** - * Determines whether the QueryBuilder uses a HAVING clause. - */ - public static function hasHavingClause(QueryBuilder $queryBuilder): bool - { - return null !== $queryBuilder->getDQLPart('having'); - } - - /** - * Determines whether the QueryBuilder has any root entity with foreign key identifier. - */ - public static function hasRootEntityWithForeignKeyIdentifier(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool - { - foreach ($queryBuilder->getRootEntities() as $rootEntity) { - /** @var ClassMetadata $rootMetadata */ - $rootMetadata = $managerRegistry - ->getManagerForClass($rootEntity) - ->getClassMetadata($rootEntity); - - if ($rootMetadata->containsForeignIdentifier) { - return true; - } - } - - return false; - } - - /** - * Determines whether the QueryBuilder has any composite identifier. - */ - public static function hasRootEntityWithCompositeIdentifier(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool - { - foreach ($queryBuilder->getRootEntities() as $rootEntity) { - /** @var ClassMetadata $rootMetadata */ - $rootMetadata = $managerRegistry - ->getManagerForClass($rootEntity) - ->getClassMetadata($rootEntity); - - if ($rootMetadata->isIdentifierComposite) { - return true; - } - } - - return false; - } - - /** - * Determines whether the QueryBuilder has a limit on the maximum number of results. - */ - public static function hasMaxResults(QueryBuilder $queryBuilder): bool - { - return null !== $queryBuilder->getMaxResults(); - } - - /** - * Determines whether the QueryBuilder has ORDER BY on a column from a fetch joined to-many association. - */ - public static function hasOrderByOnFetchJoinedToManyAssociation(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool - { - if ( - 0 === \count($selectParts = $queryBuilder->getDQLPart('select')) || - 0 === \count($queryBuilder->getDQLPart('join')) || - 0 === \count($orderByParts = $queryBuilder->getDQLPart('orderBy')) - ) { - return false; - } - - $rootAliases = $queryBuilder->getRootAliases(); - - $selectAliases = []; - - foreach ($selectParts as $select) { - foreach ($select->getParts() as $part) { - [$alias] = explode('.', $part); - - $selectAliases[] = $alias; - } - } - - $selectAliases = array_diff($selectAliases, $rootAliases); - if (0 === \count($selectAliases)) { - return false; - } - - $orderByAliases = []; - - foreach ($orderByParts as $orderBy) { - foreach ($orderBy->getParts() as $part) { - if (false !== strpos($part, '.')) { - [$alias] = explode('.', $part); - - $orderByAliases[] = $alias; - } - } - } - - $orderByAliases = array_diff($orderByAliases, $rootAliases); - if (0 === \count($orderByAliases)) { - return false; - } - - foreach ($orderByAliases as $orderByAlias) { - $inToManyContext = false; - - foreach (QueryBuilderHelper::traverseJoins($orderByAlias, $queryBuilder, $managerRegistry) as $alias => [$metadata, $association]) { - if ($inToManyContext && \in_array($alias, $selectAliases, true)) { - return true; - } - - if (null !== $association && $metadata->isCollectionValuedAssociation($association)) { - $inToManyContext = true; - } - } - } - - return false; - } - - /** - * Determines whether the QueryBuilder has ORDER BY on a column from a fetch joined to-many association. - * - * @deprecated - */ - public static function hasOrderByOnToManyJoin(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool - { - @trigger_error(sprintf('The use of "%s::hasOrderByOnToManyJoin()" is deprecated since 2.4 and will be removed in 3.0. Use "%s::hasOrderByOnFetchJoinedToManyAssociation()" instead.', __CLASS__, NewQueryChecker::class), \E_USER_DEPRECATED); - - return self::hasOrderByOnFetchJoinedToManyAssociation($queryBuilder, $managerRegistry); - } - - /** - * Determines whether the QueryBuilder already has a left join. - */ - public static function hasLeftJoin(QueryBuilder $queryBuilder): bool - { - foreach ($queryBuilder->getDQLPart('join') as $joins) { - foreach ($joins as $join) { - if (Join::LEFT_JOIN === $join->getJoinType()) { - return true; - } - } - } - - return false; - } - - /** - * Determines whether the QueryBuilder has a joined to-many association. - */ - public static function hasJoinedToManyAssociation(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool - { - if ( - 0 === \count($queryBuilder->getDQLPart('join')) - ) { - return false; - } - - $joinAliases = array_diff($queryBuilder->getAllAliases(), $queryBuilder->getRootAliases()); - if (0 === \count($joinAliases)) { - return false; - } - - foreach ($joinAliases as $joinAlias) { - foreach (QueryBuilderHelper::traverseJoins($joinAlias, $queryBuilder, $managerRegistry) as $alias => [$metadata, $association]) { - if (null !== $association && $metadata->isCollectionValuedAssociation($association)) { - return true; - } - } - } - - return false; - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Util/QueryJoinParser.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryJoinParser.php deleted file mode 100644 index 8048a7fff09..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Util/QueryJoinParser.php +++ /dev/null @@ -1,92 +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\Doctrine\Orm\Util; - -use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; -use Doctrine\ORM\Query\Expr\Join; -use Doctrine\ORM\Query\Expr\OrderBy; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\Mapping\ClassMetadata; - -/** - * Utility functions for working with Doctrine ORM query. - * - * @author Teoh Han Hui - * @author Vincent Chalamon - * - * @internal - * - * @deprecated - */ -final class QueryJoinParser -{ - private function __construct() - { - } - - /** - * Gets the class metadata from a given join alias. - * - * @deprecated - */ - public static function getClassMetadataFromJoinAlias(string $alias, QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): ClassMetadata - { - @trigger_error(sprintf('The use of "%s::getClassMetadataFromJoinAlias()" is deprecated since 2.4 and will be removed in 3.0. Use "%s::getEntityClassByAlias()" instead.', __CLASS__, QueryBuilderHelper::class), \E_USER_DEPRECATED); - - $entityClass = QueryBuilderHelper::getEntityClassByAlias($alias, $queryBuilder, $managerRegistry); - - return $managerRegistry - ->getManagerForClass($entityClass) - ->getClassMetadata($entityClass); - } - - /** - * Gets the relationship from a Join expression. - * - * @deprecated - */ - public static function getJoinRelationship(Join $join): string - { - @trigger_error(sprintf('The use of "%s::getJoinRelationship()" is deprecated since 2.3 and will be removed in 3.0. Use "%s::getJoin()" directly instead.', __CLASS__, Join::class), \E_USER_DEPRECATED); - - return $join->getJoin(); - } - - /** - * Gets the alias from a Join expression. - * - * @deprecated - */ - public static function getJoinAlias(Join $join): string - { - @trigger_error(sprintf('The use of "%s::getJoinAlias()" is deprecated since 2.3 and will be removed in 3.0. Use "%s::getAlias()" directly instead.', __CLASS__, Join::class), \E_USER_DEPRECATED); - - return $join->getAlias(); - } - - /** - * Gets the parts from an OrderBy expression. - * - * @return string[] - * - * @deprecated - */ - public static function getOrderByParts(OrderBy $orderBy): array - { - @trigger_error(sprintf('The use of "%s::getOrderByParts()" is deprecated since 2.3 and will be removed in 3.0. Use "%s::getParts()" directly instead.', __CLASS__, OrderBy::class), \E_USER_DEPRECATED); - - return $orderBy->getParts(); - } -} diff --git a/src/Core/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php b/src/Core/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php deleted file mode 100644 index 9939dc4f7cb..00000000000 --- a/src/Core/Bridge/Doctrine/Orm/Util/QueryNameGenerator.php +++ /dev/null @@ -1,22 +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\Doctrine\Orm\Util; - -class_exists(\ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator::class); - -if (false) { - final class QueryNameGenerator extends \ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Api/IdentifierExtractor.php b/src/Core/Bridge/Elasticsearch/Api/IdentifierExtractor.php deleted file mode 100644 index 137709bd584..00000000000 --- a/src/Core/Bridge/Elasticsearch/Api/IdentifierExtractor.php +++ /dev/null @@ -1,52 +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\Elasticsearch\Api; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException; - -/** - * {@inheritdoc} - * - * @experimental - * - * @author Baptiste Meyer - */ -final class IdentifierExtractor implements IdentifierExtractorInterface -{ - private $identifiersExtractor; - - public function __construct(IdentifiersExtractorInterface $identifiersExtractor) - { - $this->identifiersExtractor = $identifiersExtractor; - } - - /** - * {@inheritdoc} - */ - public function getIdentifierFromResourceClass(string $resourceClass): string - { - $identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass); - - if (0 >= $totalIdentifiers = \count($identifiers)) { - throw new NonUniqueIdentifierException(sprintf('Resource "%s" has no identifiers.', $resourceClass)); - } - - if (1 < $totalIdentifiers) { - throw new NonUniqueIdentifierException('Composite identifiers not supported.'); - } - - return reset($identifiers); - } -} diff --git a/src/Core/Bridge/Elasticsearch/Api/IdentifierExtractorInterface.php b/src/Core/Bridge/Elasticsearch/Api/IdentifierExtractorInterface.php deleted file mode 100644 index da0dd931fb9..00000000000 --- a/src/Core/Bridge/Elasticsearch/Api/IdentifierExtractorInterface.php +++ /dev/null @@ -1,33 +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\Elasticsearch\Api; - -use ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException; - -/** - * Extracts identifier for a given resource. - * - * @experimental - * - * @author Baptiste Meyer - */ -interface IdentifierExtractorInterface -{ - /** - * Finds identifier from a resource class. - * - * @throws NonUniqueIdentifierException - */ - public function getIdentifierFromResourceClass(string $resourceClass): string; -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/CollectionDataProvider.php b/src/Core/Bridge/Elasticsearch/DataProvider/CollectionDataProvider.php deleted file mode 100644 index 0cd3dc27ef1..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/CollectionDataProvider.php +++ /dev/null @@ -1,159 +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\Elasticsearch\DataProvider; - -use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface; -use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension\RequestBodySearchCollectionExtensionInterface as LegacyRequestBodySearchCollectionExtensionInterface; -use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\Pagination as LegacyPagination; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; -use ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException; -use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface; -use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; -use ApiPlatform\Elasticsearch\Util\ElasticsearchVersion; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\State\Pagination\Pagination; -use Elasticsearch\Client; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -/** - * Collection data provider for Elasticsearch. - * - * @experimental - * - * @author Baptiste Meyer - */ -final class CollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface -{ - private $client; - private $documentMetadataFactory; - private $identifierExtractor; - private $denormalizer; - private $pagination; - private $resourceMetadataFactory; - - /** - * @param LegacyRequestBodySearchCollectionExtensionInterface[]|RequestBodySearchCollectionExtensionInterface[] $collectionExtensions - * @param ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory - * @param Pagination|LegacyPagination $pagination - */ - public function __construct(Client $client, DocumentMetadataFactoryInterface $documentMetadataFactory, IdentifierExtractorInterface $identifierExtractor = null, DenormalizerInterface $denormalizer, $pagination, $resourceMetadataFactory, iterable $collectionExtensions = []) - { - $this->client = $client; - $this->documentMetadataFactory = $documentMetadataFactory; - - if ($this->identifierExtractor) { - trigger_deprecation('api-platform', '2.7', sprintf('Passing an instance of "%s" is deprecated and will not be supported in 3.0.', IdentifierExtractorInterface::class)); - } - - $this->identifierExtractor = $identifierExtractor; - $this->denormalizer = $denormalizer; - $this->pagination = $pagination; - - 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; - } - - /** - * {@inheritdoc} - */ - public function supports(string $resourceClass, ?string $operationName = null, array $context = []): bool - { - try { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (false === $resourceMetadata->getCollectionOperationAttribute($operationName, 'elasticsearch', true, true)) { - return false; - } - } catch (ResourceClassNotFoundException $e) { - return false; - } - - try { - $this->documentMetadataFactory->create($resourceClass); - } catch (IndexNotFoundException $e) { - return false; - } - - if ($this->identifierExtractor) { - try { - $this->identifierExtractor->getIdentifierFromResourceClass($resourceClass); - } catch (NonUniqueIdentifierException $e) { - return false; - } - } else { - $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($operationName); - - if (\count($operation->getIdentifiers()) > 1) { - return false; - } - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function getCollection(string $resourceClass, ?string $operationName = null, array $context = []): iterable - { - $documentMetadata = $this->documentMetadataFactory->create($resourceClass); - $body = []; - - foreach ($this->collectionExtensions as $collectionExtension) { - if ($collectionExtension instanceof LegacyRequestBodySearchCollectionExtensionInterface) { - $body = $collectionExtension->applyToCollection($body, $resourceClass, $operationName, $context); - } - - if ($collectionExtension instanceof RequestBodySearchCollectionExtensionInterface) { - $body = $collectionExtension->applyToCollection($body, $resourceClass, $context['operation'] ?? null, $context); - } - } - - if (!isset($body['query']) && !isset($body['aggs'])) { - $body['query'] = ['match_all' => new \stdClass()]; - } - - $limit = $body['size'] = $body['size'] ?? $this->pagination->getLimit($resourceClass, $operationName, $context); - $offset = $body['from'] = $body['from'] ?? $this->pagination->getOffset($resourceClass, $operationName, $context); - - $params = [ - 'index' => $documentMetadata->getIndex(), - 'body' => $body, - ]; - - if (ElasticsearchVersion::supportsMappingType()) { - $params['type'] = $documentMetadata->getType(); - } - - $documents = $this->client->search($params); - - return new Paginator( - $this->denormalizer, - $documents, - $resourceClass, - $limit, - $offset, - $context - ); - } - - private $collectionExtensions; -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php deleted file mode 100644 index 4db73e50a48..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/AbstractFilterExtension.php +++ /dev/null @@ -1,22 +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\Elasticsearch\DataProvider\Extension; - -class_exists(\ApiPlatform\Elasticsearch\Extension\AbstractFilterExtension::class); - -if (false) { - class AbstractFilterExtension extends \ApiPlatform\Elasticsearch\Extension\AbstractFilterExtension - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php deleted file mode 100644 index 34c17757c37..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/ConstantScoreFilterExtension.php +++ /dev/null @@ -1,22 +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\Elasticsearch\DataProvider\Extension; - -class_exists(\ApiPlatform\Elasticsearch\Extension\ConstantScoreFilterExtension::class); - -if (false) { - final class ConstantScoreFilterExtension extends \ApiPlatform\Elasticsearch\Extension\ConstantScoreFilterExtension - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/RequestBodySearchCollectionExtensionInterface.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/RequestBodySearchCollectionExtensionInterface.php deleted file mode 100644 index a2d73cf4f21..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/RequestBodySearchCollectionExtensionInterface.php +++ /dev/null @@ -1,28 +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\Elasticsearch\DataProvider\Extension; - -/** - * Interface of Elasticsearch request body search extensions for collection queries. - * - * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html - * - * @experimental - * - * @author Baptiste Meyer - */ -interface RequestBodySearchCollectionExtensionInterface -{ - public function applyToCollection(array $requestBody, string $resourceClass, ?string $operationName = null, array $context = []): array; -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php deleted file mode 100644 index efaac85fe8f..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortExtension.php +++ /dev/null @@ -1,22 +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\Elasticsearch\DataProvider\Extension; - -class_exists(\ApiPlatform\Elasticsearch\Extension\SortExtension::class); - -if (false) { - final class SortExtension extends \ApiPlatform\Elasticsearch\Extension\SortExtension - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php b/src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php deleted file mode 100644 index 41d41d46eee..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Extension/SortFilterExtension.php +++ /dev/null @@ -1,22 +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\Elasticsearch\DataProvider\Extension; - -class_exists(\ApiPlatform\Elasticsearch\Extension\SortFilterExtension::class); - -if (false) { - final class SortFilterExtension extends \ApiPlatform\Elasticsearch\Extension\SortFilterExtension - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php deleted file mode 100644 index 4be367ca6e0..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php +++ /dev/null @@ -1,145 +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\Elasticsearch\DataProvider\Filter; - -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Bridge\Elasticsearch\Util\FieldDatatypeTrait; -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Exception\ResourceClassNotFoundException; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Abstract class with helpers for easing the implementation of a filter. - * - * @experimental - * - * @author Baptiste Meyer - */ -abstract class AbstractFilter implements FilterInterface -{ - use FieldDatatypeTrait { getNestedFieldPath as protected; } - - protected $properties; - protected $propertyNameCollectionFactory; - protected $nameConverter; - - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, ?NameConverterInterface $nameConverter = null, ?array $properties = null) - { - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->resourceClassResolver = $resourceClassResolver; - $this->nameConverter = $nameConverter; - $this->properties = $properties; - } - - /** - * Gets all enabled properties for the given resource class. - */ - protected function getProperties(string $resourceClass): \Traversable - { - if (null !== $this->properties) { - return yield from array_keys($this->properties); - } - - try { - yield from $this->propertyNameCollectionFactory->create($resourceClass); - } catch (ResourceClassNotFoundException $e) { - } - } - - /** - * Is the given property enabled? - */ - protected function hasProperty(string $resourceClass, string $property): bool - { - return \in_array($property, iterator_to_array($this->getProperties($resourceClass)), true); - } - - /** - * Gets info about the decomposed given property for the given resource class. - * - * Returns an array with the following info as values: - * - the {@see Type} of the decomposed given property - * - is the decomposed given property an association? - * - the resource class of the decomposed given property - * - the property name of the decomposed given property - */ - protected function getMetadata(string $resourceClass, string $property): array - { - $noop = [null, null, null, null]; - - if (!$this->hasProperty($resourceClass, $property)) { - return $noop; - } - - $properties = explode('.', $property); - $totalProperties = \count($properties); - $currentResourceClass = $resourceClass; - $hasAssociation = false; - $currentProperty = null; - $type = null; - - foreach ($properties as $index => $currentProperty) { - try { - $propertyMetadata = $this->propertyMetadataFactory->create($currentResourceClass, $currentProperty); - } catch (PropertyNotFoundException $e) { - return $noop; - } - - if (null === $type = $propertyMetadata->getType()) { - return $noop; - } - - ++$index; - $builtinType = $type->getBuiltinType(); - - if (Type::BUILTIN_TYPE_OBJECT !== $builtinType && Type::BUILTIN_TYPE_ARRAY !== $builtinType) { - if ($totalProperties === $index) { - break; - } - - return $noop; - } - - if ($type->isCollection() && null === $type = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType()) { - return $noop; - } - - if (Type::BUILTIN_TYPE_ARRAY === $builtinType && Type::BUILTIN_TYPE_OBJECT !== $type->getBuiltinType()) { - if ($totalProperties === $index) { - break; - } - - return $noop; - } - - if (null === $className = $type->getClassName()) { - return $noop; - } - - if ($isResourceClass = $this->resourceClassResolver->isResourceClass($className)) { - $currentResourceClass = $className; - } elseif ($totalProperties !== $index) { - return $noop; - } - - $hasAssociation = $totalProperties === $index && $isResourceClass; - } - - return [$type, $hasAssociation, $currentResourceClass, $currentProperty]; - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php deleted file mode 100644 index 4b066ac40cd..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php +++ /dev/null @@ -1,187 +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\Elasticsearch\DataProvider\Filter; - -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Abstract class with helpers for easing the implementation of a search filter like a term filter or a match filter. - * - * @experimental - * - * @internal - * - * @author Baptiste Meyer - */ -abstract class AbstractSearchFilter extends AbstractFilter implements ConstantScoreFilterInterface -{ - protected $identifierExtractor; - protected $iriConverter; - protected $propertyAccessor; - - /** - * {@inheritdoc} - */ - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, IdentifierExtractorInterface $identifierExtractor, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor, ?NameConverterInterface $nameConverter = null, ?array $properties = null) - { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceClassResolver, $nameConverter, $properties); - - $this->identifierExtractor = $identifierExtractor; - $this->iriConverter = $iriConverter; - $this->propertyAccessor = $propertyAccessor; - } - - /** - * {@inheritdoc} - */ - public function apply(array $clauseBody, string $resourceClass, ?string $operationName = null, array $context = []): array - { - $searches = []; - - foreach ($context['filters'] ?? [] as $property => $values) { - [$type, $hasAssociation, $nestedResourceClass, $nestedProperty] = $this->getMetadata($resourceClass, $property); - - if (!$type || !$values = (array) $values) { - continue; - } - - if ($hasAssociation || $this->isIdentifier($nestedResourceClass, $nestedProperty)) { - $values = array_map([$this, 'getIdentifierValue'], $values, array_fill(0, \count($values), $nestedProperty)); - } - - if (!$this->hasValidValues($values, $type)) { - continue; - } - - $property = null === $this->nameConverter ? $property : $this->nameConverter->normalize($property, $resourceClass, null, $context); - $nestedPath = $this->getNestedFieldPath($resourceClass, $property); // @phpstan-ignore-line - $nestedPath = null === $nestedPath || null === $this->nameConverter ? $nestedPath : $this->nameConverter->normalize($nestedPath, $resourceClass, null, $context); - - $searches[] = $this->getQuery($property, $values, $nestedPath); - } - - if (!$searches) { - return $clauseBody; - } - - return array_merge_recursive($clauseBody, [ - 'bool' => [ - 'must' => $searches, - ], - ]); - } - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - foreach ($this->getProperties($resourceClass) as $property) { - [$type, $hasAssociation] = $this->getMetadata($resourceClass, $property); - - if (!$type) { - continue; - } - - foreach ([$property, "${property}[]"] as $filterParameterName) { - $description[$filterParameterName] = [ - 'property' => $property, - 'type' => $hasAssociation ? 'string' : $this->getPhpType($type), - 'required' => false, - ]; - } - } - - return $description; - } - - /** - * Gets the Elasticsearch query corresponding to the current search filter. - */ - abstract protected function getQuery(string $property, array $values, ?string $nestedPath): array; - - /** - * Converts the given {@see Type} in PHP type. - */ - protected function getPhpType(Type $type): string - { - switch ($builtinType = $type->getBuiltinType()) { - case Type::BUILTIN_TYPE_ARRAY: - case Type::BUILTIN_TYPE_INT: - case Type::BUILTIN_TYPE_FLOAT: - case Type::BUILTIN_TYPE_BOOL: - case Type::BUILTIN_TYPE_STRING: - return $builtinType; - case Type::BUILTIN_TYPE_OBJECT: - if (null !== ($className = $type->getClassName()) && is_a($className, \DateTimeInterface::class, true)) { - return \DateTimeInterface::class; - } - - // no break - default: - return 'string'; - } - } - - /** - * Is the given property of the given resource class an identifier? - */ - protected function isIdentifier(string $resourceClass, string $property): bool - { - return $property === $this->identifierExtractor->getIdentifierFromResourceClass($resourceClass); - } - - /** - * Gets the ID from an IRI or a raw ID. - */ - protected function getIdentifierValue(string $iri, string $property) - { - try { - if ($item = $this->iriConverter->getItemFromIri($iri, ['fetch_data' => false])) { - return $this->propertyAccessor->getValue($item, $property); - } - } catch (InvalidArgumentException $e) { - } - - return $iri; - } - - /** - * Are the given values valid according to the given {@see Type}? - */ - protected function hasValidValues(array $values, Type $type): bool - { - foreach ($values as $value) { - if ( - null !== $value - && Type::BUILTIN_TYPE_INT === $type->getBuiltinType() - && false === filter_var($value, \FILTER_VALIDATE_INT) - ) { - return false; - } - } - - return true; - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/ConstantScoreFilterInterface.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/ConstantScoreFilterInterface.php deleted file mode 100644 index 99dd2a1fa8d..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/ConstantScoreFilterInterface.php +++ /dev/null @@ -1,25 +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\Elasticsearch\DataProvider\Filter; - -/** - * Elasticsearch filter interface for a constant score query. - * - * @experimental - * - * @author Baptiste Meyer - */ -interface ConstantScoreFilterInterface extends FilterInterface -{ -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php deleted file mode 100644 index 67b2d40ccf9..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php +++ /dev/null @@ -1,28 +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\Elasticsearch\DataProvider\Filter; - -use ApiPlatform\Core\Api\FilterInterface as BaseFilterInterface; - -/** - * Elasticsearch filter interface. - * - * @experimental - * - * @author Baptiste Meyer - */ -interface FilterInterface extends BaseFilterInterface -{ - public function apply(array $clauseBody, string $resourceClass, ?string $operationName = null, array $context = []): array; -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/MatchFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/MatchFilter.php deleted file mode 100644 index c3d9f9a82d1..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/MatchFilter.php +++ /dev/null @@ -1,46 +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\Elasticsearch\DataProvider\Filter; - -/** - * Filter the collection by given properties using a full text query. - * - * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html - * - * @experimental - * - * @author Baptiste Meyer - */ -final class MatchFilter extends AbstractSearchFilter -{ - /** - * {@inheritdoc} - */ - protected function getQuery(string $property, array $values, ?string $nestedPath): array - { - $matches = []; - - foreach ($values as $value) { - $matches[] = ['match' => [$property => $value]]; - } - - $matchQuery = isset($matches[1]) ? ['bool' => ['should' => $matches]] : $matches[0]; - - if (null !== $nestedPath) { - $matchQuery = ['nested' => ['path' => $nestedPath, 'query' => $matchQuery]]; - } - - return $matchQuery; - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php deleted file mode 100644 index 16f359796a2..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php +++ /dev/null @@ -1,111 +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\Elasticsearch\DataProvider\Filter; - -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Order the collection by given properties. - * - * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html - * - * @experimental - * - * @author Baptiste Meyer - */ -final class OrderFilter extends AbstractFilter implements SortFilterInterface -{ - private $orderParameterName; - - /** - * {@inheritdoc} - */ - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, ?NameConverterInterface $nameConverter = null, string $orderParameterName = 'order', ?array $properties = null) - { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceClassResolver, $nameConverter, $properties); - - $this->orderParameterName = $orderParameterName; - } - - /** - * {@inheritdoc} - */ - public function apply(array $clauseBody, string $resourceClass, ?string $operationName = null, array $context = []): array - { - if (!\is_array($properties = $context['filters'][$this->orderParameterName] ?? [])) { - return $clauseBody; - } - - $orders = []; - - foreach ($properties as $property => $direction) { - [$type] = $this->getMetadata($resourceClass, $property); - - if (!$type) { - continue; - } - - if (empty($direction) && null !== $defaultDirection = $this->properties[$property] ?? null) { - $direction = $defaultDirection; - } - - if (!\in_array($direction = strtolower($direction), ['asc', 'desc'], true)) { - continue; - } - - $order = ['order' => $direction]; - - if (null !== $nestedPath = $this->getNestedFieldPath($resourceClass, $property)) { // @phpstan-ignore-line - $nestedPath = null === $this->nameConverter ? $nestedPath : $this->nameConverter->normalize($nestedPath, $resourceClass, null, $context); - $order['nested'] = ['path' => $nestedPath]; - } - - $property = null === $this->nameConverter ? $property : $this->nameConverter->normalize($property, $resourceClass, null, $context); - $orders[] = [$property => $order]; - } - - if (!$orders) { - return $clauseBody; - } - - return array_merge_recursive($clauseBody, $orders); - } - - /** - * {@inheritdoc} - */ - public function getDescription(string $resourceClass): array - { - $description = []; - - foreach ($this->getProperties($resourceClass) as $property) { - [$type] = $this->getMetadata($resourceClass, $property); - - if (!$type) { - continue; - } - - $description["$this->orderParameterName[$property]"] = [ - 'property' => $property, - 'type' => 'string', - 'required' => false, - ]; - } - - return $description; - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/SortFilterInterface.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/SortFilterInterface.php deleted file mode 100644 index 151fe1379f4..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/SortFilterInterface.php +++ /dev/null @@ -1,25 +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\Elasticsearch\DataProvider\Filter; - -/** - * Elasticsearch filter interface for sorting. - * - * @experimental - * - * @author Baptiste Meyer - */ -interface SortFilterInterface extends FilterInterface -{ -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/TermFilter.php b/src/Core/Bridge/Elasticsearch/DataProvider/Filter/TermFilter.php deleted file mode 100644 index 3afc8e357dd..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Filter/TermFilter.php +++ /dev/null @@ -1,45 +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\Elasticsearch\DataProvider\Filter; - -/** - * Filter the collection by given properties using a term level query. - * - * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html - * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html - * - * @experimental - * - * @author Baptiste Meyer - */ -final class TermFilter extends AbstractSearchFilter -{ - /** - * {@inheritdoc} - */ - protected function getQuery(string $property, array $values, ?string $nestedPath): array - { - if (1 === \count($values)) { - $termQuery = ['term' => [$property => reset($values)]]; - } else { - $termQuery = ['terms' => [$property => $values]]; - } - - if (null !== $nestedPath) { - $termQuery = ['nested' => ['path' => $nestedPath, 'query' => $termQuery]]; - } - - return $termQuery; - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/ItemDataProvider.php b/src/Core/Bridge/Elasticsearch/DataProvider/ItemDataProvider.php deleted file mode 100644 index 1a47a480720..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/ItemDataProvider.php +++ /dev/null @@ -1,117 +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\Elasticsearch\DataProvider; - -use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; -use ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException; -use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; -use ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer; -use ApiPlatform\Elasticsearch\Util\ElasticsearchVersion; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use Elasticsearch\Client; -use Elasticsearch\Common\Exceptions\Missing404Exception; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -/** - * Item data provider for Elasticsearch. - * - * @experimental - * - * @author Baptiste Meyer - */ -final class ItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface -{ - private $client; - private $documentMetadataFactory; - private $identifierExtractor; - private $denormalizer; - private $resourceMetadataFactory; - - public function __construct(Client $client, DocumentMetadataFactoryInterface $documentMetadataFactory, IdentifierExtractorInterface $identifierExtractor, DenormalizerInterface $denormalizer, ResourceMetadataFactoryInterface $resourceMetadataFactory) - { - $this->client = $client; - $this->documentMetadataFactory = $documentMetadataFactory; - $this->identifierExtractor = $identifierExtractor; - $this->denormalizer = $denormalizer; - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - /** - * {@inheritdoc} - */ - public function supports(string $resourceClass, ?string $operationName = null, array $context = []): bool - { - try { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (false === $resourceMetadata->getItemOperationAttribute($operationName, 'elasticsearch', true, true)) { - return false; - } - } catch (ResourceClassNotFoundException $e) { - return false; - } - - try { - $this->documentMetadataFactory->create($resourceClass); - } catch (IndexNotFoundException $e) { - return false; - } - - try { - $this->identifierExtractor->getIdentifierFromResourceClass($resourceClass); - } catch (NonUniqueIdentifierException $e) { - return false; - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function getItem(string $resourceClass, $id, ?string $operationName = null, array $context = []) - { - if (\is_array($id)) { - $id = $id[$this->identifierExtractor->getIdentifierFromResourceClass($resourceClass)]; - } - - $documentMetadata = $this->documentMetadataFactory->create($resourceClass); - - try { - $params = [ - 'index' => $documentMetadata->getIndex(), - 'id' => (string) $id, - ]; - - if (ElasticsearchVersion::supportsMappingType()) { - $params['type'] = $documentMetadata->getType(); - } - - $document = $this->client->get($params); - } catch (Missing404Exception $e) { - return null; - } - - $item = $this->denormalizer->denormalize($document, $resourceClass, DocumentNormalizer::FORMAT, [AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => true]); - if (!\is_object($item) && null !== $item) { - throw new \UnexpectedValueException('Expected item to be an object or null.'); - } - - return $item; - } -} diff --git a/src/Core/Bridge/Elasticsearch/DataProvider/Paginator.php b/src/Core/Bridge/Elasticsearch/DataProvider/Paginator.php deleted file mode 100644 index 4e0fb589221..00000000000 --- a/src/Core/Bridge/Elasticsearch/DataProvider/Paginator.php +++ /dev/null @@ -1,22 +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\Elasticsearch\DataProvider; - -class_exists(\ApiPlatform\Elasticsearch\Paginator::class); - -if (false) { - final class Paginator extends \ApiPlatform\Elasticsearch\Paginator - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Exception/IndexNotFoundException.php b/src/Core/Bridge/Elasticsearch/Exception/IndexNotFoundException.php deleted file mode 100644 index e0763c4c81a..00000000000 --- a/src/Core/Bridge/Elasticsearch/Exception/IndexNotFoundException.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Exception; - -class_exists(\ApiPlatform\Elasticsearch\Exception\IndexNotFoundException::class); - -if (false) { - final class IndexNotFoundException extends \ApiPlatform\Elasticsearch\Exception\IndexNotFoundException - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Exception/NonUniqueIdentifierException.php b/src/Core/Bridge/Elasticsearch/Exception/NonUniqueIdentifierException.php deleted file mode 100644 index 9ea219a8237..00000000000 --- a/src/Core/Bridge/Elasticsearch/Exception/NonUniqueIdentifierException.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Exception; - -class_exists(\ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException::class); - -if (false) { - final class NonUniqueIdentifierException extends \ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Metadata/Document/DocumentMetadata.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/DocumentMetadata.php deleted file mode 100644 index ec74aea8ca2..00000000000 --- a/src/Core/Bridge/Elasticsearch/Metadata/Document/DocumentMetadata.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Metadata\Document; - -class_exists(\ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata::class); - -if (false) { - final class DocumentMetadata extends \ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php deleted file mode 100644 index 57e6ab8689c..00000000000 --- a/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Metadata\Document\Factory; - -class_exists(\ApiPlatform\Elasticsearch\Metadata\Document\Factory\AttributeDocumentMetadataFactory::class); - -if (false) { - final class AttributeDocumentMetadataFactory extends \ApiPlatform\Elasticsearch\Metadata\Document\Factory\AttributeDocumentMetadataFactory - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php deleted file mode 100644 index 904c2b63bc8..00000000000 --- a/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Metadata\Document\Factory; - -class_exists(\ApiPlatform\Elasticsearch\Metadata\Document\Factory\CachedDocumentMetadataFactory::class); - -if (false) { - final class CachedDocumentMetadataFactory extends \ApiPlatform\Elasticsearch\Metadata\Document\Factory\CachedDocumentMetadataFactory - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php deleted file mode 100644 index 09c9e6500aa..00000000000 --- a/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Metadata\Document\Factory; - -class_exists(\ApiPlatform\Elasticsearch\Metadata\Document\Factory\CatDocumentMetadataFactory::class); - -if (false) { - final class CatDocumentMetadataFactory extends \ApiPlatform\Elasticsearch\Metadata\Document\Factory\CatDocumentMetadataFactory - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php deleted file mode 100644 index 2f126d745fa..00000000000 --- a/src/Core/Bridge/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Metadata\Document\Factory; - -class_exists(\ApiPlatform\Elasticsearch\Metadata\Document\Factory\ConfiguredDocumentMetadataFactory::class); - -if (false) { - final class ConfiguredDocumentMetadataFactory extends \ApiPlatform\Elasticsearch\Metadata\Document\Factory\ConfiguredDocumentMetadataFactory - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactory.php b/src/Core/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactory.php deleted file mode 100644 index d7cf855da08..00000000000 --- a/src/Core/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactory.php +++ /dev/null @@ -1,52 +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\Elasticsearch\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; - -/** - * Creates read-only operations for Elasticsearch. - * - * @experimental - * - * @author Baptiste Meyer - */ -final class ElasticsearchOperationResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - private $decorated; - - public function __construct(ResourceMetadataFactoryInterface $decorated) - { - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $resourceMetadata = $this->decorated->create($resourceClass); - - if (null === $resourceMetadata->getCollectionOperations()) { - $resourceMetadata = $resourceMetadata->withCollectionOperations(['get' => ['method' => 'GET']]); - } - - if (null === $resourceMetadata->getItemOperations()) { - $resourceMetadata = $resourceMetadata->withItemOperations(['get' => ['method' => 'GET']]); - } - - return $resourceMetadata; - } -} diff --git a/src/Core/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php b/src/Core/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php deleted file mode 100644 index eb59f592f40..00000000000 --- a/src/Core/Bridge/Elasticsearch/Serializer/DocumentNormalizer.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Serializer; - -class_exists(\ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer::class); - -if (false) { - final class DocumentNormalizer extends \ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Serializer/ItemNormalizer.php b/src/Core/Bridge/Elasticsearch/Serializer/ItemNormalizer.php deleted file mode 100644 index d263305ffe6..00000000000 --- a/src/Core/Bridge/Elasticsearch/Serializer/ItemNormalizer.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Serializer; - -class_exists(\ApiPlatform\Elasticsearch\Serializer\ItemNormalizer::class); - -if (false) { - final class ItemNormalizer extends \ApiPlatform\Elasticsearch\Serializer\ItemNormalizer - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php b/src/Core/Bridge/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php deleted file mode 100644 index ee7a3d14880..00000000000 --- a/src/Core/Bridge/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php +++ /dev/null @@ -1,22 +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\Elasticsearch\Serializer\NameConverter; - -class_exists(\ApiPlatform\Elasticsearch\Serializer\NameConverter\InnerFieldsNameConverter::class); - -if (false) { - final class InnerFieldsNameConverter extends \ApiPlatform\Elasticsearch\Serializer\NameConverter\InnerFieldsNameConverter - { - } -} diff --git a/src/Core/Bridge/Elasticsearch/Util/FieldDatatypeTrait.php b/src/Core/Bridge/Elasticsearch/Util/FieldDatatypeTrait.php deleted file mode 100644 index 71dd32bd3fb..00000000000 --- a/src/Core/Bridge/Elasticsearch/Util/FieldDatatypeTrait.php +++ /dev/null @@ -1,23 +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\Elasticsearch\Util; - -class_exists(\ApiPlatform\Elasticsearch\Util\FieldDatatypeTrait::class); - -if (false) { - trait FieldDatatypeTrait - { - use \ApiPlatform\Elasticsearch\Util\FieldDatatypeTrait; - } -} diff --git a/src/Core/Bridge/FosUser/EventListener.php b/src/Core/Bridge/FosUser/EventListener.php deleted file mode 100644 index 41b77dd78a4..00000000000 --- a/src/Core/Bridge/FosUser/EventListener.php +++ /dev/null @@ -1,58 +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\FosUser; - -use ApiPlatform\Util\RequestAttributesExtractor; -use FOS\UserBundle\Model\UserInterface; -use FOS\UserBundle\Model\UserManagerInterface; -use Symfony\Component\HttpKernel\Event\ViewEvent; - -/** - * Bridges between FOSUserBundle and API Platform Core. - * - * @author Kévin Dunglas - * @author Théo FIDRY - */ -final class EventListener -{ - private $userManager; - - public function __construct(UserManagerInterface $userManager) - { - $this->userManager = $userManager; - } - - /** - * Persists, updates or delete data return by the controller if applicable. - */ - public function onKernelView(ViewEvent $event): void - { - $request = $event->getRequest(); - if (!RequestAttributesExtractor::extractAttributes($request)) { - return; - } - - $user = $event->getControllerResult(); - if (!$user instanceof UserInterface || $request->isMethodSafe()) { - return; - } - - if ('DELETE' === $request->getMethod()) { - $this->userManager->deleteUser($user); - $event->setControllerResult(null); - } else { - $this->userManager->updateUser($user); - } - } -} diff --git a/src/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php b/src/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php deleted file mode 100644 index 7fec3d40f61..00000000000 --- a/src/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php +++ /dev/null @@ -1,215 +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\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Documentation\Documentation; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -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 deleted file mode 100644 index fc591363d64..00000000000 --- a/src/Core/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php +++ /dev/null @@ -1,280 +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\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 ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Metadata\ApiProperty; -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 - * @param ApiProperty|PropertyMetadata $propertyMetadata - */ - private function parseProperty(ResourceMetadata $resourceMetadata, $propertyMetadata, $io, Type $type = null, array $visited = []): array - { - $data = [ - 'dataType' => null, - 'required' => $propertyMetadata->isRequired(), - 'description' => $propertyMetadata->getDescription(), - 'readonly' => !$propertyMetadata->isWritable(), - ]; - - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : $propertyMetadata->getBuiltinTypes()[0] ?? null; - if (null === $type && null === $type) { - // 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/Core/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php b/src/Core/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php deleted file mode 100644 index 10c0743f0c3..00000000000 --- a/src/Core/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php +++ /dev/null @@ -1,56 +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\RamseyUuid\Identifier\Normalizer; - -use ApiPlatform\Exception\InvalidIdentifierException; -use Ramsey\Uuid\Exception\InvalidUuidStringException; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -/** - * Denormalizes an UUID string to an instance of Ramsey\Uuid. - * - * @author Antoine Bluchet - */ -final class UuidNormalizer implements DenormalizerInterface -{ - /** - * {@inheritdoc} - * - * @param mixed $data - * @param string $class - * @param null $format - * - * @throws InvalidIdentifierException - * - * @return mixed - */ - public function denormalize($data, $class, $format = null, array $context = []) - { - try { - return Uuid::fromString($data); - } catch (InvalidUuidStringException $e) { - throw new InvalidIdentifierException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function supportsDenormalization($data, $type, $format = null): bool - { - return \is_string($data) && is_a($type, UuidInterface::class, true); - } -} diff --git a/src/Core/Bridge/RamseyUuid/Serializer/UuidDenormalizer.php b/src/Core/Bridge/RamseyUuid/Serializer/UuidDenormalizer.php deleted file mode 100644 index f9e8ece6a91..00000000000 --- a/src/Core/Bridge/RamseyUuid/Serializer/UuidDenormalizer.php +++ /dev/null @@ -1,22 +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\RamseyUuid\Serializer; - -class_exists(\ApiPlatform\RamseyUuid\Serializer\UuidDenormalizer::class); - -if (false) { - final class UuidDenormalizer extends \ApiPlatform\RamseyUuid\Serializer\UuidDenormalizer - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php b/src/Core/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php deleted file mode 100644 index d0865adb0b5..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php +++ /dev/null @@ -1,229 +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\Symfony\Bundle\Action; - -use ApiPlatform\Core\Api\FormatsProviderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Documentation\Documentation; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Symfony\Bundle\SwaggerUi\SwaggerUiAction as OpenApiSwaggerUiAction; -use ApiPlatform\Util\RequestAttributesExtractor; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Twig\Environment as TwigEnvironment; - -/** - * Displays the documentation. - * - * @deprecated please refer to ApiPlatform\Symfony\Bundle\SwaggerUi\SwaggerUiAction for further changes - * - * @author Kévin Dunglas - */ -final class SwaggerUiAction -{ - private $resourceNameCollectionFactory; - /** @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface */ - private $resourceMetadataFactory; - private $normalizer; - private $twig; - private $urlGenerator; - private $title; - private $description; - private $version; - private $showWebby; - private $formats; - private $oauthEnabled; - private $oauthClientId; - private $oauthClientSecret; - private $oauthType; - private $oauthFlow; - private $oauthTokenUrl; - private $oauthAuthorizationUrl; - private $oauthScopes; - private $oauthPkce; - private $formatsProvider; - private $swaggerUiEnabled; - private $reDocEnabled; - private $graphqlEnabled; - private $graphiQlEnabled; - private $graphQlPlaygroundEnabled; - private $swaggerVersions; - private $swaggerUiAction; - private $assetPackage; - private $swaggerUiExtraConfiguration; - - /** - * @param int[] $swaggerVersions - * @param mixed|null $assetPackage - * @param mixed $formats - * @param mixed $oauthEnabled - * @param mixed $oauthClientId - * @param mixed $oauthClientSecret - * @param mixed $oauthType - * @param mixed $oauthFlow - * @param mixed $oauthTokenUrl - * @param mixed $oauthAuthorizationUrl - * @param mixed $oauthScopes - * @param mixed $resourceMetadataFactory - */ - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, $resourceMetadataFactory, NormalizerInterface $normalizer, ?TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', $formats = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [], bool $showWebby = true, bool $swaggerUiEnabled = false, bool $reDocEnabled = false, bool $graphqlEnabled = false, bool $graphiQlEnabled = false, bool $graphQlPlaygroundEnabled = false, array $swaggerVersions = [2, 3], OpenApiSwaggerUiAction $swaggerUiAction = null, $assetPackage = null, array $swaggerUiExtraConfiguration = [], bool $oauthPkce = false) - { - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->normalizer = $normalizer; - $this->twig = $twig; - $this->urlGenerator = $urlGenerator; - $this->title = $title; - $this->showWebby = $showWebby; - $this->description = $description; - $this->version = $version; - $this->oauthEnabled = $oauthEnabled; - $this->oauthClientId = $oauthClientId; - $this->oauthClientSecret = $oauthClientSecret; - $this->oauthType = $oauthType; - $this->oauthFlow = $oauthFlow; - $this->oauthTokenUrl = $oauthTokenUrl; - $this->oauthAuthorizationUrl = $oauthAuthorizationUrl; - $this->oauthScopes = $oauthScopes; - $this->swaggerUiEnabled = $swaggerUiEnabled; - $this->reDocEnabled = $reDocEnabled; - $this->graphqlEnabled = $graphqlEnabled; - $this->graphiQlEnabled = $graphiQlEnabled; - $this->graphQlPlaygroundEnabled = $graphQlPlaygroundEnabled; - $this->swaggerVersions = $swaggerVersions; - $this->swaggerUiAction = $swaggerUiAction; - $this->swaggerUiExtraConfiguration = $swaggerUiExtraConfiguration; - $this->assetPackage = $assetPackage; - $this->oauthPkce = $oauthPkce; - - if (null === $this->twig) { - throw new \RuntimeException('The documentation cannot be displayed since the Twig bundle is not installed. Try running "composer require symfony/twig-bundle".'); - } - - if (null === $this->swaggerUiAction) { - @trigger_error(sprintf('The use of "%s" is deprecated since API Platform 2.6, use "%s" instead.', __CLASS__, OpenApiSwaggerUiAction::class), \E_USER_DEPRECATED); - } - - if (\is_array($formats)) { - $this->formats = $formats; - - return; - } - - @trigger_error(sprintf('Passing an array or an instance of "%s" as 5th parameter of the constructor of "%s" is deprecated since API Platform 2.5, pass an array instead', FormatsProviderInterface::class, __CLASS__), \E_USER_DEPRECATED); - $this->formatsProvider = $formats; - } - - public function __invoke(Request $request) - { - if ($this->swaggerUiAction) { - return $this->swaggerUiAction->__invoke($request); - } - - $attributes = RequestAttributesExtractor::extractAttributes($request); - - // BC check to be removed in 3.0 - if (null === $this->formatsProvider) { - $formats = $attributes ? $this - ->resourceMetadataFactory - ->create($attributes['resource_class']) - ->getOperationAttribute($attributes, 'output_formats', [], true) : $this->formats; - } else { - $formats = $this->formatsProvider->getFormatsFromAttributes($attributes); - } - - $documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version); - - return new Response($this->twig->render('@ApiPlatform/SwaggerUi/index.html.twig', $this->getContext($request, $documentation) + ['formats' => $formats])); - } - - /** - * Gets the base Twig context. - */ - private function getContext(Request $request, Documentation $documentation): array - { - $context = [ - 'title' => $this->title, - 'description' => $this->description, - 'showWebby' => $this->showWebby, - 'swaggerUiEnabled' => $this->swaggerUiEnabled, - 'reDocEnabled' => $this->reDocEnabled, - 'graphqlEnabled' => $this->graphqlEnabled, - 'graphiQlEnabled' => $this->graphiQlEnabled, - 'graphQlPlaygroundEnabled' => $this->graphQlPlaygroundEnabled, - 'assetPackage' => $this->assetPackage, - ]; - - $swaggerContext = ['spec_version' => $request->query->getInt('spec_version', $this->swaggerVersions[0] ?? 2)]; - if ('' !== $baseUrl = $request->getBaseUrl()) { - $swaggerContext['base_url'] = $baseUrl; - } - - $swaggerData = [ - 'url' => $this->urlGenerator->generate('api_doc', ['format' => 'json']), - 'spec' => $this->normalizer->normalize($documentation, 'json', $swaggerContext), - 'extraConfiguration' => $this->swaggerUiExtraConfiguration, - ]; - - $swaggerData['oauth'] = [ - 'enabled' => $this->oauthEnabled, - 'clientId' => $this->oauthClientId, - 'clientSecret' => $this->oauthClientSecret, - 'pkce' => $this->oauthPkce, - 'type' => $this->oauthType, - 'flow' => $this->oauthFlow, - 'tokenUrl' => $this->oauthTokenUrl, - 'authorizationUrl' => $this->oauthAuthorizationUrl, - 'scopes' => $this->oauthScopes, - ]; - - if ($request->isMethodSafe() && null !== $resourceClass = $request->attributes->get('_api_resource_class')) { - $swaggerData['id'] = $request->attributes->get('id'); - $swaggerData['queryParameters'] = $request->query->all(); - - $metadata = $this->resourceMetadataFactory->create($resourceClass); - $swaggerData['shortName'] = $metadata instanceof ResourceMetadata ? $metadata->getShortName() : $metadata[0]->getShortName(); - - if (null !== $collectionOperationName = $request->attributes->get('_api_collection_operation_name')) { - $swaggerData['operationId'] = sprintf('%s%sCollection', $collectionOperationName, ucfirst($swaggerData['shortName'])); - } elseif (null !== $itemOperationName = $request->attributes->get('_api_item_operation_name')) { - $swaggerData['operationId'] = sprintf('%s%sItem', $itemOperationName, ucfirst($swaggerData['shortName'])); - } elseif (null !== $subresourceOperationContext = $request->attributes->get('_api_subresource_context')) { - $swaggerData['operationId'] = $subresourceOperationContext['operationId']; - } - - [$swaggerData['path'], $swaggerData['method']] = $this->getPathAndMethod($swaggerData); - } - - return $context + ['swagger_data' => $swaggerData]; - } - - private function getPathAndMethod(array $swaggerData): array - { - foreach ($swaggerData['spec']['paths'] as $path => $operations) { - foreach ($operations as $method => $operation) { - if ($operation['operationId'] === $swaggerData['operationId']) { - return [$path, $method]; - } - } - } - - throw new RuntimeException(sprintf('The operation "%s" cannot be found in the Swagger specification.', $swaggerData['operationId'])); - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/ApiPlatformBundle.php b/src/Core/Bridge/Symfony/Bundle/ApiPlatformBundle.php deleted file mode 100644 index f766d5ca6ac..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/ApiPlatformBundle.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle; - -class_exists(\ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class); - -if (false) { - final class ApiPlatformBundle extends \ApiPlatform\Symfony\Bundle\ApiPlatformBundle - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php b/src/Core/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php deleted file mode 100644 index 8b8eedc7b3b..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\ArgumentResolver; - -class_exists(\ApiPlatform\Symfony\Bundle\ArgumentResolver\PayloadArgumentResolver::class); - -if (false) { - final class PayloadArgumentResolver extends \ApiPlatform\Symfony\Bundle\ArgumentResolver\PayloadArgumentResolver - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/src/Core/Bridge/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php deleted file mode 100644 index 5cc4c49cb6d..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\CacheWarmer; - -class_exists(\ApiPlatform\Symfony\Bundle\CacheWarmer\CachePoolClearerCacheWarmer::class); - -if (false) { - final class CachePoolClearerCacheWarmer extends \ApiPlatform\Symfony\Bundle\CacheWarmer\CachePoolClearerCacheWarmer - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php b/src/Core/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php deleted file mode 100644 index 3cbf77c8e19..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Command/GraphQlExportCommand.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\Command; - -class_exists(\ApiPlatform\Symfony\Bundle\Command\GraphQlExportCommand::class); - -if (false) { - class GraphQlExportCommand extends \ApiPlatform\Symfony\Bundle\Command\GraphQlExportCommand - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Command/OpenApiCommand.php b/src/Core/Bridge/Symfony/Bundle/Command/OpenApiCommand.php deleted file mode 100644 index d35c3c7216f..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Command/OpenApiCommand.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\Command; - -class_exists(\ApiPlatform\Symfony\Bundle\Command\OpenApiCommand::class); - -if (false) { - final class OpenApiCommand extends \ApiPlatform\Symfony\Bundle\Command\OpenApiCommand - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Command/SwaggerCommand.php b/src/Core/Bridge/Symfony/Bundle/Command/SwaggerCommand.php deleted file mode 100644 index 9666b5fa5b2..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Command/SwaggerCommand.php +++ /dev/null @@ -1,118 +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\Symfony\Bundle\Command; - -use ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer; -use ApiPlatform\Documentation\Documentation; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\OpenApi\Serializer\ApiGatewayNormalizer; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\InvalidOptionException; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Yaml\Yaml; - -/** - * Console command to dump Swagger API documentations. - * - * @author Amrouche Hamza - */ -final class SwaggerCommand extends Command -{ - protected static $defaultName = 'api:swagger:export'; - - private $normalizer; - private $resourceNameCollectionFactory; - private $apiTitle; - private $apiDescription; - private $apiVersion; - private $apiFormats; - private $swaggerVersions; - private $legacyMode; - - /** - * @param int[] $swaggerVersions - */ - public function __construct(NormalizerInterface $normalizer, ResourceNameCollectionFactoryInterface $resourceNameCollection, string $apiTitle, string $apiDescription, string $apiVersion, array $apiFormats = null, array $swaggerVersions = [2, 3], bool $legacyMode = false) - { - $this->normalizer = $normalizer; - $this->resourceNameCollectionFactory = $resourceNameCollection; - $this->apiTitle = $apiTitle; - $this->apiDescription = $apiDescription; - $this->apiVersion = $apiVersion; - $this->apiFormats = $apiFormats; - $this->swaggerVersions = $swaggerVersions; - $this->legacyMode = $legacyMode; - - if (null !== $apiFormats) { - @trigger_error(sprintf('Passing a 6th parameter to the constructor of "%s" is deprecated since API Platform 2.5', __CLASS__), \E_USER_DEPRECATED); - } - - parent::__construct(); - } - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this - ->setDescription('Dump the Swagger v2 documentation') - ->addOption('yaml', 'y', InputOption::VALUE_NONE, 'Dump the documentation in YAML') - ->addOption('spec-version', null, InputOption::VALUE_OPTIONAL, sprintf('OpenAPI version to use (%s)', implode(' or ', $this->swaggerVersions)), (string) ($this->swaggerVersions[0] ?? 2)) - ->addOption('output', 'o', InputOption::VALUE_OPTIONAL, 'Write output to file') - ->addOption('api-gateway', null, InputOption::VALUE_NONE, 'API Gateway compatibility'); - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - - /** @var string $version */ - $version = $input->getOption('spec-version'); - - if (!\in_array((int) $version, $this->swaggerVersions, true)) { - throw new InvalidOptionException(sprintf('This tool only supports versions %s of the OpenAPI specification ("%s" given).', implode(', ', $this->swaggerVersions), $version)); - } - - if (3 === (int) $version) { - @trigger_error('The command "api:swagger:export" is deprecated for the spec version 3 use "api:openapi:export".', \E_USER_DEPRECATED); - } - - if (!$this->legacyMode) { - @trigger_error('The command "api:swagger:export" is using pre-2.7 metadata, new metadata will not appear, use "api:openapi:export" instead.', \E_USER_DEPRECATED); - } - - $documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->apiTitle, $this->apiDescription, $this->apiVersion, $this->apiFormats); - $data = $this->normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['spec_version' => (int) $version, ApiGatewayNormalizer::API_GATEWAY => $input->getOption('api-gateway')]); - $content = $input->getOption('yaml') - ? Yaml::dump($data, 10, 2, Yaml::DUMP_OBJECT_AS_MAP | Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK) - : (json_encode($data, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES) ?: ''); - - if (!empty($filename = $input->getOption('output')) && \is_string($filename)) { - file_put_contents($filename, $content); - $io->success(sprintf('Data written to %s (specification version %s).', $filename, $version)); - } else { - $output->writeln($content); - } - - return 0; - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommand.php b/src/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommand.php deleted file mode 100644 index 6df0eacea29..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommand.php +++ /dev/null @@ -1,218 +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\Symfony\Bundle\Command; - -use ApiPlatform\Core\Annotation\ApiResource; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; -use ApiPlatform\Core\Upgrade\ColorConsoleDiffFormatter; -use ApiPlatform\Core\Upgrade\SubresourceTransformer; -use ApiPlatform\Core\Upgrade\UpgradeApiResourceVisitor; -use ApiPlatform\Core\Upgrade\UpgradeApiSubresourceVisitor; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use Doctrine\Common\Annotations\AnnotationReader; -use PhpParser\Lexer\Emulative; -use PhpParser\NodeTraverser; -use PhpParser\Parser\Php7; -use PhpParser\PrettyPrinter\Standard; -use SebastianBergmann\Diff\Differ; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; - -final class UpgradeApiResourceCommand extends Command -{ - protected static $defaultName = 'api:upgrade-resource'; - - private $resourceNameCollectionFactory; - private $resourceMetadataFactory; - private $subresourceOperationFactory; - private $subresourceTransformer; - private $reader; - private $identifiersExtractor; - - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubresourceOperationFactoryInterface $subresourceOperationFactory, SubresourceTransformer $subresourceTransformer, IdentifiersExtractorInterface $identifiersExtractor, AnnotationReader $reader = null) - { - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->subresourceOperationFactory = $subresourceOperationFactory; - $this->subresourceTransformer = $subresourceTransformer; - $this->identifiersExtractor = $identifiersExtractor; - $this->reader = $reader; - - parent::__construct(); - } - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this - ->setDescription('The "api:upgrade-resource" command upgrades your API Platform metadata from versions below 2.6 to the new metadata from versions above 2.7. -Once you executed this script, make sure that the "metadata_backward_compatibility_layer" flag is set to "false" in the API Platform configuration. -This will remove "ApiPlatform\Core\Annotation\ApiResource" annotation/attribute and use the "ApiPlatform\Metadata\ApiResource" attribute instead.') - ->addOption('dry-run', '-d', InputOption::VALUE_NEGATABLE, 'Dry mode outputs a diff instead of writing files.', true) - ->addOption('silent', '-s', InputOption::VALUE_NONE, 'Silent output.') - ->addOption('force', '-f', InputOption::VALUE_NONE, 'Writes the files in place and skips PHP version check.'); - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - if (!$input->getOption('force') && \PHP_VERSION_ID < 80100) { - $output->write('The new metadata system only works with PHP 8.1 and above.'); - - return Command::INVALID; - } - - $subresources = $this->getSubresources(); - - $prettyPrinter = new Standard(); - foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { - try { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - } catch (ResourceClassNotFoundException $e) { - continue; - } - - $lexer = new Emulative([ - 'usedAttributes' => [ - 'comments', - 'startLine', - 'endLine', - 'startTokenPos', - 'endTokenPos', - ], - ]); - $parser = new Php7($lexer); - $fileName = (new \ReflectionClass($resourceClass))->getFilename(); - - $traverser = new NodeTraverser(); - [$attribute, $isAnnotation] = $this->readApiResource($resourceClass); - - if (!$attribute) { - continue; - } - - $traverser->addVisitor(new UpgradeApiResourceVisitor($attribute, $isAnnotation, $this->identifiersExtractor, $resourceClass)); - - if (isset($subresources[$resourceClass])) { - $referenceType = $resourceMetadata->getAttribute('url_generation_strategy'); - foreach ($subresources[$resourceClass] as $subresourceMetadata) { - $traverser->addVisitor(new UpgradeApiSubresourceVisitor($subresourceMetadata, $referenceType)); - } - } - - $oldCode = file_get_contents($fileName); - $oldStmts = $parser->parse($oldCode); - $oldTokens = $lexer->getTokens(); - - $newStmts = $traverser->traverse($oldStmts); - $newCode = $prettyPrinter->printFormatPreserving($newStmts, $oldStmts, $oldTokens); - - if (!$input->getOption('force') && $input->getOption('dry-run')) { - if ($input->getOption('silent')) { - continue; - } - - $this->printDiff($oldCode, $newCode, $output); - continue; - } - - file_put_contents($fileName, $newCode); - } - - return Command::SUCCESS; - } - - /** - * This computes a local cache with resource classes having subresources. - * We first loop over all the classes and re-map the metadata on the correct Resource class. - * Then we transform the ApiSubresource to an ApiResource class. - */ - private function getSubresources(): array - { - $localCache = []; - foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { - try { - new \ReflectionClass($resourceClass); - } catch (\Exception $e) { - continue; - } - - if (!isset($localCache[$resourceClass])) { - $localCache[$resourceClass] = []; - } - - foreach ($this->subresourceOperationFactory->create($resourceClass) as $subresourceMetadata) { - if (!isset($localCache[$subresourceMetadata['resource_class']])) { - $localCache[$subresourceMetadata['resource_class']] = []; - } - - foreach ($localCache[$subresourceMetadata['resource_class']] as $currentSubresourceMetadata) { - if ($currentSubresourceMetadata['path'] === $subresourceMetadata['path']) { - continue 2; - } - } - $localCache[$subresourceMetadata['resource_class']][] = $subresourceMetadata; - } - } - - // Compute URI variables - foreach ($localCache as $class => $subresources) { - if (!$subresources) { - unset($localCache[$class]); - continue; - } - - foreach ($subresources as $i => $subresourceMetadata) { - $localCache[$class][$i]['uri_variables'] = $this->subresourceTransformer->toUriVariables($subresourceMetadata); - } - } - - return $localCache; - } - - private function printDiff(string $oldCode, string $newCode, OutputInterface $output): void - { - $consoleFormatter = new ColorConsoleDiffFormatter(); - $differ = new Differ(); - $diff = $differ->diff($oldCode, $newCode); - $output->write($consoleFormatter->format($diff)); - } - - /** - * @return array[ApiResource, bool] - */ - private function readApiResource(string $resourceClass): array - { - $reflectionClass = new \ReflectionClass($resourceClass); - - if (\PHP_VERSION_ID >= 80000 && $attributes = $reflectionClass->getAttributes(ApiResource::class)) { - return [$attributes[0]->newInstance(), false]; - } - - if (null === $this->reader) { - throw new \RuntimeException(sprintf('Resource "%s" not found.', $resourceClass)); - } - - return [$this->reader->getClassAnnotation($reflectionClass, ApiResource::class), true]; - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php b/src/Core/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php deleted file mode 100644 index 2b7c993cf5d..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php +++ /dev/null @@ -1,194 +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\Symfony\Bundle\DataCollector; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataPersister\TraceableChainDataPersister; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainCollectionDataProvider; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainItemDataProvider; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainSubresourceDataProvider; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Util\RequestAttributesExtractor; -use PackageVersions\Versions; -use Psr\Container\ContainerInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\DataCollector\DataCollector; - -/** - * @author Julien DENIAU - * @author Anthony GRASSIOT - */ -final class RequestDataCollector extends DataCollector -{ - private $metadataFactory; - private $filterLocator; - private $collectionDataProvider; - private $itemDataProvider; - private $subresourceDataProvider; - private $dataPersister; - - public function __construct(ResourceMetadataFactoryInterface $metadataFactory, ContainerInterface $filterLocator, CollectionDataProviderInterface $collectionDataProvider = null, ItemDataProviderInterface $itemDataProvider = null, SubresourceDataProviderInterface $subresourceDataProvider = null, DataPersisterInterface $dataPersister = null) - { - $this->metadataFactory = $metadataFactory; - $this->filterLocator = $filterLocator; - $this->collectionDataProvider = $collectionDataProvider; - $this->itemDataProvider = $itemDataProvider; - $this->subresourceDataProvider = $subresourceDataProvider; - $this->dataPersister = $dataPersister; - } - - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Throwable $exception = null) - { - $counters = ['ignored_filters' => 0]; - $resourceClass = $request->attributes->get('_api_resource_class'); - $resourceMetadata = $resourceClass ? $this->metadataFactory->create($resourceClass) : null; - - $filters = []; - foreach ($resourceMetadata ? $resourceMetadata->getAttribute('filters', []) : [] as $id) { - if ($this->filterLocator->has($id)) { - $filters[$id] = \get_class($this->filterLocator->get($id)); - continue; - } - - $filters[$id] = null; - ++$counters['ignored_filters']; - } - - $requestAttributes = RequestAttributesExtractor::extractAttributes($request); - if (isset($requestAttributes['previous_data'])) { - $requestAttributes['previous_data'] = $this->cloneVar($requestAttributes['previous_data']); - } - - $this->data = [ - 'resource_class' => $resourceClass, - 'resource_metadata' => $resourceMetadata ? $this->cloneVar($resourceMetadata) : null, - 'acceptable_content_types' => $request->getAcceptableContentTypes(), - 'filters' => $filters, - 'counters' => $counters, - 'dataProviders' => [], - 'dataPersisters' => ['responses' => []], - 'request_attributes' => $requestAttributes, - ]; - - if ($this->collectionDataProvider instanceof TraceableChainCollectionDataProvider) { - $this->data['dataProviders']['collection'] = [ - 'context' => $this->cloneVar($this->collectionDataProvider->getContext()), - 'responses' => $this->collectionDataProvider->getProvidersResponse(), - ]; - } - - if ($this->itemDataProvider instanceof TraceableChainItemDataProvider) { - $this->data['dataProviders']['item'] = [ - 'context' => $this->cloneVar($this->itemDataProvider->getContext()), - 'responses' => $this->itemDataProvider->getProvidersResponse(), - ]; - } - - if ($this->subresourceDataProvider instanceof TraceableChainSubresourceDataProvider) { - $this->data['dataProviders']['subresource'] = [ - 'context' => $this->cloneVar($this->subresourceDataProvider->getContext()), - 'responses' => $this->subresourceDataProvider->getProvidersResponse(), - ]; - } - - if ($this->dataPersister instanceof TraceableChainDataPersister) { - $this->data['dataPersisters']['responses'] = $this->dataPersister->getPersistersResponse(); - } - } - - public function getAcceptableContentTypes(): array - { - return $this->data['acceptable_content_types'] ?? []; - } - - public function getResourceClass() - { - return $this->data['resource_class'] ?? null; - } - - public function getResourceMetadata() - { - return $this->data['resource_metadata'] ?? null; - } - - public function getRequestAttributes(): array - { - return $this->data['request_attributes'] ?? []; - } - - public function getFilters(): array - { - return $this->data['filters'] ?? []; - } - - public function getCounters(): array - { - return $this->data['counters'] ?? []; - } - - public function getCollectionDataProviders(): array - { - return $this->data['dataProviders']['collection'] ?? ['context' => [], 'responses' => []]; - } - - public function getItemDataProviders(): array - { - return $this->data['dataProviders']['item'] ?? ['context' => [], 'responses' => []]; - } - - public function getSubresourceDataProviders(): array - { - return $this->data['dataProviders']['subresource'] ?? ['context' => [], 'responses' => []]; - } - - public function getDataPersisters(): array - { - return $this->data['dataPersisters'] ?? ['responses' => []]; - } - - public function getVersion(): ?string - { - if (!class_exists(Versions::class)) { - return null; - } - - $version = Versions::getVersion('api-platform/core'); - preg_match('/^v(.*?)@/', $version, $output); - - return $output[1] ?? strtok($version, '@'); - } - - /** - * {@inheritdoc} - */ - public function getName(): string - { - return 'api_platform.data_collector.request'; - } - - /** - * {@inheritdoc} - */ - public function reset() - { - $this->data = []; - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersister.php b/src/Core/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersister.php deleted file mode 100644 index a85a79f2219..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersister.php +++ /dev/null @@ -1,84 +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\Symfony\Bundle\DataPersister; - -use ApiPlatform\Core\DataPersister\ChainDataPersister; -use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\DataPersister\ResumableDataPersisterInterface; - -/** - * @author Anthony GRASSIOT - */ -final class TraceableChainDataPersister implements ContextAwareDataPersisterInterface -{ - private $persisters = []; - private $persistersResponse = []; - private $decorated; - - public function __construct(DataPersisterInterface $dataPersister) - { - if ($dataPersister instanceof ChainDataPersister) { - $this->decorated = $dataPersister; - $this->persisters = $dataPersister->persisters; - } - } - - public function getPersistersResponse(): array - { - return $this->persistersResponse; - } - - /** - * {@inheritdoc} - */ - public function supports($data, array $context = []): bool - { - return $this->decorated->supports($data, $context); - } - - /** - * {@inheritdoc} - */ - public function persist($data, array $context = []) - { - $this->tracePersisters($data, $context); - - return $this->decorated->persist($data, $context); - } - - /** - * {@inheritdoc} - */ - public function remove($data, array $context = []) - { - $this->tracePersisters($data, $context); - - return $this->decorated->remove($data, $context); - } - - private function tracePersisters($data, array $context = []) - { - $found = false; - foreach ($this->persisters as $persister) { - if ( - ($this->persistersResponse[\get_class($persister)] = $found ? false : $persister->supports($data, $context)) - && - !($persister instanceof ResumableDataPersisterInterface && $persister->resumable()) && !$found - ) { - $found = true; - } - } - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php b/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php deleted file mode 100644 index 836270f040f..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataProvider.php +++ /dev/null @@ -1,77 +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\Symfony\Bundle\DataProvider; - -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\Exception\ResourceClassNotSupportedException; - -/** - * @author Anthony GRASSIOT - */ -final class TraceableChainCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface -{ - use RestrictDataProviderTrait; - - private $context = []; - private $providersResponse = []; - - public function __construct(CollectionDataProviderInterface $collectionDataProvider) - { - if ($collectionDataProvider instanceof ChainCollectionDataProvider) { - $this->dataProviders = $collectionDataProvider->dataProviders; - } - } - - public function getProvidersResponse(): array - { - return $this->providersResponse; - } - - public function getContext(): array - { - return $this->context; - } - - public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable - { - $this->context = $context; - $results = null; - $match = false; - - foreach ($this->dataProviders as $dataProvider) { - $this->providersResponse[\get_class($dataProvider)] = $match ? null : false; - if ($match) { - continue; - } - try { - if ($dataProvider instanceof RestrictedDataProviderInterface - && !$dataProvider->supports($resourceClass, $operationName, $context)) { - continue; - } - - $results = $dataProvider->getCollection($resourceClass, $operationName, $context); - $this->providersResponse[\get_class($dataProvider)] = $match = true; - } catch (ResourceClassNotSupportedException $e) { - @trigger_error(sprintf('Throwing a "%s" in a data provider is deprecated in favor of implementing "%s"', ResourceClassNotSupportedException::class, RestrictedDataProviderInterface::class), \E_USER_DEPRECATED); - continue; - } - } - - return $results ?? []; - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php b/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php deleted file mode 100644 index a3116cf882e..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataProvider.php +++ /dev/null @@ -1,87 +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\Symfony\Bundle\DataProvider; - -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\Exception\ResourceClassNotSupportedException; - -/** - * @author Anthony GRASSIOT - */ -final class TraceableChainItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface -{ - use RestrictDataProviderTrait; - - private $context = []; - private $providersResponse = []; - - public function __construct(ItemDataProviderInterface $itemDataProvider) - { - if ($itemDataProvider instanceof ChainItemDataProvider) { - $this->dataProviders = $itemDataProvider->dataProviders; - } - } - - public function getProvidersResponse(): array - { - return $this->providersResponse; - } - - public function getContext(): array - { - return $this->context; - } - - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - $this->context = $context; - $match = false; - $result = null; - - foreach ($this->dataProviders as $dataProvider) { - $this->providersResponse[\get_class($dataProvider)] = $match ? null : false; - if ($match) { - continue; - } - try { - if ($dataProvider instanceof RestrictedDataProviderInterface - && !$dataProvider->supports($resourceClass, $operationName, $context)) { - continue; - } - - $identifier = $id; - if (!$dataProvider instanceof DenormalizedIdentifiersAwareItemDataProviderInterface && $identifier && \is_array($identifier)) { - if (\count($identifier) > 1) { - @trigger_error(sprintf('Receiving "$id" as non-array in an item data provider is deprecated in 2.3 in favor of implementing "%s".', DenormalizedIdentifiersAwareItemDataProviderInterface::class), \E_USER_DEPRECATED); - $identifier = http_build_query($identifier, '', ';'); - } else { - $identifier = current($identifier); - } - } - - $result = $dataProvider->getItem($resourceClass, $identifier, $operationName, $context); - $this->providersResponse[\get_class($dataProvider)] = $match = true; - } catch (ResourceClassNotSupportedException $e) { - @trigger_error(sprintf('Throwing a "%s" is deprecated in favor of implementing "%s"', \get_class($e), RestrictedDataProviderInterface::class), \E_USER_DEPRECATED); - continue; - } - } - - return $result; - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataProvider.php b/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataProvider.php deleted file mode 100644 index 867375b4785..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataProvider.php +++ /dev/null @@ -1,77 +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\Symfony\Bundle\DataProvider; - -use ApiPlatform\Core\DataProvider\ChainSubresourceDataProvider; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Exception\ResourceClassNotSupportedException; - -/** - * @author Anthony GRASSIOT - */ -final class TraceableChainSubresourceDataProvider implements SubresourceDataProviderInterface -{ - private $dataProviders = []; - private $context = []; - private $providersResponse = []; - - public function __construct(SubresourceDataProviderInterface $subresourceDataProvider) - { - if ($subresourceDataProvider instanceof ChainSubresourceDataProvider) { - $this->dataProviders = $subresourceDataProvider->dataProviders; - } - } - - public function getProvidersResponse(): array - { - return $this->providersResponse; - } - - public function getContext(): array - { - return $this->context; - } - - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - $this->context = $context; - $match = false; - $result = null; - - foreach ($this->dataProviders as $dataProvider) { - $this->providersResponse[\get_class($dataProvider)] = $match ? null : false; - if ($match) { - continue; - } - try { - if ($dataProvider instanceof RestrictedDataProviderInterface && !$dataProvider->supports($resourceClass, $operationName, $context)) { - continue; - } - - $result = $dataProvider->getSubresource($resourceClass, $identifiers, $context, $operationName); - $this->providersResponse[\get_class($dataProvider)] = $match = true; - } catch (ResourceClassNotSupportedException $e) { - @trigger_error(sprintf('Throwing a "%s" in a data provider is deprecated in favor of implementing "%s"', ResourceClassNotSupportedException::class, RestrictedDataProviderInterface::class), \E_USER_DEPRECATED); - continue; - } - } - - if ($match) { - return $result; - } - - return ($context['collection'] ?? false) ? [] : null; - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php deleted file mode 100644 index 3f87256cfa1..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\ApiPlatformExtension::class); - -if (false) { - final class ApiPlatformExtension extends \ApiPlatform\Symfony\Bundle\DependencyInjection\ApiPlatformExtension - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php deleted file mode 100644 index 155f4b25721..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass::class); - -if (false) { - final class AnnotationFilterPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php deleted file mode 100644 index 1ade44a2bb3..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AuthenticatorManagerPass::class); - -if (false) { - final class AuthenticatorManagerPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AuthenticatorManagerPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php deleted file mode 100644 index 0eb98670e35..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass::class); - -if (false) { - final class DataProviderPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php deleted file mode 100644 index 0a416411a9c..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DeprecateMercurePublisherPass::class); - -if (false) { - final class DeprecateMercurePublisherPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DeprecateMercurePublisherPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php deleted file mode 100644 index 21403760cdf..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass::class); - -if (false) { - final class ElasticsearchClientPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php deleted file mode 100644 index d38e09b6b88..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\FilterPass::class); - -if (false) { - final class FilterPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\FilterPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php deleted file mode 100644 index aa64933292b..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlMutationResolverPass::class); - -if (false) { - final class GraphQlMutationResolverPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlMutationResolverPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php deleted file mode 100644 index 69f3eda55ba..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlQueryResolverPass::class); - -if (false) { - final class GraphQlQueryResolverPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlQueryResolverPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php deleted file mode 100644 index fa6edee6809..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass::class); - -if (false) { - final class GraphQlTypePass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php deleted file mode 100644 index ae891b50bff..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass::class); - -if (false) { - final class MetadataAwareNameConverterPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php deleted file mode 100644 index ada12cd16c4..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection\Compiler; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass::class); - -if (false) { - final class TestClientPass extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php deleted file mode 100644 index 39a8c39c4b2..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\DependencyInjection; - -class_exists(\ApiPlatform\Symfony\Bundle\DependencyInjection\Configuration::class); - -if (false) { - final class Configuration extends \ApiPlatform\Symfony\Bundle\DependencyInjection\Configuration - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php b/src/Core/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php deleted file mode 100644 index 934dad77e94..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/EventListener/SwaggerUiListener.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\EventListener; - -class_exists(\ApiPlatform\Symfony\Bundle\EventListener\SwaggerUiListener::class); - -if (false) { - final class SwaggerUiListener extends \ApiPlatform\Symfony\Bundle\EventListener\SwaggerUiListener - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/SwaggerUi/SwaggerUiAction.php b/src/Core/Bridge/Symfony/Bundle/SwaggerUi/SwaggerUiAction.php deleted file mode 100644 index 2fb7f4ace0c..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/SwaggerUi/SwaggerUiAction.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\SwaggerUi; - -class_exists(\ApiPlatform\Symfony\Bundle\SwaggerUi\SwaggerUiAction::class); - -if (false) { - final class SwaggerUiAction extends \ApiPlatform\Symfony\Bundle\SwaggerUi\SwaggerUiAction - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php b/src/Core/Bridge/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php deleted file mode 100644 index 729a84e3298..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\SwaggerUi; - -class_exists(\ApiPlatform\Symfony\Bundle\SwaggerUi\SwaggerUiContext::class); - -if (false) { - final class SwaggerUiContext extends \ApiPlatform\Symfony\Bundle\SwaggerUi\SwaggerUiContext - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php b/src/Core/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php deleted file mode 100644 index a616e27ec37..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/ApiTestAssertionsTrait.php +++ /dev/null @@ -1,23 +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\Symfony\Bundle\Test; - -class_exists(\ApiPlatform\Symfony\Bundle\Test\ApiTestAssertionsTrait::class); - -if (false) { - trait ApiTestAssertionsTrait - { - use \ApiPlatform\Symfony\Bundle\Test\ApiTestAssertionsTrait; - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/ApiTestCase.php b/src/Core/Bridge/Symfony/Bundle/Test/ApiTestCase.php deleted file mode 100644 index d2835b4fe54..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/ApiTestCase.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\Test; - -class_exists(\ApiPlatform\Symfony\Bundle\Test\ApiTestCase::class); - -if (false) { - class ApiTestCase extends \ApiPlatform\Symfony\Bundle\Test\ApiTestCase - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/Client.php b/src/Core/Bridge/Symfony/Bundle/Test/Client.php deleted file mode 100644 index 4399df64840..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/Client.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\Test; - -class_exists(\ApiPlatform\Symfony\Bundle\Test\Client::class); - -if (false) { - final class Client extends \ApiPlatform\Symfony\Bundle\Test\Client - { - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubset.php b/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubset.php deleted file mode 100644 index a55455514fb..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubset.php +++ /dev/null @@ -1,31 +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\Symfony\Bundle\Test\Constraint; - -use PHPUnit\Runner\Version; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use SebastianBergmann\Comparator\ComparisonFailure as LegacyComparisonFailure; - -if (!class_exists(ComparisonFailure::class)) { - class_alias(LegacyComparisonFailure::class, 'PHPUnit\SebastianBergmann\Comparator\ComparisonFailure'); -} - -trigger_deprecation('api-platform/core', '2.7', sprintf('The class %s is deprecated, use %s instead.', 'ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\ArraySubset', 'ApiPlatform\Symfony\Bundle\Test\Constraint')); - -// Aliases as string to avoid loading the class -if (\PHP_VERSION_ID >= 80000 || (float) Version::series() >= 9) { - class_alias('ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\ArraySubsetV9', 'ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\ArraySubset'); -} else { - class_alias('ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\ArraySubsetLegacy', 'ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Constraint\ArraySubset'); -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php b/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php deleted file mode 100644 index 0911672ce01..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php +++ /dev/null @@ -1,34 +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\Symfony\Bundle\Test\Constraint; - -use PHPUnit\Framework\Constraint\Constraint; - -/** - * Is used for phpunit < 8. - * - * @internal - */ -final class ArraySubsetLegacy extends Constraint -{ - use ArraySubsetTrait; - - /** - * {@inheritdoc} - */ - public function evaluate($other, $description = '', $returnResult = false) - { - return $this->_evaluate($other, $description, $returnResult); - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetTrait.php b/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetTrait.php deleted file mode 100644 index cebb2217c31..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetTrait.php +++ /dev/null @@ -1,98 +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\Symfony\Bundle\Test\Constraint; - -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; - -/** - * Constraint that asserts that the array it is evaluated for has a specified subset. - * - * Uses array_replace_recursive() to check if a key value subset is part of the - * subject array. - * - * Imported from dms/phpunit-arraysubset-asserts, because the original constraint has been deprecated. - * - * @copyright Sebastian Bergmann - * @copyright Rafael Dohms - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3494 - */ -trait ArraySubsetTrait -{ - private $subset; - private $strict; - - public function __construct(iterable $subset, bool $strict = false) - { - $this->strict = $strict; - $this->subset = $subset; - } - - private function _evaluate($other, string $description = '', bool $returnResult = false): ?bool - { - // type cast $other & $this->subset as an array to allow - // support in standard array functions. - $other = $this->toArray($other); - $this->subset = $this->toArray($this->subset); - $patched = array_replace_recursive($other, $this->subset); - if ($this->strict) { - $result = $other === $patched; - } else { - $result = $other == $patched; - } - if ($returnResult) { - return $result; - } - if ($result) { - return null; - } - - $f = new ComparisonFailure( - $patched, - $other, - var_export($patched, true), - var_export($other, true) - ); - $this->fail($other, $description, $f); - } - - /** - * {@inheritdoc} - */ - public function toString(): string - { - return 'has the subset '.$this->exporter()->export($this->subset); - } - - /** - * {@inheritdoc} - */ - protected function failureDescription($other): string - { - return 'an array '.$this->toString(); - } - - private function toArray(iterable $other): array - { - if (\is_array($other)) { - return $other; - } - if ($other instanceof \ArrayObject) { - return $other->getArrayCopy(); - } - - return iterator_to_array($other); - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetV9.php b/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetV9.php deleted file mode 100644 index ccb16def0e6..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetV9.php +++ /dev/null @@ -1,34 +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\Symfony\Bundle\Test\Constraint; - -use PHPUnit\Framework\Constraint\Constraint; - -/** - * Is used for phpunit >= 9. - * - * @internal - */ -final class ArraySubsetV9 extends Constraint -{ - use ArraySubsetTrait; - - /** - * {@inheritdoc} - */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool - { - return $this->_evaluate($other, $description, $returnResult); - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php b/src/Core/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php deleted file mode 100644 index 910d3990a87..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php +++ /dev/null @@ -1,116 +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\Symfony\Bundle\Test\Constraint; - -use JsonSchema\Validator; -use PHPUnit\Framework\Constraint\Constraint; - -/** - * Asserts that a JSON document matches a given JSON Schema. - * - * @author Kévin Dunglas - * - * @experimental - */ -final class MatchesJsonSchema extends Constraint -{ - /** - * @var object|array - */ - private $schema; - private $checkMode; - - /** - * @param object|array|string $schema - */ - public function __construct($schema, ?int $checkMode = null) - { - $this->schema = \is_string($schema) ? json_decode($schema) : $schema; - $this->checkMode = $checkMode; - } - - /** - * {@inheritdoc} - */ - public function toString(): string - { - return 'matches the provided JSON Schema'; - } - - /** - * {@inheritdoc} - */ - protected function matches($other): bool - { - if (!class_exists(Validator::class)) { - throw new \LogicException('The "justinrainbow/json-schema" library must be installed to use "assertMatchesJsonSchema()". Try running "composer require --dev justinrainbow/json-schema".'); - } - - $other = $this->normalizeJson($other); - - $validator = new Validator(); - $validator->validate($other, $this->schema, $this->checkMode); - - return $validator->isValid(); - } - - /** - * {@inheritdoc} - */ - protected function additionalFailureDescription($other): string - { - $other = $this->normalizeJson($other); - - $validator = new Validator(); - $validator->validate($other, $this->schema, $this->checkMode); - - $errors = []; - foreach ($validator->getErrors() as $error) { - $property = $error['property'] ? $error['property'].': ' : ''; - $errors[] = $property.$error['message']; - } - - return implode("\n", $errors); - } - - /** - * Normalizes a JSON document. - * - * Specifically, we should ensure that: - * 1. a JSON object is represented as a PHP object, not as an associative array. - * - * @param mixed $document - */ - private function normalizeJson($document) - { - if (\is_scalar($document) || \is_object($document)) { - return $document; - } - - if (!\is_array($document)) { - throw new \InvalidArgumentException('Document must be scalar, array or object.'); - } - - $document = json_encode($document); - if (!\is_string($document)) { - throw new \UnexpectedValueException('JSON encode failed.'); - } - $document = json_decode($document); - if (!\is_array($document) && !\is_object($document)) { - throw new \UnexpectedValueException('JSON decode failed.'); - } - - return $document; - } -} diff --git a/src/Core/Bridge/Symfony/Bundle/Test/Response.php b/src/Core/Bridge/Symfony/Bundle/Test/Response.php deleted file mode 100644 index d1b77770bdf..00000000000 --- a/src/Core/Bridge/Symfony/Bundle/Test/Response.php +++ /dev/null @@ -1,22 +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\Symfony\Bundle\Test; - -class_exists(\ApiPlatform\Symfony\Bundle\Test\Response::class); - -if (false) { - final class Response extends \ApiPlatform\Symfony\Bundle\Test\Response - { - } -} diff --git a/src/Core/Bridge/Symfony/Identifier/Normalizer/UlidNormalizer.php b/src/Core/Bridge/Symfony/Identifier/Normalizer/UlidNormalizer.php deleted file mode 100644 index c1675e87d94..00000000000 --- a/src/Core/Bridge/Symfony/Identifier/Normalizer/UlidNormalizer.php +++ /dev/null @@ -1,46 +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\Symfony\Identifier\Normalizer; - -use ApiPlatform\Exception\InvalidIdentifierException; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; -use Symfony\Component\Uid\Ulid; - -/** - * Denormalizes an ULID string to an instance of Symfony\Component\Uid\Ulid. - */ -final class UlidNormalizer implements DenormalizerInterface -{ - /** - * {@inheritdoc} - * - * @return mixed - */ - public function denormalize($data, $class, $format = null, array $context = []) - { - try { - return Ulid::fromString($data); - } catch (\InvalidArgumentException $e) { - throw new InvalidIdentifierException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function supportsDenormalization($data, $type, $format = null): bool - { - return \is_string($data) && is_a($type, Ulid::class, true); - } -} diff --git a/src/Core/Bridge/Symfony/Identifier/Normalizer/UuidNormalizer.php b/src/Core/Bridge/Symfony/Identifier/Normalizer/UuidNormalizer.php deleted file mode 100644 index a51f074a316..00000000000 --- a/src/Core/Bridge/Symfony/Identifier/Normalizer/UuidNormalizer.php +++ /dev/null @@ -1,46 +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\Symfony\Identifier\Normalizer; - -use ApiPlatform\Exception\InvalidIdentifierException; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; -use Symfony\Component\Uid\Uuid; - -/** - * Denormalizes an UUID string to an instance of Symfony\Component\Uid\Uuid. - */ -final class UuidNormalizer implements DenormalizerInterface -{ - /** - * {@inheritdoc} - * - * @return mixed - */ - public function denormalize($data, $class, $format = null, array $context = []) - { - try { - return Uuid::fromString($data); - } catch (\InvalidArgumentException|\ValueError $e) { // catching ValueError will not be necessary anymore when https://github.com/symfony/symfony/pull/39636 will be released - throw new InvalidIdentifierException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function supportsDenormalization($data, $type, $format = null): bool - { - return \is_string($data) && is_a($type, Uuid::class, true); - } -} diff --git a/src/Core/Bridge/Symfony/Maker/MakeDataPersister.php b/src/Core/Bridge/Symfony/Maker/MakeDataPersister.php deleted file mode 100644 index 13daf7d6a74..00000000000 --- a/src/Core/Bridge/Symfony/Maker/MakeDataPersister.php +++ /dev/null @@ -1,116 +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\Symfony\Maker; - -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use Symfony\Bundle\MakerBundle\ConsoleStyle; -use Symfony\Bundle\MakerBundle\DependencyBuilder; -use Symfony\Bundle\MakerBundle\Generator; -use Symfony\Bundle\MakerBundle\InputConfiguration; -use Symfony\Bundle\MakerBundle\Maker\AbstractMaker; -use Symfony\Bundle\MakerBundle\Str; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Question\Question; - -final class MakeDataPersister extends AbstractMaker -{ - private $resourceNameCollection; - - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollection) - { - $this->resourceNameCollection = $resourceNameCollection; - } - - /** - * {@inheritdoc} - */ - public static function getCommandName(): string - { - return 'make:data-persister'; - } - - /** - * {@inheritdoc} - */ - public static function getCommandDescription(): string - { - return 'Creates an API Platform data persister'; - } - - /** - * {@inheritdoc} - */ - public function configureCommand(Command $command, InputConfiguration $inputConfig) - { - $command - ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your data persister (e.g. AwesomeDataPersister)') - ->addArgument('resource-class', InputArgument::OPTIONAL, 'Choose a Resource class') - ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataPersister.txt')); - - $inputConfig->setArgumentAsNonInteractive('resource-class'); - } - - /** - * {@inheritdoc} - */ - public function configureDependencies(DependencyBuilder $dependencies) - { - } - - public function interact(InputInterface $input, ConsoleStyle $io, Command $command) - { - if (null === $input->getArgument('resource-class')) { - $argument = $command->getDefinition()->getArgument('resource-class'); - - $resourceClasses = $this->resourceNameCollection->create(); - - $question = new Question($argument->getDescription()); - $question->setAutocompleterValues($resourceClasses); - - $value = $io->askQuestion($question); - - $input->setArgument('resource-class', $value); - } - } - - /** - * {@inheritdoc} - */ - public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) - { - $dataPersisterClassNameDetails = $generator->createClassNameDetails( - $input->getArgument('name'), - 'DataPersister\\' - ); - $resourceClass = $input->getArgument('resource-class'); - - $generator->generateClass( - $dataPersisterClassNameDetails->getFullName(), - __DIR__.'/Resources/skeleton/DataPersister.tpl.php', - [ - 'resource_class' => null !== $resourceClass ? Str::getShortClassName($resourceClass) : null, - 'resource_full_class_name' => $resourceClass, - ] - ); - $generator->writeChanges(); - - $this->writeSuccessMessage($io); - $io->text([ - 'Next: Open your new data persister class and start customizing it.', - 'Find the documentation at https://api-platform.com/docs/core/data-persisters/', - ]); - } -} diff --git a/src/Core/Bridge/Symfony/Maker/MakeDataProvider.php b/src/Core/Bridge/Symfony/Maker/MakeDataProvider.php deleted file mode 100644 index 9db71ced56f..00000000000 --- a/src/Core/Bridge/Symfony/Maker/MakeDataProvider.php +++ /dev/null @@ -1,122 +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\Symfony\Maker; - -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use Symfony\Bundle\MakerBundle\ConsoleStyle; -use Symfony\Bundle\MakerBundle\DependencyBuilder; -use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; -use Symfony\Bundle\MakerBundle\Generator; -use Symfony\Bundle\MakerBundle\InputConfiguration; -use Symfony\Bundle\MakerBundle\Maker\AbstractMaker; -use Symfony\Bundle\MakerBundle\Str; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Question\Question; - -class MakeDataProvider extends AbstractMaker -{ - private $resourceNameCollection; - - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollection) - { - $this->resourceNameCollection = $resourceNameCollection; - } - - /** - * {@inheritdoc} - */ - public static function getCommandName(): string - { - return 'make:data-provider'; - } - - /** - * {@inheritdoc} - */ - public static function getCommandDescription(): string - { - return 'Creates an API Platform data provider'; - } - - /** - * {@inheritdoc} - */ - public function configureCommand(Command $command, InputConfiguration $inputConfig) - { - $command - ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your data provider (e.g. AwesomeDataProvider)') - ->addArgument('resource-class', InputArgument::OPTIONAL, 'Choose a Resource class') - ->addOption('item-only', null, InputOption::VALUE_NONE, 'Generate only an item data provider') - ->addOption('collection-only', null, InputOption::VALUE_NONE, 'Generate only a collection data provider') - ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataProvider.txt')); - - $inputConfig->setArgumentAsNonInteractive('resource-class'); - } - - /** - * {@inheritdoc} - */ - public function configureDependencies(DependencyBuilder $dependencies) - { - } - - public function interact(InputInterface $input, ConsoleStyle $io, Command $command) - { - if ($input->getOption('item-only') && $input->getOption('collection-only')) { - throw new RuntimeCommandException('You should at least generate an item or a collection data provider'); - } - - if (null === $input->getArgument('resource-class')) { - $argument = $command->getDefinition()->getArgument('resource-class'); - - $question = new Question($argument->getDescription()); - $question->setAutocompleterValues($this->resourceNameCollection->create()); - - $input->setArgument('resource-class', $io->askQuestion($question)); - } - } - - /** - * {@inheritdoc} - */ - public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) - { - $dataProviderClassNameDetails = $generator->createClassNameDetails( - $input->getArgument('name'), - 'DataProvider\\' - ); - $resourceClass = $input->getArgument('resource-class'); - - $generator->generateClass( - $dataProviderClassNameDetails->getFullName(), - __DIR__.'/Resources/skeleton/DataProvider.tpl.php', - [ - 'resource_class' => null !== $resourceClass ? Str::getShortClassName($resourceClass) : null, - 'resource_full_class_name' => $resourceClass, - 'generate_collection' => !$input->getOption('item-only'), - 'generate_item' => !$input->getOption('collection-only'), - ] - ); - $generator->writeChanges(); - - $this->writeSuccessMessage($io); - $io->text([ - 'Next: Open your new data provider class and start customizing it.', - 'Find the documentation at https://api-platform.com/docs/core/data-providers/', - ]); - } -} diff --git a/src/Core/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt b/src/Core/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt deleted file mode 100644 index 7afe500d462..00000000000 --- a/src/Core/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt +++ /dev/null @@ -1,5 +0,0 @@ -The %command.name% command generates a new API Platform data persister class. - -php %command.full_name% AwesomeDataPersister - -If the argument is missing, the command will ask for the class name interactively. diff --git a/src/Core/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt b/src/Core/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt deleted file mode 100644 index 73064481560..00000000000 --- a/src/Core/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt +++ /dev/null @@ -1,5 +0,0 @@ -The %command.name% command generates a new API Platform data provider class. - -php %command.full_name% AwesomeDataProvider - -If the argument is missing, the command will ask for the class name interactively. diff --git a/src/Core/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php b/src/Core/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php deleted file mode 100644 index 5ed85510a77..00000000000 --- a/src/Core/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php +++ /dev/null @@ -1,57 +0,0 @@ - - -namespace ; - -use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; -use ApiPlatform\Core\DataPersister\ResumableDataPersisterInterface; - -use ; - - -final class implements ContextAwareDataPersisterInterface, ResumableDataPersisterInterface -{ - /** - * {@inheritdoc} - */ - public function supports($data, array $context = []): bool - { - - return $data instanceof ::class; // Add your custom conditions here - - return false; // Add your custom conditions here - - } - - /** - * {@inheritdoc} - */ - public function resumable(array $context = []): bool - { - return false; // Set it to true if you want to call the other data persisters - } - - /** - * {@inheritdoc} - */ - public function persist($data, array $context = [])= 70200) { - echo ': object'; - }?> - - { - // Call your persistence layer to save $data - - return $data; - } - - /** - * {@inheritdoc} - */ - public function remove($data, array $context = []): void - { - // Call your persistence layer to delete $data - } -} diff --git a/src/Core/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php b/src/Core/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php deleted file mode 100644 index 0d8160c8589..00000000000 --- a/src/Core/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php +++ /dev/null @@ -1,64 +0,0 @@ - - -namespace ; - - -use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; - - -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; - -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; - -use ; - - -final class implements RestrictedDataProviderInterface -{ - /** - * {@inheritdoc} - */ - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - - return ::class === $resourceClass; // Add your custom conditions here - - return false; // Add your custom conditions here - - } - - - /** - * {@inheritdoc} - */ - public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable - { - // Retrieve the collection from somewhere - } - - - - /** - * {@inheritdoc} - */ - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])= 70200) { - echo ': ?object'; - }?> - - { - // Retrieve the item from somewhere then return it or null if not found - } - -} diff --git a/src/Core/Bridge/Symfony/Messenger/ContextStamp.php b/src/Core/Bridge/Symfony/Messenger/ContextStamp.php deleted file mode 100644 index a127b3dd06f..00000000000 --- a/src/Core/Bridge/Symfony/Messenger/ContextStamp.php +++ /dev/null @@ -1,22 +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\Symfony\Messenger; - -class_exists(\ApiPlatform\Symfony\Messenger\ContextStamp::class); - -if (false) { - final class ContextStamp extends \ApiPlatform\Symfony\Messenger\ContextStamp - { - } -} diff --git a/src/Core/Bridge/Symfony/Messenger/DataPersister.php b/src/Core/Bridge/Symfony/Messenger/DataPersister.php deleted file mode 100644 index f2b13d85a74..00000000000 --- a/src/Core/Bridge/Symfony/Messenger/DataPersister.php +++ /dev/null @@ -1,122 +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\Symfony\Messenger; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Symfony\Messenger\DispatchTrait; -use ApiPlatform\Util\ClassInfoTrait; -use Symfony\Component\Messenger\Envelope; -use Symfony\Component\Messenger\MessageBusInterface; -use Symfony\Component\Messenger\Stamp\HandledStamp; - -/** - * Dispatches the given resource using the message bus of Symfony Messenger. - * - * @experimental - * - * @author Kévin Dunglas - */ -final class DataPersister implements ContextAwareDataPersisterInterface -{ - use ClassInfoTrait; - use DispatchTrait; - - /** - * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface - */ - private $resourceMetadataFactory; - - public function __construct($resourceMetadataFactory, MessageBusInterface $messageBus) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->messageBus = $messageBus; - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - } - - /** - * {@inheritdoc} - */ - public function supports($data, array $context = []): bool - { - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - try { - $resourceMetadataCollection = $this->resourceMetadataFactory->create($context['resource_class'] ?? $this->getObjectClass($data)); - $operation = $resourceMetadataCollection->getOperation($context['operation_name'] ?? null); - - return false !== ($operation->getMessenger() ?? false); - } catch (OperationNotFoundException $e) { - return false; - } - } - - try { - $resourceMetadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? $this->getObjectClass($data)); - } catch (ResourceClassNotFoundException $e) { - return false; - } - - if (null !== $operationName = $context['collection_operation_name'] ?? $context['item_operation_name'] ?? null) { - return false !== $resourceMetadata->getTypedOperationAttribute( - $context['collection_operation_name'] ?? false ? OperationType::COLLECTION : OperationType::ITEM, - $operationName, - 'messenger', - false, - true - ); - } - - if (isset($context['graphql_operation_name'])) { - return false !== $resourceMetadata->getGraphqlAttribute($context['graphql_operation_name'], 'messenger', false, true); - } - - return false !== $resourceMetadata->getAttribute('messenger', false); - } - - /** - * {@inheritdoc} - */ - public function persist($data, array $context = []) - { - $envelope = $this->dispatch( - (new Envelope($data)) - ->with(new ContextStamp($context)) - ); - - $handledStamp = $envelope->last(HandledStamp::class); - if (!$handledStamp instanceof HandledStamp) { - return $data; - } - - return $handledStamp->getResult(); - } - - /** - * {@inheritdoc} - */ - public function remove($data, array $context = []) - { - $this->dispatch( - (new Envelope($data)) - ->with(new RemoveStamp()) - ); - } -} diff --git a/src/Core/Bridge/Symfony/Messenger/DataTransformer.php b/src/Core/Bridge/Symfony/Messenger/DataTransformer.php deleted file mode 100644 index d940788fbf9..00000000000 --- a/src/Core/Bridge/Symfony/Messenger/DataTransformer.php +++ /dev/null @@ -1,98 +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\Symfony\Messenger; - -use ApiPlatform\Core\DataTransformer\DataTransformerInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Util\ClassInfoTrait; - -/** - * Transforms an Input to itself. This gives the ability to send the Input to a - * message handler and process it asynchronously. - * - * @author Antoine Bluchet - */ -final class DataTransformer implements DataTransformerInterface -{ - use ClassInfoTrait; - - /** - * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface - */ - private $resourceMetadataFactory; - - public function __construct($resourceMetadataFactory) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - } - - /** - * {@inheritdoc} - * - * @return object - */ - public function transform($object, string $to, array $context = []) - { - return $object; - } - - /** - * {@inheritdoc} - */ - public function supportsTransformation($data, string $to, array $context = []): bool - { - if ( - \is_object($data) // data is not normalized yet, it should be an array - || - null === ($context['input']['class'] ?? null) - ) { - return false; - } - - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - try { - $resourceMetadataCollection = $this->resourceMetadataFactory->create($context['resource_class'] ?? $to); - $operation = $resourceMetadataCollection->getOperation($context['operation_name'] ?? null); - - return 'input' === $operation->getMessenger(); - } catch (OperationNotFoundException $e) { - return false; - } - } - - $metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? $to); - - if (isset($context['graphql_operation_name'])) { - return 'input' === $metadata->getGraphqlAttribute($context['graphql_operation_name'], 'messenger', null, true); - } - - if (!isset($context['operation_type'])) { - return 'input' === $metadata->getAttribute('messenger'); - } - - return 'input' === $metadata->getTypedOperationAttribute( - $context['operation_type'], - $context[$context['operation_type'].'_operation_name'] ?? '', - 'messenger', - null, - true - ); - } -} diff --git a/src/Core/Bridge/Symfony/Messenger/DispatchTrait.php b/src/Core/Bridge/Symfony/Messenger/DispatchTrait.php deleted file mode 100644 index 7ae211cd77f..00000000000 --- a/src/Core/Bridge/Symfony/Messenger/DispatchTrait.php +++ /dev/null @@ -1,23 +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\Symfony\Messenger; - -class_exists(\ApiPlatform\Symfony\Messenger\DispatchTrait::class); - -if (false) { - trait DispatchTrait - { - use \ApiPlatform\Symfony\Messenger\DispatchTrait; - } -} diff --git a/src/Core/Bridge/Symfony/Messenger/RemoveStamp.php b/src/Core/Bridge/Symfony/Messenger/RemoveStamp.php deleted file mode 100644 index e5f1dc898ad..00000000000 --- a/src/Core/Bridge/Symfony/Messenger/RemoveStamp.php +++ /dev/null @@ -1,22 +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\Symfony\Messenger; - -class_exists(\ApiPlatform\Symfony\Messenger\RemoveStamp::class); - -if (false) { - final class RemoveStamp extends \ApiPlatform\Symfony\Messenger\RemoveStamp - { - } -} diff --git a/src/Core/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyMetadataFactory.php b/src/Core/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyMetadataFactory.php deleted file mode 100644 index 26d7a76b657..00000000000 --- a/src/Core/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyMetadataFactory.php +++ /dev/null @@ -1,89 +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\Symfony\PropertyInfo\Metadata\Property; - -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; - -/** - * PropertyInfo metadata loader decorator. - * - * @author Kévin Dunglas - */ -final class PropertyInfoPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - private $propertyInfo; - private $decorated; - - public function __construct(PropertyInfoExtractorInterface $propertyInfo, PropertyMetadataFactoryInterface $decorated = null) - { - $this->propertyInfo = $propertyInfo; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - if (null === $this->decorated) { - $propertyMetadata = new PropertyMetadata(); - } else { - try { - $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); - } catch (PropertyNotFoundException $propertyNotFoundException) { - $propertyMetadata = new PropertyMetadata(); - } - } - - if (null === $propertyMetadata->getType()) { - $types = $this->propertyInfo->getTypes($resourceClass, $property, $options); - if (isset($types[0])) { - $propertyMetadata = $propertyMetadata->withType($types[0]); - } - } - - if (null === $propertyMetadata->getDescription() && null !== $description = $this->propertyInfo->getShortDescription($resourceClass, $property, $options)) { - $propertyMetadata = $propertyMetadata->withDescription($description); - } - - if (null === $propertyMetadata->isReadable() && null !== $readable = $this->propertyInfo->isReadable($resourceClass, $property, $options)) { - $propertyMetadata = $propertyMetadata->withReadable($readable); - } - - if (null === $propertyMetadata->isWritable() && null !== $writable = $this->propertyInfo->isWritable($resourceClass, $property, $options)) { - $propertyMetadata = $propertyMetadata->withWritable($writable); - } - - if (method_exists($this->propertyInfo, 'isInitializable')) { - if (null === $propertyMetadata->isInitializable() && null !== $initializable = $this->propertyInfo->isInitializable($resourceClass, $property, $options)) { - $propertyMetadata = $propertyMetadata->withInitializable($initializable); - } - } else { - // BC layer for Symfony < 4.2 - $ref = new \ReflectionClass($resourceClass); - if ($ref->isInstantiable() && $constructor = $ref->getConstructor()) { - foreach ($constructor->getParameters() as $constructorParameter) { - if ($constructorParameter->name === $property && null === $propertyMetadata->isInitializable()) { - $propertyMetadata = $propertyMetadata->withInitializable(true); - } - } - } - } - - return $propertyMetadata; - } -} diff --git a/src/Core/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyNameCollectionFactory.php b/src/Core/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyNameCollectionFactory.php deleted file mode 100644 index 6e1839aa845..00000000000 --- a/src/Core/Bridge/Symfony/PropertyInfo/Metadata/Property/PropertyInfoPropertyNameCollectionFactory.php +++ /dev/null @@ -1,22 +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\Symfony\PropertyInfo\Metadata\Property; - -class_exists(\ApiPlatform\Metadata\Property\Factory\PropertyInfoPropertyNameCollectionFactory::class); - -if (false) { - final class PropertyInfoPropertyNameCollectionFactory extends \ApiPlatform\Metadata\Property\Factory\PropertyInfoPropertyNameCollectionFactory - { - } -} diff --git a/src/Core/Bridge/Symfony/Routing/ApiLoader.php b/src/Core/Bridge/Symfony/Routing/ApiLoader.php deleted file mode 100644 index 5265467c555..00000000000 --- a/src/Core/Bridge/Symfony/Routing/ApiLoader.php +++ /dev/null @@ -1,22 +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\Symfony\Routing; - -class_exists(\ApiPlatform\Symfony\Routing\ApiLoader::class); - -if (false) { - final class ApiLoader extends \ApiPlatform\Symfony\Routing\ApiLoader - { - } -} diff --git a/src/Core/Bridge/Symfony/Routing/CachedRouteNameResolver.php b/src/Core/Bridge/Symfony/Routing/CachedRouteNameResolver.php deleted file mode 100644 index 7cdcf4461c5..00000000000 --- a/src/Core/Bridge/Symfony/Routing/CachedRouteNameResolver.php +++ /dev/null @@ -1,50 +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\Symfony\Routing; - -use ApiPlatform\Util\CachedTrait; -use Psr\Cache\CacheItemPoolInterface; - -/** - * {@inheritdoc} - * - * @author Teoh Han Hui - */ -final class CachedRouteNameResolver implements RouteNameResolverInterface -{ - use CachedTrait; - - public const CACHE_KEY_PREFIX = 'route_name_'; - - private $decorated; - - public function __construct(CacheItemPoolInterface $cacheItemPool, RouteNameResolverInterface $decorated) - { - $this->cacheItemPool = $cacheItemPool; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function getRouteName(string $resourceClass, $operationType /* , array $context = [] */): string - { - $context = \func_num_args() > 2 ? func_get_arg(2) : []; - $cacheKey = self::CACHE_KEY_PREFIX.md5(serialize([$resourceClass, $operationType, $context['subresource_resources'] ?? null])); - - return $this->getCached($cacheKey, function () use ($resourceClass, $operationType, $context) { - return $this->decorated->getRouteName($resourceClass, $operationType, $context); - }); - } -} diff --git a/src/Core/Bridge/Symfony/Routing/IriConverter.php b/src/Core/Bridge/Symfony/Routing/IriConverter.php deleted file mode 100644 index a3c0bfc3533..00000000000 --- a/src/Core/Bridge/Symfony/Routing/IriConverter.php +++ /dev/null @@ -1,186 +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\Symfony\Routing; - -use ApiPlatform\Core\Api\IdentifiersExtractor; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\Api\OperationType; -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\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\Exception\InvalidArgumentException; -use ApiPlatform\Exception\InvalidIdentifierException; -use ApiPlatform\Exception\ItemNotFoundException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Symfony\Routing\IriConverter as NewIriConverter; -use ApiPlatform\Util\AttributesExtractor; -use ApiPlatform\Util\ResourceClassInfoTrait; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; -use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface; -use Symfony\Component\Routing\RouterInterface; - -/** - * {@inheritdoc} - * - * @author Kévin Dunglas - */ -final class IriConverter implements IriConverterInterface -{ - use OperationDataProviderTrait; - use ResourceClassInfoTrait; - - private $routeNameResolver; - private $router; - private $identifiersExtractor; - - 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) - { - $this->itemDataProvider = $itemDataProvider; - $this->routeNameResolver = $routeNameResolver; - $this->router = $router; - $this->subresourceDataProvider = $subresourceDataProvider; - $this->identifierConverter = $identifierConverter; - $this->resourceClassResolver = $resourceClassResolver; - $this->identifiersExtractor = $identifiersExtractor ?: new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, $propertyAccessor ?? PropertyAccess::createPropertyAccessor()); - $this->resourceMetadataFactory = $resourceMetadataFactory; - - trigger_deprecation('api-platform/core', '2.7', sprintf('The service "%s" is deprecated, use %s instead.', self::class, NewIriConverter::class)); - } - - /** - * {@inheritdoc} - * - * @return object - */ - public function getItemFromIri(string $iri, array $context = []) - { - try { - $parameters = $this->router->match($iri); - } catch (RoutingExceptionInterface $e) { - throw new InvalidArgumentException(sprintf('No route matches "%s".', $iri), $e->getCode(), $e); - } - - if (!isset($parameters['_api_resource_class'])) { - throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri)); - } - - if (isset($parameters['_api_collection_operation_name'])) { - throw new InvalidArgumentException(sprintf('The iri "%s" references a collection not an item.', $iri)); - } - - $attributes = AttributesExtractor::extractAttributes($parameters); - - try { - $identifiers = $this->extractIdentifiers($parameters, $attributes); - } catch (InvalidIdentifierException $e) { - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); - } - - if ($this->identifierConverter) { - $context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] = true; - } - - 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)); - } - - if ($item = $this->getItemData($identifiers, $attributes, $context)) { - return $item; - } - - throw new ItemNotFoundException(sprintf('Item not found for "%s".', $iri)); - } - - /** - * {@inheritdoc} - */ - public function getIriFromItem($item, int $referenceType = null): string - { - $resourceClass = $this->getResourceClass($item, true); - - try { - $identifiers = $this->identifiersExtractor->getIdentifiersFromItem($item); - } 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)); - } - - /** - * {@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)); - } catch (RoutingExceptionInterface $e) { - throw new InvalidArgumentException(sprintf('Unable to generate an IRI for "%s".', $resourceClass), $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function getItemIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = null): string - { - $routeName = $this->routeNameResolver->getRouteName($resourceClass, OperationType::ITEM); - $metadata = $this->resourceMetadataFactory->create($resourceClass); - - if (\count($identifiers) > 1 && true === $metadata->getAttribute('composite_identifier', true)) { - $identifiers = ['id' => CompositeIdentifierParser::stringify($identifiers)]; - } - - 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); - } - } - - /** - * {@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); - } - } - - 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'); - } - - return $referenceType ?? UrlGeneratorInterface::ABS_PATH; - } -} diff --git a/src/Core/Bridge/Symfony/Routing/OperationMethodResolver.php b/src/Core/Bridge/Symfony/Routing/OperationMethodResolver.php deleted file mode 100644 index d236b4534c0..00000000000 --- a/src/Core/Bridge/Symfony/Routing/OperationMethodResolver.php +++ /dev/null @@ -1,153 +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\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Exception\RuntimeException; -use Symfony\Component\Routing\Route; -use Symfony\Component\Routing\RouterInterface; - -/** - * Resolves the HTTP method associated with an operation, extended for Symfony routing. - * - * @author Kévin Dunglas - * @author Teoh Han Hui - * - * @deprecated since API Platform 2.5, use the "method" attribute instead - */ -final class OperationMethodResolver implements OperationMethodResolverInterface -{ - private $router; - private $resourceMetadataFactory; - - public function __construct(RouterInterface $router, ResourceMetadataFactoryInterface $resourceMetadataFactory) - { - @trigger_error(sprintf('The "%s" class is deprecated since API Platform 2.5, use the "method" attribute instead.', __CLASS__), \E_USER_DEPRECATED); - - $this->router = $router; - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - /** - * {@inheritdoc} - */ - public function getCollectionOperationMethod(string $resourceClass, string $operationName): string - { - return $this->getOperationMethod($resourceClass, $operationName, OperationType::COLLECTION); - } - - /** - * {@inheritdoc} - */ - public function getItemOperationMethod(string $resourceClass, string $operationName): string - { - return $this->getOperationMethod($resourceClass, $operationName, OperationType::ITEM); - } - - /** - * {@inheritdoc} - */ - public function getCollectionOperationRoute(string $resourceClass, string $operationName): Route - { - return $this->getOperationRoute($resourceClass, $operationName, OperationType::COLLECTION); - } - - /** - * {@inheritdoc} - */ - public function getItemOperationRoute(string $resourceClass, string $operationName): Route - { - return $this->getOperationRoute($resourceClass, $operationName, OperationType::ITEM); - } - - /** - * @throws RuntimeException - */ - private function getOperationMethod(string $resourceClass, string $operationName, string $operationType): string - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if (OperationType::ITEM === $operationType) { - $method = $resourceMetadata->getItemOperationAttribute($operationName, 'method'); - } else { - $method = $resourceMetadata->getCollectionOperationAttribute($operationName, 'method'); - } - - if (null !== $method) { - return strtoupper($method); - } - - if (null === $routeName = $this->getRouteName($resourceMetadata, $operationName, $operationType)) { - throw new RuntimeException(sprintf('Either a "route_name" or a "method" operation attribute must exist for the operation "%s" of the resource "%s".', $operationName, $resourceClass)); - } - - return $this->getRoute($routeName)->getMethods()[0] ?? 'GET'; - } - - /** - * Gets the route related to the given operation. - * - * @throws RuntimeException - */ - private function getOperationRoute(string $resourceClass, string $operationName, string $operationType): Route - { - $routeName = $this->getRouteName($this->resourceMetadataFactory->create($resourceClass), $operationName, $operationType); - if (null !== $routeName) { - return $this->getRoute($routeName); - } - - $operationNameKey = sprintf('_api_%s_operation_name', $operationType); - - foreach ($this->router->getRouteCollection()->all() as $routeName => $route) { - $currentResourceClass = $route->getDefault('_api_resource_class'); - $currentOperationName = $route->getDefault($operationNameKey); - - if ($resourceClass === $currentResourceClass && $operationName === $currentOperationName) { - return $route; - } - } - - throw new RuntimeException(sprintf('No route found for operation "%s" for type "%s".', $operationName, $resourceClass)); - } - - /** - * Gets the route name or null if not defined. - */ - private function getRouteName(ResourceMetadata $resourceMetadata, string $operationName, string $operationType): ?string - { - if (OperationType::ITEM === $operationType) { - return $resourceMetadata->getItemOperationAttribute($operationName, 'route_name'); - } - - return $resourceMetadata->getCollectionOperationAttribute($operationName, 'route_name'); - } - - /** - * Gets the route with the given name. - * - * @throws RuntimeException - */ - private function getRoute(string $routeName): Route - { - foreach ($this->router->getRouteCollection() as $name => $route) { - if ($routeName === $name) { - return $route; - } - } - - throw new RuntimeException(sprintf('The route "%s" does not exist.', $routeName)); - } -} diff --git a/src/Core/Bridge/Symfony/Routing/OperationMethodResolverInterface.php b/src/Core/Bridge/Symfony/Routing/OperationMethodResolverInterface.php deleted file mode 100644 index 1b17511229c..00000000000 --- a/src/Core/Bridge/Symfony/Routing/OperationMethodResolverInterface.php +++ /dev/null @@ -1,38 +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\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationMethodResolverInterface as BaseOperationMethodResolverInterface; -use ApiPlatform\Exception\RuntimeException; -use Symfony\Component\Routing\Route; - -/** - * Resolves the HTTP method associated with an operation, extended for Symfony routing. - * - * @author Teoh Han Hui - * - * @deprecated since API Platform 2.5, use the "method" attribute instead - */ -interface OperationMethodResolverInterface extends BaseOperationMethodResolverInterface -{ - /** - * @throws RuntimeException - */ - public function getCollectionOperationRoute(string $resourceClass, string $operationName): Route; - - /** - * @throws RuntimeException - */ - public function getItemOperationRoute(string $resourceClass, string $operationName): Route; -} diff --git a/src/Core/Bridge/Symfony/Routing/RouteNameGenerator.php b/src/Core/Bridge/Symfony/Routing/RouteNameGenerator.php deleted file mode 100644 index 4fce8174b38..00000000000 --- a/src/Core/Bridge/Symfony/Routing/RouteNameGenerator.php +++ /dev/null @@ -1,77 +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\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Api\OperationTypeDeprecationHelper; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Util\Inflector; - -/** - * Generates the Symfony route name associated with an operation name and a resource short name. - * - * @internal - * - * @author Baptiste Meyer - */ -final class RouteNameGenerator -{ - public const ROUTE_NAME_PREFIX = 'api_'; - - private function __construct() - { - } - - /** - * Generates a Symfony route name. - * - * @param string|bool $operationType - * - * @throws InvalidArgumentException - */ - public static function generate(string $operationName, string $resourceShortName, $operationType): string - { - if (OperationType::SUBRESOURCE === $operationType = OperationTypeDeprecationHelper::getOperationType($operationType)) { - throw new InvalidArgumentException('Subresource operations are not supported by the RouteNameGenerator.'); - } - - $operationName = sprintf( - '%s%s_%s', - static::ROUTE_NAME_PREFIX, - self::inflector($resourceShortName), - $operationName - ); - - // prevent api_foo_get_collection_collection - if ("_$operationType" !== substr($operationName, -\strlen("_$operationType"))) { - $operationName .= "_$operationType"; - } - - return $operationName; - } - - /** - * Transforms a given string to a tableized, pluralized string. - * - * @param string $name usually a ResourceMetadata shortname - * - * @return string A string that is a part of the route name - */ - public static function inflector(string $name, bool $pluralize = true): string - { - $name = Inflector::tableize($name); - - return $pluralize ? Inflector::pluralize($name) : $name; - } -} diff --git a/src/Core/Bridge/Symfony/Routing/RouteNameResolver.php b/src/Core/Bridge/Symfony/Routing/RouteNameResolver.php deleted file mode 100644 index 5034f959556..00000000000 --- a/src/Core/Bridge/Symfony/Routing/RouteNameResolver.php +++ /dev/null @@ -1,76 +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\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Api\OperationTypeDeprecationHelper; -use ApiPlatform\Exception\InvalidArgumentException; -use Symfony\Component\Routing\RouterInterface; - -/** - * {@inheritdoc} - * - * @author Kévin Dunglas - */ -final class RouteNameResolver implements RouteNameResolverInterface -{ - private $router; - - public function __construct(RouterInterface $router) - { - $this->router = $router; - } - - /** - * {@inheritdoc} - */ - public function getRouteName(string $resourceClass, $operationType /* , array $context = [] */): string - { - if (\func_num_args() > 2) { - $context = func_get_arg(2); - } else { - $context = []; - } - - $operationType = OperationTypeDeprecationHelper::getOperationType($operationType); - - foreach ($this->router->getRouteCollection()->all() as $routeName => $route) { - $currentResourceClass = $route->getDefault('_api_resource_class'); - $operation = $route->getDefault(sprintf('_api_%s_operation_name', $operationType)); - $methods = $route->getMethods(); - - if ($resourceClass === $currentResourceClass && null !== $operation && (empty($methods) || \in_array('GET', $methods, true))) { - if (OperationType::SUBRESOURCE === $operationType && false === $this->isSameSubresource($context, $route->getDefault('_api_subresource_context'))) { - continue; - } - - return $routeName; - } - } - - throw new InvalidArgumentException(sprintf('No %s route associated with the type "%s".', $operationType, $resourceClass)); - } - - private function isSameSubresource(array $context, array $currentContext): bool - { - $subresources = array_keys($context['subresource_resources']); - $currentSubresources = []; - - foreach ($currentContext['identifiers'] as [$class]) { - $currentSubresources[] = $class; - } - - return $currentSubresources === $subresources; - } -} diff --git a/src/Core/Bridge/Symfony/Routing/RouteNameResolverInterface.php b/src/Core/Bridge/Symfony/Routing/RouteNameResolverInterface.php deleted file mode 100644 index 91078b25cc8..00000000000 --- a/src/Core/Bridge/Symfony/Routing/RouteNameResolverInterface.php +++ /dev/null @@ -1,33 +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\Symfony\Routing; - -use ApiPlatform\Exception\InvalidArgumentException; - -/** - * Resolves the Symfony route name associated with a resource. - * - * @author Teoh Han Hui - */ -interface RouteNameResolverInterface -{ - /** - * Finds the route name for a resource. - * - * @param bool|string $operationType - * - * @throws InvalidArgumentException - */ - public function getRouteName(string $resourceClass, $operationType /* , array $context = [] */): string; -} diff --git a/src/Core/Bridge/Symfony/Routing/Router.php b/src/Core/Bridge/Symfony/Routing/Router.php deleted file mode 100644 index 52326c81c3f..00000000000 --- a/src/Core/Bridge/Symfony/Routing/Router.php +++ /dev/null @@ -1,22 +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\Symfony\Routing; - -class_exists(\ApiPlatform\Symfony\Routing\Router::class); - -if (false) { - final class Router extends \ApiPlatform\Symfony\Routing\Router - { - } -} diff --git a/src/Core/Bridge/Symfony/Routing/RouterOperationPathResolver.php b/src/Core/Bridge/Symfony/Routing/RouterOperationPathResolver.php deleted file mode 100644 index dd2d9c95399..00000000000 --- a/src/Core/Bridge/Symfony/Routing/RouterOperationPathResolver.php +++ /dev/null @@ -1,72 +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\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Api\OperationTypeDeprecationHelper; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\PathResolver\OperationPathResolverInterface; -use Symfony\Component\Routing\RouterInterface; - -/** - * Resolves the operations path using a Symfony route. - * TODO: remove this in 3.0. - * - * @author Guilhem N. - */ -final class RouterOperationPathResolver implements OperationPathResolverInterface -{ - private $router; - private $deferred; - - public function __construct(RouterInterface $router, OperationPathResolverInterface $deferred) - { - $this->router = $router; - $this->deferred = $deferred; - } - - /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - */ - public function resolveOperationPath(string $resourceShortName, array $operation, $operationType/* , string $operationName = null */): string - { - if (\func_num_args() >= 4) { - $operationName = (string) func_get_arg(3); - } else { - @trigger_error(sprintf('Method %s() will have a 4th `string $operationName` argument in version 3.0. Not defining it is deprecated since 2.1.', __METHOD__), \E_USER_DEPRECATED); - - $operationName = null; - } - - if (isset($operation['route_name'])) { - $routeName = $operation['route_name']; - } elseif (OperationType::SUBRESOURCE === $operationType) { - throw new InvalidArgumentException('Subresource operations are not supported by the RouterOperationPathResolver without a route name.'); - } elseif (null === $operationName) { - return $this->deferred->resolveOperationPath($resourceShortName, $operation, OperationTypeDeprecationHelper::getOperationType($operationType), $operationName); - } elseif (isset($operation['uri_template'])) { - return $operation['uri_template']; - } else { - $routeName = RouteNameGenerator::generate($operationName, $resourceShortName, $operationType); - } - - if (!$route = $this->router->getRouteCollection()->get($routeName)) { - throw new InvalidArgumentException(sprintf('The route "%s" of the resource "%s" was not found.', $routeName, $resourceShortName)); - } - - return $route->getPath(); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/EventListener/ValidateListener.php b/src/Core/Bridge/Symfony/Validator/EventListener/ValidateListener.php deleted file mode 100644 index 7cb7f162fee..00000000000 --- a/src/Core/Bridge/Symfony/Validator/EventListener/ValidateListener.php +++ /dev/null @@ -1,90 +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\Symfony\Validator\EventListener; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Symfony\EventListener\ValidateListener as MainValidateListener; -use ApiPlatform\Symfony\Validator\Exception\ValidationException; -use ApiPlatform\Util\RequestAttributesExtractor; -use Psr\Container\ContainerInterface; -use Symfony\Component\HttpKernel\Event\ViewEvent; -use Symfony\Component\Validator\Validator\ValidatorInterface; - -/** - * Validates data. - * - * @deprecated - * - * @author Kévin Dunglas - */ -final class ValidateListener -{ - private $validator; - private $resourceMetadataFactory; - private $container; - - public function __construct(ValidatorInterface $validator, ResourceMetadataFactoryInterface $resourceMetadataFactory, ContainerInterface $container = null) - { - @trigger_error(sprintf('Using "%s" is deprecated since API Platform 2.2 and will not be possible anymore in API Platform 3. Use "%s" instead.', __CLASS__, MainValidateListener::class), \E_USER_DEPRECATED); - - $this->validator = $validator; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->container = $container; - } - - /** - * Validates data returned by the controller if applicable. - * - * @throws ValidationException - */ - public function onKernelView(ViewEvent $event): void - { - $request = $event->getRequest(); - if ( - $request->isMethodSafe() - || $request->isMethod('DELETE') - || !($attributes = RequestAttributesExtractor::extractAttributes($request)) - || !$attributes['receive'] - ) { - return; - } - - $data = $event->getControllerResult(); - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - - $validationGroups = $resourceMetadata->getOperationAttribute($attributes, 'validation_groups'); - - if (!$validationGroups) { - // Fallback to the resource - $validationGroups = $resourceMetadata->getAttributes()['validation_groups'] ?? null; - } - - if ( - $this->container && - \is_string($validationGroups) && - $this->container->has($validationGroups) && - ($service = $this->container->get($validationGroups)) && - \is_callable($service) - ) { - $validationGroups = $service($data); - } elseif (\is_callable($validationGroups)) { - $validationGroups = $validationGroups($data); - } - - $violations = $this->validator->validate($data, null, (array) $validationGroups); - if (0 !== \count($violations)) { - throw new ValidationException($violations); - } - } -} diff --git a/src/Core/Bridge/Symfony/Validator/EventListener/ValidationExceptionListener.php b/src/Core/Bridge/Symfony/Validator/EventListener/ValidationExceptionListener.php deleted file mode 100644 index a78906ea488..00000000000 --- a/src/Core/Bridge/Symfony/Validator/EventListener/ValidationExceptionListener.php +++ /dev/null @@ -1,22 +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\Symfony\Validator\EventListener; - -class_exists(\ApiPlatform\Symfony\Validator\EventListener\ValidationExceptionListener::class); - -if (false) { - final class ValidationExceptionListener extends \ApiPlatform\Symfony\Validator\EventListener\ValidationExceptionListener - { - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Exception/ValidationException.php b/src/Core/Bridge/Symfony/Validator/Exception/ValidationException.php deleted file mode 100644 index 7893d93d87b..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Exception/ValidationException.php +++ /dev/null @@ -1,22 +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\Symfony\Validator\Exception; - -class_exists(\ApiPlatform\Symfony\Validator\Exception\ValidationException::class); - -if (false) { - final class ValidationException extends \ApiPlatform\Symfony\Validator\Exception\ValidationException - { - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php deleted file mode 100644 index 527f0efb1c8..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaChoiceRestriction.php +++ /dev/null @@ -1,78 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Choice; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaChoiceRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - * - * @param Choice $constraint - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - $choices = []; - - if (\is_callable($constraint->callback)) { - $choices = ($constraint->callback)(); - } elseif (\is_array($constraint->choices)) { - $choices = $constraint->choices; - } - - if (!$choices) { - return []; - } - - $restriction = []; - - if (!$constraint->multiple) { - $restriction['enum'] = $choices; - - return $restriction; - } - - $restriction['type'] = 'array'; - - $type = $propertyMetadata->getType() ?? null; - if ($type) { - $restriction['items'] = ['type' => Type::BUILTIN_TYPE_STRING === $type->getBuiltinType() ? 'string' : 'number', 'enum' => $choices]; - } - - if (null !== $constraint->min) { - $restriction['minItems'] = $constraint->min; - } - - if (null !== $constraint->max) { - $restriction['maxItems'] = $constraint->max; - } - - return $restriction; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof Choice && null !== ($type = $propertyMetadata->getType() ?? null) && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_STRING, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestriction.php deleted file mode 100644 index eecad4e891a..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestriction.php +++ /dev/null @@ -1,96 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Collection; -use Symfony\Component\Validator\Constraints\Optional; -use Symfony\Component\Validator\Constraints\Required; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaCollectionRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * @var iterable - */ - private $restrictionsMetadata; - - /** - * @param iterable $restrictionsMetadata - */ - public function __construct(iterable $restrictionsMetadata = []) - { - $this->restrictionsMetadata = $restrictionsMetadata; - } - - /** - * {@inheritdoc} - * - * @param Collection $constraint - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - $restriction = [ - 'type' => 'object', - 'properties' => [], - 'additionalProperties' => $constraint->allowExtraFields, - ]; - $required = []; - - foreach ($constraint->fields as $field => $baseConstraint) { - /** @var Required|Optional $baseConstraint */ - if ($baseConstraint instanceof Required && !$constraint->allowMissingFields) { - $required[] = $field; - } - - $restriction['properties'][$field] = $this->mergeConstraintRestrictions($baseConstraint, $propertyMetadata); - } - - if ($required) { - $restriction['required'] = $required; - } - - return $restriction; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof Collection; - } - - /** - * @param Required|Optional $constraint - */ - private function mergeConstraintRestrictions(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - $propertyRestrictions = []; - $nestedConstraints = method_exists($constraint, 'getNestedContraints') ? $constraint->getNestedContraints() : $constraint->constraints; - - foreach ($nestedConstraints as $nestedConstraint) { - foreach ($this->restrictionsMetadata as $restrictionMetadata) { - if ($restrictionMetadata->supports($nestedConstraint, $propertyMetadata) && !empty($nestedConstraintRestriction = $restrictionMetadata->create($nestedConstraint, $propertyMetadata))) { - $propertyRestrictions[] = $nestedConstraintRestriction; - } - } - } - - return array_merge([], ...$propertyRestrictions); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCountRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCountRestriction.php deleted file mode 100644 index d09f5835b0b..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCountRestriction.php +++ /dev/null @@ -1,52 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Count; - -/** - * @author Tomas Norkūnas - */ -class PropertySchemaCountRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - * - * @param Count $constraint - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - $restriction = []; - - if (null !== $constraint->min) { - $restriction['minItems'] = $constraint->min; - } - - if (null !== $constraint->max) { - $restriction['maxItems'] = $constraint->max; - } - - return $restriction; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof Count; - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php deleted file mode 100644 index c6cec232b6f..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php +++ /dev/null @@ -1,67 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Email; -use Symfony\Component\Validator\Constraints\Ip; -use Symfony\Component\Validator\Constraints\Ulid; -use Symfony\Component\Validator\Constraints\Uuid; - -/** - * Class PropertySchemaFormat. - * - * @author Andrii Penchuk penja7@gmail.com - */ -class PropertySchemaFormat implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - if ($constraint instanceof Email) { - return ['format' => 'email']; - } - - if ($constraint instanceof Uuid) { - return ['format' => 'uuid']; - } - - if ($constraint instanceof Ulid) { - return ['format' => 'ulid']; - } - - if ($constraint instanceof Ip) { - if ($constraint->version === $constraint::V4) { - return ['format' => 'ipv4']; - } - - return ['format' => 'ipv6']; - } - - return []; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - $schema = $propertyMetadata->getSchema(); - - return empty($schema['format']) && ($constraint instanceof Email || $constraint instanceof Uuid || $constraint instanceof Ulid || $constraint instanceof Ip); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php deleted file mode 100644 index c3d63b5a9e0..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanOrEqualRestriction.php +++ /dev/null @@ -1,45 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaGreaterThanOrEqualRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - * - * @param GreaterThanOrEqual $constraint - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - return [ - 'minimum' => $constraint->value, - ]; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof GreaterThanOrEqual && is_numeric($constraint->value) && ($type = $propertyMetadata->getType() ?? null) && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php deleted file mode 100644 index edde8500593..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaGreaterThanRestriction.php +++ /dev/null @@ -1,46 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\GreaterThan; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaGreaterThanRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - * - * @param GreaterThan $constraint - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - return [ - 'minimum' => $constraint->value, - 'exclusiveMinimum' => true, - ]; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof GreaterThan && is_numeric($constraint->value) && null !== ($type = $propertyMetadata->getType() ?? null) && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php deleted file mode 100644 index 47acf0d8f9e..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php +++ /dev/null @@ -1,53 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Length; - -/** - * Class PropertySchemaLengthRestrictions. - * - * @author Andrii Penchuk penja7@gmail.com - */ -class PropertySchemaLengthRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - $restriction = []; - - if (isset($constraint->min)) { - $restriction['minLength'] = (int) $constraint->min; - } - - if (isset($constraint->max)) { - $restriction['maxLength'] = (int) $constraint->max; - } - - return $restriction; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof Length && null !== ($type = $propertyMetadata->getType()) && Type::BUILTIN_TYPE_STRING === $type->getBuiltinType(); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php deleted file mode 100644 index 1cb0cbfe99d..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanOrEqualRestriction.php +++ /dev/null @@ -1,45 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\LessThanOrEqual; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaLessThanOrEqualRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - * - * @param LessThanOrEqual $constraint - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - return [ - 'maximum' => $constraint->value, - ]; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof LessThanOrEqual && is_numeric($constraint->value) && null !== ($type = $propertyMetadata->getType() ?? null) && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php deleted file mode 100644 index ee1aee51580..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLessThanRestriction.php +++ /dev/null @@ -1,46 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\LessThan; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaLessThanRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - * - * @param LessThan $constraint - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - return [ - 'maximum' => $constraint->value, - 'exclusiveMaximum' => true, - ]; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof LessThan && is_numeric($constraint->value) && null !== ($type = $propertyMetadata->getType() ?? null) && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestriction.php deleted file mode 100644 index badbbcf7a25..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaOneOfRestriction.php +++ /dev/null @@ -1,70 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\AtLeastOneOf; - -/** - * @author Alan Poulain - */ -final class PropertySchemaOneOfRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * @var iterable - */ - private $restrictionsMetadata; - - /** - * @param iterable $restrictionsMetadata - */ - public function __construct(iterable $restrictionsMetadata = []) - { - $this->restrictionsMetadata = $restrictionsMetadata; - } - - /** - * {@inheritdoc} - * - * @param AtLeastOneOf $constraint - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - $oneOfConstraints = method_exists($constraint, 'getNestedContraints') ? $constraint->getNestedContraints() : $constraint->constraints; - $oneOfRestrictions = []; - - foreach ($oneOfConstraints as $oneOfConstraint) { - foreach ($this->restrictionsMetadata as $restrictionMetadata) { - if ($restrictionMetadata->supports($oneOfConstraint, $propertyMetadata) && !empty($oneOfRestriction = $restrictionMetadata->create($oneOfConstraint, $propertyMetadata))) { - $oneOfRestrictions[] = $oneOfRestriction; - } - } - } - - if (!empty($oneOfRestrictions)) { - return ['oneOf' => $oneOfRestrictions]; - } - - return []; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof AtLeastOneOf; - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php deleted file mode 100644 index 51f78cf3e36..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php +++ /dev/null @@ -1,51 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Range; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaRangeRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - $restriction = []; - - if (isset($constraint->min) && is_numeric($constraint->min)) { - $restriction['minimum'] = $constraint->min; - } - - if (isset($constraint->max) && is_numeric($constraint->max)) { - $restriction['maximum'] = $constraint->max; - } - - return $restriction; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof Range && null !== ($type = $propertyMetadata->getType() ?? null) && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true); - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestriction.php deleted file mode 100644 index 5a075b9398e..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestriction.php +++ /dev/null @@ -1,42 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Regex; - -/** - * Class PropertySchemaRegexRestriction. - * - * @author Andrii Penchuk penja7@gmail.com - */ -class PropertySchemaRegexRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - return $constraint instanceof Regex && $constraint->getHtmlPattern() ? ['pattern' => '^('.$constraint->getHtmlPattern().')$'] : []; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof Regex && $constraint->match; - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRestrictionMetadataInterface.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRestrictionMetadataInterface.php deleted file mode 100644 index 2486342de40..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRestrictionMetadataInterface.php +++ /dev/null @@ -1,43 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\Validator\Constraint; - -/** - * Interface PropertySchemaRestrictionsInterface. - * - * @author Andrii Penchuk penja7@gmail.com - */ -interface PropertySchemaRestrictionMetadataInterface -{ - /** - * Creates json schema restrictions based on the validation constraints. - * - * @param Constraint $constraint The validation constraint - * @param PropertyMetadata $propertyMetadata The property metadata - * - * @return array The array of restrictions - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array; - - /** - * Is the constraint supported by the schema restriction? - * - * @param Constraint $constraint The validation constraint - * @param PropertyMetadata $propertyMetadata The property metadata - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool; -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestriction.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestriction.php deleted file mode 100644 index 8a7a0359669..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaUniqueRestriction.php +++ /dev/null @@ -1,40 +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\Symfony\Validator\Metadata\Property\Restriction; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Unique; - -/** - * @author Tomas Norkūnas - */ -final class PropertySchemaUniqueRestriction implements PropertySchemaRestrictionMetadataInterface -{ - /** - * {@inheritdoc} - */ - public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array - { - return ['uniqueItems' => true]; - } - - /** - * {@inheritdoc} - */ - public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool - { - return $constraint instanceof Unique; - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php b/src/Core/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php deleted file mode 100644 index b5953ace1bf..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php +++ /dev/null @@ -1,199 +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\Symfony\Validator\Metadata\Property; - -use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Constraints\Bic; -use Symfony\Component\Validator\Constraints\CardScheme; -use Symfony\Component\Validator\Constraints\Compound; -use Symfony\Component\Validator\Constraints\Currency; -use Symfony\Component\Validator\Constraints\Date; -use Symfony\Component\Validator\Constraints\DateTime; -use Symfony\Component\Validator\Constraints\Email; -use Symfony\Component\Validator\Constraints\File; -use Symfony\Component\Validator\Constraints\Iban; -use Symfony\Component\Validator\Constraints\Image; -use Symfony\Component\Validator\Constraints\Isbn; -use Symfony\Component\Validator\Constraints\Issn; -use Symfony\Component\Validator\Constraints\NotBlank; -use Symfony\Component\Validator\Constraints\NotNull; -use Symfony\Component\Validator\Constraints\Sequentially; -use Symfony\Component\Validator\Constraints\Time; -use Symfony\Component\Validator\Constraints\Url; -use Symfony\Component\Validator\Constraints\Uuid; -use Symfony\Component\Validator\Mapping\ClassMetadataInterface as ValidatorClassMetadataInterface; -use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface as ValidatorMetadataFactoryInterface; -use Symfony\Component\Validator\Mapping\PropertyMetadataInterface as ValidatorPropertyMetadataInterface; - -/** - * Decorates a metadata loader using the validator. - * - * @author Kévin Dunglas - */ -final class ValidatorPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - /** - * @var string[] A list of constraint classes making the entity required - */ - public const REQUIRED_CONSTRAINTS = [NotBlank::class, NotNull::class]; - - public const SCHEMA_MAPPED_CONSTRAINTS = [ - Url::class => 'http://schema.org/url', - Email::class => 'http://schema.org/email', - Uuid::class => 'http://schema.org/identifier', - CardScheme::class => 'http://schema.org/identifier', - Bic::class => 'http://schema.org/identifier', - Iban::class => 'http://schema.org/identifier', - Date::class => 'http://schema.org/Date', - DateTime::class => 'http://schema.org/DateTime', - Time::class => 'http://schema.org/Time', - Image::class => 'http://schema.org/image', - File::class => 'http://schema.org/MediaObject', - Currency::class => 'http://schema.org/priceCurrency', - Isbn::class => 'http://schema.org/isbn', - Issn::class => 'http://schema.org/issn', - ]; - - private $decorated; - private $validatorMetadataFactory; - /** - * @var iterable - */ - private $restrictionsMetadata; - - /** - * @param PropertySchemaRestrictionMetadataInterface[] $restrictionsMetadata - */ - public function __construct(ValidatorMetadataFactoryInterface $validatorMetadataFactory, PropertyMetadataFactoryInterface $decorated, iterable $restrictionsMetadata = []) - { - $this->validatorMetadataFactory = $validatorMetadataFactory; - $this->decorated = $decorated; - $this->restrictionsMetadata = $restrictionsMetadata; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); - - $required = $propertyMetadata->isRequired(); - $iri = $propertyMetadata->getIri(); - $schema = $propertyMetadata->getSchema(); - - if (null !== $required && null !== $iri && null !== $schema) { - return $propertyMetadata; - } - - $validatorClassMetadata = $this->validatorMetadataFactory->getMetadataFor($resourceClass); - - if (!$validatorClassMetadata instanceof ValidatorClassMetadataInterface) { - throw new \UnexpectedValueException(sprintf('Validator class metadata expected to be of type "%s".', ValidatorClassMetadataInterface::class)); - } - - $validationGroups = $this->getValidationGroups($validatorClassMetadata, $options); - $restrictions = []; - - foreach ($validatorClassMetadata->getPropertyMetadata($property) as $validatorPropertyMetadata) { - foreach ($this->getPropertyConstraints($validatorPropertyMetadata, $validationGroups) as $constraint) { - if (null === $required && $this->isRequired($constraint)) { - $required = true; - } - - if (null === $iri) { - $iri = self::SCHEMA_MAPPED_CONSTRAINTS[\get_class($constraint)] ?? null; - } - - foreach ($this->restrictionsMetadata as $restrictionMetadata) { - if ($restrictionMetadata->supports($constraint, $propertyMetadata)) { - $restrictions[] = $restrictionMetadata->create($constraint, $propertyMetadata); - } - } - } - } - - $propertyMetadata = $propertyMetadata->withIri($iri)->withRequired($required ?? false); - - if (!empty($restrictions)) { - if (null === $schema) { - $schema = []; - } - - $schema += array_merge(...$restrictions); - $propertyMetadata = $propertyMetadata->withSchema($schema); - } - - return $propertyMetadata; - } - - /** - * Returns the list of validation groups. - */ - private function getValidationGroups(ValidatorClassMetadataInterface $classMetadata, array $options): array - { - if (isset($options['validation_groups'])) { - return $options['validation_groups']; - } - - if (!method_exists($classMetadata, 'getDefaultGroup')) { - throw new \UnexpectedValueException(sprintf('Validator class metadata expected to have method "%s".', 'getDefaultGroup')); - } - - return [$classMetadata->getDefaultGroup()]; - } - - /** - * Tests if the property is required because of its validation groups. - */ - private function getPropertyConstraints( - ValidatorPropertyMetadataInterface $validatorPropertyMetadata, - array $groups - ): array { - $constraints = []; - - foreach ($groups as $validationGroup) { - if (!\is_string($validationGroup)) { - continue; - } - - foreach ($validatorPropertyMetadata->findConstraints($validationGroup) as $propertyConstraint) { - if ($propertyConstraint instanceof Sequentially || $propertyConstraint instanceof Compound) { - $constraints[] = method_exists($propertyConstraint, 'getNestedContraints') ? $propertyConstraint->getNestedContraints() : $propertyConstraint->getNestedConstraints(); - } else { - $constraints[] = [$propertyConstraint]; - } - } - } - - return array_merge([], ...$constraints); - } - - /** - * Is this constraint making the related property required? - */ - private function isRequired(Constraint $constraint): bool - { - foreach (self::REQUIRED_CONSTRAINTS as $requiredConstraint) { - if ($constraint instanceof $requiredConstraint) { - return true; - } - } - - return false; - } -} diff --git a/src/Core/Bridge/Symfony/Validator/Validator.php b/src/Core/Bridge/Symfony/Validator/Validator.php deleted file mode 100644 index 2fb61bd0647..00000000000 --- a/src/Core/Bridge/Symfony/Validator/Validator.php +++ /dev/null @@ -1,22 +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\Symfony\Validator; - -class_exists(\ApiPlatform\Symfony\Validator\Validator::class); - -if (false) { - class Validator extends \ApiPlatform\Symfony\Validator\Validator - { - } -} diff --git a/src/Core/Cache/CachedTrait.php b/src/Core/Cache/CachedTrait.php deleted file mode 100644 index 3f6ec41f861..00000000000 --- a/src/Core/Cache/CachedTrait.php +++ /dev/null @@ -1,23 +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\Cache; - -class_exists(\ApiPlatform\Util\CachedTrait::class); - -if (false) { - trait CachedTrait - { - use \ApiPlatform\Util\CachedTrait; - } -} diff --git a/src/Core/DataPersister/ChainDataPersister.php b/src/Core/DataPersister/ChainDataPersister.php deleted file mode 100644 index 85e44bb4949..00000000000 --- a/src/Core/DataPersister/ChainDataPersister.php +++ /dev/null @@ -1,87 +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\DataPersister; - -/** - * Chained data persisters. - * - * @author Baptiste Meyer - */ -final class ChainDataPersister implements ContextAwareDataPersisterInterface -{ - /** - * @var iterable - * - * @internal - */ - public $persisters; - - /** - * @param DataPersisterInterface[] $persisters - */ - public function __construct(iterable $persisters) - { - $this->persisters = $persisters; - } - - /** - * {@inheritdoc} - */ - public function supports($data, array $context = []): bool - { - foreach ($this->persisters as $persister) { - if ($persister->supports($data, $context)) { - return true; - } - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function persist($data, array $context = []) - { - foreach ($this->persisters as $persister) { - if ($persister->supports($data, $context)) { - $data = $persister->persist($data, $context); - if ($persister instanceof ResumableDataPersisterInterface && $persister->resumable($context)) { - continue; - } - - return $data; - } - } - - return $data; - } - - /** - * {@inheritdoc} - */ - public function remove($data, array $context = []) - { - foreach ($this->persisters as $persister) { - if ($persister->supports($data, $context)) { - $persister->remove($data, $context); - if ($persister instanceof ResumableDataPersisterInterface && $persister->resumable($context)) { - continue; - } - - return; - } - } - } -} diff --git a/src/Core/DataPersister/ContextAwareDataPersisterInterface.php b/src/Core/DataPersister/ContextAwareDataPersisterInterface.php deleted file mode 100644 index a581418710b..00000000000 --- a/src/Core/DataPersister/ContextAwareDataPersisterInterface.php +++ /dev/null @@ -1,37 +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\DataPersister; - -/** - * Manages data persistence. - * - * @author Antoine Bluchet - */ -interface ContextAwareDataPersisterInterface extends DataPersisterInterface -{ - /** - * {@inheritdoc} - */ - public function supports($data, array $context = []): bool; - - /** - * {@inheritdoc} - */ - public function persist($data, array $context = []); - - /** - * {@inheritdoc} - */ - public function remove($data, array $context = []); -} diff --git a/src/Core/DataPersister/DataPersisterInterface.php b/src/Core/DataPersister/DataPersisterInterface.php deleted file mode 100644 index dc2cee68362..00000000000 --- a/src/Core/DataPersister/DataPersisterInterface.php +++ /dev/null @@ -1,45 +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\DataPersister; - -/** - * Manages data persistence. - * - * @author Baptiste Meyer - */ -interface DataPersisterInterface -{ - /** - * Is the data supported by the persister? - * - * @param mixed $data - */ - public function supports($data): bool; - - /** - * Persists the data. - * - * @param mixed $data - * - * @return object|void Void will not be supported in API Platform 3, an object should always be returned - */ - public function persist($data); - - /** - * Removes the data. - * - * @param mixed $data - */ - public function remove($data); -} diff --git a/src/Core/DataPersister/ResumableDataPersisterInterface.php b/src/Core/DataPersister/ResumableDataPersisterInterface.php deleted file mode 100644 index abfc39c1f84..00000000000 --- a/src/Core/DataPersister/ResumableDataPersisterInterface.php +++ /dev/null @@ -1,26 +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\DataPersister; - -/** - * Control the resumability of the data persister chain. - */ -interface ResumableDataPersisterInterface -{ - /** - * Should we continue calling the next DataPersister or stop after this one? - * Defaults to stop the ChainDatapersister if this interface is not implemented. - */ - public function resumable(array $context = []): bool; -} diff --git a/src/Core/DataProvider/ArrayPaginator.php b/src/Core/DataProvider/ArrayPaginator.php deleted file mode 100644 index c80512b2e21..00000000000 --- a/src/Core/DataProvider/ArrayPaginator.php +++ /dev/null @@ -1,22 +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\DataProvider; - -class_exists(\ApiPlatform\State\Pagination\ArrayPaginator::class); - -if (false) { - final class ArrayPaginator extends \ApiPlatform\State\Pagination\ArrayPaginator - { - } -} diff --git a/src/Core/DataProvider/ChainCollectionDataProvider.php b/src/Core/DataProvider/ChainCollectionDataProvider.php deleted file mode 100644 index 0f0d402a267..00000000000 --- a/src/Core/DataProvider/ChainCollectionDataProvider.php +++ /dev/null @@ -1,56 +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\DataProvider; - -use ApiPlatform\Exception\ResourceClassNotSupportedException; - -/** - * Tries each configured data provider and returns the result of the first able to handle the resource class. - * - * @author Kévin Dunglas - */ -final class ChainCollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface -{ - use RestrictDataProviderTrait; - - /** - * @param CollectionDataProviderInterface[] $dataProviders - */ - public function __construct(iterable $dataProviders) - { - $this->dataProviders = $dataProviders; - } - - /** - * {@inheritdoc} - */ - public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable - { - foreach ($this->dataProviders as $dataProvider) { - try { - if ($dataProvider instanceof RestrictedDataProviderInterface - && !$dataProvider->supports($resourceClass, $operationName, $context)) { - continue; - } - - return $dataProvider->getCollection($resourceClass, $operationName, $context); - } catch (ResourceClassNotSupportedException $e) { - @trigger_error(sprintf('Throwing a "%s" in a data provider is deprecated in favor of implementing "%s"', ResourceClassNotSupportedException::class, RestrictedDataProviderInterface::class), \E_USER_DEPRECATED); - continue; - } - } - - return []; - } -} diff --git a/src/Core/DataProvider/ChainItemDataProvider.php b/src/Core/DataProvider/ChainItemDataProvider.php deleted file mode 100644 index 5be72883a41..00000000000 --- a/src/Core/DataProvider/ChainItemDataProvider.php +++ /dev/null @@ -1,66 +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\DataProvider; - -use ApiPlatform\Exception\ResourceClassNotSupportedException; - -/** - * Tries each configured data provider and returns the result of the first able to handle the resource class. - * - * @author Kévin Dunglas - */ -final class ChainItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface -{ - use RestrictDataProviderTrait; - - /** - * @param ItemDataProviderInterface[] $dataProviders - */ - public function __construct(iterable $dataProviders) - { - $this->dataProviders = $dataProviders; - } - - /** - * {@inheritdoc} - */ - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - foreach ($this->dataProviders as $dataProvider) { - try { - if ($dataProvider instanceof RestrictedDataProviderInterface - && !$dataProvider->supports($resourceClass, $operationName, $context)) { - continue; - } - - $identifier = $id; - if (!$dataProvider instanceof DenormalizedIdentifiersAwareItemDataProviderInterface && $identifier && \is_array($identifier)) { - if (\count($identifier) > 1) { - @trigger_error(sprintf('Receiving "$id" as non-array in an item data provider is deprecated in 2.3 in favor of implementing "%s".', DenormalizedIdentifiersAwareItemDataProviderInterface::class), \E_USER_DEPRECATED); - $identifier = http_build_query($identifier, '', ';'); - } else { - $identifier = current($identifier); - } - } - - return $dataProvider->getItem($resourceClass, $identifier, $operationName, $context); - } catch (ResourceClassNotSupportedException $e) { - @trigger_error(sprintf('Throwing a "%s" is deprecated in favor of implementing "%s"', \get_class($e), RestrictedDataProviderInterface::class), \E_USER_DEPRECATED); - continue; - } - } - - return null; - } -} diff --git a/src/Core/DataProvider/ChainSubresourceDataProvider.php b/src/Core/DataProvider/ChainSubresourceDataProvider.php deleted file mode 100644 index 2a44219b459..00000000000 --- a/src/Core/DataProvider/ChainSubresourceDataProvider.php +++ /dev/null @@ -1,60 +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\DataProvider; - -use ApiPlatform\Exception\ResourceClassNotSupportedException; - -/** - * Tries each configured data provider and returns the result of the first able to handle the resource class. - * - * @author Antoine Bluchet - */ -final class ChainSubresourceDataProvider implements SubresourceDataProviderInterface -{ - /** - * @var iterable - * - * @internal - */ - public $dataProviders; - - /** - * @param SubresourceDataProviderInterface[] $dataProviders - */ - public function __construct(iterable $dataProviders) - { - $this->dataProviders = $dataProviders; - } - - /** - * {@inheritdoc} - */ - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - foreach ($this->dataProviders as $dataProvider) { - try { - if ($dataProvider instanceof RestrictedDataProviderInterface && !$dataProvider->supports($resourceClass, $operationName, $context)) { - continue; - } - - return $dataProvider->getSubresource($resourceClass, $identifiers, $context, $operationName); - } catch (ResourceClassNotSupportedException $e) { - @trigger_error(sprintf('Throwing a "%s" in a data provider is deprecated in favor of implementing "%s"', ResourceClassNotSupportedException::class, RestrictedDataProviderInterface::class), \E_USER_DEPRECATED); - continue; - } - } - - return ($context['collection'] ?? false) ? [] : null; - } -} diff --git a/src/Core/DataProvider/CollectionDataProviderInterface.php b/src/Core/DataProvider/CollectionDataProviderInterface.php deleted file mode 100644 index abd765cf758..00000000000 --- a/src/Core/DataProvider/CollectionDataProviderInterface.php +++ /dev/null @@ -1,33 +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\DataProvider; - -use ApiPlatform\Exception\ResourceClassNotSupportedException; - -/** - * Retrieves items from a persistence layer. - * - * @author Kévin Dunglas - */ -interface CollectionDataProviderInterface -{ - /** - * Retrieves a collection. - * - * @throws ResourceClassNotSupportedException - * - * @return iterable - */ - public function getCollection(string $resourceClass, string $operationName = null); -} diff --git a/src/Core/DataProvider/ContextAwareCollectionDataProviderInterface.php b/src/Core/DataProvider/ContextAwareCollectionDataProviderInterface.php deleted file mode 100644 index 5389ec9bf5d..00000000000 --- a/src/Core/DataProvider/ContextAwareCollectionDataProviderInterface.php +++ /dev/null @@ -1,27 +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\DataProvider; - -/** - * Retrieves items from a persistence layer and allow to pass a context to it. - * - * @author Kévin Dunglas - */ -interface ContextAwareCollectionDataProviderInterface extends CollectionDataProviderInterface -{ - /** - * {@inheritdoc} - */ - public function getCollection(string $resourceClass, string $operationName = null, array $context = []); -} diff --git a/src/Core/DataProvider/DenormalizedIdentifiersAwareItemDataProviderInterface.php b/src/Core/DataProvider/DenormalizedIdentifiersAwareItemDataProviderInterface.php deleted file mode 100644 index 07067dc0b25..00000000000 --- a/src/Core/DataProvider/DenormalizedIdentifiersAwareItemDataProviderInterface.php +++ /dev/null @@ -1,27 +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\DataProvider; - -/** - * Marks data providers able to deal with complex identifiers denormalized as an array. - * - * @author Anthony GRASSIOT - */ -interface DenormalizedIdentifiersAwareItemDataProviderInterface extends ItemDataProviderInterface -{ - /** - * {@inheritdoc} - */ - public function getItem(string $resourceClass, /* array */ $id, string $operationName = null, array $context = []); -} diff --git a/src/Core/DataProvider/ItemDataProviderInterface.php b/src/Core/DataProvider/ItemDataProviderInterface.php deleted file mode 100644 index 5fde5bfbe55..00000000000 --- a/src/Core/DataProvider/ItemDataProviderInterface.php +++ /dev/null @@ -1,35 +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\DataProvider; - -use ApiPlatform\Exception\ResourceClassNotSupportedException; - -/** - * Retrieves items from a persistence layer. - * - * @author Kévin Dunglas - */ -interface ItemDataProviderInterface -{ - /** - * Retrieves an item. - * - * @param array|int|object|string $id - * - * @throws ResourceClassNotSupportedException - * - * @return object|null - */ - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []); -} diff --git a/src/Core/DataProvider/OperationDataProviderTrait.php b/src/Core/DataProvider/OperationDataProviderTrait.php deleted file mode 100644 index 32ad28166d8..00000000000 --- a/src/Core/DataProvider/OperationDataProviderTrait.php +++ /dev/null @@ -1,132 +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\DataProvider; - -use ApiPlatform\Core\Identifier\CompositeIdentifierParser; -use ApiPlatform\Core\Identifier\ContextAwareIdentifierConverterInterface; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Exception\InvalidIdentifierException; -use ApiPlatform\Exception\RuntimeException; - -/** - * @internal - */ -trait OperationDataProviderTrait -{ - /** - * @var CollectionDataProviderInterface - */ - private $collectionDataProvider; - - /** - * @var ItemDataProviderInterface - */ - private $itemDataProvider; - - /** - * @var SubresourceDataProviderInterface|null - */ - private $subresourceDataProvider; - - /** - * @var IdentifierConverterInterface|null - */ - private $identifierConverter; - - /** - * Retrieves data for a collection operation. - */ - private function getCollectionData(array $attributes, array $context): iterable - { - return $this->collectionDataProvider->getCollection($attributes['resource_class'], $attributes['collection_operation_name'], $context); - } - - /** - * Gets data for an item operation. - * - * @param mixed $identifiers - * - * @return object|null - */ - private function getItemData($identifiers, array $attributes, array $context) - { - return $this->itemDataProvider->getItem($attributes['resource_class'], $identifiers, $attributes['item_operation_name'], $context); - } - - /** - * Gets data for a nested operation. - * - * @param mixed $identifiers - * - * @throws RuntimeException - * - * @return array|object|null - */ - private function getSubresourceData($identifiers, array $attributes, array $context) - { - if (null === $this->subresourceDataProvider) { - throw new RuntimeException('Subresources not supported'); - } - - // TODO: SubresourceDataProvider wants: ['id' => ['id' => 1], 'relatedDummies' => ['id' => 2]], identifiers is ['id' => 1, 'relatedDummies' => 2] - $subresourceIdentifiers = []; - foreach ($attributes['identifiers'] as $parameterName => [$class, $property]) { - if (false !== ($attributes['identifiers'][$parameterName][2] ?? null)) { - $subresourceIdentifiers[$parameterName] = [$property => $identifiers[$parameterName]]; - } - } - - return $this->subresourceDataProvider->getSubresource($attributes['resource_class'], $subresourceIdentifiers, $attributes['subresource_context'] + $context, $attributes['subresource_operation_name']); - } - - /** - * @param array $parameters - usually comes from $request->attributes->all() - * - * @throws InvalidIdentifierException - */ - private function extractIdentifiers(array $parameters, array $attributes) - { - $identifiersKeys = $attributes['identifiers'] ?? ['id' => [$attributes['resource_class'], 'id']]; - $identifiers = []; - - $identifiersNumber = \count($identifiersKeys); - foreach ($identifiersKeys as $parameterName => $identifiedBy) { - if (!isset($parameters[$parameterName])) { - if ($attributes['has_composite_identifier']) { - $identifiers = CompositeIdentifierParser::parse($parameters['id']); - if (($currentIdentifiersNumber = \count($identifiers)) !== $identifiersNumber) { - throw new InvalidIdentifierException(sprintf('Expected %d identifiers, got %d', $identifiersNumber, $currentIdentifiersNumber)); - } - - return $this->identifierConverter->convert($identifiers, $identifiedBy[0]); - } - - // TODO: Subresources tuple may have a third item representing if it is a "collection", this behavior will be removed in 3.0 - if (false === ($identifiedBy[2] ?? null)) { - continue; - } - - throw new InvalidIdentifierException(sprintf('Parameter "%s" not found', $parameterName)); - } - - $identifiers[$parameterName] = $parameters[$parameterName]; - } - - if ($this->identifierConverter instanceof ContextAwareIdentifierConverterInterface) { - return $this->identifierConverter->convert($identifiers, $attributes['resource_class'], ['identifiers' => $identifiersKeys]); - } - - return $this->identifierConverter->convert($identifiers, $attributes['resource_class']); - } -} diff --git a/src/Core/DataProvider/Pagination.php b/src/Core/DataProvider/Pagination.php deleted file mode 100644 index f80907cf548..00000000000 --- a/src/Core/DataProvider/Pagination.php +++ /dev/null @@ -1,271 +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\DataProvider; - -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Exception\ResourceClassNotFoundException; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; - -/** - * Pagination configuration. - * - * @author Baptiste Meyer - */ -final class Pagination -{ - private $options; - private $graphQlOptions; - private $resourceMetadataFactory; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $options = [], array $graphQlOptions = []) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->options = array_merge([ - 'enabled' => true, - 'client_enabled' => false, - 'client_items_per_page' => false, - 'items_per_page' => 30, - 'page_default' => 1, - 'page_parameter_name' => 'page', - 'enabled_parameter_name' => 'pagination', - 'items_per_page_parameter_name' => 'itemsPerPage', - 'maximum_items_per_page' => null, - 'partial' => false, - 'client_partial' => false, - 'partial_parameter_name' => 'partial', - ], $options); - $this->graphQlOptions = array_merge([ - 'enabled' => true, - ], $graphQlOptions); - } - - /** - * Gets the current page. - * - * @throws InvalidArgumentException - */ - public function getPage(array $context = []): int - { - $page = (int) $this->getParameterFromContext( - $context, - $this->options['page_parameter_name'], - $this->options['page_default'] - ); - - if (1 > $page) { - throw new InvalidArgumentException('Page should not be less than 1'); - } - - return $page; - } - - /** - * Gets the current offset. - */ - public function getOffset(string $resourceClass = null, string $operationName = null, array $context = []): int - { - $graphql = (bool) ($context['graphql_operation_name'] ?? false); - - $limit = $this->getLimit($resourceClass, $operationName, $context); - - if ($graphql && null !== ($after = $this->getParameterFromContext($context, 'after'))) { - return false === ($after = base64_decode($after, true)) ? 0 : (int) $after + 1; - } - - if ($graphql && null !== ($before = $this->getParameterFromContext($context, 'before'))) { - return ($offset = (false === ($before = base64_decode($before, true)) ? 0 : (int) $before - $limit)) < 0 ? 0 : $offset; - } - - if ($graphql && null !== ($last = $this->getParameterFromContext($context, 'last'))) { - return ($offset = ($context['count'] ?? 0) - $last) < 0 ? 0 : $offset; - } - - $offset = ($this->getPage($context) - 1) * $limit; - - if (!\is_int($offset)) { - throw new InvalidArgumentException('Page parameter is too large.'); - } - - return $offset; - } - - /** - * Gets the current limit. - * - * @throws InvalidArgumentException - */ - public function getLimit(string $resourceClass = null, string $operationName = null, array $context = []): int - { - $graphql = (bool) ($context['graphql_operation_name'] ?? false); - - $limit = $this->options['items_per_page']; - $clientLimit = $this->options['client_items_per_page']; - - if (null !== $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $limit = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_items_per_page', $limit, true); - $clientLimit = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $clientLimit, true); - } - - if ($graphql && null !== ($first = $this->getParameterFromContext($context, 'first'))) { - $limit = $first; - } - - if ($graphql && null !== ($last = $this->getParameterFromContext($context, 'last'))) { - $limit = $last; - } - - if ($graphql && null !== ($before = $this->getParameterFromContext($context, 'before')) - && (false === ($before = base64_decode($before, true)) ? 0 : (int) $before - $limit) < 0) { - $limit = (int) $before; - } - - if ($clientLimit) { - $limit = (int) $this->getParameterFromContext($context, $this->options['items_per_page_parameter_name'], $limit); - $maxItemsPerPage = null; - - if (null !== $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $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->options['maximum_items_per_page'], true); - } - - if (null !== $maxItemsPerPage && $limit > $maxItemsPerPage) { - $limit = $maxItemsPerPage; - } - } - - if (0 > $limit) { - throw new InvalidArgumentException('Limit should not be less than 0'); - } - - return $limit; - } - - /** - * Gets info about the pagination. - * - * Returns an array with the following info as values: - * - the page {@see Pagination::getPage()} - * - the offset {@see Pagination::getOffset()} - * - the limit {@see Pagination::getLimit()} - * - * @throws InvalidArgumentException - */ - public function getPagination(string $resourceClass = null, string $operationName = null, array $context = []): array - { - $page = $this->getPage($context); - $limit = $this->getLimit($resourceClass, $operationName, $context); - - if (0 === $limit && 1 < $page) { - throw new InvalidArgumentException('Page should not be greater than 1 if limit is equal to 0'); - } - - return [$page, $this->getOffset($resourceClass, $operationName, $context), $limit]; - } - - /** - * Is the pagination enabled? - */ - public function isEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool - { - return $this->getEnabled($context, $resourceClass, $operationName); - } - - /** - * Is the pagination enabled for GraphQL? - */ - public function isGraphQlEnabled(?string $resourceClass = null, ?string $operationName = null, array $context = []): bool - { - return $this->getGraphQlEnabled($resourceClass, $operationName); - } - - /** - * Is the partial pagination enabled? - */ - public function isPartialEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool - { - return $this->getEnabled($context, $resourceClass, $operationName, true); - } - - public function getOptions(): array - { - return $this->options; - } - - public function getGraphQlPaginationType(string $resourceClass, string $operationName): string - { - try { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - } catch (ResourceClassNotFoundException $e) { - return 'cursor'; - } - - return (string) $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_type', 'cursor', true); - } - - /** - * Is the classic or partial pagination enabled? - */ - private function getEnabled(array $context, string $resourceClass = null, string $operationName = null, bool $partial = false): bool - { - $enabled = $this->options[$partial ? 'partial' : 'enabled']; - $clientEnabled = $this->options[$partial ? 'client_partial' : 'client_enabled']; - - if (null !== $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $enabled = $resourceMetadata->getCollectionOperationAttribute($operationName, $partial ? 'pagination_partial' : 'pagination_enabled', $enabled, true); - - $clientEnabled = $resourceMetadata->getCollectionOperationAttribute($operationName, $partial ? 'pagination_client_partial' : 'pagination_client_enabled', $clientEnabled, true); - } - - if ($clientEnabled) { - return filter_var($this->getParameterFromContext($context, $this->options[$partial ? 'partial_parameter_name' : 'enabled_parameter_name'], $enabled), \FILTER_VALIDATE_BOOLEAN); - } - - return (bool) $enabled; - } - - private function getGraphQlEnabled(?string $resourceClass, ?string $operationName): bool - { - $enabled = $this->graphQlOptions['enabled']; - - if (null !== $resourceClass) { - try { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - } catch (ResourceClassNotFoundException $e) { - return $enabled; - } - - return (bool) $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_enabled', $enabled, true); - } - - return $enabled; - } - - /** - * Gets the given pagination parameter name from the given context. - * - * @param mixed|null $default - */ - private function getParameterFromContext(array $context, string $parameterName, $default = null) - { - $filters = $context['filters'] ?? []; - - return \array_key_exists($parameterName, $filters) ? $filters[$parameterName] : $default; - } -} diff --git a/src/Core/DataProvider/PaginationOptions.php b/src/Core/DataProvider/PaginationOptions.php deleted file mode 100644 index 504ef049c8a..00000000000 --- a/src/Core/DataProvider/PaginationOptions.php +++ /dev/null @@ -1,22 +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\DataProvider; - -class_exists(\ApiPlatform\State\Pagination\PaginationOptions::class); - -if (false) { - final class PaginationOptions extends \ApiPlatform\State\Pagination\PaginationOptions - { - } -} diff --git a/src/Core/DataProvider/RestrictDataProviderTrait.php b/src/Core/DataProvider/RestrictDataProviderTrait.php deleted file mode 100644 index 4e2570b16f4..00000000000 --- a/src/Core/DataProvider/RestrictDataProviderTrait.php +++ /dev/null @@ -1,32 +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\DataProvider; - -trait RestrictDataProviderTrait -{ - /** @internal */ - public $dataProviders = []; - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - foreach ($this->dataProviders as $dataProvider) { - if ($dataProvider instanceof RestrictedDataProviderInterface - && $dataProvider->supports($resourceClass, $operationName, $context)) { - return true; - } - } - - return false; - } -} diff --git a/src/Core/DataProvider/RestrictedDataProviderInterface.php b/src/Core/DataProvider/RestrictedDataProviderInterface.php deleted file mode 100644 index 5eb5a560540..00000000000 --- a/src/Core/DataProvider/RestrictedDataProviderInterface.php +++ /dev/null @@ -1,22 +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\DataProvider; - -/** - * Restricts a data provider based on a condition. - */ -interface RestrictedDataProviderInterface -{ - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool; -} diff --git a/src/Core/DataProvider/SerializerAwareDataProviderInterface.php b/src/Core/DataProvider/SerializerAwareDataProviderInterface.php deleted file mode 100644 index b3989c5d52d..00000000000 --- a/src/Core/DataProvider/SerializerAwareDataProviderInterface.php +++ /dev/null @@ -1,26 +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\DataProvider; - -use Psr\Container\ContainerInterface; - -/** - * Injects serializer in data providers. - * - * @author Vincent Chalamon - */ -interface SerializerAwareDataProviderInterface -{ - public function setSerializerLocator(ContainerInterface $serializerLocator); -} diff --git a/src/Core/DataProvider/SerializerAwareDataProviderTrait.php b/src/Core/DataProvider/SerializerAwareDataProviderTrait.php deleted file mode 100644 index 275df97d172..00000000000 --- a/src/Core/DataProvider/SerializerAwareDataProviderTrait.php +++ /dev/null @@ -1,43 +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\DataProvider; - -use Psr\Container\ContainerInterface; -use Symfony\Component\Serializer\SerializerInterface; - -/** - * Injects serializer in data providers. - * - * @author Vincent Chalamon - */ -trait SerializerAwareDataProviderTrait -{ - /** - * @internal - * - * @var ContainerInterface - */ - private $serializerLocator; - - public function setSerializerLocator(ContainerInterface $serializerLocator): void - { - $this->serializerLocator = $serializerLocator; - } - - private function getSerializer(): SerializerInterface - { - /* @var SerializerInterface */ - return $this->serializerLocator->get('serializer'); - } -} diff --git a/src/Core/DataProvider/SubresourceDataProviderInterface.php b/src/Core/DataProvider/SubresourceDataProviderInterface.php deleted file mode 100644 index 1b569c0bded..00000000000 --- a/src/Core/DataProvider/SubresourceDataProviderInterface.php +++ /dev/null @@ -1,38 +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\DataProvider; - -use ApiPlatform\Exception\ResourceClassNotSupportedException; - -/** - * Retrieves subresources from a persistence layer. - * - * @author Antoine Bluchet - */ -interface SubresourceDataProviderInterface -{ - /** - * Retrieves a subresource of an item. - * - * @param string $resourceClass The root resource class - * @param array $identifiers Identifiers and their values - * @param array $context The context indicates the conjunction between collection properties (identifiers) and their class - * @param string $operationName - * - * @throws ResourceClassNotSupportedException - * - * @return iterable|object|null - */ - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null); -} diff --git a/src/Core/DataProvider/TraversablePaginator.php b/src/Core/DataProvider/TraversablePaginator.php deleted file mode 100644 index 0a0cd9c791c..00000000000 --- a/src/Core/DataProvider/TraversablePaginator.php +++ /dev/null @@ -1,22 +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\DataProvider; - -class_exists(\ApiPlatform\State\Pagination\TraversablePaginator::class); - -if (false) { - final class TraversablePaginator extends \ApiPlatform\State\Pagination\TraversablePaginator - { - } -} diff --git a/src/Core/DataTransformer/DataTransformerInitializerInterface.php b/src/Core/DataTransformer/DataTransformerInitializerInterface.php deleted file mode 100644 index 85007c818e1..00000000000 --- a/src/Core/DataTransformer/DataTransformerInitializerInterface.php +++ /dev/null @@ -1,26 +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\DataTransformer; - -interface DataTransformerInitializerInterface extends DataTransformerInterface -{ - /** - * Creates a new DTO object that the data will then be serialized into (using object_to_populate). - * - * This is useful to "initialize" the DTO object based on the current resource's data. - * - * @return object|null - */ - public function initialize(string $inputClass, array $context = []); -} diff --git a/src/Core/DataTransformer/DataTransformerInterface.php b/src/Core/DataTransformer/DataTransformerInterface.php deleted file mode 100644 index 156c0ffe437..00000000000 --- a/src/Core/DataTransformer/DataTransformerInterface.php +++ /dev/null @@ -1,39 +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\DataTransformer; - -/** - * Transforms a DTO or an Anonymous class to a Resource object. - * - * @author Antoine Bluchet - */ -interface DataTransformerInterface -{ - /** - * Transforms the given object to something else, usually another object. - * This must return the original object if no transformations have been done. - * - * @param object $object - * - * @return object - */ - public function transform($object, string $to, array $context = []); - - /** - * Checks whether the transformation is supported for a given data and context. - * - * @param object|array $data object on normalize / array on denormalize - */ - public function supportsTransformation($data, string $to, array $context = []): bool; -} diff --git a/src/Core/Documentation/Action/DocumentationAction.php b/src/Core/Documentation/Action/DocumentationAction.php deleted file mode 100644 index e8993c4c910..00000000000 --- a/src/Core/Documentation/Action/DocumentationAction.php +++ /dev/null @@ -1,22 +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\Documentation\Action; - -class_exists(\ApiPlatform\Documentation\Action\DocumentationAction::class); - -if (false) { - final class DocumentationAction extends \ApiPlatform\Documentation\Action\DocumentationAction - { - } -} diff --git a/src/Core/Documentation/Documentation.php b/src/Core/Documentation/Documentation.php deleted file mode 100644 index 190348f24bc..00000000000 --- a/src/Core/Documentation/Documentation.php +++ /dev/null @@ -1,22 +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\Documentation; - -class_exists(\ApiPlatform\Documentation\Documentation::class); - -if (false) { - final class Documentation extends \ApiPlatform\Documentation\Documentation - { - } -} diff --git a/src/Core/EventListener/AddFormatListener.php b/src/Core/EventListener/AddFormatListener.php deleted file mode 100644 index c7f8a0849f6..00000000000 --- a/src/Core/EventListener/AddFormatListener.php +++ /dev/null @@ -1,22 +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\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\AddFormatListener::class); - -if (false) { - final class AddFormatListener extends \ApiPlatform\Symfony\EventListener\AddFormatListener - { - } -} diff --git a/src/Core/EventListener/DeserializeListener.php b/src/Core/EventListener/DeserializeListener.php deleted file mode 100644 index 82b852022aa..00000000000 --- a/src/Core/EventListener/DeserializeListener.php +++ /dev/null @@ -1,22 +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\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\DeserializeListener::class); - -if (false) { - final class DeserializeListener extends \ApiPlatform\Symfony\EventListener\DeserializeListener - { - } -} diff --git a/src/Core/EventListener/EventPriorities.php b/src/Core/EventListener/EventPriorities.php deleted file mode 100644 index 48b44ea76e6..00000000000 --- a/src/Core/EventListener/EventPriorities.php +++ /dev/null @@ -1,22 +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\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\EventPriorities::class); - -if (false) { - final class EventPriorities extends \ApiPlatform\Symfony\EventListener\EventPriorities - { - } -} diff --git a/src/Core/EventListener/ExceptionListener.php b/src/Core/EventListener/ExceptionListener.php deleted file mode 100644 index 1e5a20e9413..00000000000 --- a/src/Core/EventListener/ExceptionListener.php +++ /dev/null @@ -1,22 +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\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\ExceptionListener::class); - -if (false) { - final class ExceptionListener extends \ApiPlatform\Symfony\EventListener\ExceptionListener - { - } -} diff --git a/src/Core/EventListener/QueryParameterValidateListener.php b/src/Core/EventListener/QueryParameterValidateListener.php deleted file mode 100644 index 9db39bd2db2..00000000000 --- a/src/Core/EventListener/QueryParameterValidateListener.php +++ /dev/null @@ -1,22 +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\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\QueryParameterValidateListener::class); - -if (false) { - final class QueryParameterValidateListener extends \ApiPlatform\Symfony\EventListener\QueryParameterValidateListener - { - } -} diff --git a/src/Core/EventListener/ReadListener.php b/src/Core/EventListener/ReadListener.php deleted file mode 100644 index b795c81ea03..00000000000 --- a/src/Core/EventListener/ReadListener.php +++ /dev/null @@ -1,143 +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\EventListener; - -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\OperationDataProviderTrait; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; -use ApiPlatform\Exception\InvalidIdentifierException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Symfony\EventListener\ReadListener as SymfonyReadListener; -use ApiPlatform\Util\CloneTrait; -use ApiPlatform\Util\OperationRequestInitiatorTrait; -use ApiPlatform\Util\RequestAttributesExtractor; -use ApiPlatform\Util\RequestParser; -use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -/** - * Retrieves data from the applicable data provider and sets it as a request parameter called data. - * - * @author Kévin Dunglas - * - * @deprecated - */ -final class ReadListener -{ - use CloneTrait; - use OperationDataProviderTrait; - use OperationRequestInitiatorTrait; - use ToggleableOperationAttributeTrait; - - public const OPERATION_ATTRIBUTE_KEY = 'read'; - - private $serializerContextBuilder; - private $metadataBackwardCompatibilityLayer; - - public function __construct(CollectionDataProviderInterface $collectionDataProvider, ItemDataProviderInterface $itemDataProvider, SubresourceDataProviderInterface $subresourceDataProvider = null, SerializerContextBuilderInterface $serializerContextBuilder = null, IdentifierConverterInterface $identifierConverter = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, bool $metadataBackwardCompatibilityLayer = null) - { - $this->collectionDataProvider = $collectionDataProvider; - $this->itemDataProvider = $itemDataProvider; - $this->subresourceDataProvider = $subresourceDataProvider; - $this->serializerContextBuilder = $serializerContextBuilder; - $this->identifierConverter = $identifierConverter; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; - $this->metadataBackwardCompatibilityLayer = $metadataBackwardCompatibilityLayer; - - if ($metadataBackwardCompatibilityLayer || null === $metadataBackwardCompatibilityLayer) { - trigger_deprecation('api-platform/core', '2.7', sprintf('The listener "%s" is deprecated and will be replaced by "%s" in 3.0.', __CLASS__, SymfonyReadListener::class)); - } - } - - /** - * Calls the data provider and sets the data attribute. - * - * @throws NotFoundHttpException - */ - public function onKernelRequest(RequestEvent $event): void - { - $request = $event->getRequest(); - $operation = $this->initializeOperation($request); - - if ( - !($attributes = RequestAttributesExtractor::extractAttributes($request)) - || !$attributes['receive'] - || ($request->isMethod('POST') && isset($attributes['collection_operation_name'])) - || ($operation && !($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) && !($operation->getExtraProperties()['is_legacy_subresource'] ?? false)) - || ($operation && false === $operation->canRead()) - || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) - ) { - return; - } - - if (false === $this->metadataBackwardCompatibilityLayer) { - trigger_deprecation('api-platform/core', '2.7', 'The operation you requested uses legacy metadata, switch to #[ApiPlatform\Metadata\ApiResource].'); - } - - if (null === $filters = $request->attributes->get('_api_filters')) { - $queryString = RequestParser::getQueryString($request); - $filters = $queryString ? RequestParser::parseRequestParams($queryString) : null; - } - - $context = null === $filters ? [] : ['filters' => $filters]; - if ($this->serializerContextBuilder) { - // Builtin data providers are able to use the serialization context to automatically add join clauses - $context += $normalizationContext = $this->serializerContextBuilder->createFromRequest($request, true, $attributes); - $request->attributes->set('_api_normalization_context', $normalizationContext); - } - - if (isset($attributes['collection_operation_name'])) { - $request->attributes->set('data', $this->getCollectionData($attributes, $context)); - - return; - } - - $data = []; - - if ($this->identifierConverter) { - $context[IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER] = true; - } - - try { - $identifiers = $this->extractIdentifiers($request->attributes->all(), $attributes); - - if (isset($attributes['item_operation_name'])) { - $data = $this->getItemData($identifiers, $attributes, $context); - } elseif (isset($attributes['subresource_operation_name'])) { - // Legacy - if (null === $this->subresourceDataProvider) { - throw new RuntimeException('No subresource data provider.'); - } - - $data = $this->getSubresourceData($identifiers, $attributes, $context); - } - } catch (InvalidIdentifierException $e) { - throw new NotFoundHttpException('Invalid identifier value or configuration.', $e); - } - - if (null === $data) { - throw new NotFoundHttpException('Not Found'); - } - - $request->attributes->set('data', $data); - $request->attributes->set('previous_data', $this->clone($data)); - } -} diff --git a/src/Core/EventListener/RespondListener.php b/src/Core/EventListener/RespondListener.php deleted file mode 100644 index 26530c66564..00000000000 --- a/src/Core/EventListener/RespondListener.php +++ /dev/null @@ -1,22 +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\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\RespondListener::class); - -if (false) { - final class RespondListener extends \ApiPlatform\Symfony\EventListener\RespondListener - { - } -} diff --git a/src/Core/EventListener/SerializeListener.php b/src/Core/EventListener/SerializeListener.php deleted file mode 100644 index 470982b00c1..00000000000 --- a/src/Core/EventListener/SerializeListener.php +++ /dev/null @@ -1,22 +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\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\SerializeListener::class); - -if (false) { - final class SerializeListener extends \ApiPlatform\Symfony\EventListener\SerializeListener - { - } -} diff --git a/src/Core/EventListener/WriteListener.php b/src/Core/EventListener/WriteListener.php deleted file mode 100644 index 870687bfd1e..00000000000 --- a/src/Core/EventListener/WriteListener.php +++ /dev/null @@ -1,134 +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\EventListener; - -use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Util\OperationRequestInitiatorTrait; -use ApiPlatform\Util\RequestAttributesExtractor; -use ApiPlatform\Util\ResourceClassInfoTrait; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\ViewEvent; - -/** - * Bridges persistence and the API system. - * - * @author Kévin Dunglas - * @author Baptiste Meyer - * - * @deprecated - */ -final class WriteListener -{ - use OperationRequestInitiatorTrait; - use ResourceClassInfoTrait; - use ToggleableOperationAttributeTrait; - - public const OPERATION_ATTRIBUTE_KEY = 'write'; - - private $dataPersister; - /** @var LegacyIriConverterInterface|IriConverterInterface|null */ - private $iriConverter; - private $metadataBackwardCompatibilityLayer; - - public function __construct(DataPersisterInterface $dataPersister, $iriConverter = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null, ResourceClassResolverInterface $resourceClassResolver = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ?bool $metadataBackwardCompatibilityLayer = null) - { - $this->dataPersister = $dataPersister; - $this->iriConverter = $iriConverter; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->resourceClassResolver = $resourceClassResolver; - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; - $this->metadataBackwardCompatibilityLayer = $metadataBackwardCompatibilityLayer; - - if ($metadataBackwardCompatibilityLayer || null === $metadataBackwardCompatibilityLayer) { - trigger_deprecation('api-platform/core', '2.7', sprintf('The listener "%s" is deprecated and will be removed in 3.0, use "%s" instead', __CLASS__, \ApiPlatform\Symfony\EventListener\WriteListener::class)); - } - } - - /** - * Persists, updates or delete data return by the controller if applicable. - */ - public function onKernelView(ViewEvent $event): void - { - $controllerResult = $event->getControllerResult(); - $request = $event->getRequest(); - $operation = $this->initializeOperation($request); - - if ( - $controllerResult instanceof Response - || $request->isMethodSafe() - || !($attributes = RequestAttributesExtractor::extractAttributes($request)) - || !$attributes['persist'] - || ($operation && !($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false)) - || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) - ) { - return; - } - - if (false === $this->metadataBackwardCompatibilityLayer) { - trigger_deprecation('api-platform/core', '2.7', 'The operation you requested uses legacy metadata, switch to #[ApiPlatform\Metadata\ApiResource].'); - } - - if (!$this->dataPersister->supports($controllerResult, $attributes)) { - return; - } - - switch ($request->getMethod()) { - case 'PUT': - case 'PATCH': - case 'POST': - $persistResult = $this->dataPersister->persist($controllerResult, $attributes); - - if (!\is_object($persistResult)) { - @trigger_error(sprintf('Not returning an object from %s::persist() is deprecated since API Platform 2.3 and will not be supported in API Platform 3.', DataPersisterInterface::class), \E_USER_DEPRECATED); - } else { - $controllerResult = $persistResult; - $event->setControllerResult($controllerResult); - } - - if ($controllerResult instanceof Response) { - break; - } - - $hasOutput = true; - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - $outputMetadata = $resourceMetadata->getOperationAttribute($attributes, 'output', [ - 'class' => $attributes['resource_class'], - ], true); - - $hasOutput = \array_key_exists('class', $outputMetadata) && null !== $outputMetadata['class']; - } - - if (!$hasOutput) { - break; - } - - if ($this->isResourceClass($this->getObjectClass($controllerResult))) { - $request->attributes->set('_api_write_item_iri', $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($controllerResult) : $this->iriConverter->getIriFromResource($controllerResult)); - } - - break; - case 'DELETE': - $this->dataPersister->remove($controllerResult, $attributes); - $event->setControllerResult(null); - break; - } - } -} diff --git a/src/Core/Exception/DeserializationException.php b/src/Core/Exception/DeserializationException.php deleted file mode 100644 index eb4ba833f02..00000000000 --- a/src/Core/Exception/DeserializationException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\DeserializationException::class); - -if (false) { - class DeserializationException extends \ApiPlatform\Exception\DeserializationException - { - } -} diff --git a/src/Core/Exception/FilterValidationException.php b/src/Core/Exception/FilterValidationException.php deleted file mode 100644 index 3fe044a9f10..00000000000 --- a/src/Core/Exception/FilterValidationException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\FilterValidationException::class); - -if (false) { - final class FilterValidationException extends \ApiPlatform\Exception\FilterValidationException - { - } -} diff --git a/src/Core/Exception/InvalidArgumentException.php b/src/Core/Exception/InvalidArgumentException.php deleted file mode 100644 index 6b2be87d603..00000000000 --- a/src/Core/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\InvalidArgumentException::class); - -if (false) { - class InvalidArgumentException extends \ApiPlatform\Exception\InvalidArgumentException - { - } -} diff --git a/src/Core/Exception/InvalidIdentifierException.php b/src/Core/Exception/InvalidIdentifierException.php deleted file mode 100644 index 2ab797b1b3b..00000000000 --- a/src/Core/Exception/InvalidIdentifierException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\InvalidIdentifierException::class); - -if (false) { - final class InvalidIdentifierException extends \ApiPlatform\Exception\InvalidIdentifierException - { - } -} diff --git a/src/Core/Exception/InvalidResourceException.php b/src/Core/Exception/InvalidResourceException.php deleted file mode 100644 index dafb4df9a64..00000000000 --- a/src/Core/Exception/InvalidResourceException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\InvalidResourceException::class); - -if (false) { - class InvalidResourceException extends \ApiPlatform\Exception\InvalidResourceException - { - } -} diff --git a/src/Core/Exception/InvalidValueException.php b/src/Core/Exception/InvalidValueException.php deleted file mode 100644 index a0fc7cc2bc3..00000000000 --- a/src/Core/Exception/InvalidValueException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\InvalidValueException::class); - -if (false) { - class InvalidValueException extends \ApiPlatform\Exception\InvalidValueException - { - } -} diff --git a/src/Core/Exception/ItemNotFoundException.php b/src/Core/Exception/ItemNotFoundException.php deleted file mode 100644 index d6d83fb3d7c..00000000000 --- a/src/Core/Exception/ItemNotFoundException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\ItemNotFoundException::class); - -if (false) { - class ItemNotFoundException extends \ApiPlatform\Exception\ItemNotFoundException - { - } -} diff --git a/src/Core/Exception/PropertyNotFoundException.php b/src/Core/Exception/PropertyNotFoundException.php deleted file mode 100644 index 1bc4ac9c520..00000000000 --- a/src/Core/Exception/PropertyNotFoundException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\PropertyNotFoundException::class); - -if (false) { - class PropertyNotFoundException extends \ApiPlatform\Exception\PropertyNotFoundException - { - } -} diff --git a/src/Core/Exception/ResourceClassNotFoundException.php b/src/Core/Exception/ResourceClassNotFoundException.php deleted file mode 100644 index 1becbf78554..00000000000 --- a/src/Core/Exception/ResourceClassNotFoundException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\ResourceClassNotFoundException::class); - -if (false) { - class ResourceClassNotFoundException extends \ApiPlatform\Exception\ResourceClassNotFoundException - { - } -} diff --git a/src/Core/Exception/ResourceClassNotSupportedException.php b/src/Core/Exception/ResourceClassNotSupportedException.php deleted file mode 100644 index a30cdcf38e4..00000000000 --- a/src/Core/Exception/ResourceClassNotSupportedException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\ResourceClassNotSupportedException::class); - -if (false) { - class ResourceClassNotSupportedException extends \ApiPlatform\Exception\ResourceClassNotSupportedException - { - } -} diff --git a/src/Core/Exception/RuntimeException.php b/src/Core/Exception/RuntimeException.php deleted file mode 100644 index 3fc53363342..00000000000 --- a/src/Core/Exception/RuntimeException.php +++ /dev/null @@ -1,22 +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\Exception; - -class_exists(\ApiPlatform\Exception\RuntimeException::class); - -if (false) { - class RuntimeException extends \ApiPlatform\Exception\RuntimeException - { - } -} diff --git a/src/Core/Filter/QueryParameterValidator.php b/src/Core/Filter/QueryParameterValidator.php deleted file mode 100644 index 0d080b6be1b..00000000000 --- a/src/Core/Filter/QueryParameterValidator.php +++ /dev/null @@ -1,22 +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\Filter; - -class_exists(\ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator::class); - -if (false) { - class QueryParameterValidator extends \ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator - { - } -} diff --git a/src/Core/Filter/Validator/ArrayItems.php b/src/Core/Filter/Validator/ArrayItems.php deleted file mode 100644 index 99e914f9ec9..00000000000 --- a/src/Core/Filter/Validator/ArrayItems.php +++ /dev/null @@ -1,22 +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\Filter\Validator; - -class_exists(\ApiPlatform\Api\QueryParameterValidator\Validator\ArrayItems::class); - -if (false) { - final class ArrayItems extends \ApiPlatform\Api\QueryParameterValidator\Validator\ArrayItems - { - } -} diff --git a/src/Core/Filter/Validator/Bounds.php b/src/Core/Filter/Validator/Bounds.php deleted file mode 100644 index 6b27ba69bda..00000000000 --- a/src/Core/Filter/Validator/Bounds.php +++ /dev/null @@ -1,22 +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\Filter\Validator; - -class_exists(\ApiPlatform\Api\QueryParameterValidator\Validator\Bounds::class); - -if (false) { - final class Bounds extends \ApiPlatform\Api\QueryParameterValidator\Validator\Bounds - { - } -} diff --git a/src/Core/Filter/Validator/Enum.php b/src/Core/Filter/Validator/Enum.php deleted file mode 100644 index ad3ee478608..00000000000 --- a/src/Core/Filter/Validator/Enum.php +++ /dev/null @@ -1,22 +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\Filter\Validator; - -class_exists(\ApiPlatform\Api\QueryParameterValidator\Validator\Enum::class); - -if (false) { - final class Enum extends \ApiPlatform\Api\QueryParameterValidator\Validator\Enum - { - } -} diff --git a/src/Core/Filter/Validator/Length.php b/src/Core/Filter/Validator/Length.php deleted file mode 100644 index 6c743c0a997..00000000000 --- a/src/Core/Filter/Validator/Length.php +++ /dev/null @@ -1,22 +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\Filter\Validator; - -class_exists(\ApiPlatform\Api\QueryParameterValidator\Validator\Length::class); - -if (false) { - final class Length extends \ApiPlatform\Api\QueryParameterValidator\Validator\Length - { - } -} diff --git a/src/Core/Filter/Validator/MultipleOf.php b/src/Core/Filter/Validator/MultipleOf.php deleted file mode 100644 index fa4f3180cc6..00000000000 --- a/src/Core/Filter/Validator/MultipleOf.php +++ /dev/null @@ -1,22 +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\Filter\Validator; - -class_exists(\ApiPlatform\Api\QueryParameterValidator\Validator\MultipleOf::class); - -if (false) { - final class MultipleOf extends \ApiPlatform\Api\QueryParameterValidator\Validator\MultipleOf - { - } -} diff --git a/src/Core/Filter/Validator/Pattern.php b/src/Core/Filter/Validator/Pattern.php deleted file mode 100644 index bbb0aaa1cdd..00000000000 --- a/src/Core/Filter/Validator/Pattern.php +++ /dev/null @@ -1,22 +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\Filter\Validator; - -class_exists(\ApiPlatform\Api\QueryParameterValidator\Validator\Pattern::class); - -if (false) { - final class Pattern extends \ApiPlatform\Api\QueryParameterValidator\Validator\Pattern - { - } -} diff --git a/src/Core/Filter/Validator/Required.php b/src/Core/Filter/Validator/Required.php deleted file mode 100644 index 71f13283d90..00000000000 --- a/src/Core/Filter/Validator/Required.php +++ /dev/null @@ -1,22 +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\Filter\Validator; - -class_exists(\ApiPlatform\Api\QueryParameterValidator\Validator\Required::class); - -if (false) { - final class Required extends \ApiPlatform\Api\QueryParameterValidator\Validator\Required - { - } -} diff --git a/src/Core/GraphQl/Action/EntrypointAction.php b/src/Core/GraphQl/Action/EntrypointAction.php deleted file mode 100644 index 771447e1ef9..00000000000 --- a/src/Core/GraphQl/Action/EntrypointAction.php +++ /dev/null @@ -1,22 +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\GraphQl\Action; - -class_exists(\ApiPlatform\GraphQl\Action\EntrypointAction::class); - -if (false) { - final class EntrypointAction extends \ApiPlatform\GraphQl\Action\EntrypointAction - { - } -} diff --git a/src/Core/GraphQl/Action/GraphQlPlaygroundAction.php b/src/Core/GraphQl/Action/GraphQlPlaygroundAction.php deleted file mode 100644 index ff553d7aa31..00000000000 --- a/src/Core/GraphQl/Action/GraphQlPlaygroundAction.php +++ /dev/null @@ -1,22 +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\GraphQl\Action; - -class_exists(\ApiPlatform\GraphQl\Action\GraphQlPlaygroundAction::class); - -if (false) { - final class GraphQlPlaygroundAction extends \ApiPlatform\GraphQl\Action\GraphQlPlaygroundAction - { - } -} diff --git a/src/Core/GraphQl/Action/GraphiQlAction.php b/src/Core/GraphQl/Action/GraphiQlAction.php deleted file mode 100644 index ee069ed2b08..00000000000 --- a/src/Core/GraphQl/Action/GraphiQlAction.php +++ /dev/null @@ -1,22 +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\GraphQl\Action; - -class_exists(\ApiPlatform\GraphQl\Action\GraphiQlAction::class); - -if (false) { - final class GraphiQlAction extends \ApiPlatform\GraphQl\Action\GraphiQlAction - { - } -} diff --git a/src/Core/GraphQl/Error/ErrorHandler.php b/src/Core/GraphQl/Error/ErrorHandler.php deleted file mode 100644 index 1061371edeb..00000000000 --- a/src/Core/GraphQl/Error/ErrorHandler.php +++ /dev/null @@ -1,22 +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\GraphQl\Error; - -class_exists(\ApiPlatform\GraphQl\Error\ErrorHandler::class); - -if (false) { - final class ErrorHandler extends \ApiPlatform\GraphQl\Error\ErrorHandler - { - } -} diff --git a/src/Core/GraphQl/Executor.php b/src/Core/GraphQl/Executor.php deleted file mode 100644 index f0cc9948085..00000000000 --- a/src/Core/GraphQl/Executor.php +++ /dev/null @@ -1,22 +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\GraphQl; - -class_exists(\ApiPlatform\GraphQl\Executor::class); - -if (false) { - final class Executor extends \ApiPlatform\GraphQl\Executor - { - } -} diff --git a/src/Core/GraphQl/Resolver/Factory/CollectionResolverFactory.php b/src/Core/GraphQl/Resolver/Factory/CollectionResolverFactory.php deleted file mode 100644 index 8fff34f5643..00000000000 --- a/src/Core/GraphQl/Resolver/Factory/CollectionResolverFactory.php +++ /dev/null @@ -1,105 +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\GraphQl\Resolver\Factory; - -use ApiPlatform\Core\GraphQl\Resolver\QueryCollectionResolverInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Util\CloneTrait; -use GraphQL\Type\Definition\ResolveInfo; -use Psr\Container\ContainerInterface; -use Symfony\Component\HttpFoundation\RequestStack; - -/** - * Creates a function retrieving a collection to resolve a GraphQL query or a field returned by a mutation. - * - * @experimental - * - * @author Alan Poulain - * @author Kévin Dunglas - * @author Vincent Chalamon - */ -final class CollectionResolverFactory implements ResolverFactoryInterface -{ - use CloneTrait; - - private $readStage; - private $securityStage; - private $securityPostDenormalizeStage; - private $serializeStage; - private $queryResolverLocator; - private $requestStack; - private $resourceMetadataFactory; - - public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataFactoryInterface $resourceMetadataFactory, RequestStack $requestStack = null) - { - $this->readStage = $readStage; - $this->securityStage = $securityStage; - $this->securityPostDenormalizeStage = $securityPostDenormalizeStage; - $this->serializeStage = $serializeStage; - $this->queryResolverLocator = $queryResolverLocator; - $this->requestStack = $requestStack; - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable - { - return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) { - if (null === $resourceClass || null === $rootClass) { - return null; - } - - if ($this->requestStack && null !== $request = $this->requestStack->getCurrentRequest()) { - $request->attributes->set( - '_graphql_collections_args', - [$resourceClass => $args] + $request->attributes->get('_graphql_collections_args', []) - ); - } - - $operationName = $operationName ?? 'collection_query'; - $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false]; - - $collection = ($this->readStage)($resourceClass, $rootClass, $operationName, $resolverContext); - if (!is_iterable($collection)) { - throw new \LogicException('Collection from read stage should be iterable.'); - } - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - $queryResolverId = $resourceMetadata->getGraphqlAttribute($operationName, 'collection_query'); - if (null !== $queryResolverId) { - /** @var QueryCollectionResolverInterface $queryResolver */ - $queryResolver = $this->queryResolverLocator->get($queryResolverId); - $collection = $queryResolver($collection, $resolverContext); - } - - ($this->securityStage)($resourceClass, $operationName, $resolverContext + [ - 'extra_variables' => [ - 'object' => $collection, - ], - ]); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [ - 'extra_variables' => [ - 'object' => $collection, - 'previous_object' => $this->clone($collection), - ], - ]); - - return ($this->serializeStage)($collection, $resourceClass, $operationName, $resolverContext); - }; - } -} diff --git a/src/Core/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php b/src/Core/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php deleted file mode 100644 index 45fc233f00a..00000000000 --- a/src/Core/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php +++ /dev/null @@ -1,128 +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\GraphQl\Resolver\Factory; - -use ApiPlatform\Core\GraphQl\Resolver\MutationResolverInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\DeserializeStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\ValidateStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\WriteStageInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Util\ClassInfoTrait; -use ApiPlatform\Core\Util\CloneTrait; -use GraphQL\Type\Definition\ResolveInfo; -use Psr\Container\ContainerInterface; - -/** - * Creates a function resolving a GraphQL mutation of an item. - * - * @experimental - * - * @author Alan Poulain - * @author Vincent Chalamon - */ -final class ItemMutationResolverFactory implements ResolverFactoryInterface -{ - use ClassInfoTrait; - use CloneTrait; - - private $readStage; - private $securityStage; - private $securityPostDenormalizeStage; - private $serializeStage; - private $deserializeStage; - private $writeStage; - private $validateStage; - private $mutationResolverLocator; - private $resourceMetadataFactory; - - public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, DeserializeStageInterface $deserializeStage, WriteStageInterface $writeStage, ValidateStageInterface $validateStage, ContainerInterface $mutationResolverLocator, ResourceMetadataFactoryInterface $resourceMetadataFactory) - { - $this->readStage = $readStage; - $this->securityStage = $securityStage; - $this->securityPostDenormalizeStage = $securityPostDenormalizeStage; - $this->serializeStage = $serializeStage; - $this->deserializeStage = $deserializeStage; - $this->writeStage = $writeStage; - $this->validateStage = $validateStage; - $this->mutationResolverLocator = $mutationResolverLocator; - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable - { - return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) { - if (null === $resourceClass || null === $operationName) { - return null; - } - - $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => true, 'is_subscription' => false]; - - $item = ($this->readStage)($resourceClass, $rootClass, $operationName, $resolverContext); - if (null !== $item && !\is_object($item)) { - throw new \LogicException('Item from read stage should be a nullable object.'); - } - ($this->securityStage)($resourceClass, $operationName, $resolverContext + [ - 'extra_variables' => [ - 'object' => $item, - ], - ]); - $previousItem = $this->clone($item); - - if ('delete' === $operationName) { - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [ - 'extra_variables' => [ - 'object' => $item, - 'previous_object' => $previousItem, - ], - ]); - $item = ($this->writeStage)($item, $resourceClass, $operationName, $resolverContext); - - return ($this->serializeStage)($item, $resourceClass, $operationName, $resolverContext); - } - - $item = ($this->deserializeStage)($item, $resourceClass, $operationName, $resolverContext); - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - $mutationResolverId = $resourceMetadata->getGraphqlAttribute($operationName, 'mutation'); - if (null !== $mutationResolverId) { - /** @var MutationResolverInterface $mutationResolver */ - $mutationResolver = $this->mutationResolverLocator->get($mutationResolverId); - $item = $mutationResolver($item, $resolverContext); - if (null !== $item && $resourceClass !== $itemClass = $this->getObjectClass($item)) { - throw new \LogicException(sprintf('Custom mutation resolver "%s" has to return an item of class %s but returned an item of class %s.', $mutationResolverId, $resourceMetadata->getShortName(), (new \ReflectionClass($itemClass))->getShortName())); - } - } - - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [ - 'extra_variables' => [ - 'object' => $item, - 'previous_object' => $previousItem, - ], - ]); - - if (null !== $item) { - ($this->validateStage)($item, $resourceClass, $operationName, $resolverContext); - - $persistResult = ($this->writeStage)($item, $resourceClass, $operationName, $resolverContext); - } - - return ($this->serializeStage)($persistResult ?? $item, $resourceClass, $operationName, $resolverContext); - }; - } -} diff --git a/src/Core/GraphQl/Resolver/Factory/ItemResolverFactory.php b/src/Core/GraphQl/Resolver/Factory/ItemResolverFactory.php deleted file mode 100644 index f96794dd964..00000000000 --- a/src/Core/GraphQl/Resolver/Factory/ItemResolverFactory.php +++ /dev/null @@ -1,128 +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\GraphQl\Resolver\Factory; - -use ApiPlatform\Core\GraphQl\Resolver\QueryItemResolverInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Util\ClassInfoTrait; -use ApiPlatform\Core\Util\CloneTrait; -use GraphQL\Type\Definition\ResolveInfo; -use Psr\Container\ContainerInterface; - -/** - * Creates a function retrieving an item to resolve a GraphQL query. - * - * @experimental - * - * @author Alan Poulain - * @author Kévin Dunglas - * @author Vincent Chalamon - */ -final class ItemResolverFactory implements ResolverFactoryInterface -{ - use ClassInfoTrait; - use CloneTrait; - - private $readStage; - private $securityStage; - private $securityPostDenormalizeStage; - private $serializeStage; - private $queryResolverLocator; - private $resourceMetadataFactory; - - public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataFactoryInterface $resourceMetadataFactory) - { - $this->readStage = $readStage; - $this->securityStage = $securityStage; - $this->securityPostDenormalizeStage = $securityPostDenormalizeStage; - $this->serializeStage = $serializeStage; - $this->queryResolverLocator = $queryResolverLocator; - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable - { - return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) { - // Data already fetched and normalized (field or nested resource) - if ($source && \array_key_exists($info->fieldName, $source)) { - return $source[$info->fieldName]; - } - - $operationName = $operationName ?? 'item_query'; - $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => false]; - - $item = ($this->readStage)($resourceClass, $rootClass, $operationName, $resolverContext); - if (null !== $item && !\is_object($item)) { - throw new \LogicException('Item from read stage should be a nullable object.'); - } - - $resourceClass = $this->getResourceClass($item, $resourceClass); - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - $queryResolverId = $resourceMetadata->getGraphqlAttribute($operationName, 'item_query'); - if (null !== $queryResolverId) { - /** @var QueryItemResolverInterface $queryResolver */ - $queryResolver = $this->queryResolverLocator->get($queryResolverId); - $item = $queryResolver($item, $resolverContext); - $resourceClass = $this->getResourceClass($item, $resourceClass, sprintf('Custom query resolver "%s"', $queryResolverId).' has to return an item of class %s but returned an item of class %s.'); - } - - ($this->securityStage)($resourceClass, $operationName, $resolverContext + [ - 'extra_variables' => [ - 'object' => $item, - ], - ]); - ($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [ - 'extra_variables' => [ - 'object' => $item, - 'previous_object' => $this->clone($item), - ], - ]); - - return ($this->serializeStage)($item, $resourceClass, $operationName, $resolverContext); - }; - } - - /** - * @param object|null $item - * - * @throws \UnexpectedValueException - */ - private function getResourceClass($item, ?string $resourceClass, string $errorMessage = 'Resolver only handles items of class %s but retrieved item is of class %s.'): string - { - if (null === $item) { - if (null === $resourceClass) { - throw new \UnexpectedValueException('Resource class cannot be determined.'); - } - - return $resourceClass; - } - - $itemClass = $this->getObjectClass($item); - - if (null === $resourceClass) { - return $itemClass; - } - - if ($resourceClass !== $itemClass) { - throw new \UnexpectedValueException(sprintf($errorMessage, (new \ReflectionClass($resourceClass))->getShortName(), (new \ReflectionClass($itemClass))->getShortName())); - } - - return $resourceClass; - } -} diff --git a/src/Core/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php b/src/Core/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php deleted file mode 100644 index 33b86c409ec..00000000000 --- a/src/Core/GraphQl/Resolver/Factory/ItemSubscriptionResolverFactory.php +++ /dev/null @@ -1,92 +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\GraphQl\Resolver\Factory; - -use ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface; -use ApiPlatform\Core\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface; -use ApiPlatform\Core\GraphQl\Subscription\SubscriptionManagerInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Util\ClassInfoTrait; -use ApiPlatform\Core\Util\CloneTrait; -use GraphQL\Type\Definition\ResolveInfo; - -/** - * Creates a function resolving a GraphQL subscription of an item. - * - * @experimental - * - * @author Alan Poulain - */ -final class ItemSubscriptionResolverFactory implements ResolverFactoryInterface -{ - use ClassInfoTrait; - use CloneTrait; - - private $readStage; - private $securityStage; - private $serializeStage; - private $resourceMetadataFactory; - private $subscriptionManager; - private $mercureSubscriptionIriGenerator; - - public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SerializeStageInterface $serializeStage, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubscriptionManagerInterface $subscriptionManager, ?MercureSubscriptionIriGeneratorInterface $mercureSubscriptionIriGenerator) - { - $this->readStage = $readStage; - $this->securityStage = $securityStage; - $this->serializeStage = $serializeStage; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->subscriptionManager = $subscriptionManager; - $this->mercureSubscriptionIriGenerator = $mercureSubscriptionIriGenerator; - } - - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable - { - return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operationName) { - if (null === $resourceClass || null === $operationName) { - return null; - } - - $resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true]; - - $item = ($this->readStage)($resourceClass, $rootClass, $operationName, $resolverContext); - if (null !== $item && !\is_object($item)) { - throw new \LogicException('Item from read stage should be a nullable object.'); - } - ($this->securityStage)($resourceClass, $operationName, $resolverContext + [ - 'extra_variables' => [ - 'object' => $item, - ], - ]); - - $result = ($this->serializeStage)($item, $resourceClass, $operationName, $resolverContext); - - $subscriptionId = $this->subscriptionManager->retrieveSubscriptionId($resolverContext, $result); - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if ($subscriptionId && ($mercure = $resourceMetadata->getAttribute('mercure', false))) { - if (!$this->mercureSubscriptionIriGenerator) { - throw new \LogicException('Cannot use Mercure for subscriptions when MercureBundle is not installed. Try running "composer require mercure".'); - } - - $hub = \is_array($mercure) ? ($mercure['hub'] ?? null) : null; - $result['mercureUrl'] = $this->mercureSubscriptionIriGenerator->generateMercureUrl($subscriptionId, $hub); - } - - return $result; - }; - } -} diff --git a/src/Core/GraphQl/Resolver/Factory/ResolverFactoryInterface.php b/src/Core/GraphQl/Resolver/Factory/ResolverFactoryInterface.php deleted file mode 100644 index f406bc7a3b2..00000000000 --- a/src/Core/GraphQl/Resolver/Factory/ResolverFactoryInterface.php +++ /dev/null @@ -1,26 +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\GraphQl\Resolver\Factory; - -/** - * Builds a GraphQL resolver. - * - * @experimental - * - * @author Kévin Dunglas - */ -interface ResolverFactoryInterface -{ - public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable; -} diff --git a/src/Core/GraphQl/Resolver/ResourceFieldResolver.php b/src/Core/GraphQl/Resolver/ResourceFieldResolver.php deleted file mode 100644 index 1e1f846e705..00000000000 --- a/src/Core/GraphQl/Resolver/ResourceFieldResolver.php +++ /dev/null @@ -1,56 +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\GraphQl\Resolver; - -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer; -use ApiPlatform\Core\Util\ClassInfoTrait; -use GraphQL\Type\Definition\ResolveInfo; - -/** - * A field resolver that resolves IDs to IRIs and allow to access to the raw ID using the "#id" field. - * - * @experimental - * - * @author Kévin Dunglas - */ -final class ResourceFieldResolver -{ - use ClassInfoTrait; - - private $iriConverter; - - public function __construct(IriConverterInterface $iriConverter) - { - $this->iriConverter = $iriConverter; - } - - public function __invoke(?array $source, array $args, $context, ResolveInfo $info) - { - $property = null; - if ('id' === $info->fieldName && !isset($source['_id']) && isset($source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY])) { - return $this->iriConverter->getItemIriFromResourceClass($source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY]); - } - - if ('_id' === $info->fieldName && !isset($source['_id']) && isset($source['id'])) { - $property = $source['id']; - } elseif (\is_array($source) && isset($source[$info->fieldName])) { - $property = $source[$info->fieldName]; - } elseif (isset($source->{$info->fieldName})) { - $property = $source->{$info->fieldName}; - } - - return $property instanceof \Closure ? $property($source, $args, $context) : $property; - } -} diff --git a/src/Core/GraphQl/Resolver/Stage/DeserializeStage.php b/src/Core/GraphQl/Resolver/Stage/DeserializeStage.php deleted file mode 100644 index 6786a03f191..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/DeserializeStage.php +++ /dev/null @@ -1,65 +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\GraphQl\Resolver\Stage; - -use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer; -use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -/** - * Deserialize stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -final class DeserializeStage implements DeserializeStageInterface -{ - private $resourceMetadataFactory; - private $denormalizer; - private $serializerContextBuilder; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, DenormalizerInterface $denormalizer, SerializerContextBuilderInterface $serializerContextBuilder) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->denormalizer = $denormalizer; - $this->serializerContextBuilder = $serializerContextBuilder; - } - - /** - * {@inheritdoc} - */ - public function __invoke($objectToPopulate, string $resourceClass, string $operationName, array $context) - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (!$resourceMetadata->getGraphqlAttribute($operationName, 'deserialize', true, true)) { - return $objectToPopulate; - } - - $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, false); - if (null !== $objectToPopulate) { - $denormalizationContext[AbstractNormalizer::OBJECT_TO_POPULATE] = $objectToPopulate; - } - - $item = $this->denormalizer->denormalize($context['args']['input'], $resourceClass, ItemNormalizer::FORMAT, $denormalizationContext); - - if (!\is_object($item)) { - throw new \UnexpectedValueException('Expected item to be an object.'); - } - - return $item; - } -} diff --git a/src/Core/GraphQl/Resolver/Stage/DeserializeStageInterface.php b/src/Core/GraphQl/Resolver/Stage/DeserializeStageInterface.php deleted file mode 100644 index be57bbe4126..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/DeserializeStageInterface.php +++ /dev/null @@ -1,31 +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\GraphQl\Resolver\Stage; - -/** - * Deserialize stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -interface DeserializeStageInterface -{ - /** - * @param object|null $objectToPopulate - * - * @return object|null - */ - public function __invoke($objectToPopulate, string $resourceClass, string $operationName, array $context); -} diff --git a/src/Core/GraphQl/Resolver/Stage/ReadStage.php b/src/Core/GraphQl/Resolver/Stage/ReadStage.php deleted file mode 100644 index 344b2f49df0..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/ReadStage.php +++ /dev/null @@ -1,188 +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\GraphQl\Resolver\Stage; - -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\Exception\ItemNotFoundException; -use ApiPlatform\Core\GraphQl\Resolver\Util\IdentifierTrait; -use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer; -use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Util\ArrayTrait; -use ApiPlatform\Core\Util\ClassInfoTrait; -use GraphQL\Type\Definition\ResolveInfo; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -/** - * Read stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -final class ReadStage implements ReadStageInterface -{ - use ArrayTrait; - use ClassInfoTrait; - use IdentifierTrait; - - private $resourceMetadataFactory; - private $iriConverter; - private $collectionDataProvider; - private $subresourceDataProvider; - private $serializerContextBuilder; - private $nestingSeparator; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, IriConverterInterface $iriConverter, ContextAwareCollectionDataProviderInterface $collectionDataProvider, SubresourceDataProviderInterface $subresourceDataProvider, SerializerContextBuilderInterface $serializerContextBuilder, string $nestingSeparator) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->iriConverter = $iriConverter; - $this->collectionDataProvider = $collectionDataProvider; - $this->subresourceDataProvider = $subresourceDataProvider; - $this->serializerContextBuilder = $serializerContextBuilder; - $this->nestingSeparator = $nestingSeparator; - } - - /** - * {@inheritdoc} - */ - public function __invoke(?string $resourceClass, ?string $rootClass, string $operationName, array $context) - { - $resourceMetadata = $resourceClass ? $this->resourceMetadataFactory->create($resourceClass) : null; - if ($resourceMetadata && !$resourceMetadata->getGraphqlAttribute($operationName, 'read', true, true)) { - return $context['is_collection'] ? [] : null; - } - - $args = $context['args']; - $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, true); - - if (!$context['is_collection']) { - $identifier = $this->getIdentifierFromContext($context); - $item = $this->getItem($identifier, $normalizationContext); - - if ($identifier && ($context['is_mutation'] || $context['is_subscription'])) { - if (null === $item) { - throw new NotFoundHttpException(sprintf('Item "%s" not found.', $args['input']['id'])); - } - - if ($resourceClass !== $this->getObjectClass($item)) { - throw new \UnexpectedValueException(sprintf('Item "%s" did not match expected type "%s".', $args['input']['id'], $resourceMetadata->getShortName())); - } - } - - return $item; - } - - if (null === $rootClass) { - return []; - } - - $normalizationContext['filters'] = $this->getNormalizedFilters($args); - - $source = $context['source']; - /** @var ResolveInfo $info */ - $info = $context['info']; - if (isset($source[$rootProperty = $info->fieldName], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY], $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) { - $rootResolvedFields = $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY]; - $rootResolvedClass = $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY]; - $subresourceCollection = $this->getSubresource($rootResolvedClass, $rootResolvedFields, $rootProperty, $resourceClass, $normalizationContext, $operationName); - if (!is_iterable($subresourceCollection)) { - throw new \UnexpectedValueException('Expected subresource collection to be iterable.'); - } - - return $subresourceCollection; - } - - return $this->collectionDataProvider->getCollection($resourceClass, $operationName, $normalizationContext); - } - - /** - * @return object|null - */ - private function getItem(?string $identifier, array $normalizationContext) - { - if (null === $identifier) { - return null; - } - - try { - $item = $this->iriConverter->getItemFromIri($identifier, $normalizationContext); - } catch (ItemNotFoundException $e) { - return null; - } - - return $item; - } - - private function getNormalizedFilters(array $args): array - { - $filters = $args; - - foreach ($filters as $name => $value) { - if (\is_array($value)) { - if (strpos($name, '_list')) { - $name = substr($name, 0, \strlen($name) - \strlen('_list')); - } - - // If the value contains arrays, we need to merge them for the filters to understand this syntax, proper to GraphQL to preserve the order of the arguments. - if ($this->isSequentialArrayOfArrays($value)) { - if (\count($value[0]) > 1) { - $deprecationMessage = "The filter syntax \"$name: {"; - $filterArgsOld = []; - $filterArgsNew = []; - foreach ($value[0] as $filterArgName => $filterArgValue) { - $filterArgsOld[] = "$filterArgName: \"$filterArgValue\""; - $filterArgsNew[] = sprintf('{%s: "%s"}', $filterArgName, $filterArgValue); - } - $deprecationMessage .= sprintf('%s}" is deprecated since API Platform 2.6, use the following syntax instead: "%s: [%s]".', implode(', ', $filterArgsOld), $name, implode(', ', $filterArgsNew)); - @trigger_error($deprecationMessage, \E_USER_DEPRECATED); - } - $value = array_merge(...$value); - } - $filters[$name] = $this->getNormalizedFilters($value); - } - - if (\is_string($name) && strpos($name, $this->nestingSeparator)) { - // Gives a chance to relations/nested fields. - $index = array_search($name, array_keys($filters), true); - $filters = - \array_slice($filters, 0, $index + 1) + - [str_replace($this->nestingSeparator, '.', $name) => $value] + - \array_slice($filters, $index + 1); - } - } - - return $filters; - } - - /** - * @return iterable|object|null - */ - private function getSubresource(string $rootResolvedClass, array $rootResolvedFields, string $rootProperty, string $subresourceClass, array $normalizationContext, string $operationName) - { - $resolvedIdentifiers = []; - $rootIdentifiers = array_keys($rootResolvedFields); - foreach ($rootIdentifiers as $rootIdentifier) { - $resolvedIdentifiers[$rootIdentifier] = [$rootResolvedClass, $rootIdentifier]; - } - - return $this->subresourceDataProvider->getSubresource($subresourceClass, $rootResolvedFields, $normalizationContext + [ - 'property' => $rootProperty, - 'identifiers' => $resolvedIdentifiers, - 'collection' => true, - ], $operationName); - } -} diff --git a/src/Core/GraphQl/Resolver/Stage/ReadStageInterface.php b/src/Core/GraphQl/Resolver/Stage/ReadStageInterface.php deleted file mode 100644 index 7acc16101be..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/ReadStageInterface.php +++ /dev/null @@ -1,29 +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\GraphQl\Resolver\Stage; - -/** - * Read stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -interface ReadStageInterface -{ - /** - * @return object|iterable|null - */ - public function __invoke(?string $resourceClass, ?string $rootClass, string $operationName, array $context); -} diff --git a/src/Core/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php b/src/Core/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php deleted file mode 100644 index a5db9f35bde..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/SecurityPostDenormalizeStage.php +++ /dev/null @@ -1,65 +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\GraphQl\Resolver\Stage; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Security\ResourceAccessCheckerInterface; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; - -/** - * Security post denormalize stage of GraphQL resolvers. - * - * @experimental - * - * @author Vincent Chalamon - */ -final class SecurityPostDenormalizeStage implements SecurityPostDenormalizeStageInterface -{ - private $resourceMetadataFactory; - private $resourceAccessChecker; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ?ResourceAccessCheckerInterface $resourceAccessChecker) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->resourceAccessChecker = $resourceAccessChecker; - } - - /** - * {@inheritdoc} - */ - public function __invoke(string $resourceClass, string $operationName, array $context): void - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - $isGranted = $resourceMetadata->getGraphqlAttribute($operationName, 'security_post_denormalize', null, true); - - if (null === $isGranted) { - // Backward compatibility - $isGranted = $resourceMetadata->getGraphqlAttribute($operationName, 'access_control', null, true); - if (null !== $isGranted) { - @trigger_error('Attribute "access_control" is deprecated since API Platform 2.5, prefer using "security" attribute instead', \E_USER_DEPRECATED); - } - } - - if (null !== $isGranted && null === $this->resourceAccessChecker) { - throw new \LogicException('Cannot check security expression when SecurityBundle is not installed. Try running "composer require symfony/security-bundle".'); - } - - if (null === $isGranted || $this->resourceAccessChecker->isGranted($resourceClass, (string) $isGranted, $context['extra_variables'])) { - return; - } - - throw new AccessDeniedHttpException($resourceMetadata->getGraphqlAttribute($operationName, 'security_post_denormalize_message', 'Access Denied.')); - } -} diff --git a/src/Core/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageInterface.php b/src/Core/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageInterface.php deleted file mode 100644 index d60b951cd8e..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/SecurityPostDenormalizeStageInterface.php +++ /dev/null @@ -1,31 +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\GraphQl\Resolver\Stage; - -use GraphQL\Error\Error; - -/** - * Security post deserialization stage of GraphQL resolvers. - * - * @experimental - * - * @author Vincent Chalamon - */ -interface SecurityPostDenormalizeStageInterface -{ - /** - * @throws Error - */ - public function __invoke(string $resourceClass, string $operationName, array $context): void; -} diff --git a/src/Core/GraphQl/Resolver/Stage/SecurityStage.php b/src/Core/GraphQl/Resolver/Stage/SecurityStage.php deleted file mode 100644 index 6297c6eba4d..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/SecurityStage.php +++ /dev/null @@ -1,57 +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\GraphQl\Resolver\Stage; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Security\ResourceAccessCheckerInterface; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; - -/** - * Security stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -final class SecurityStage implements SecurityStageInterface -{ - private $resourceMetadataFactory; - private $resourceAccessChecker; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ?ResourceAccessCheckerInterface $resourceAccessChecker) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->resourceAccessChecker = $resourceAccessChecker; - } - - /** - * {@inheritdoc} - */ - public function __invoke(string $resourceClass, string $operationName, array $context): void - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - $isGranted = $resourceMetadata->getGraphqlAttribute($operationName, 'security', null, true); - - if (null !== $isGranted && null === $this->resourceAccessChecker) { - throw new \LogicException('Cannot check security expression when SecurityBundle is not installed. Try running "composer require symfony/security-bundle".'); - } - - if (null === $isGranted || $this->resourceAccessChecker->isGranted($resourceClass, (string) $isGranted, $context['extra_variables'])) { - return; - } - - throw new AccessDeniedHttpException($resourceMetadata->getGraphqlAttribute($operationName, 'security_message', 'Access Denied.')); - } -} diff --git a/src/Core/GraphQl/Resolver/Stage/SecurityStageInterface.php b/src/Core/GraphQl/Resolver/Stage/SecurityStageInterface.php deleted file mode 100644 index 1e0c4acbb20..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/SecurityStageInterface.php +++ /dev/null @@ -1,32 +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\GraphQl\Resolver\Stage; - -use GraphQL\Error\Error; - -/** - * Security stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - * @author Vincent Chalamon - */ -interface SecurityStageInterface -{ - /** - * @throws Error - */ - public function __invoke(string $resourceClass, string $operationName, array $context): void; -} diff --git a/src/Core/GraphQl/Resolver/Stage/SerializeStage.php b/src/Core/GraphQl/Resolver/Stage/SerializeStage.php deleted file mode 100644 index 56484bc1c1d..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/SerializeStage.php +++ /dev/null @@ -1,223 +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\GraphQl\Resolver\Stage; - -use ApiPlatform\Core\DataProvider\Pagination; -use ApiPlatform\Core\DataProvider\PaginatorInterface; -use ApiPlatform\Core\DataProvider\PartialPaginatorInterface; -use ApiPlatform\Core\GraphQl\Resolver\Util\IdentifierTrait; -use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer; -use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Serialize stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -final class SerializeStage implements SerializeStageInterface -{ - use IdentifierTrait; - - private $resourceMetadataFactory; - private $normalizer; - private $serializerContextBuilder; - private $pagination; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, SerializerContextBuilderInterface $serializerContextBuilder, Pagination $pagination) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->normalizer = $normalizer; - $this->serializerContextBuilder = $serializerContextBuilder; - $this->pagination = $pagination; - } - - /** - * {@inheritdoc} - */ - public function __invoke($itemOrCollection, string $resourceClass, string $operationName, array $context): ?array - { - $isCollection = $context['is_collection']; - $isMutation = $context['is_mutation']; - $isSubscription = $context['is_subscription']; - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (!$resourceMetadata->getGraphqlAttribute($operationName, 'serialize', true, true)) { - if ($isCollection) { - if ($this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) { - return 'cursor' === $this->pagination->getGraphQlPaginationType($resourceClass, $operationName) ? - $this->getDefaultCursorBasedPaginatedData() : - $this->getDefaultPageBasedPaginatedData(); - } - - return []; - } - - if ($isMutation) { - return $this->getDefaultMutationData($context); - } - - if ($isSubscription) { - return $this->getDefaultSubscriptionData($context); - } - - return null; - } - - $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, true); - - $data = null; - if (!$isCollection) { - if ($isMutation && 'delete' === $operationName) { - $data = ['id' => $this->getIdentifierFromContext($context)]; - } else { - $data = $this->normalizer->normalize($itemOrCollection, ItemNormalizer::FORMAT, $normalizationContext); - } - } - - if ($isCollection && is_iterable($itemOrCollection)) { - if (!$this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) { - $data = []; - foreach ($itemOrCollection as $index => $object) { - $data[$index] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext); - } - } else { - $data = 'cursor' === $this->pagination->getGraphQlPaginationType($resourceClass, $operationName) ? - $this->serializeCursorBasedPaginatedCollection($itemOrCollection, $normalizationContext, $context) : - $this->serializePageBasedPaginatedCollection($itemOrCollection, $normalizationContext); - } - } - - if (null !== $data && !\is_array($data)) { - throw new \UnexpectedValueException('Expected serialized data to be a nullable array.'); - } - - if ($isMutation || $isSubscription) { - $wrapFieldName = lcfirst($resourceMetadata->getShortName()); - - return [$wrapFieldName => $data] + ($isMutation ? $this->getDefaultMutationData($context) : $this->getDefaultSubscriptionData($context)); - } - - return $data; - } - - /** - * @throws \LogicException - * @throws \UnexpectedValueException - */ - private function serializeCursorBasedPaginatedCollection(iterable $collection, array $normalizationContext, array $context): array - { - $args = $context['args']; - - if (!($collection instanceof PartialPaginatorInterface)) { - throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s or %s.', PaginatorInterface::class, PartialPaginatorInterface::class)); - } - - $offset = 0; - $totalItems = 1; // For partial pagination, always consider there is at least one item. - $nbPageItems = $collection->count(); - if (isset($args['after'])) { - $after = base64_decode($args['after'], true); - if (false === $after || '' === $args['after']) { - throw new \UnexpectedValueException('' === $args['after'] ? 'Empty cursor is invalid' : sprintf('Cursor %s is invalid', $args['after'])); - } - $offset = 1 + (int) $after; - } - - if ($collection instanceof PaginatorInterface) { - $totalItems = $collection->getTotalItems(); - - if (isset($args['before'])) { - $before = base64_decode($args['before'], true); - if (false === $before || '' === $args['before']) { - throw new \UnexpectedValueException('' === $args['before'] ? 'Empty cursor is invalid' : sprintf('Cursor %s is invalid', $args['before'])); - } - $offset = (int) $before - $nbPageItems; - } - if (isset($args['last']) && !isset($args['before'])) { - $offset = $totalItems - $args['last']; - } - } - - $offset = 0 > $offset ? 0 : $offset; - - $data = $this->getDefaultCursorBasedPaginatedData(); - if ($totalItems > 0) { - $data['pageInfo']['startCursor'] = base64_encode((string) $offset); - $end = $offset + $nbPageItems - 1; - $data['pageInfo']['endCursor'] = base64_encode((string) ($end >= 0 ? $end : 0)); - $data['pageInfo']['hasPreviousPage'] = $offset > 0; - if ($collection instanceof PaginatorInterface) { - $data['totalCount'] = $totalItems; - $itemsPerPage = $collection->getItemsPerPage(); - $data['pageInfo']['hasNextPage'] = (float) ($itemsPerPage > 0 ? $offset % $itemsPerPage : $offset) + $itemsPerPage * $collection->getCurrentPage() < $totalItems; - } - } - - $index = 0; - foreach ($collection as $object) { - $data['edges'][$index] = [ - 'node' => $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext), - 'cursor' => base64_encode((string) ($index + $offset)), - ]; - ++$index; - } - - return $data; - } - - /** - * @throws \LogicException - */ - private function serializePageBasedPaginatedCollection(iterable $collection, array $normalizationContext): array - { - if (!($collection instanceof PaginatorInterface)) { - throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s.', PaginatorInterface::class)); - } - - $data = $this->getDefaultPageBasedPaginatedData(); - $data['paginationInfo']['totalCount'] = $collection->getTotalItems(); - $data['paginationInfo']['lastPage'] = $collection->getLastPage(); - $data['paginationInfo']['itemsPerPage'] = $collection->getItemsPerPage(); - - foreach ($collection as $object) { - $data['collection'][] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext); - } - - return $data; - } - - private function getDefaultCursorBasedPaginatedData(): array - { - return ['totalCount' => 0., 'edges' => [], 'pageInfo' => ['startCursor' => null, 'endCursor' => null, 'hasNextPage' => false, 'hasPreviousPage' => false]]; - } - - private function getDefaultPageBasedPaginatedData(): array - { - return ['collection' => [], 'paginationInfo' => ['itemsPerPage' => 0., 'totalCount' => 0., 'lastPage' => 0.]]; - } - - private function getDefaultMutationData(array $context): array - { - return ['clientMutationId' => $context['args']['input']['clientMutationId'] ?? null]; - } - - private function getDefaultSubscriptionData(array $context): array - { - return ['clientSubscriptionId' => $context['args']['input']['clientSubscriptionId'] ?? null]; - } -} diff --git a/src/Core/GraphQl/Resolver/Stage/SerializeStageInterface.php b/src/Core/GraphQl/Resolver/Stage/SerializeStageInterface.php deleted file mode 100644 index be96883832e..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/SerializeStageInterface.php +++ /dev/null @@ -1,29 +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\GraphQl\Resolver\Stage; - -/** - * Serialize stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -interface SerializeStageInterface -{ - /** - * @param object|iterable|null $itemOrCollection - */ - public function __invoke($itemOrCollection, string $resourceClass, string $operationName, array $context): ?array; -} diff --git a/src/Core/GraphQl/Resolver/Stage/ValidateStage.php b/src/Core/GraphQl/Resolver/Stage/ValidateStage.php deleted file mode 100644 index 3c46aea7746..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/ValidateStage.php +++ /dev/null @@ -1,50 +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\GraphQl\Resolver\Stage; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Validator\ValidatorInterface; - -/** - * Validate stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -final class ValidateStage implements ValidateStageInterface -{ - private $resourceMetadataFactory; - private $validator; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ValidatorInterface $validator) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->validator = $validator; - } - - /** - * {@inheritdoc} - */ - public function __invoke($object, string $resourceClass, string $operationName, array $context): void - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (!$resourceMetadata->getGraphqlAttribute($operationName, 'validate', true, true)) { - return; - } - - $validationGroups = $resourceMetadata->getGraphqlAttribute($operationName, 'validation_groups', null, true); - $this->validator->validate($object, ['groups' => $validationGroups]); - } -} diff --git a/src/Core/GraphQl/Resolver/Stage/ValidateStageInterface.php b/src/Core/GraphQl/Resolver/Stage/ValidateStageInterface.php deleted file mode 100644 index e798a820671..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/ValidateStageInterface.php +++ /dev/null @@ -1,33 +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\GraphQl\Resolver\Stage; - -use GraphQL\Error\Error; - -/** - * Validate stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -interface ValidateStageInterface -{ - /** - * @param object $object - * - * @throws Error - */ - public function __invoke($object, string $resourceClass, string $operationName, array $context): void; -} diff --git a/src/Core/GraphQl/Resolver/Stage/WriteStage.php b/src/Core/GraphQl/Resolver/Stage/WriteStage.php deleted file mode 100644 index 5d0b9842941..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/WriteStage.php +++ /dev/null @@ -1,68 +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\GraphQl\Resolver\Stage; - -use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; - -/** - * Write stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -final class WriteStage implements WriteStageInterface -{ - private $resourceMetadataFactory; - private $dataPersister; - private $serializerContextBuilder; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ContextAwareDataPersisterInterface $dataPersister, SerializerContextBuilderInterface $serializerContextBuilder) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->dataPersister = $dataPersister; - $this->serializerContextBuilder = $serializerContextBuilder; - } - - /** - * {@inheritdoc} - */ - public function __invoke($data, string $resourceClass, string $operationName, array $context) - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (null === $data || !$resourceMetadata->getGraphqlAttribute($operationName, 'write', true, true)) { - return $data; - } - - $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, false); - - if ('delete' === $operationName) { - $this->dataPersister->remove($data, $denormalizationContext); - - return null; - } - - $persistResult = $this->dataPersister->persist($data, $denormalizationContext); - - if (!\is_object($persistResult)) { - @trigger_error(sprintf('Not returning an object from %s::persist() is deprecated since API Platform 2.3 and will not be supported in API Platform 3.', DataPersisterInterface::class), \E_USER_DEPRECATED); - $persistResult = null; - } - - return $persistResult; - } -} diff --git a/src/Core/GraphQl/Resolver/Stage/WriteStageInterface.php b/src/Core/GraphQl/Resolver/Stage/WriteStageInterface.php deleted file mode 100644 index 1b54363d443..00000000000 --- a/src/Core/GraphQl/Resolver/Stage/WriteStageInterface.php +++ /dev/null @@ -1,31 +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\GraphQl\Resolver\Stage; - -/** - * Write stage of GraphQL resolvers. - * - * @experimental - * - * @author Alan Poulain - */ -interface WriteStageInterface -{ - /** - * @param object|null $data - * - * @return object|null - */ - public function __invoke($data, string $resourceClass, string $operationName, array $context); -} diff --git a/src/Core/GraphQl/Resolver/Util/IdentifierTrait.php b/src/Core/GraphQl/Resolver/Util/IdentifierTrait.php deleted file mode 100644 index 225074b0254..00000000000 --- a/src/Core/GraphQl/Resolver/Util/IdentifierTrait.php +++ /dev/null @@ -1,23 +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\GraphQl\Resolver\Util; - -class_exists(\ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait::class); - -if (false) { - trait IdentifierTrait - { - use \ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait; - } -} diff --git a/src/Core/GraphQl/Serializer/Exception/ErrorNormalizer.php b/src/Core/GraphQl/Serializer/Exception/ErrorNormalizer.php deleted file mode 100644 index 05b214f1c58..00000000000 --- a/src/Core/GraphQl/Serializer/Exception/ErrorNormalizer.php +++ /dev/null @@ -1,44 +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\GraphQl\Serializer\Exception; - -use GraphQL\Error\Error; -use GraphQL\Error\FormattedError; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Normalize GraphQL error (fallback). - * - * @experimental - * - * @author Alan Poulain - */ -final class ErrorNormalizer implements NormalizerInterface -{ - /** - * {@inheritdoc} - */ - public function normalize($object, $format = null, array $context = []): array - { - return FormattedError::createFromException($object); - } - - /** - * {@inheritdoc} - */ - public function supportsNormalization($data, $format = null): bool - { - return $data instanceof Error; - } -} diff --git a/src/Core/GraphQl/Serializer/Exception/HttpExceptionNormalizer.php b/src/Core/GraphQl/Serializer/Exception/HttpExceptionNormalizer.php deleted file mode 100644 index 4c7e97c2b90..00000000000 --- a/src/Core/GraphQl/Serializer/Exception/HttpExceptionNormalizer.php +++ /dev/null @@ -1,52 +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\GraphQl\Serializer\Exception; - -use GraphQL\Error\Error; -use GraphQL\Error\FormattedError; -use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Normalize HTTP exceptions. - * - * @experimental - * - * @author Alan Poulain - */ -final class HttpExceptionNormalizer implements NormalizerInterface -{ - /** - * {@inheritdoc} - */ - public function normalize($object, $format = null, array $context = []): array - { - /** @var HttpExceptionInterface */ - $httpException = $object->getPrevious(); - $error = FormattedError::createFromException($object); - $error['message'] = $httpException->getMessage(); - $error['extensions']['status'] = $statusCode = $httpException->getStatusCode(); - $error['extensions']['category'] = $statusCode < 500 ? 'user' : Error::CATEGORY_INTERNAL; - - return $error; - } - - /** - * {@inheritdoc} - */ - public function supportsNormalization($data, $format = null): bool - { - return $data instanceof Error && $data->getPrevious() instanceof HttpExceptionInterface; - } -} diff --git a/src/Core/GraphQl/Serializer/Exception/RuntimeExceptionNormalizer.php b/src/Core/GraphQl/Serializer/Exception/RuntimeExceptionNormalizer.php deleted file mode 100644 index 61bc85fe7e8..00000000000 --- a/src/Core/GraphQl/Serializer/Exception/RuntimeExceptionNormalizer.php +++ /dev/null @@ -1,49 +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\GraphQl\Serializer\Exception; - -use GraphQL\Error\Error; -use GraphQL\Error\FormattedError; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Normalize runtime exceptions to have the right message in production mode. - * - * @experimental - * - * @author Alan Poulain - */ -final class RuntimeExceptionNormalizer implements NormalizerInterface -{ - /** - * {@inheritdoc} - */ - public function normalize($object, $format = null, array $context = []): array - { - /** @var \RuntimeException */ - $runtimeException = $object->getPrevious(); - $error = FormattedError::createFromException($object); - $error['message'] = $runtimeException->getMessage(); - - return $error; - } - - /** - * {@inheritdoc} - */ - public function supportsNormalization($data, $format = null): bool - { - return $data instanceof Error && $data->getPrevious() instanceof \RuntimeException; - } -} diff --git a/src/Core/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php b/src/Core/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php deleted file mode 100644 index 6e4ad1117dd..00000000000 --- a/src/Core/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php +++ /dev/null @@ -1,82 +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\GraphQl\Serializer\Exception; - -use ApiPlatform\Symfony\Validator\Exception\ValidationException; -use GraphQL\Error\Error; -use GraphQL\Error\FormattedError; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Validator\ConstraintViolation; - -/** - * Normalize validation exceptions. - * - * @experimental - * - * @author Mahmood Bazdar - * @author Alan Poulain - */ -final class ValidationExceptionNormalizer implements NormalizerInterface -{ - private $exceptionToStatus; - - public function __construct(array $exceptionToStatus = []) - { - $this->exceptionToStatus = $exceptionToStatus; - } - - /** - * {@inheritdoc} - */ - public function normalize($object, $format = null, array $context = []): array - { - /** @var ValidationException */ - $validationException = $object->getPrevious(); - $error = FormattedError::createFromException($object); - $error['message'] = $validationException->getMessage(); - - $exceptionClass = \get_class($validationException); - $statusCode = Response::HTTP_UNPROCESSABLE_ENTITY; - - foreach ($this->exceptionToStatus as $class => $status) { - if (is_a($exceptionClass, $class, true)) { - $statusCode = $status; - - break; - } - } - $error['extensions']['status'] = $statusCode; - $error['extensions']['category'] = 'user'; - $error['extensions']['violations'] = []; - - /** @var ConstraintViolation $violation */ - foreach ($validationException->getConstraintViolationList() as $violation) { - $error['extensions']['violations'][] = [ - 'path' => $violation->getPropertyPath(), - 'message' => $violation->getMessage(), - ]; - } - - return $error; - } - - /** - * {@inheritdoc} - */ - public function supportsNormalization($data, $format = null): bool - { - return $data instanceof Error && $data->getPrevious() instanceof ValidationException; - } -} diff --git a/src/Core/GraphQl/Serializer/ItemNormalizer.php b/src/Core/GraphQl/Serializer/ItemNormalizer.php deleted file mode 100644 index 738c4040bdd..00000000000 --- a/src/Core/GraphQl/Serializer/ItemNormalizer.php +++ /dev/null @@ -1,133 +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\GraphQl\Serializer; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Serializer\ItemNormalizer as BaseItemNormalizer; -use ApiPlatform\Core\Util\ClassInfoTrait; -use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; -use Symfony\Component\Serializer\Exception\UnexpectedValueException; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * GraphQL normalizer. - * - * @author Kévin Dunglas - */ -final class ItemNormalizer extends BaseItemNormalizer -{ - use ClassInfoTrait; - - public const FORMAT = 'graphql'; - public const ITEM_RESOURCE_CLASS_KEY = '#itemResourceClass'; - public const ITEM_IDENTIFIERS_KEY = '#itemIdentifiers'; - - private $identifiersExtractor; - - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, ItemDataProviderInterface $itemDataProvider = null, bool $allowPlainIdentifiers = false, LoggerInterface $logger = null, iterable $dataTransformers = [], ResourceMetadataFactoryInterface $resourceMetadataFactory = null) - { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $itemDataProvider, $allowPlainIdentifiers, $logger ?: new NullLogger(), $dataTransformers, $resourceMetadataFactory); - - $this->identifiersExtractor = $identifiersExtractor; - } - - /** - * {@inheritdoc} - */ - public function supportsNormalization($data, $format = null): bool - { - return self::FORMAT === $format && parent::supportsNormalization($data, $format); - } - - /** - * {@inheritdoc} - * - * @throws UnexpectedValueException - * - * @return array|string|int|float|bool|\ArrayObject|null - */ - public function normalize($object, $format = null, array $context = []) - { - if (null !== $this->getOutputClass($this->getObjectClass($object), $context)) { - return parent::normalize($object, $format, $context); - } - - $data = parent::normalize($object, $format, $context); - if (!\is_array($data)) { - throw new UnexpectedValueException('Expected data to be an array.'); - } - - if (!($context['no_resolver_data'] ?? false)) { - $data[self::ITEM_RESOURCE_CLASS_KEY] = $this->getObjectClass($object); - $data[self::ITEM_IDENTIFIERS_KEY] = $this->identifiersExtractor->getIdentifiersFromItem($object); - } - - return $data; - } - - /** - * {@inheritdoc} - */ - protected function normalizeCollectionOfRelations($propertyMetadata, $attributeValue, string $resourceClass, ?string $format, array $context): array - { - // to-many are handled directly by the GraphQL resolver - return []; - } - - /** - * {@inheritdoc} - */ - public function supportsDenormalization($data, $type, $format = null): bool - { - return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format); - } - - /** - * {@inheritdoc} - * - * @return array|bool - */ - protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false) - { - $allowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); - - if (($context['api_denormalize'] ?? false) && \is_array($allowedAttributes) && false !== ($indexId = array_search('id', $allowedAttributes, true))) { - $allowedAttributes[] = '_id'; - array_splice($allowedAttributes, (int) $indexId, 1); - } - - return $allowedAttributes; - } - - /** - * {@inheritdoc} - */ - protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = []): void - { - if ('_id' === $attribute) { - $attribute = 'id'; - } - - parent::setAttributeValue($object, $attribute, $value, $format, $context); - } -} diff --git a/src/Core/GraphQl/Serializer/ObjectNormalizer.php b/src/Core/GraphQl/Serializer/ObjectNormalizer.php deleted file mode 100644 index fe0547429c4..00000000000 --- a/src/Core/GraphQl/Serializer/ObjectNormalizer.php +++ /dev/null @@ -1,97 +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\GraphQl\Serializer; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\Util\ClassInfoTrait; -use Symfony\Component\Serializer\Exception\UnexpectedValueException; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Decorates the output with GraphQL metadata when appropriate, but otherwise just - * passes through to the decorated normalizer. - */ -final class ObjectNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface -{ - use ClassInfoTrait; - - public const FORMAT = 'graphql'; - public const ITEM_RESOURCE_CLASS_KEY = '#itemResourceClass'; - public const ITEM_IDENTIFIERS_KEY = '#itemIdentifiers'; - - private $decorated; - private $iriConverter; - private $identifiersExtractor; - - public function __construct(NormalizerInterface $decorated, IriConverterInterface $iriConverter, IdentifiersExtractorInterface $identifiersExtractor) - { - $this->decorated = $decorated; - $this->iriConverter = $iriConverter; - $this->identifiersExtractor = $identifiersExtractor; - } - - /** - * {@inheritdoc} - */ - public function supportsNormalization($data, $format = null, array $context = []): bool - { - return self::FORMAT === $format && $this->decorated->supportsNormalization($data, $format, $context); - } - - /** - * {@inheritdoc} - */ - public function hasCacheableSupportsMethod(): bool - { - return $this->decorated instanceof CacheableSupportsMethodInterface && $this->decorated->hasCacheableSupportsMethod(); - } - - /** - * {@inheritdoc} - * - * @throws UnexpectedValueException - * - * @return array|string|int|float|bool|\ArrayObject|null - */ - public function normalize($object, $format = null, array $context = []) - { - if (isset($context['api_resource'])) { - $originalResource = $context['api_resource']; - unset($context['api_resource']); - } - - $data = $this->decorated->normalize($object, $format, $context); - if (!\is_array($data)) { - throw new UnexpectedValueException('Expected data to be an array.'); - } - - if (!isset($originalResource)) { - return $data; - } - - if (isset($data['id'])) { - $data['_id'] = $data['id']; - $data['id'] = $this->iriConverter->getIriFromItem($originalResource); - } - - if (!($context['no_resolver_data'] ?? false)) { - $data[self::ITEM_RESOURCE_CLASS_KEY] = $this->getObjectClass($originalResource); - $data[self::ITEM_IDENTIFIERS_KEY] = $this->identifiersExtractor->getIdentifiersFromItem($originalResource); - } - - return $data; - } -} diff --git a/src/Core/GraphQl/Serializer/SerializerContextBuilder.php b/src/Core/GraphQl/Serializer/SerializerContextBuilder.php deleted file mode 100644 index eeeccdabcb6..00000000000 --- a/src/Core/GraphQl/Serializer/SerializerContextBuilder.php +++ /dev/null @@ -1,124 +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\GraphQl\Serializer; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use GraphQL\Type\Definition\ResolveInfo; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Builds the context used by the Symfony Serializer. - * - * @experimental - * - * @author Alan Poulain - */ -final class SerializerContextBuilder implements SerializerContextBuilderInterface -{ - private $resourceMetadataFactory; - private $nameConverter; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ?NameConverterInterface $nameConverter) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->nameConverter = $nameConverter; - } - - public function create(?string $resourceClass, string $operationName, array $resolverContext, bool $normalization): array - { - $resourceMetadata = $resourceClass ? $this->resourceMetadataFactory->create($resourceClass) : null; - - $context = [ - 'resource_class' => $resourceClass, - 'graphql_operation_name' => $operationName, - ]; - - if (isset($resolverContext['fields'])) { - $context['no_resolver_data'] = true; - } - - if ($resourceMetadata) { - $context['input'] = $resourceMetadata->getGraphqlAttribute($operationName, 'input', null, true); - $context['output'] = $resourceMetadata->getGraphqlAttribute($operationName, 'output', null, true); - - $key = $normalization ? 'normalization_context' : 'denormalization_context'; - $context = array_merge($resourceMetadata->getGraphqlAttribute($operationName, $key, [], true), $context); - } - - if ($normalization) { - $context['attributes'] = $this->fieldsToAttributes($resourceClass, $resourceMetadata, $resolverContext, $context); - } - - return $context; - } - - /** - * Retrieves fields, recursively replaces the "_id" key (the raw id) by "id" (the name of the property expected by the Serializer) and flattens edge and node structures (pagination). - */ - private function fieldsToAttributes(?string $resourceClass, ?ResourceMetadata $resourceMetadata, array $resolverContext, array $context): array - { - if (isset($resolverContext['fields'])) { - $fields = $resolverContext['fields']; - } else { - /** @var ResolveInfo $info */ - $info = $resolverContext['info']; - $fields = $info->getFieldSelection(\PHP_INT_MAX); - } - - $attributes = $this->replaceIdKeys($fields['edges']['node'] ?? $fields['collection'] ?? $fields, $resourceClass, $context); - - if ($resolverContext['is_mutation'] || $resolverContext['is_subscription']) { - if (!$resourceMetadata) { - throw new \LogicException('ResourceMetadata should always exist for a mutation or a subscription.'); - } - - $wrapFieldName = lcfirst($resourceMetadata->getShortName()); - - return $attributes[$wrapFieldName] ?? []; - } - - return $attributes; - } - - private function replaceIdKeys(array $fields, ?string $resourceClass, array $context): array - { - $denormalizedFields = []; - - foreach ($fields as $key => $value) { - if ('_id' === $key) { - $denormalizedFields['id'] = $fields['_id']; - - continue; - } - - $denormalizedFields[$this->denormalizePropertyName((string) $key, $resourceClass, $context)] = \is_array($fields[$key]) ? $this->replaceIdKeys($fields[$key], $resourceClass, $context) : $value; - } - - return $denormalizedFields; - } - - private function denormalizePropertyName(string $property, ?string $resourceClass, array $context): string - { - if (null === $this->nameConverter) { - return $property; - } - if ($this->nameConverter instanceof AdvancedNameConverterInterface) { - return $this->nameConverter->denormalize($property, $resourceClass, null, $context); - } - - return $this->nameConverter->denormalize($property); - } -} diff --git a/src/Core/GraphQl/Serializer/SerializerContextBuilderInterface.php b/src/Core/GraphQl/Serializer/SerializerContextBuilderInterface.php deleted file mode 100644 index cacf362fd28..00000000000 --- a/src/Core/GraphQl/Serializer/SerializerContextBuilderInterface.php +++ /dev/null @@ -1,26 +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\GraphQl\Serializer; - -/** - * Builds the context used by the Symfony Serializer. - * - * @experimental - * - * @author Alan Poulain - */ -interface SerializerContextBuilderInterface -{ - public function create(string $resourceClass, string $operationName, array $resolverContext, bool $normalization): array; -} diff --git a/src/Core/GraphQl/Subscription/MercureSubscriptionIriGenerator.php b/src/Core/GraphQl/Subscription/MercureSubscriptionIriGenerator.php deleted file mode 100644 index 7e0b49897de..00000000000 --- a/src/Core/GraphQl/Subscription/MercureSubscriptionIriGenerator.php +++ /dev/null @@ -1,60 +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\GraphQl\Subscription; - -use Symfony\Component\Mercure\HubRegistry; -use Symfony\Component\Routing\RequestContext; - -/** - * Generates Mercure-related IRIs from a subscription ID. - * - * @experimental - * - * @author Alan Poulain - */ -final class MercureSubscriptionIriGenerator implements MercureSubscriptionIriGeneratorInterface -{ - private $requestContext; - private $registry; - - /** - * @param HubRegistry|string $registry - */ - public function __construct(RequestContext $requestContext, $registry) - { - $this->requestContext = $requestContext; - $this->registry = $registry; - } - - public function generateTopicIri(string $subscriptionId): string - { - if ('' === $scheme = $this->requestContext->getScheme()) { - $scheme = 'https'; - } - if ('' === $host = $this->requestContext->getHost()) { - $host = 'api-platform.com'; - } - - return "$scheme://$host/subscriptions/$subscriptionId"; - } - - public function generateMercureUrl(string $subscriptionId, ?string $hub = null): string - { - if (!$this->registry instanceof HubRegistry) { - return sprintf('%s?topic=%s', $this->registry, $this->generateTopicIri($subscriptionId)); - } - - return sprintf('%s?topic=%s', $this->registry->getHub($hub)->getUrl(), $this->generateTopicIri($subscriptionId)); - } -} diff --git a/src/Core/GraphQl/Subscription/MercureSubscriptionIriGeneratorInterface.php b/src/Core/GraphQl/Subscription/MercureSubscriptionIriGeneratorInterface.php deleted file mode 100644 index 8f6d0920fa6..00000000000 --- a/src/Core/GraphQl/Subscription/MercureSubscriptionIriGeneratorInterface.php +++ /dev/null @@ -1,28 +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\GraphQl\Subscription; - -/** - * Generates Mercure-related IRIs from a subscription ID. - * - * @experimental - * - * @author Alan Poulain - */ -interface MercureSubscriptionIriGeneratorInterface -{ - public function generateTopicIri(string $subscriptionId): string; - - public function generateMercureUrl(string $subscriptionId, ?string $hub = null): string; -} diff --git a/src/Core/GraphQl/Subscription/SubscriptionIdentifierGenerator.php b/src/Core/GraphQl/Subscription/SubscriptionIdentifierGenerator.php deleted file mode 100644 index b64786e45b5..00000000000 --- a/src/Core/GraphQl/Subscription/SubscriptionIdentifierGenerator.php +++ /dev/null @@ -1,31 +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\GraphQl\Subscription; - -/** - * Generates an identifier used to identify a subscription. - * - * @experimental - * - * @author Alan Poulain - */ -final class SubscriptionIdentifierGenerator implements SubscriptionIdentifierGeneratorInterface -{ - public function generateSubscriptionIdentifier(array $fields): string - { - unset($fields['mercureUrl'], $fields['clientSubscriptionId']); - - return hash('sha256', print_r($fields, true)); - } -} diff --git a/src/Core/GraphQl/Subscription/SubscriptionIdentifierGeneratorInterface.php b/src/Core/GraphQl/Subscription/SubscriptionIdentifierGeneratorInterface.php deleted file mode 100644 index bcef927f80f..00000000000 --- a/src/Core/GraphQl/Subscription/SubscriptionIdentifierGeneratorInterface.php +++ /dev/null @@ -1,26 +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\GraphQl\Subscription; - -/** - * Generates an identifier used to identify a subscription. - * - * @experimental - * - * @author Alan Poulain - */ -interface SubscriptionIdentifierGeneratorInterface -{ - public function generateSubscriptionIdentifier(array $fields): string; -} diff --git a/src/Core/GraphQl/Subscription/SubscriptionManager.php b/src/Core/GraphQl/Subscription/SubscriptionManager.php deleted file mode 100644 index f8aae7ce6b6..00000000000 --- a/src/Core/GraphQl/Subscription/SubscriptionManager.php +++ /dev/null @@ -1,124 +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\GraphQl\Subscription; - -use ApiPlatform\Core\Api\IriConverterInterface; -use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface; -use ApiPlatform\Core\GraphQl\Resolver\Util\IdentifierTrait; -use ApiPlatform\Core\Util\ResourceClassInfoTrait; -use ApiPlatform\Core\Util\SortTrait; -use GraphQL\Type\Definition\ResolveInfo; -use Psr\Cache\CacheItemPoolInterface; - -/** - * Manages all the queried subscriptions by creating their ID - * and saving to a cache the information needed to publish updated data. - * - * @experimental - * - * @author Alan Poulain - */ -final class SubscriptionManager implements SubscriptionManagerInterface -{ - use IdentifierTrait; - use ResourceClassInfoTrait; - use SortTrait; - - private $subscriptionsCache; - private $subscriptionIdentifierGenerator; - private $serializeStage; - private $iriConverter; - - public function __construct(CacheItemPoolInterface $subscriptionsCache, SubscriptionIdentifierGeneratorInterface $subscriptionIdentifierGenerator, SerializeStageInterface $serializeStage, IriConverterInterface $iriConverter) - { - $this->subscriptionsCache = $subscriptionsCache; - $this->subscriptionIdentifierGenerator = $subscriptionIdentifierGenerator; - $this->serializeStage = $serializeStage; - $this->iriConverter = $iriConverter; - } - - public function retrieveSubscriptionId(array $context, ?array $result): ?string - { - /** @var ResolveInfo $info */ - $info = $context['info']; - $fields = $info->getFieldSelection(\PHP_INT_MAX); - $this->arrayRecursiveSort($fields, 'ksort'); - $iri = $this->getIdentifierFromContext($context); - if (null === $iri) { - return null; - } - $subscriptionsCacheItem = $this->subscriptionsCache->getItem($this->encodeIriToCacheKey($iri)); - $subscriptions = []; - if ($subscriptionsCacheItem->isHit()) { - $subscriptions = $subscriptionsCacheItem->get(); - foreach ($subscriptions as [$subscriptionId, $subscriptionFields, $subscriptionResult]) { - if ($subscriptionFields === $fields) { - return $subscriptionId; - } - } - } - - $subscriptionId = $this->subscriptionIdentifierGenerator->generateSubscriptionIdentifier($fields); - unset($result['clientSubscriptionId']); - $subscriptions[] = [$subscriptionId, $fields, $result]; - $subscriptionsCacheItem->set($subscriptions); - $this->subscriptionsCache->save($subscriptionsCacheItem); - - return $subscriptionId; - } - - /** - * @param object $object - */ - public function getPushPayloads($object): array - { - $iri = $this->iriConverter->getIriFromItem($object); - $subscriptions = $this->getSubscriptionsFromIri($iri); - - $resourceClass = $this->getObjectClass($object); - - $payloads = []; - foreach ($subscriptions as [$subscriptionId, $subscriptionFields, $subscriptionResult]) { - $resolverContext = ['fields' => $subscriptionFields, 'is_collection' => false, 'is_mutation' => false, 'is_subscription' => true]; - - $data = ($this->serializeStage)($object, $resourceClass, 'update', $resolverContext); - unset($data['clientSubscriptionId']); - - if ($data !== $subscriptionResult) { - $payloads[] = [$subscriptionId, $data]; - } - } - - return $payloads; - } - - /** - * @return array - */ - private function getSubscriptionsFromIri(string $iri): array - { - $subscriptionsCacheItem = $this->subscriptionsCache->getItem($this->encodeIriToCacheKey($iri)); - - if ($subscriptionsCacheItem->isHit()) { - return $subscriptionsCacheItem->get(); - } - - return []; - } - - private function encodeIriToCacheKey(string $iri): string - { - return str_replace('/', '_', $iri); - } -} diff --git a/src/Core/GraphQl/Subscription/SubscriptionManagerInterface.php b/src/Core/GraphQl/Subscription/SubscriptionManagerInterface.php deleted file mode 100644 index e745b754b51..00000000000 --- a/src/Core/GraphQl/Subscription/SubscriptionManagerInterface.php +++ /dev/null @@ -1,28 +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\GraphQl\Subscription; - -/** - * Manages all the queried subscriptions and creates their ID. - * - * @experimental - * - * @author Alan Poulain - */ -interface SubscriptionManagerInterface -{ - public function retrieveSubscriptionId(array $context, ?array $result): ?string; - - public function getPushPayloads($object): array; -} diff --git a/src/Core/GraphQl/Type/Definition/IterableType.php b/src/Core/GraphQl/Type/Definition/IterableType.php deleted file mode 100644 index f51fbd7f437..00000000000 --- a/src/Core/GraphQl/Type/Definition/IterableType.php +++ /dev/null @@ -1,22 +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\GraphQl\Type\Definition; - -class_exists(\ApiPlatform\GraphQl\Type\Definition\IterableType::class); - -if (false) { - final class IterableType extends \ApiPlatform\GraphQl\Type\Definition\IterableType - { - } -} diff --git a/src/Core/GraphQl/Type/Definition/UploadType.php b/src/Core/GraphQl/Type/Definition/UploadType.php deleted file mode 100644 index e1222e7f789..00000000000 --- a/src/Core/GraphQl/Type/Definition/UploadType.php +++ /dev/null @@ -1,22 +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\GraphQl\Type\Definition; - -class_exists(\ApiPlatform\GraphQl\Type\Definition\UploadType::class); - -if (false) { - final class UploadType extends \ApiPlatform\GraphQl\Type\Definition\UploadType - { - } -} diff --git a/src/Core/GraphQl/Type/FieldsBuilder.php b/src/Core/GraphQl/Type/FieldsBuilder.php deleted file mode 100644 index ee549828262..00000000000 --- a/src/Core/GraphQl/Type/FieldsBuilder.php +++ /dev/null @@ -1,530 +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\GraphQl\Type; - -use ApiPlatform\Core\DataProvider\Pagination; -use ApiPlatform\Core\GraphQl\Resolver\Factory\ResolverFactoryInterface as ResolverFactoryLegacyInterface; -use ApiPlatform\Core\GraphQl\Type\TypesContainerInterface as TypesContainerLegacyInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface; -use ApiPlatform\GraphQl\Type\Definition\TypeInterface; -use ApiPlatform\GraphQl\Type\TypesContainerInterface; -use ApiPlatform\Util\Inflector; -use GraphQL\Type\Definition\InputObjectType; -use GraphQL\Type\Definition\NullableType; -use GraphQL\Type\Definition\Type as GraphQLType; -use GraphQL\Type\Definition\WrappingType; -use Psr\Container\ContainerInterface; -use Symfony\Component\Config\Definition\Exception\InvalidTypeException; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - -/** - * Builds the GraphQL fields. - * - * @author Alan Poulain - */ -final class FieldsBuilder implements FieldsBuilderInterface -{ - private $propertyNameCollectionFactory; - private $propertyMetadataFactory; - private $resourceMetadataFactory; - /** @var TypesContainerLegacyInterface|TypesContainerInterface */ - private $typesContainer; - private $typeBuilder; - /** @var TypeConverterInterface */ - private $typeConverter; - /** @var ResolverFactoryLegacyInterface|ResolverFactoryInterface */ - private $itemResolverFactory; - /** @var ResolverFactoryLegacyInterface|ResolverFactoryInterface */ - private $collectionResolverFactory; - /** @var ResolverFactoryLegacyInterface|ResolverFactoryInterface */ - private $itemMutationResolverFactory; - /** @var ResolverFactoryLegacyInterface|ResolverFactoryInterface */ - private $itemSubscriptionResolverFactory; - private $filterLocator; - private $pagination; - private $nameConverter; - private $nestingSeparator; - - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, $typesContainer, TypeBuilderInterface $typeBuilder, $typeConverter, $itemResolverFactory, $collectionResolverFactory, $itemMutationResolverFactory, $itemSubscriptionResolverFactory, ContainerInterface $filterLocator, Pagination $pagination, ?NameConverterInterface $nameConverter, string $nestingSeparator) - { - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->typesContainer = $typesContainer; - $this->typeBuilder = $typeBuilder; - $this->typeConverter = $typeConverter; - $this->itemResolverFactory = $itemResolverFactory; - $this->collectionResolverFactory = $collectionResolverFactory; - $this->itemMutationResolverFactory = $itemMutationResolverFactory; - $this->itemSubscriptionResolverFactory = $itemSubscriptionResolverFactory; - $this->filterLocator = $filterLocator; - $this->pagination = $pagination; - $this->nameConverter = $nameConverter; - $this->nestingSeparator = $nestingSeparator; - } - - /** - * {@inheritdoc} - */ - public function getNodeQueryFields(): array - { - return [ - 'type' => $this->typeBuilder->getNodeInterface(), - 'args' => [ - 'id' => ['type' => GraphQLType::nonNull(GraphQLType::id())], - ], - 'resolve' => ($this->itemResolverFactory)(), - ]; - } - - /** - * {@inheritdoc} - */ - public function getItemQueryFields(string $resourceClass, ResourceMetadata $resourceMetadata, string $queryName, array $configuration): array - { - $shortName = $resourceMetadata->getShortName(); - $fieldName = lcfirst('item_query' === $queryName ? $shortName : $queryName.$shortName); - $description = $resourceMetadata->getGraphqlAttribute($queryName, 'description'); - $deprecationReason = $resourceMetadata->getGraphqlAttribute($queryName, 'deprecation_reason', null, true); - - if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass, false, $queryName, null, null)) { - $args = $this->resolveResourceArgs($configuration['args'] ?? [], $queryName, $shortName); - $configuration['args'] = $args ?: $configuration['args'] ?? ['id' => ['type' => GraphQLType::nonNull(GraphQLType::id())]]; - - return [$fieldName => array_merge($fieldConfiguration, $configuration)]; - } - - return []; - } - - /** - * {@inheritdoc} - */ - public function getCollectionQueryFields(string $resourceClass, ResourceMetadata $resourceMetadata, string $queryName, array $configuration): array - { - $shortName = $resourceMetadata->getShortName(); - $fieldName = lcfirst('collection_query' === $queryName ? $shortName : $queryName.$shortName); - $description = $resourceMetadata->getGraphqlAttribute($queryName, 'description'); - $deprecationReason = $resourceMetadata->getGraphqlAttribute($queryName, 'deprecation_reason', null, true); - - if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass, false, $queryName, null, null)) { - $args = $this->resolveResourceArgs($configuration['args'] ?? [], $queryName, $shortName); - $configuration['args'] = $args ?: $configuration['args'] ?? $fieldConfiguration['args']; - - return [Inflector::pluralize($fieldName) => array_merge($fieldConfiguration, $configuration)]; - } - - return []; - } - - /** - * {@inheritdoc} - */ - public function getMutationFields(string $resourceClass, ResourceMetadata $resourceMetadata, string $mutationName): array - { - $mutationFields = []; - $shortName = $resourceMetadata->getShortName(); - $resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass); - $description = $resourceMetadata->getGraphqlAttribute($mutationName, 'description', ucfirst("{$mutationName}s a $shortName."), false); - $deprecationReason = $resourceMetadata->getGraphqlAttribute($mutationName, 'deprecation_reason', null, true); - - if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $deprecationReason, $resourceType, $resourceClass, false, null, $mutationName, null)) { - $fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, null, $deprecationReason, $resourceType, $resourceClass, true, null, $mutationName, null)]; - } - - $mutationFields[$mutationName.$resourceMetadata->getShortName()] = $fieldConfiguration ?? []; - - return $mutationFields; - } - - /** - * {@inheritdoc} - */ - public function getSubscriptionFields(string $resourceClass, ResourceMetadata $resourceMetadata, string $subscriptionName): array - { - $subscriptionFields = []; - $shortName = $resourceMetadata->getShortName(); - $resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass); - $description = $resourceMetadata->getGraphqlAttribute($subscriptionName, 'description', "Subscribes to the $subscriptionName event of a $shortName.", false); - $deprecationReason = $resourceMetadata->getGraphqlAttribute($subscriptionName, 'deprecation_reason', null, true); - - if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $description, $deprecationReason, $resourceType, $resourceClass, false, null, null, $subscriptionName)) { - $fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, null, $deprecationReason, $resourceType, $resourceClass, true, null, null, $subscriptionName)]; - } - - if (!$fieldConfiguration) { - return []; - } - - $subscriptionFields[$subscriptionName.$resourceMetadata->getShortName().'Subscribe'] = $fieldConfiguration; - - return $subscriptionFields; - } - - /** - * {@inheritdoc} - */ - public function getResourceObjectTypeFields(?string $resourceClass, ResourceMetadata $resourceMetadata, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, int $depth = 0, ?array $ioMetadata = null): array - { - $fields = []; - $idField = ['type' => GraphQLType::nonNull(GraphQLType::id())]; - $optionalIdField = ['type' => GraphQLType::id()]; - $clientMutationId = GraphQLType::string(); - $clientSubscriptionId = GraphQLType::string(); - - if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null === $ioMetadata['class']) { - if ($input) { - return ['clientMutationId' => $clientMutationId]; - } - - return []; - } - - if (null !== $subscriptionName && $input) { - return [ - 'id' => $idField, - 'clientSubscriptionId' => $clientSubscriptionId, - ]; - } - - if ('delete' === $mutationName) { - $fields = [ - 'id' => $idField, - ]; - - if ($input) { - $fields['clientMutationId'] = $clientMutationId; - } - - return $fields; - } - - if (!$input || 'create' !== $mutationName) { - $fields['id'] = $idField; - } - if ($input && $depth >= 1) { - $fields['id'] = $optionalIdField; - } - - ++$depth; // increment the depth for the call to getResourceFieldConfiguration. - - if (null !== $resourceClass) { - foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, ['graphql_operation_name' => $subscriptionName ?? $mutationName ?? $queryName]); - if ( - null === ($propertyType = $propertyMetadata->getType()) - || (!$input && false === $propertyMetadata->isReadable()) - || ($input && null !== $mutationName && false === $propertyMetadata->isWritable()) - ) { - continue; - } - - if ($fieldConfiguration = $this->getResourceFieldConfiguration($property, $propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', null), $propertyType, $resourceClass, $input, $queryName, $mutationName, $subscriptionName, $depth, null !== $propertyMetadata->getAttribute('security'))) { - $fields['id' === $property ? '_id' : $this->normalizePropertyName($property, $resourceClass)] = $fieldConfiguration; - } - } - } - - if (null !== $mutationName && $input) { - $fields['clientMutationId'] = $clientMutationId; - } - - return $fields; - } - - /** - * {@inheritdoc} - */ - public function resolveResourceArgs(array $args, string $operationName, string $shortName): array - { - foreach ($args as $id => $arg) { - if (!isset($arg['type'])) { - throw new \InvalidArgumentException(sprintf('The argument "%s" of the custom operation "%s" in %s needs a "type" option.', $id, $operationName, $shortName)); - } - - $args[$id]['type'] = $this->typeConverter->resolveType($arg['type']); - } - - return $args; - } - - /** - * Get the field configuration of a resource. - * - * @see http://webonyx.github.io/graphql-php/type-system/object-types/ - */ - private function getResourceFieldConfiguration(?string $property, ?string $fieldDescription, ?string $deprecationReason, Type $type, string $rootResource, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, int $depth = 0, bool $forceNullable = false): ?array - { - try { - if ( - $this->typeBuilder->isCollection($type) && - $collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType() - ) { - $resourceClass = $collectionValueType->getClassName(); - } else { - $resourceClass = $type->getClassName(); - } - - if (null === $graphqlType = $this->convertType($type, $input, $queryName, $mutationName, $subscriptionName, $resourceClass ?? '', $rootResource, $property, $depth, $forceNullable)) { - return null; - } - - $graphqlWrappedType = $graphqlType instanceof WrappingType ? $graphqlType->getWrappedType(true) : $graphqlType; - $isStandardGraphqlType = \in_array($graphqlWrappedType, GraphQLType::getStandardTypes(), true); - if ($isStandardGraphqlType) { - $resourceClass = ''; - } - - $resourceMetadata = null; - if (!empty($resourceClass)) { - try { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - } catch (ResourceClassNotFoundException $e) { - } - } - - // Check mercure attribute if it's a subscription at the root level. - if ($subscriptionName && null === $property && (!$resourceMetadata || !$resourceMetadata->getAttribute('mercure', false))) { - return null; - } - - $args = []; - if (!$input && null === $mutationName && null === $subscriptionName && !$isStandardGraphqlType && $this->typeBuilder->isCollection($type)) { - if ($this->pagination->isGraphQlEnabled($resourceClass, $queryName)) { - $args = $this->getGraphQlPaginationArgs($resourceClass, $queryName); - } - - $args = $this->getFilterArgs($args, $resourceClass, $resourceMetadata, $rootResource, $property, $queryName, $mutationName, $depth); - } - - if ($isStandardGraphqlType || $input) { - $resolve = null; - } elseif (($mutationName || $subscriptionName) && $depth <= 0) { - if ($mutationName) { - $resolve = ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $mutationName); - } else { - $resolve = ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $subscriptionName); - } - } elseif ($this->typeBuilder->isCollection($type)) { - $resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $queryName); - } else { - $resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $queryName); - } - - return [ - 'type' => $graphqlType, - 'description' => $fieldDescription, - 'args' => $args, - 'resolve' => $resolve, - 'deprecationReason' => $deprecationReason, - ]; - } catch (InvalidTypeException $e) { - // just ignore invalid types - } - - return null; - } - - private function getGraphQlPaginationArgs(string $resourceClass, string $queryName): array - { - $paginationType = $this->pagination->getGraphQlPaginationType($resourceClass, $queryName); - - if ('cursor' === $paginationType) { - return [ - 'first' => [ - 'type' => GraphQLType::int(), - 'description' => 'Returns the first n elements from the list.', - ], - 'last' => [ - 'type' => GraphQLType::int(), - 'description' => 'Returns the last n elements from the list.', - ], - 'before' => [ - 'type' => GraphQLType::string(), - 'description' => 'Returns the elements in the list that come before the specified cursor.', - ], - 'after' => [ - 'type' => GraphQLType::string(), - 'description' => 'Returns the elements in the list that come after the specified cursor.', - ], - ]; - } - - $paginationOptions = $this->pagination->getOptions(); - - $args = [ - $paginationOptions['page_parameter_name'] => [ - 'type' => GraphQLType::int(), - 'description' => 'Returns the current page.', - ], - ]; - - if ($paginationOptions['client_items_per_page']) { - $args[$paginationOptions['items_per_page_parameter_name']] = [ - 'type' => GraphQLType::int(), - 'description' => 'Returns the number of items per page.', - ]; - } - - return $args; - } - - private function getFilterArgs(array $args, ?string $resourceClass, ?ResourceMetadata $resourceMetadata, string $rootResource, ?string $property, ?string $queryName, ?string $mutationName, int $depth): array - { - if (null === $resourceMetadata || null === $resourceClass) { - return $args; - } - - foreach ($resourceMetadata->getGraphqlAttribute($queryName, 'filters', [], true) as $filterId) { - if (null === $this->filterLocator || !$this->filterLocator->has($filterId)) { - continue; - } - - foreach ($this->filterLocator->get($filterId)->getDescription($resourceClass) as $key => $value) { - $nullable = isset($value['required']) ? !$value['required'] : true; - $filterType = \in_array($value['type'], Type::$builtinTypes, true) ? new Type($value['type'], $nullable) : new Type('object', $nullable, $value['type']); - $graphqlFilterType = $this->convertType($filterType, false, $queryName, $mutationName, null, $resourceClass, $rootResource, $property, $depth); - - if ('[]' === substr($key, -2)) { - $graphqlFilterType = GraphQLType::listOf($graphqlFilterType); - $key = substr($key, 0, -2).'_list'; - } - - /** @var string $key */ - $key = str_replace('.', $this->nestingSeparator, $key); - - parse_str($key, $parsed); - if (\array_key_exists($key, $parsed) && \is_array($parsed[$key])) { - $parsed = [$key => '']; - } - array_walk_recursive($parsed, static function (&$value) use ($graphqlFilterType) { - $value = $graphqlFilterType; - }); - $args = $this->mergeFilterArgs($args, $parsed, $resourceMetadata, $key); - } - } - - return $this->convertFilterArgsToTypes($args); - } - - private function mergeFilterArgs(array $args, array $parsed, ResourceMetadata $resourceMetadata = null, $original = ''): array - { - foreach ($parsed as $key => $value) { - // Never override keys that cannot be merged - if (isset($args[$key]) && !\is_array($args[$key])) { - continue; - } - - if (\is_array($value)) { - $value = $this->mergeFilterArgs($args[$key] ?? [], $value); - if (!isset($value['#name'])) { - $name = (false === $pos = strrpos($original, '[')) ? $original : substr($original, 0, (int) $pos); - $value['#name'] = ($resourceMetadata ? $resourceMetadata->getShortName() : '').'Filter_'.strtr($name, ['[' => '_', ']' => '', '.' => '__']); - } - } - - $args[$key] = $value; - } - - return $args; - } - - private function convertFilterArgsToTypes(array $args): array - { - foreach ($args as $key => $value) { - if (strpos($key, '.')) { - // Declare relations/nested fields in a GraphQL compatible syntax. - $args[str_replace('.', $this->nestingSeparator, $key)] = $value; - unset($args[$key]); - } - } - - foreach ($args as $key => $value) { - if (!\is_array($value) || !isset($value['#name'])) { - continue; - } - - $name = $value['#name']; - - if ($this->typesContainer->has($name)) { - $args[$key] = $this->typesContainer->get($name); - continue; - } - - unset($value['#name']); - - $filterArgType = GraphQLType::listOf(new InputObjectType([ - 'name' => $name, - 'fields' => $this->convertFilterArgsToTypes($value), - ])); - - $this->typesContainer->set($name, $filterArgType); - - $args[$key] = $filterArgType; - } - - return $args; - } - - /** - * Converts a built-in type to its GraphQL equivalent. - * - * @throws InvalidTypeException - */ - private function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, string $resourceClass, string $rootResource, ?string $property, int $depth, bool $forceNullable = false) - { - $graphqlType = $this->typeConverter->convertType($type, $input, $queryName, $mutationName, $subscriptionName, $resourceClass, $rootResource, $property, $depth); - - if (null === $graphqlType) { - throw new InvalidTypeException(sprintf('The type "%s" is not supported.', $type->getBuiltinType())); - } - - if (\is_string($graphqlType)) { - if (!$this->typesContainer->has($graphqlType)) { - throw new InvalidTypeException(sprintf('The GraphQL type %s is not valid. Valid types are: %s. Have you registered this type by implementing %s?', $graphqlType, implode(', ', array_keys($this->typesContainer->all())), TypeInterface::class)); - } - - $graphqlType = $this->typesContainer->get($graphqlType); - } - - if ($this->typeBuilder->isCollection($type)) { - $operationName = $queryName ?? $mutationName ?? $subscriptionName; - - return $this->pagination->isGraphQlEnabled($resourceClass, $operationName) && !$input ? $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $operationName) : GraphQLType::listOf($graphqlType); - } - - return $forceNullable || !$graphqlType instanceof NullableType || $type->isNullable() || (null !== $mutationName && 'update' === $mutationName) - ? $graphqlType - : GraphQLType::nonNull($graphqlType); - } - - private function normalizePropertyName(string $property, string $resourceClass): string - { - if (null === $this->nameConverter) { - return $property; - } - if ($this->nameConverter instanceof AdvancedNameConverterInterface) { - return $this->nameConverter->normalize($property, $resourceClass); - } - - return $this->nameConverter->normalize($property); - } -} diff --git a/src/Core/GraphQl/Type/FieldsBuilderInterface.php b/src/Core/GraphQl/Type/FieldsBuilderInterface.php deleted file mode 100644 index c693b9a5e9a..00000000000 --- a/src/Core/GraphQl/Type/FieldsBuilderInterface.php +++ /dev/null @@ -1,59 +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\GraphQl\Type; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; - -/** - * Interface implemented to build GraphQL fields. - * - * @author Alan Poulain - */ -interface FieldsBuilderInterface -{ - /** - * Gets the fields of a node for a query. - */ - public function getNodeQueryFields(): array; - - /** - * Gets the item query fields of the schema. - */ - public function getItemQueryFields(string $resourceClass, ResourceMetadata $resourceMetadata, string $queryName, array $configuration): array; - - /** - * Gets the collection query fields of the schema. - */ - public function getCollectionQueryFields(string $resourceClass, ResourceMetadata $resourceMetadata, string $queryName, array $configuration): array; - - /** - * Gets the mutation fields of the schema. - */ - public function getMutationFields(string $resourceClass, ResourceMetadata $resourceMetadata, string $mutationName): array; - - /** - * Gets the subscription fields of the schema. - */ - public function getSubscriptionFields(string $resourceClass, ResourceMetadata $resourceMetadata, string $subscriptionName): array; - - /** - * Gets the fields of the type of the given resource. - */ - public function getResourceObjectTypeFields(?string $resourceClass, ResourceMetadata $resourceMetadata, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, int $depth, ?array $ioMetadata): array; - - /** - * Resolve the args of a resource by resolving its types. - */ - public function resolveResourceArgs(array $args, string $operationName, string $shortName): array; -} diff --git a/src/Core/GraphQl/Type/SchemaBuilder.php b/src/Core/GraphQl/Type/SchemaBuilder.php deleted file mode 100644 index f9107e55f28..00000000000 --- a/src/Core/GraphQl/Type/SchemaBuilder.php +++ /dev/null @@ -1,132 +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\GraphQl\Type; - -use ApiPlatform\Core\GraphQl\Type\TypesContainerInterface as TypesContainerLegacyInterface; -use ApiPlatform\Core\GraphQl\Type\TypesFactoryInterface as TypesFactoryLegacyInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\GraphQl\Type\TypesContainerInterface; -use ApiPlatform\GraphQl\Type\TypesFactoryInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use GraphQL\Type\Definition\ObjectType; -use GraphQL\Type\Definition\WrappingType; -use GraphQL\Type\Schema; - -/** - * Builds the GraphQL schema. - * - * @author Raoul Clais - * @author Alan Poulain - * @author Kévin Dunglas - */ -final class SchemaBuilder implements SchemaBuilderInterface -{ - private $resourceNameCollectionFactory; - private $resourceMetadataFactory; - /** @var TypesFactoryLegacyInterface|TypesFactoryInterface */ - private $typesFactory; - /** @var TypesContainerLegacyInterface|TypesContainerInterface */ - private $typesContainer; - private $fieldsBuilder; - - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, $typesFactory, $typesContainer, FieldsBuilderInterface $fieldsBuilder) - { - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->typesFactory = $typesFactory; - $this->typesContainer = $typesContainer; - $this->fieldsBuilder = $fieldsBuilder; - } - - public function getSchema(): Schema - { - $types = $this->typesFactory->getTypes(); - foreach ($types as $typeId => $type) { - $this->typesContainer->set($typeId, $type); - } - - $queryFields = ['node' => $this->fieldsBuilder->getNodeQueryFields()]; - $mutationFields = []; - $subscriptionFields = []; - - foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - /** @var array $graphqlConfiguration */ - $graphqlConfiguration = $resourceMetadata->getGraphql() ?? []; - foreach ($graphqlConfiguration as $operationName => $value) { - if ('item_query' === $operationName) { - $queryFields += $this->fieldsBuilder->getItemQueryFields($resourceClass, $resourceMetadata, $operationName, []); - - continue; - } - - if ('collection_query' === $operationName) { - $queryFields += $this->fieldsBuilder->getCollectionQueryFields($resourceClass, $resourceMetadata, $operationName, []); - - continue; - } - - if ($resourceMetadata->getGraphqlAttribute($operationName, 'item_query')) { - $queryFields += $this->fieldsBuilder->getItemQueryFields($resourceClass, $resourceMetadata, $operationName, $value); - - continue; - } - - if ($resourceMetadata->getGraphqlAttribute($operationName, 'collection_query')) { - $queryFields += $this->fieldsBuilder->getCollectionQueryFields($resourceClass, $resourceMetadata, $operationName, $value); - - continue; - } - - if ('update' === $operationName) { - $subscriptionFields += $this->fieldsBuilder->getSubscriptionFields($resourceClass, $resourceMetadata, $operationName); - } - - $mutationFields += $this->fieldsBuilder->getMutationFields($resourceClass, $resourceMetadata, $operationName); - } - } - - $schema = [ - 'query' => new ObjectType([ - 'name' => 'Query', - 'fields' => $queryFields, - ]), - 'typeLoader' => function ($name) { - $type = $this->typesContainer->get($name); - - if ($type instanceof WrappingType) { - return $type->getWrappedType(true); - } - - return $type; - }, - ]; - - if ($mutationFields) { - $schema['mutation'] = new ObjectType([ - 'name' => 'Mutation', - 'fields' => $mutationFields, - ]); - } - - if ($subscriptionFields) { - $schema['subscription'] = new ObjectType([ - 'name' => 'Subscription', - 'fields' => $subscriptionFields, - ]); - } - - return new Schema($schema); - } -} diff --git a/src/Core/GraphQl/Type/SchemaBuilderInterface.php b/src/Core/GraphQl/Type/SchemaBuilderInterface.php deleted file mode 100644 index 69fd7f7748a..00000000000 --- a/src/Core/GraphQl/Type/SchemaBuilderInterface.php +++ /dev/null @@ -1,26 +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\GraphQl\Type; - -use GraphQL\Type\Schema; - -/** - * Builds a GraphQL schema. - * - * @author Alan Poulain - */ -interface SchemaBuilderInterface -{ - public function getSchema(): Schema; -} diff --git a/src/Core/GraphQl/Type/TypeBuilder.php b/src/Core/GraphQl/Type/TypeBuilder.php deleted file mode 100644 index 8132587bb50..00000000000 --- a/src/Core/GraphQl/Type/TypeBuilder.php +++ /dev/null @@ -1,287 +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\GraphQl\Type; - -use ApiPlatform\Core\DataProvider\Pagination; -use ApiPlatform\Core\GraphQl\Type\TypesContainerInterface as TypesContainerLegacyInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\GraphQl\Serializer\ItemNormalizer; -use ApiPlatform\GraphQl\Type\TypesContainerInterface; -use GraphQL\Type\Definition\InputObjectType; -use GraphQL\Type\Definition\InterfaceType; -use GraphQL\Type\Definition\NonNull; -use GraphQL\Type\Definition\ObjectType; -use GraphQL\Type\Definition\Type as GraphQLType; -use Psr\Container\ContainerInterface; -use Symfony\Component\PropertyInfo\Type; - -/** - * Builds the GraphQL types. - * - * @author Alan Poulain - */ -final class TypeBuilder implements TypeBuilderInterface -{ - /** @var TypesContainerLegacyInterface|TypesContainerInterface */ - private $typesContainer; - private $defaultFieldResolver; - private $fieldsBuilderLocator; - private $pagination; - - public function __construct($typesContainer, callable $defaultFieldResolver, ContainerInterface $fieldsBuilderLocator, Pagination $pagination) - { - $this->typesContainer = $typesContainer; - $this->defaultFieldResolver = $defaultFieldResolver; - $this->fieldsBuilderLocator = $fieldsBuilderLocator; - $this->pagination = $pagination; - } - - /** - * {@inheritdoc} - */ - public function getResourceObjectType(?string $resourceClass, ResourceMetadata $resourceMetadata, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, bool $wrapped = false, int $depth = 0): GraphQLType - { - $shortName = $resourceMetadata->getShortName(); - - $ioMetadata = $resourceMetadata->getGraphqlAttribute($subscriptionName ?? $mutationName ?? $queryName, $input ? 'input' : 'output', null, true); - if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) { - $resourceClass = $ioMetadata['class']; - $shortName = $ioMetadata['name']; - } - - if (null !== $mutationName) { - $shortName = $mutationName.ucfirst($shortName); - } - if (null !== $subscriptionName) { - $shortName = $subscriptionName.ucfirst($shortName).'Subscription'; - } - if ($input) { - if ($depth > 0) { - $shortName .= 'Nested'; - } - $shortName .= 'Input'; - } elseif (null !== $mutationName || null !== $subscriptionName) { - if ($depth > 0) { - $shortName .= 'Nested'; - } - $shortName .= 'Payload'; - } - if (('item_query' === $queryName || 'collection_query' === $queryName) - && $resourceMetadata->getGraphqlAttribute('item_query', 'normalization_context', [], true) !== $resourceMetadata->getGraphqlAttribute('collection_query', 'normalization_context', [], true)) { - if ('item_query' === $queryName) { - $shortName .= 'Item'; - } - if ('collection_query' === $queryName) { - $shortName .= 'Collection'; - } - } - if ($wrapped && (null !== $mutationName || null !== $subscriptionName)) { - $shortName .= 'Data'; - } - - if ($this->typesContainer->has($shortName)) { - $resourceObjectType = $this->typesContainer->get($shortName); - if (!($resourceObjectType instanceof ObjectType || $resourceObjectType instanceof NonNull)) { - throw new \LogicException(sprintf('Expected GraphQL type "%s" to be %s.', $shortName, implode('|', [ObjectType::class, NonNull::class]))); - } - - return $resourceObjectType; - } - - $wrapData = !$wrapped && (null !== $mutationName || null !== $subscriptionName) && !$input && $depth < 1; - - $configuration = [ - 'name' => $shortName, - 'description' => $resourceMetadata->getDescription(), - 'resolveField' => $this->defaultFieldResolver, - 'fields' => function () use ($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, $subscriptionName, $wrapData, $depth, $ioMetadata) { - if ($wrapData) { - $queryNormalizationContext = $resourceMetadata->getGraphqlAttribute($queryName ?? '', 'normalization_context', [], true); - $mutationNormalizationContext = $resourceMetadata->getGraphqlAttribute($mutationName ?? $subscriptionName ?? '', 'normalization_context', [], true); - // Use a new type for the wrapped object only if there is a specific normalization context for the mutation or the subscription. - // If not, use the query type in order to ensure the client cache could be used. - $useWrappedType = $queryNormalizationContext !== $mutationNormalizationContext; - - $fields = [ - lcfirst($resourceMetadata->getShortName()) => $useWrappedType ? - $this->getResourceObjectType($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, $subscriptionName, true, $depth) : - $this->getResourceObjectType($resourceClass, $resourceMetadata, $input, $queryName ?? 'item_query', null, null, true, $depth), - ]; - - if (null !== $subscriptionName) { - $fields['clientSubscriptionId'] = GraphQLType::string(); - if ($resourceMetadata->getAttribute('mercure', false)) { - $fields['mercureUrl'] = GraphQLType::string(); - } - - return $fields; - } - - return $fields + ['clientMutationId' => GraphQLType::string()]; - } - - /** @var FieldsBuilderInterface $fieldsBuilder */ - $fieldsBuilder = $this->fieldsBuilderLocator->get('api_platform.graphql.fields_builder'); - $fields = $fieldsBuilder->getResourceObjectTypeFields($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, $subscriptionName, $depth, $ioMetadata); - - if ($input && null !== $mutationName && null !== $mutationArgs = $resourceMetadata->getGraphql()[$mutationName]['args'] ?? null) { - return $fieldsBuilder->resolveResourceArgs($mutationArgs, $mutationName, $resourceMetadata->getShortName()) + ['clientMutationId' => $fields['clientMutationId']]; - } - - return $fields; - }, - 'interfaces' => $wrapData ? [] : [$this->getNodeInterface()], - ]; - - $resourceObjectType = $input ? GraphQLType::nonNull(new InputObjectType($configuration)) : new ObjectType($configuration); - $this->typesContainer->set($shortName, $resourceObjectType); - - return $resourceObjectType; - } - - /** - * {@inheritdoc} - */ - public function getNodeInterface(): InterfaceType - { - if ($this->typesContainer->has('Node')) { - $nodeInterface = $this->typesContainer->get('Node'); - if (!$nodeInterface instanceof InterfaceType) { - throw new \LogicException(sprintf('Expected GraphQL type "Node" to be %s.', InterfaceType::class)); - } - - return $nodeInterface; - } - - $nodeInterface = new InterfaceType([ - 'name' => 'Node', - 'description' => 'A node, according to the Relay specification.', - 'fields' => [ - 'id' => [ - 'type' => GraphQLType::nonNull(GraphQLType::id()), - 'description' => 'The id of this node.', - ], - ], - 'resolveType' => function ($value) { - if (!isset($value[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) { - return null; - } - - $shortName = (new \ReflectionClass($value[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY]))->getShortName(); - - return $this->typesContainer->has($shortName) ? $this->typesContainer->get($shortName) : null; - }, - ]); - - $this->typesContainer->set('Node', $nodeInterface); - - return $nodeInterface; - } - - /** - * {@inheritdoc} - */ - public function getResourcePaginatedCollectionType(GraphQLType $resourceType, string $resourceClass, string $operationName): GraphQLType - { - $shortName = $resourceType->name; - $paginationType = $this->pagination->getGraphQlPaginationType($resourceClass, $operationName); - - $connectionTypeKey = sprintf('%s%sConnection', $shortName, ucfirst($paginationType)); - if ($this->typesContainer->has($connectionTypeKey)) { - return $this->typesContainer->get($connectionTypeKey); - } - - $paginationType = $this->pagination->getGraphQlPaginationType($resourceClass, $operationName); - - $fields = 'cursor' === $paginationType ? - $this->getCursorBasedPaginationFields($resourceType) : - $this->getPageBasedPaginationFields($resourceType); - - $configuration = [ - 'name' => $connectionTypeKey, - 'description' => sprintf("%s connection for $shortName.", ucfirst($paginationType)), - 'fields' => $fields, - ]; - - $resourcePaginatedCollectionType = new ObjectType($configuration); - $this->typesContainer->set($connectionTypeKey, $resourcePaginatedCollectionType); - - return $resourcePaginatedCollectionType; - } - - /** - * {@inheritdoc} - */ - public function isCollection(Type $type): bool - { - return $type->isCollection() && ($collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType()) && null !== $collectionValueType->getClassName(); - } - - private function getCursorBasedPaginationFields(GraphQLType $resourceType): array - { - $shortName = $resourceType->name; - - $edgeObjectTypeConfiguration = [ - 'name' => "{$shortName}Edge", - 'description' => "Edge of $shortName.", - 'fields' => [ - 'node' => $resourceType, - 'cursor' => GraphQLType::nonNull(GraphQLType::string()), - ], - ]; - $edgeObjectType = new ObjectType($edgeObjectTypeConfiguration); - $this->typesContainer->set("{$shortName}Edge", $edgeObjectType); - - $pageInfoObjectTypeConfiguration = [ - 'name' => "{$shortName}PageInfo", - 'description' => 'Information about the current page.', - 'fields' => [ - 'endCursor' => GraphQLType::string(), - 'startCursor' => GraphQLType::string(), - 'hasNextPage' => GraphQLType::nonNull(GraphQLType::boolean()), - 'hasPreviousPage' => GraphQLType::nonNull(GraphQLType::boolean()), - ], - ]; - $pageInfoObjectType = new ObjectType($pageInfoObjectTypeConfiguration); - $this->typesContainer->set("{$shortName}PageInfo", $pageInfoObjectType); - - return [ - 'edges' => GraphQLType::listOf($edgeObjectType), - 'pageInfo' => GraphQLType::nonNull($pageInfoObjectType), - 'totalCount' => GraphQLType::nonNull(GraphQLType::int()), - ]; - } - - private function getPageBasedPaginationFields(GraphQLType $resourceType): array - { - $shortName = $resourceType->name; - - $paginationInfoObjectTypeConfiguration = [ - 'name' => "{$shortName}PaginationInfo", - 'description' => 'Information about the pagination.', - 'fields' => [ - 'itemsPerPage' => GraphQLType::nonNull(GraphQLType::int()), - 'lastPage' => GraphQLType::nonNull(GraphQLType::int()), - 'totalCount' => GraphQLType::nonNull(GraphQLType::int()), - ], - ]; - $paginationInfoObjectType = new ObjectType($paginationInfoObjectTypeConfiguration); - $this->typesContainer->set("{$shortName}PaginationInfo", $paginationInfoObjectType); - - return [ - 'collection' => GraphQLType::listOf($resourceType), - 'paginationInfo' => GraphQLType::nonNull($paginationInfoObjectType), - ]; - } -} diff --git a/src/Core/GraphQl/Type/TypeBuilderInterface.php b/src/Core/GraphQl/Type/TypeBuilderInterface.php deleted file mode 100644 index 3270a8d881c..00000000000 --- a/src/Core/GraphQl/Type/TypeBuilderInterface.php +++ /dev/null @@ -1,51 +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\GraphQl\Type; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use GraphQL\Type\Definition\InterfaceType; -use GraphQL\Type\Definition\NonNull; -use GraphQL\Type\Definition\ObjectType; -use GraphQL\Type\Definition\Type as GraphQLType; -use Symfony\Component\PropertyInfo\Type; - -/** - * Interface implemented to build a GraphQL type. - * - * @author Alan Poulain - */ -interface TypeBuilderInterface -{ - /** - * Gets the object type of the given resource. - * - * @return ObjectType|NonNull the object type, possibly wrapped by NonNull - */ - public function getResourceObjectType(?string $resourceClass, ResourceMetadata $resourceMetadata, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, bool $wrapped, int $depth): GraphQLType; - - /** - * Get the interface type of a node. - */ - public function getNodeInterface(): InterfaceType; - - /** - * Gets the type of a paginated collection of the given resource type. - */ - public function getResourcePaginatedCollectionType(GraphQLType $resourceType, string $resourceClass, string $operationName): GraphQLType; - - /** - * Returns true if a type is a collection. - */ - public function isCollection(Type $type): bool; -} diff --git a/src/Core/GraphQl/Type/TypeConverter.php b/src/Core/GraphQl/Type/TypeConverter.php deleted file mode 100644 index 96e58ad2502..00000000000 --- a/src/Core/GraphQl/Type/TypeConverter.php +++ /dev/null @@ -1,205 +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\GraphQl\Type; - -use ApiPlatform\Core\GraphQl\Type\TypesContainerInterface as TypesContainerLegacyInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\GraphQl\Type\TypesContainerInterface; -use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use GraphQL\Error\SyntaxError; -use GraphQL\Language\AST\ListTypeNode; -use GraphQL\Language\AST\NamedTypeNode; -use GraphQL\Language\AST\NonNullTypeNode; -use GraphQL\Language\AST\TypeNode; -use GraphQL\Language\Parser; -use GraphQL\Type\Definition\NullableType; -use GraphQL\Type\Definition\Type as GraphQLType; -use Symfony\Component\PropertyInfo\Type; - -/** - * Converts a type to its GraphQL equivalent. - * - * @author Alan Poulain - */ -final class TypeConverter implements TypeConverterInterface -{ - private $typeBuilder; - /** @var TypesContainerLegacyInterface|TypesContainerInterface */ - private $typesContainer; - private $resourceMetadataFactory; - /** @var LegacyPropertyMetadataFactoryInterface|PropertyMetadataFactoryInterface|null */ - private $propertyMetadataFactory; - - public function __construct(TypeBuilderInterface $typeBuilder, $typesContainer, ResourceMetadataFactoryInterface $resourceMetadataFactory, $propertyMetadataFactory = null) - { - $this->typeBuilder = $typeBuilder; - $this->typesContainer = $typesContainer; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - - if (null === $this->propertyMetadataFactory) { - @trigger_error(sprintf('Not injecting %s in the TypeConverter is deprecated since 2.7 and will not be supported in 3.0.', PropertyMetadataFactoryInterface::class), \E_USER_DEPRECATED); - } - } - - /** - * {@inheritdoc} - */ - public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, string $resourceClass, string $rootResource, ?string $property, int $depth) - { - switch ($type->getBuiltinType()) { - case Type::BUILTIN_TYPE_BOOL: - return GraphQLType::boolean(); - case Type::BUILTIN_TYPE_INT: - return GraphQLType::int(); - case Type::BUILTIN_TYPE_FLOAT: - return GraphQLType::float(); - case Type::BUILTIN_TYPE_STRING: - return GraphQLType::string(); - case Type::BUILTIN_TYPE_ARRAY: - case Type::BUILTIN_TYPE_ITERABLE: - if ($resourceType = $this->getResourceType($type, $input, $queryName, $mutationName, $subscriptionName, $rootResource, $property, $depth)) { - return $resourceType; - } - - return 'Iterable'; - case Type::BUILTIN_TYPE_OBJECT: - if (is_a($type->getClassName(), \DateTimeInterface::class, true)) { - return GraphQLType::string(); - } - - return $this->getResourceType($type, $input, $queryName, $mutationName, $subscriptionName, $rootResource, $property, $depth); - default: - return null; - } - } - - /** - * {@inheritdoc} - */ - public function resolveType(string $type): ?GraphQLType - { - try { - $astTypeNode = Parser::parseType($type); - } catch (SyntaxError $e) { - throw new InvalidArgumentException(sprintf('"%s" is not a valid GraphQL type.', $type), 0, $e); - } - - if ($graphQlType = $this->resolveAstTypeNode($astTypeNode, $type)) { - return $graphQlType; - } - - throw new InvalidArgumentException(sprintf('The type "%s" was not resolved.', $type)); - } - - private function getResourceType(Type $type, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, string $rootResource, ?string $property, int $depth): ?GraphQLType - { - if ( - $this->typeBuilder->isCollection($type) && - $collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType() - ) { - $resourceClass = $collectionValueType->getClassName(); - } else { - $resourceClass = $type->getClassName(); - } - - if (null === $resourceClass) { - return null; - } - - try { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (null === $resourceMetadata->getGraphql()) { - return null; - } - if ('Node' === $resourceMetadata->getShortName()) { - throw new \UnexpectedValueException('A "Node" resource cannot be used with GraphQL because the type is already used by the Relay specification.'); - } - } catch (ResourceClassNotFoundException $e) { - // Skip objects that are not resources for now - return null; - } - - $propertyMetadata = null; - if ($property && $this->propertyMetadataFactory) { - $rootResourceMetadata = null; - try { - $rootResourceMetadata = $this->resourceMetadataFactory->create($rootResource); - } catch (ResourceClassNotFoundException $e) { - } - - $context = [ - 'normalization_groups' => $rootResourceMetadata ? $rootResourceMetadata->getGraphqlAttribute($queryName ?? $mutationName ?? $subscriptionName, 'normalization_context', [], true)['groups'] ?? null : null, - 'denormalization_groups' => $rootResourceMetadata ? $rootResourceMetadata->getGraphqlAttribute($queryName ?? $mutationName ?? $subscriptionName, 'denormalization_context', [], true)['groups'] ?? null : null, - ]; - $propertyMetadata = $this->propertyMetadataFactory->create($rootResource, $property, $context); - } - - if ($input && $depth > 0 && (!$propertyMetadata || !$propertyMetadata->isWritableLink())) { - return GraphQLType::string(); - } - - return $this->typeBuilder->getResourceObjectType($resourceClass, $resourceMetadata, $input, $queryName, $mutationName, $subscriptionName, false, $depth); - } - - private function resolveAstTypeNode(TypeNode $astTypeNode, string $fromType): ?GraphQLType - { - if ($astTypeNode instanceof NonNullTypeNode) { - /** @var NullableType|null $nullableAstTypeNode */ - $nullableAstTypeNode = $this->resolveNullableAstTypeNode($astTypeNode->type, $fromType); - - return $nullableAstTypeNode ? GraphQLType::nonNull($nullableAstTypeNode) : null; - } - - return $this->resolveNullableAstTypeNode($astTypeNode, $fromType); - } - - private function resolveNullableAstTypeNode(TypeNode $astTypeNode, string $fromType): ?GraphQLType - { - if ($astTypeNode instanceof ListTypeNode) { - /** @var TypeNode $astTypeNodeElement */ - $astTypeNodeElement = $astTypeNode->type; - - return GraphQLType::listOf($this->resolveAstTypeNode($astTypeNodeElement, $fromType)); - } - - if (!$astTypeNode instanceof NamedTypeNode) { - return null; - } - - $typeName = $astTypeNode->name->value; - - switch ($typeName) { - case GraphQLType::STRING: - return GraphQLType::string(); - case GraphQLType::INT: - return GraphQLType::int(); - case GraphQLType::BOOLEAN: - return GraphQLType::boolean(); - case GraphQLType::FLOAT: - return GraphQLType::float(); - case GraphQLType::ID: - return GraphQLType::id(); - default: - if ($this->typesContainer->has($typeName)) { - return $this->typesContainer->get($typeName); - } - - return null; - } - } -} diff --git a/src/Core/GraphQl/Type/TypeConverterInterface.php b/src/Core/GraphQl/Type/TypeConverterInterface.php deleted file mode 100644 index 53c65c1c592..00000000000 --- a/src/Core/GraphQl/Type/TypeConverterInterface.php +++ /dev/null @@ -1,38 +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\GraphQl\Type; - -use GraphQL\Type\Definition\Type as GraphQLType; -use Symfony\Component\PropertyInfo\Type; - -/** - * Converts a type to its GraphQL equivalent. - * - * @author Alan Poulain - */ -interface TypeConverterInterface -{ - /** - * Converts a built-in type to its GraphQL equivalent. - * A string can be returned for a custom registered type. - * - * @return string|GraphQLType|null - */ - public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, string $resourceClass, string $rootResource, ?string $property, int $depth); - - /** - * Resolves a type written with the GraphQL type system to its object representation. - */ - public function resolveType(string $type): ?GraphQLType; -} diff --git a/src/Core/GraphQl/Type/TypeNotFoundException.php b/src/Core/GraphQl/Type/TypeNotFoundException.php deleted file mode 100644 index b09ea7634f1..00000000000 --- a/src/Core/GraphQl/Type/TypeNotFoundException.php +++ /dev/null @@ -1,22 +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\GraphQl\Type; - -class_exists(\ApiPlatform\GraphQl\Type\TypeNotFoundException::class); - -if (false) { - final class TypeNotFoundException extends \ApiPlatform\GraphQl\Type\TypeNotFoundException - { - } -} diff --git a/src/Core/GraphQl/Type/TypesContainer.php b/src/Core/GraphQl/Type/TypesContainer.php deleted file mode 100644 index ae852470804..00000000000 --- a/src/Core/GraphQl/Type/TypesContainer.php +++ /dev/null @@ -1,22 +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\GraphQl\Type; - -class_exists(\ApiPlatform\GraphQl\Type\TypesContainer::class); - -if (false) { - final class TypesContainer extends \ApiPlatform\GraphQl\Type\TypesContainer - { - } -} diff --git a/src/Core/GraphQl/Type/TypesFactory.php b/src/Core/GraphQl/Type/TypesFactory.php deleted file mode 100644 index b359c63adda..00000000000 --- a/src/Core/GraphQl/Type/TypesFactory.php +++ /dev/null @@ -1,22 +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\GraphQl\Type; - -class_exists(\ApiPlatform\GraphQl\Type\TypesFactory::class); - -if (false) { - final class TypesFactory extends \ApiPlatform\GraphQl\Type\TypesFactory - { - } -} diff --git a/src/Core/Hal/JsonSchema/SchemaFactory.php b/src/Core/Hal/JsonSchema/SchemaFactory.php deleted file mode 100644 index eaebc028937..00000000000 --- a/src/Core/Hal/JsonSchema/SchemaFactory.php +++ /dev/null @@ -1,137 +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\Hal\JsonSchema; - -use ApiPlatform\Core\JsonSchema\Schema; -use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; - -/** - * Decorator factory which adds HAL properties to the JSON Schema document. - * - * @experimental - * - * @author Kévin Dunglas - * @author Jachim Coudenys - */ -final class SchemaFactory implements SchemaFactoryInterface -{ - private const HREF_PROP = [ - 'href' => [ - 'type' => 'string', - 'format' => 'iri-reference', - ], - ]; - private const BASE_PROPS = [ - '_links' => [ - 'type' => 'object', - 'properties' => [ - 'self' => [ - 'type' => 'object', - 'properties' => self::HREF_PROP, - ], - ], - ], - ]; - - private $schemaFactory; - - public function __construct(SchemaFactoryInterface $schemaFactory) - { - $this->schemaFactory = $schemaFactory; - - $this->addDistinctFormat('jsonhal'); - } - - /** - * {@inheritdoc} - */ - public function buildSchema(string $className, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema - { - $schema = $this->schemaFactory->buildSchema($className, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection); - if ('jsonhal' !== $format) { - return $schema; - } - - $definitions = $schema->getDefinitions(); - if ($key = $schema->getRootDefinitionKey()) { - $definitions[$key]['properties'] = self::BASE_PROPS + ($definitions[$key]['properties'] ?? []); - - return $schema; - } - if ($key = $schema->getItemsDefinitionKey()) { - $definitions[$key]['properties'] = self::BASE_PROPS + ($definitions[$key]['properties'] ?? []); - } - - if (($schema['type'] ?? '') === 'array') { - $items = $schema['items']; - unset($schema['items']); - - $schema['type'] = 'object'; - $schema['properties'] = [ - '_embedded' => [ - 'type' => 'array', - 'items' => $items, - ], - 'totalItems' => [ - 'type' => 'integer', - 'minimum' => 0, - ], - 'itemsPerPage' => [ - 'type' => 'integer', - 'minimum' => 0, - ], - '_links' => [ - 'type' => 'object', - 'properties' => [ - 'self' => [ - 'type' => 'object', - 'properties' => self::HREF_PROP, - ], - 'first' => [ - 'type' => 'object', - 'properties' => self::HREF_PROP, - ], - 'last' => [ - 'type' => 'object', - 'properties' => self::HREF_PROP, - ], - 'next' => [ - 'type' => 'object', - 'properties' => self::HREF_PROP, - ], - 'previous' => [ - 'type' => 'object', - 'properties' => self::HREF_PROP, - ], - ], - ], - ]; - $schema['required'] = [ - '_links', - '_embedded', - ]; - - return $schema; - } - - return $schema; - } - - public function addDistinctFormat(string $format): void - { - if (method_exists($this->schemaFactory, 'addDistinctFormat')) { - $this->schemaFactory->addDistinctFormat($format); - } - } -} diff --git a/src/Core/Hal/Serializer/CollectionNormalizer.php b/src/Core/Hal/Serializer/CollectionNormalizer.php deleted file mode 100644 index f3eb171ef3b..00000000000 --- a/src/Core/Hal/Serializer/CollectionNormalizer.php +++ /dev/null @@ -1,22 +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\Hal\Serializer; - -class_exists(\ApiPlatform\Hal\Serializer\CollectionNormalizer::class); - -if (false) { - final class CollectionNormalizer extends \ApiPlatform\Hal\Serializer\CollectionNormalizer - { - } -} diff --git a/src/Core/Hal/Serializer/EntrypointNormalizer.php b/src/Core/Hal/Serializer/EntrypointNormalizer.php deleted file mode 100644 index 1ab80f92df8..00000000000 --- a/src/Core/Hal/Serializer/EntrypointNormalizer.php +++ /dev/null @@ -1,22 +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\Hal\Serializer; - -class_exists(\ApiPlatform\Hal\Serializer\EntrypointNormalizer::class); - -if (false) { - final class EntrypointNormalizer extends \ApiPlatform\Hal\Serializer\EntrypointNormalizer - { - } -} diff --git a/src/Core/Hal/Serializer/ItemNormalizer.php b/src/Core/Hal/Serializer/ItemNormalizer.php deleted file mode 100644 index 33a67f551b2..00000000000 --- a/src/Core/Hal/Serializer/ItemNormalizer.php +++ /dev/null @@ -1,22 +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\Hal\Serializer; - -class_exists(\ApiPlatform\Hal\Serializer\ItemNormalizer::class); - -if (false) { - final class ItemNormalizer extends \ApiPlatform\Hal\Serializer\ItemNormalizer - { - } -} diff --git a/src/Core/Hal/Serializer/ObjectNormalizer.php b/src/Core/Hal/Serializer/ObjectNormalizer.php deleted file mode 100644 index b418b891813..00000000000 --- a/src/Core/Hal/Serializer/ObjectNormalizer.php +++ /dev/null @@ -1,22 +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\Hal\Serializer; - -class_exists(\ApiPlatform\Hal\Serializer\ObjectNormalizer::class); - -if (false) { - final class ObjectNormalizer extends \ApiPlatform\Hal\Serializer\ObjectNormalizer - { - } -} diff --git a/src/Core/HttpCache/EventListener/AddHeadersListener.php b/src/Core/HttpCache/EventListener/AddHeadersListener.php deleted file mode 100644 index 7dd640e01bf..00000000000 --- a/src/Core/HttpCache/EventListener/AddHeadersListener.php +++ /dev/null @@ -1,22 +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\HttpCache\EventListener; - -class_exists(\ApiPlatform\HttpCache\EventListener\AddHeadersListener::class); - -if (false) { - final class AddHeadersListener extends \ApiPlatform\HttpCache\EventListener\AddHeadersListener - { - } -} diff --git a/src/Core/HttpCache/EventListener/AddTagsListener.php b/src/Core/HttpCache/EventListener/AddTagsListener.php deleted file mode 100644 index 27d74e1f9ca..00000000000 --- a/src/Core/HttpCache/EventListener/AddTagsListener.php +++ /dev/null @@ -1,22 +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\HttpCache\EventListener; - -class_exists(\ApiPlatform\HttpCache\EventListener\AddTagsListener::class); - -if (false) { - final class AddTagsListener extends \ApiPlatform\HttpCache\EventListener\AddTagsListener - { - } -} diff --git a/src/Core/HttpCache/PurgerInterface.php b/src/Core/HttpCache/PurgerInterface.php deleted file mode 100644 index b1c14ccd911..00000000000 --- a/src/Core/HttpCache/PurgerInterface.php +++ /dev/null @@ -1,31 +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\HttpCache; - -/** - * Purges resources from the cache. - * - * @author Kévin Dunglas - * - * @experimental - */ -interface PurgerInterface -{ - /** - * Purges all responses containing the given resources from the cache. - * - * @param string[] $iris - */ - public function purge(array $iris): void; -} diff --git a/src/Core/HttpCache/VarnishPurger.php b/src/Core/HttpCache/VarnishPurger.php deleted file mode 100644 index 944b66b965d..00000000000 --- a/src/Core/HttpCache/VarnishPurger.php +++ /dev/null @@ -1,22 +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\HttpCache; - -class_exists(\ApiPlatform\HttpCache\VarnishPurger::class); - -if (false) { - final class VarnishPurger extends \ApiPlatform\HttpCache\VarnishPurger - { - } -} diff --git a/src/Core/HttpCache/VarnishXKeyPurger.php b/src/Core/HttpCache/VarnishXKeyPurger.php deleted file mode 100644 index 06f19ee752e..00000000000 --- a/src/Core/HttpCache/VarnishXKeyPurger.php +++ /dev/null @@ -1,22 +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\HttpCache; - -class_exists(\ApiPlatform\HttpCache\VarnishXKeyPurger::class); - -if (false) { - final class VarnishXKeyPurger extends \ApiPlatform\HttpCache\VarnishXKeyPurger - { - } -} diff --git a/src/Core/Hydra/EventListener/AddLinkHeaderListener.php b/src/Core/Hydra/EventListener/AddLinkHeaderListener.php deleted file mode 100644 index 0a02983bd4a..00000000000 --- a/src/Core/Hydra/EventListener/AddLinkHeaderListener.php +++ /dev/null @@ -1,22 +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\Hydra\EventListener; - -class_exists(\ApiPlatform\Hydra\EventListener\AddLinkHeaderListener::class); - -if (false) { - final class AddLinkHeaderListener extends \ApiPlatform\Hydra\EventListener\AddLinkHeaderListener - { - } -} diff --git a/src/Core/Hydra/JsonSchema/SchemaFactory.php b/src/Core/Hydra/JsonSchema/SchemaFactory.php deleted file mode 100644 index 03a8ad49aad..00000000000 --- a/src/Core/Hydra/JsonSchema/SchemaFactory.php +++ /dev/null @@ -1,189 +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\Hydra\JsonSchema; - -use ApiPlatform\Core\JsonLd\ContextBuilder; -use ApiPlatform\Core\JsonSchema\Schema; -use ApiPlatform\Core\JsonSchema\SchemaFactory as BaseSchemaFactory; -use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; - -/** - * Decorator factory which adds Hydra properties to the JSON Schema document. - * - * @experimental - * - * @author Kévin Dunglas - */ -final class SchemaFactory implements SchemaFactoryInterface -{ - private const BASE_PROP = [ - 'readOnly' => true, - 'type' => 'string', - ]; - private const BASE_PROPS = [ - '@id' => self::BASE_PROP, - '@type' => self::BASE_PROP, - ]; - private const BASE_ROOT_PROPS = [ - '@context' => [ - 'readOnly' => true, - 'oneOf' => [ - ['type' => 'string'], - [ - 'type' => 'object', - 'properties' => [ - '@vocab' => [ - 'type' => 'string', - ], - 'hydra' => [ - 'type' => 'string', - 'enum' => [ContextBuilder::HYDRA_NS], - ], - ], - 'required' => ['@vocab', 'hydra'], - 'additionalProperties' => true, - ], - ], - ], - ] + self::BASE_PROPS; - - private $schemaFactory; - - public function __construct(SchemaFactoryInterface $schemaFactory) - { - $this->schemaFactory = $schemaFactory; - - $this->addDistinctFormat('jsonld'); - } - - /** - * {@inheritdoc} - */ - public function buildSchema(string $className, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema - { - $schema = $this->schemaFactory->buildSchema($className, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection); - if ('jsonld' !== $format) { - return $schema; - } - - $definitions = $schema->getDefinitions(); - if ($key = $schema->getRootDefinitionKey()) { - $definitions[$key]['properties'] = self::BASE_ROOT_PROPS + ($definitions[$key]['properties'] ?? []); - - return $schema; - } - if ($key = $schema->getItemsDefinitionKey()) { - $definitions[$key]['properties'] = self::BASE_PROPS + ($definitions[$key]['properties'] ?? []); - } - - if (($schema['type'] ?? '') === 'array') { - // hydra:collection - $items = $schema['items']; - unset($schema['items']); - - $nullableStringDefinition = ['type' => 'string']; - - switch ($schema->getVersion()) { - case Schema::VERSION_JSON_SCHEMA: - $nullableStringDefinition = ['type' => ['string', 'null']]; - break; - case Schema::VERSION_OPENAPI: - $nullableStringDefinition = ['type' => 'string', 'nullable' => true]; - break; - } - - $schema['type'] = 'object'; - $schema['properties'] = [ - 'hydra:member' => [ - 'type' => 'array', - 'items' => $items, - ], - 'hydra:totalItems' => [ - 'type' => 'integer', - 'minimum' => 0, - ], - 'hydra:view' => [ - 'type' => 'object', - 'properties' => [ - '@id' => [ - 'type' => 'string', - 'format' => 'iri-reference', - ], - '@type' => [ - 'type' => 'string', - ], - 'hydra:first' => [ - 'type' => 'string', - 'format' => 'iri-reference', - ], - 'hydra:last' => [ - 'type' => 'string', - 'format' => 'iri-reference', - ], - 'hydra:previous' => [ - 'type' => 'string', - 'format' => 'iri-reference', - ], - 'hydra:next' => [ - 'type' => 'string', - 'format' => 'iri-reference', - ], - ], - 'example' => [ - '@id' => 'string', - 'type' => 'string', - 'hydra:first' => 'string', - 'hydra:last' => 'string', - 'hydra:previous' => 'string', - 'hydra:next' => 'string', - ], - ], - 'hydra:search' => [ - 'type' => 'object', - 'properties' => [ - '@type' => ['type' => 'string'], - 'hydra:template' => ['type' => 'string'], - 'hydra:variableRepresentation' => ['type' => 'string'], - 'hydra:mapping' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'object', - 'properties' => [ - '@type' => ['type' => 'string'], - 'variable' => ['type' => 'string'], - 'property' => $nullableStringDefinition, - 'required' => ['type' => 'boolean'], - ], - ], - ], - ], - ], - ]; - $schema['required'] = [ - 'hydra:member', - ]; - - return $schema; - } - - return $schema; - } - - public function addDistinctFormat(string $format): void - { - if ($this->schemaFactory instanceof BaseSchemaFactory) { - $this->schemaFactory->addDistinctFormat($format); - } - } -} diff --git a/src/Core/Hydra/Serializer/CollectionFiltersNormalizer.php b/src/Core/Hydra/Serializer/CollectionFiltersNormalizer.php deleted file mode 100644 index b0e8deda7fd..00000000000 --- a/src/Core/Hydra/Serializer/CollectionFiltersNormalizer.php +++ /dev/null @@ -1,22 +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\Hydra\Serializer; - -class_exists(\ApiPlatform\Hydra\Serializer\CollectionFiltersNormalizer::class); - -if (false) { - final class CollectionFiltersNormalizer extends \ApiPlatform\Hydra\Serializer\CollectionFiltersNormalizer - { - } -} diff --git a/src/Core/Hydra/Serializer/CollectionNormalizer.php b/src/Core/Hydra/Serializer/CollectionNormalizer.php deleted file mode 100644 index d3a43938f1d..00000000000 --- a/src/Core/Hydra/Serializer/CollectionNormalizer.php +++ /dev/null @@ -1,22 +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\Hydra\Serializer; - -class_exists(\ApiPlatform\Hydra\Serializer\CollectionNormalizer::class); - -if (false) { - final class CollectionNormalizer extends \ApiPlatform\Hydra\Serializer\CollectionNormalizer - { - } -} diff --git a/src/Core/Hydra/Serializer/ConstraintViolationListNormalizer.php b/src/Core/Hydra/Serializer/ConstraintViolationListNormalizer.php deleted file mode 100644 index 631ae16dc87..00000000000 --- a/src/Core/Hydra/Serializer/ConstraintViolationListNormalizer.php +++ /dev/null @@ -1,22 +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\Hydra\Serializer; - -class_exists(\ApiPlatform\Hydra\Serializer\ConstraintViolationListNormalizer::class); - -if (false) { - final class ConstraintViolationListNormalizer extends \ApiPlatform\Hydra\Serializer\ConstraintViolationListNormalizer - { - } -} diff --git a/src/Core/Hydra/Serializer/DocumentationNormalizer.php b/src/Core/Hydra/Serializer/DocumentationNormalizer.php deleted file mode 100644 index 26824ab3a40..00000000000 --- a/src/Core/Hydra/Serializer/DocumentationNormalizer.php +++ /dev/null @@ -1,22 +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\Hydra\Serializer; - -class_exists(\ApiPlatform\Hydra\Serializer\DocumentationNormalizer::class); - -if (false) { - final class DocumentationNormalizer extends \ApiPlatform\Hydra\Serializer\DocumentationNormalizer - { - } -} diff --git a/src/Core/Hydra/Serializer/EntrypointNormalizer.php b/src/Core/Hydra/Serializer/EntrypointNormalizer.php deleted file mode 100644 index a7682e82fae..00000000000 --- a/src/Core/Hydra/Serializer/EntrypointNormalizer.php +++ /dev/null @@ -1,22 +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\Hydra\Serializer; - -class_exists(\ApiPlatform\Hydra\Serializer\EntrypointNormalizer::class); - -if (false) { - final class EntrypointNormalizer extends \ApiPlatform\Hydra\Serializer\EntrypointNormalizer - { - } -} diff --git a/src/Core/Hydra/Serializer/ErrorNormalizer.php b/src/Core/Hydra/Serializer/ErrorNormalizer.php deleted file mode 100644 index 57de73d96c4..00000000000 --- a/src/Core/Hydra/Serializer/ErrorNormalizer.php +++ /dev/null @@ -1,22 +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\Hydra\Serializer; - -class_exists(\ApiPlatform\Hydra\Serializer\ErrorNormalizer::class); - -if (false) { - final class ErrorNormalizer extends \ApiPlatform\Hydra\Serializer\ErrorNormalizer - { - } -} diff --git a/src/Core/Hydra/Serializer/PartialCollectionViewNormalizer.php b/src/Core/Hydra/Serializer/PartialCollectionViewNormalizer.php deleted file mode 100644 index 6f8e6e1095c..00000000000 --- a/src/Core/Hydra/Serializer/PartialCollectionViewNormalizer.php +++ /dev/null @@ -1,22 +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\Hydra\Serializer; - -class_exists(\ApiPlatform\Hydra\Serializer\PartialCollectionViewNormalizer::class); - -if (false) { - final class PartialCollectionViewNormalizer extends \ApiPlatform\Hydra\Serializer\PartialCollectionViewNormalizer - { - } -} diff --git a/src/Core/Identifier/ContextAwareIdentifierConverterInterface.php b/src/Core/Identifier/ContextAwareIdentifierConverterInterface.php deleted file mode 100644 index 76f33ad81f0..00000000000 --- a/src/Core/Identifier/ContextAwareIdentifierConverterInterface.php +++ /dev/null @@ -1,27 +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\Identifier; - -/** - * Gives access to the context in the IdentifierConverter. - * - * @author Antoine Bluchet - */ -interface ContextAwareIdentifierConverterInterface extends IdentifierConverterInterface -{ - /** - * {@inheritdoc} - */ - public function convert($data, string $class, array $context = []): array; -} diff --git a/src/Core/Identifier/IdentifierConverter.php b/src/Core/Identifier/IdentifierConverter.php deleted file mode 100644 index 9af469b4f96..00000000000 --- a/src/Core/Identifier/IdentifierConverter.php +++ /dev/null @@ -1,91 +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\Identifier; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\InvalidIdentifierException; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -/** - * Identifier converter that chains identifier denormalizers. - * - * @author Antoine Bluchet - */ -final class IdentifierConverter implements ContextAwareIdentifierConverterInterface -{ - private $propertyMetadataFactory; - private $identifiersExtractor; - private $identifierDenormalizers; - private $resourceMetadataFactory; - - /** - * TODO: rename identifierDenormalizers to identifierTransformers in 3.0 and change their interfaces to a IdentifierTransformerInterface. - * - * @param iterable $identifierDenormalizers - */ - public function __construct(IdentifiersExtractorInterface $identifiersExtractor, PropertyMetadataFactoryInterface $propertyMetadataFactory, iterable $identifierDenormalizers, ResourceMetadataFactoryInterface $resourceMetadataFactory = null) - { - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->identifiersExtractor = $identifiersExtractor; - $this->identifierDenormalizers = $identifierDenormalizers; - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - - /** - * {@inheritdoc} - */ - public function convert($data, string $class, array $context = []): array - { - if (!\is_array($data)) { - @trigger_error(sprintf('Not using an array as the first argument of "%s->convert" is deprecated since API Platform 2.6 and will not be possible anymore in API Platform 3', self::class), \E_USER_DEPRECATED); - $data = ['id' => $data]; - } - - $identifiers = $data; - - foreach ($data as $parameter => $value) { - if (null === $type = $this->getIdentifierType($context['identifiers'][$parameter][0] ?? $class, $context['identifiers'][$parameter][1] ?? $parameter)) { - continue; - } - - /* @var DenormalizerInterface[] */ - foreach ($this->identifierDenormalizers as $identifierTransformer) { - if (!$identifierTransformer->supportsDenormalization($value, $type)) { - continue; - } - - try { - $identifiers[$parameter] = $identifierTransformer->denormalize($value, $type); - break; - } catch (InvalidIdentifierException $e) { - throw new InvalidIdentifierException(sprintf('Identifier "%s" could not be denormalized.', $parameter), $e->getCode(), $e); - } - } - } - - return $identifiers; - } - - private function getIdentifierType(string $resourceClass, string $property): ?string - { - if (!$type = $this->propertyMetadataFactory->create($resourceClass, $property)->getType()) { - return null; - } - - return Type::BUILTIN_TYPE_OBJECT === ($builtinType = $type->getBuiltinType()) ? $type->getClassName() : $builtinType; - } -} diff --git a/src/Core/Identifier/IdentifierConverterInterface.php b/src/Core/Identifier/IdentifierConverterInterface.php deleted file mode 100644 index 983525fc132..00000000000 --- a/src/Core/Identifier/IdentifierConverterInterface.php +++ /dev/null @@ -1,41 +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\Identifier; - -use ApiPlatform\Exception\InvalidIdentifierException; - -/** - * Identifier converter. - * - * @author Antoine Bluchet - */ -interface IdentifierConverterInterface -{ - /** - * @internal - */ - public const HAS_IDENTIFIER_CONVERTER = 'has_identifier_converter'; - - /** - * Takes an array of strings representing identifiers and transform their values to the expected type. - * - * @param mixed $data Identifier to convert to php values - * @param string $class The class to which the identifiers belong - * - * @throws InvalidIdentifierException - * - * @return array Indexed by identifiers properties with their values denormalized - */ - public function convert($data, string $class): array; -} diff --git a/src/Core/Identifier/Normalizer/DateTimeIdentifierDenormalizer.php b/src/Core/Identifier/Normalizer/DateTimeIdentifierDenormalizer.php deleted file mode 100644 index 9b73c1eca2a..00000000000 --- a/src/Core/Identifier/Normalizer/DateTimeIdentifierDenormalizer.php +++ /dev/null @@ -1,43 +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\Identifier\Normalizer; - -use ApiPlatform\Exception\InvalidIdentifierException; -use Symfony\Component\Serializer\Exception\NotNormalizableValueException; -use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; - -final class DateTimeIdentifierDenormalizer extends DateTimeNormalizer -{ - /** - * @param mixed|null $format - * @param mixed $data - * @param mixed $class - */ - public function denormalize($data, $class, $format = null, array $context = []): \DateTimeInterface - { - try { - return parent::denormalize($data, $class, $format, $context); - } catch (NotNormalizableValueException $e) { - throw new InvalidIdentifierException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function hasCacheableSupportsMethod(): bool - { - return true; - } -} diff --git a/src/Core/Identifier/Normalizer/IntegerDenormalizer.php b/src/Core/Identifier/Normalizer/IntegerDenormalizer.php deleted file mode 100644 index d75e1e9cf00..00000000000 --- a/src/Core/Identifier/Normalizer/IntegerDenormalizer.php +++ /dev/null @@ -1,45 +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\Identifier\Normalizer; - -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -final class IntegerDenormalizer implements DenormalizerInterface, CacheableSupportsMethodInterface -{ - /** - * {@inheritdoc} - */ - public function denormalize($data, $class, $format = null, array $context = []): int - { - return (int) $data; - } - - /** - * {@inheritdoc} - */ - public function supportsDenormalization($data, $type, $format = null): bool - { - return Type::BUILTIN_TYPE_INT === $type && \is_string($data); - } - - /** - * {@inheritdoc} - */ - public function hasCacheableSupportsMethod(): bool - { - return true; - } -} diff --git a/src/Core/JsonApi/EventListener/TransformFieldsetsParametersListener.php b/src/Core/JsonApi/EventListener/TransformFieldsetsParametersListener.php deleted file mode 100644 index f2b5cb4d7ce..00000000000 --- a/src/Core/JsonApi/EventListener/TransformFieldsetsParametersListener.php +++ /dev/null @@ -1,22 +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\JsonApi\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\JsonApi\TransformFieldsetsParametersListener::class); - -if (false) { - final class TransformFieldsetsParametersListener extends \ApiPlatform\Symfony\EventListener\JsonApi\TransformFieldsetsParametersListener - { - } -} diff --git a/src/Core/JsonApi/EventListener/TransformFilteringParametersListener.php b/src/Core/JsonApi/EventListener/TransformFilteringParametersListener.php deleted file mode 100644 index efbc636de69..00000000000 --- a/src/Core/JsonApi/EventListener/TransformFilteringParametersListener.php +++ /dev/null @@ -1,22 +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\JsonApi\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\JsonApi\TransformFilteringParametersListener::class); - -if (false) { - final class TransformFilteringParametersListener extends \ApiPlatform\Symfony\EventListener\JsonApi\TransformFilteringParametersListener - { - } -} diff --git a/src/Core/JsonApi/EventListener/TransformPaginationParametersListener.php b/src/Core/JsonApi/EventListener/TransformPaginationParametersListener.php deleted file mode 100644 index c8757daf9b9..00000000000 --- a/src/Core/JsonApi/EventListener/TransformPaginationParametersListener.php +++ /dev/null @@ -1,22 +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\JsonApi\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\JsonApi\TransformPaginationParametersListener::class); - -if (false) { - final class TransformPaginationParametersListener extends \ApiPlatform\Symfony\EventListener\JsonApi\TransformPaginationParametersListener - { - } -} diff --git a/src/Core/JsonApi/EventListener/TransformSortingParametersListener.php b/src/Core/JsonApi/EventListener/TransformSortingParametersListener.php deleted file mode 100644 index ad743feb683..00000000000 --- a/src/Core/JsonApi/EventListener/TransformSortingParametersListener.php +++ /dev/null @@ -1,22 +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\JsonApi\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\JsonApi\TransformSortingParametersListener::class); - -if (false) { - final class TransformSortingParametersListener extends \ApiPlatform\Symfony\EventListener\JsonApi\TransformSortingParametersListener - { - } -} diff --git a/src/Core/JsonApi/Serializer/CollectionNormalizer.php b/src/Core/JsonApi/Serializer/CollectionNormalizer.php deleted file mode 100644 index c82d3bcf872..00000000000 --- a/src/Core/JsonApi/Serializer/CollectionNormalizer.php +++ /dev/null @@ -1,22 +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\JsonApi\Serializer; - -class_exists(\ApiPlatform\JsonApi\Serializer\CollectionNormalizer::class); - -if (false) { - final class CollectionNormalizer extends \ApiPlatform\JsonApi\Serializer\CollectionNormalizer - { - } -} diff --git a/src/Core/JsonApi/Serializer/ConstraintViolationListNormalizer.php b/src/Core/JsonApi/Serializer/ConstraintViolationListNormalizer.php deleted file mode 100644 index 1b8ad3247bc..00000000000 --- a/src/Core/JsonApi/Serializer/ConstraintViolationListNormalizer.php +++ /dev/null @@ -1,22 +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\JsonApi\Serializer; - -class_exists(\ApiPlatform\JsonApi\Serializer\ConstraintViolationListNormalizer::class); - -if (false) { - final class ConstraintViolationListNormalizer extends \ApiPlatform\JsonApi\Serializer\ConstraintViolationListNormalizer - { - } -} diff --git a/src/Core/JsonApi/Serializer/EntrypointNormalizer.php b/src/Core/JsonApi/Serializer/EntrypointNormalizer.php deleted file mode 100644 index 0edf014ac61..00000000000 --- a/src/Core/JsonApi/Serializer/EntrypointNormalizer.php +++ /dev/null @@ -1,22 +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\JsonApi\Serializer; - -class_exists(\ApiPlatform\JsonApi\Serializer\EntrypointNormalizer::class); - -if (false) { - final class EntrypointNormalizer extends \ApiPlatform\JsonApi\Serializer\EntrypointNormalizer - { - } -} diff --git a/src/Core/JsonApi/Serializer/ErrorNormalizer.php b/src/Core/JsonApi/Serializer/ErrorNormalizer.php deleted file mode 100644 index d30771f80a8..00000000000 --- a/src/Core/JsonApi/Serializer/ErrorNormalizer.php +++ /dev/null @@ -1,22 +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\JsonApi\Serializer; - -class_exists(\ApiPlatform\JsonApi\Serializer\ErrorNormalizer::class); - -if (false) { - final class ErrorNormalizer extends \ApiPlatform\JsonApi\Serializer\ErrorNormalizer - { - } -} diff --git a/src/Core/JsonApi/Serializer/ItemNormalizer.php b/src/Core/JsonApi/Serializer/ItemNormalizer.php deleted file mode 100644 index 45df1a32192..00000000000 --- a/src/Core/JsonApi/Serializer/ItemNormalizer.php +++ /dev/null @@ -1,22 +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\JsonApi\Serializer; - -class_exists(\ApiPlatform\JsonApi\Serializer\ItemNormalizer::class); - -if (false) { - final class ItemNormalizer extends \ApiPlatform\JsonApi\Serializer\ItemNormalizer - { - } -} diff --git a/src/Core/JsonApi/Serializer/ObjectNormalizer.php b/src/Core/JsonApi/Serializer/ObjectNormalizer.php deleted file mode 100644 index 5e838cfbe62..00000000000 --- a/src/Core/JsonApi/Serializer/ObjectNormalizer.php +++ /dev/null @@ -1,22 +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\JsonApi\Serializer; - -class_exists(\ApiPlatform\JsonApi\Serializer\ObjectNormalizer::class); - -if (false) { - final class ObjectNormalizer extends \ApiPlatform\JsonApi\Serializer\ObjectNormalizer - { - } -} diff --git a/src/Core/JsonApi/Serializer/ReservedAttributeNameConverter.php b/src/Core/JsonApi/Serializer/ReservedAttributeNameConverter.php deleted file mode 100644 index ec61d3df8df..00000000000 --- a/src/Core/JsonApi/Serializer/ReservedAttributeNameConverter.php +++ /dev/null @@ -1,22 +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\JsonApi\Serializer; - -class_exists(\ApiPlatform\JsonApi\Serializer\ReservedAttributeNameConverter::class); - -if (false) { - final class ReservedAttributeNameConverter extends \ApiPlatform\JsonApi\Serializer\ReservedAttributeNameConverter - { - } -} diff --git a/src/Core/JsonLd/Action/ContextAction.php b/src/Core/JsonLd/Action/ContextAction.php deleted file mode 100644 index f2e668a3ab8..00000000000 --- a/src/Core/JsonLd/Action/ContextAction.php +++ /dev/null @@ -1,22 +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\JsonLd\Action; - -class_exists(\ApiPlatform\JsonLd\Action\ContextAction::class); - -if (false) { - final class ContextAction extends \ApiPlatform\JsonLd\Action\ContextAction - { - } -} diff --git a/src/Core/JsonLd/ContextBuilder.php b/src/Core/JsonLd/ContextBuilder.php deleted file mode 100644 index a65df6d2e1e..00000000000 --- a/src/Core/JsonLd/ContextBuilder.php +++ /dev/null @@ -1,22 +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\JsonLd; - -class_exists(\ApiPlatform\JsonLd\ContextBuilder::class); - -if (false) { - final class ContextBuilder extends \ApiPlatform\JsonLd\ContextBuilder - { - } -} diff --git a/src/Core/JsonLd/Serializer/ItemNormalizer.php b/src/Core/JsonLd/Serializer/ItemNormalizer.php deleted file mode 100644 index ceef30d9f9f..00000000000 --- a/src/Core/JsonLd/Serializer/ItemNormalizer.php +++ /dev/null @@ -1,22 +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\JsonLd\Serializer; - -class_exists(\ApiPlatform\JsonLd\Serializer\ItemNormalizer::class); - -if (false) { - final class ItemNormalizer extends \ApiPlatform\JsonLd\Serializer\ItemNormalizer - { - } -} diff --git a/src/Core/JsonLd/Serializer/JsonLdContextTrait.php b/src/Core/JsonLd/Serializer/JsonLdContextTrait.php deleted file mode 100644 index 84a6f94c001..00000000000 --- a/src/Core/JsonLd/Serializer/JsonLdContextTrait.php +++ /dev/null @@ -1,23 +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\JsonLd\Serializer; - -class_exists(\ApiPlatform\JsonLd\Serializer\JsonLdContextTrait::class); - -if (false) { - trait JsonLdContextTrait - { - use \ApiPlatform\JsonLd\Serializer\JsonLdContextTrait; - } -} diff --git a/src/Core/JsonLd/Serializer/ObjectNormalizer.php b/src/Core/JsonLd/Serializer/ObjectNormalizer.php deleted file mode 100644 index cca5348c115..00000000000 --- a/src/Core/JsonLd/Serializer/ObjectNormalizer.php +++ /dev/null @@ -1,22 +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\JsonLd\Serializer; - -class_exists(\ApiPlatform\JsonLd\Serializer\ObjectNormalizer::class); - -if (false) { - final class ObjectNormalizer extends \ApiPlatform\JsonLd\Serializer\ObjectNormalizer - { - } -} diff --git a/src/Core/JsonSchema/Command/JsonSchemaGenerateCommand.php b/src/Core/JsonSchema/Command/JsonSchemaGenerateCommand.php deleted file mode 100644 index 5427bd17c6a..00000000000 --- a/src/Core/JsonSchema/Command/JsonSchemaGenerateCommand.php +++ /dev/null @@ -1,22 +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\JsonSchema\Command; - -class_exists(\ApiPlatform\JsonSchema\Command\JsonSchemaGenerateCommand::class); - -if (false) { - final class JsonSchemaGenerateCommand extends \ApiPlatform\JsonSchema\Command\JsonSchemaGenerateCommand - { - } -} diff --git a/src/Core/JsonSchema/Schema.php b/src/Core/JsonSchema/Schema.php deleted file mode 100644 index 759a42ee924..00000000000 --- a/src/Core/JsonSchema/Schema.php +++ /dev/null @@ -1,22 +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\JsonSchema; - -class_exists(\ApiPlatform\JsonSchema\Schema::class); - -if (false) { - final class Schema extends \ApiPlatform\JsonSchema\Schema - { - } -} diff --git a/src/Core/JsonSchema/SchemaFactory.php b/src/Core/JsonSchema/SchemaFactory.php deleted file mode 100644 index 372eb63c3dc..00000000000 --- a/src/Core/JsonSchema/SchemaFactory.php +++ /dev/null @@ -1,447 +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\JsonSchema; - -use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface as LegacyPropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer; -use ApiPlatform\JsonSchema\TypeFactoryInterface; -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Metadata\HttpOperation; -use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; -use ApiPlatform\OpenApi\Factory\OpenApiFactory; -use ApiPlatform\Util\ResourceClassInfoTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - -/** - * {@inheritdoc} - * - * @experimental - * - * @author Kévin Dunglas - */ -final class SchemaFactory implements SchemaFactoryInterface -{ - use ResourceClassInfoTrait; - - private $typeFactory; - /** - * @var LegacyPropertyNameCollectionFactoryInterface|PropertyNameCollectionFactoryInterface - */ - private $propertyNameCollectionFactory; - /** - * @var LegacyPropertyMetadataFactoryInterface|PropertyMetadataFactoryInterface - */ - private $propertyMetadataFactory; - private $nameConverter; - private $distinctFormats = []; - - /** - * @param TypeFactoryInterface $typeFactory - * @param mixed $resourceMetadataFactory - * @param mixed $propertyNameCollectionFactory - * @param mixed $propertyMetadataFactory - */ - public function __construct($typeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, NameConverterInterface $nameConverter = null, ResourceClassResolverInterface $resourceClassResolver = null) - { - $this->typeFactory = $typeFactory; - 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; - $this->nameConverter = $nameConverter; - $this->resourceClassResolver = $resourceClassResolver; - } - - /** - * When added to the list, the given format will lead to the creation of a new definition. - * - * @internal - */ - public function addDistinctFormat(string $format): void - { - $this->distinctFormats[$format] = true; - } - - /** - * {@inheritdoc} - */ - public function buildSchema(string $className, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema - { - $schema = $schema ? clone $schema : new Schema(); - if (null === $metadata = $this->getMetadata($className, $type, $operationType, $operationName, $serializerContext)) { - return $schema; - } - - [$resourceMetadata, $serializerContext, $validationGroups, $inputOrOutputClass] = $metadata; - - if (null === $resourceMetadata && (null !== $operationType || null !== $operationName)) { - throw new \LogicException('The $operationType and $operationName arguments must be null for non-resource class.'); - } - - $operation = $resourceMetadata instanceof ResourceMetadataCollection ? $resourceMetadata->getOperation($operationName, OperationType::COLLECTION === $operationType) : null; - - $version = $schema->getVersion(); - $definitionName = $this->buildDefinitionName($className, $format, $inputOrOutputClass, $resourceMetadata instanceof ResourceMetadata ? $resourceMetadata : $operation, $serializerContext); - - $method = $operation instanceof HttpOperation ? $operation->getMethod() : 'GET'; - if (!$operation && (null === $operationType || null === $operationName)) { - $method = Schema::TYPE_INPUT === $type ? 'POST' : 'GET'; - } elseif ($resourceMetadata instanceof ResourceMetadata) { - $method = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method', 'GET'); - } - - if (Schema::TYPE_OUTPUT !== $type && !\in_array($method, ['POST', 'PATCH', 'PUT'], true)) { - return $schema; - } - - if (!isset($schema['$ref']) && !isset($schema['type'])) { - $ref = Schema::VERSION_OPENAPI === $version ? '#/components/schemas/'.$definitionName : '#/definitions/'.$definitionName; - - if ($forceCollection || (OperationType::COLLECTION === $operationType && 'POST' !== $method)) { - $schema['type'] = 'array'; - $schema['items'] = ['$ref' => $ref]; - } else { - $schema['$ref'] = $ref; - } - } - - $definitions = $schema->getDefinitions(); - if (isset($definitions[$definitionName])) { - // Already computed - return $schema; - } - - /** @var \ArrayObject $definition */ - $definition = new \ArrayObject(['type' => 'object']); - $definitions[$definitionName] = $definition; - - if ($resourceMetadata instanceof ResourceMetadata) { - $definition['description'] = $resourceMetadata->getDescription() ?? ''; - } else { - $definition['description'] = $operation ? ($operation->getDescription() ?? '') : ''; - } - - // additionalProperties are allowed by default, so it does not need to be set explicitly, unless allow_extra_attributes is false - // See https://json-schema.org/understanding-json-schema/reference/object.html#properties - if (false === ($serializerContext[AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES] ?? true)) { - $definition['additionalProperties'] = false; - } - - // see https://github.com/json-schema-org/json-schema-spec/pull/737 - if ( - Schema::VERSION_SWAGGER !== $version - ) { - if (($resourceMetadata instanceof ResourceMetadata && - ($operationType && $operationName ? $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true) : $resourceMetadata->getAttribute('deprecation_reason', null)) - ) || ($operation && $operation->getDeprecationReason()) - ) { - $definition['deprecated'] = true; - } - } - - // externalDocs is an OpenAPI specific extension, but JSON Schema allows additional keys, so we always add it - // See https://json-schema.org/latest/json-schema-core.html#rfc.section.6.4 - if ($resourceMetadata instanceof ResourceMetadata && $resourceMetadata->getIri()) { - $definition['externalDocs'] = ['url' => $resourceMetadata->getIri()]; - } elseif ($operation instanceof HttpOperation && ($operation->getTypes()[0] ?? null)) { - $definition['externalDocs'] = ['url' => $operation->getTypes()[0]]; - } - - // TODO: getFactoryOptions should be refactored because Item & Collection Operations don't exist anymore (API Platform 3.0) - $options = $this->getFactoryOptions($serializerContext, $validationGroups, $operationType, $operationName, $operation instanceof HttpOperation ? $operation : null); - foreach ($this->propertyNameCollectionFactory->create($inputOrOutputClass, $options) as $propertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($inputOrOutputClass, $propertyName, $options); - if (!$propertyMetadata->isReadable() && !$propertyMetadata->isWritable()) { - continue; - } - - $normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName, $inputOrOutputClass, $format, $serializerContext) : $propertyName; - if ($propertyMetadata->isRequired()) { - $definition['required'][] = $normalizedPropertyName; - } - - $this->buildPropertySchema($schema, $definitionName, $normalizedPropertyName, $propertyMetadata, $serializerContext, $format); - } - - return $schema; - } - - private function buildPropertySchema(Schema $schema, string $definitionName, string $normalizedPropertyName, $propertyMetadata, array $serializerContext, string $format): void - { - $version = $schema->getVersion(); - $swagger = Schema::VERSION_SWAGGER === $version; - $propertySchema = $propertyMetadata->getSchema() ?? []; - - if ($propertyMetadata instanceof ApiProperty) { - $additionalPropertySchema = $propertyMetadata->getOpenapiContext() ?? []; - } else { - switch ($version) { - case Schema::VERSION_SWAGGER: - $basePropertySchemaAttribute = 'swagger_context'; - break; - case Schema::VERSION_OPENAPI: - $basePropertySchemaAttribute = 'openapi_context'; - break; - default: - $basePropertySchemaAttribute = 'json_schema_context'; - } - - $additionalPropertySchema = $propertyMetadata->getAttributes()[$basePropertySchemaAttribute] ?? []; - } - - $propertySchema = array_merge( - $propertySchema, - $additionalPropertySchema - ); - - if (false === $propertyMetadata->isWritable() && !$propertyMetadata->isInitializable()) { - $propertySchema['readOnly'] = true; - } - if (!$swagger && false === $propertyMetadata->isReadable()) { - $propertySchema['writeOnly'] = true; - } - if (null !== $description = $propertyMetadata->getDescription()) { - $propertySchema['description'] = $description; - } - - $deprecationReason = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getAttribute('deprecation_reason') : $propertyMetadata->getDeprecationReason(); - - // see https://github.com/json-schema-org/json-schema-spec/pull/737 - if (!$swagger && null !== $deprecationReason) { - $propertySchema['deprecated'] = true; - } - // externalDocs is an OpenAPI specific extension, but JSON Schema allows additional keys, so we always add it - // See https://json-schema.org/latest/json-schema-core.html#rfc.section.6.4 - $iri = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getIri() : $propertyMetadata->getTypes()[0] ?? null; - if (null !== $iri) { - $propertySchema['externalDocs'] = ['url' => $iri]; - } - - if (!isset($propertySchema['default']) && !empty($default = $propertyMetadata->getDefault())) { - $propertySchema['default'] = $default; - } - - if (!isset($propertySchema['example']) && !empty($example = $propertyMetadata->getExample())) { - $propertySchema['example'] = $example; - } - - if (!isset($propertySchema['example']) && isset($propertySchema['default'])) { - $propertySchema['example'] = $propertySchema['default']; - } - - $valueSchema = []; - // TODO: 3.0 support multiple types, default value of types will be [] instead of null - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : $propertyMetadata->getBuiltinTypes()[0] ?? null; - if (null !== $type) { - if ($isCollection = $type->isCollection()) { - $keyType = method_exists(Type::class, 'getCollectionKeyTypes') ? ($type->getCollectionKeyTypes()[0] ?? null) : $type->getCollectionKeyType(); - $valueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType(); - } else { - $keyType = null; - $valueType = $type; - } - - if (null === $valueType) { - $builtinType = 'string'; - $className = null; - } else { - $builtinType = $valueType->getBuiltinType(); - $className = $valueType->getClassName(); - } - - $valueSchema = $this->typeFactory->getType(new Type($builtinType, $type->isNullable(), $className, $isCollection, $keyType, $valueType), $format, $propertyMetadata->isReadableLink(), $serializerContext, $schema); - } - - if (\array_key_exists('type', $propertySchema) && \array_key_exists('$ref', $valueSchema)) { - $propertySchema = new \ArrayObject($propertySchema); - } else { - $propertySchema = new \ArrayObject($propertySchema + $valueSchema); - } - $schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema; - } - - private function buildDefinitionName(string $className, string $format = 'json', ?string $inputOrOutputClass = null, $resourceMetadata = null, ?array $serializerContext = null): string - { - if ($resourceMetadata) { - $prefix = $resourceMetadata instanceof ResourceMetadata ? $resourceMetadata->getShortName() : $resourceMetadata->getShortName(); - } - - if (!isset($prefix)) { - $prefix = (new \ReflectionClass($className))->getShortName(); - } - - if (null !== $inputOrOutputClass && $className !== $inputOrOutputClass) { - $parts = explode('\\', $inputOrOutputClass); - $shortName = end($parts); - $prefix .= '.'.$shortName; - } - - if (isset($this->distinctFormats[$format])) { - // JSON is the default, and so isn't included in the definition name - $prefix .= '.'.$format; - } - - $definitionName = $serializerContext[OpenApiFactory::OPENAPI_DEFINITION_NAME] ?? $serializerContext[DocumentationNormalizer::SWAGGER_DEFINITION_NAME] ?? null; - if ($definitionName) { - $name = sprintf('%s-%s', $prefix, $definitionName); - } else { - $groups = (array) ($serializerContext[AbstractNormalizer::GROUPS] ?? []); - $name = $groups ? sprintf('%s-%s', $prefix, implode('_', $groups)) : $prefix; - } - - return $this->encodeDefinitionName($name); - } - - private function encodeDefinitionName(string $name): string - { - return preg_replace('/[^a-zA-Z0-9.\-_]/', '.', $name); - } - - private function getMetadata(string $className, string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?array $serializerContext = null): ?array - { - if (!$this->isResourceClass($className)) { - return [ - null, - $serializerContext ?? [], - [], - $className, - ]; - } - - /** @var ResourceMetadata|ResourceMetadataCollection $resourceMetadata */ - $resourceMetadata = $this->resourceMetadataFactory->create($className); - $attribute = Schema::TYPE_OUTPUT === $type ? 'output' : 'input'; - $operation = ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) ? null : $resourceMetadata->getOperation($operationName); - - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - if (null === $operationType || null === $operationName) { - $inputOrOutput = $resourceMetadata->getAttribute($attribute, ['class' => $className]); - } else { - $inputOrOutput = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, $attribute, ['class' => $className], true); - } - } elseif ($operation) { - $inputOrOutput = (Schema::TYPE_OUTPUT === $type ? $operation->getOutput() : $operation->getInput()) ?? ['class' => $className]; - } else { - $inputOrOutput = ['class' => $className]; - } - - if (null === ($inputOrOutput['class'] ?? $inputOrOutput->class ?? null)) { - // input or output disabled - return null; - } - - return [ - $resourceMetadata, - $serializerContext ?? $this->getSerializerContext($resourceMetadata, $type, $operationType, $operationName), - $this->getValidationGroups($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface ? $resourceMetadata : $operation, $operationType, $operationName), - $inputOrOutput['class'] ?? $inputOrOutput->class, - ]; - } - - private function getSerializerContext($resourceMetadata, string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null): array - { - if ($resourceMetadata instanceof ResourceMetadata) { - $attribute = Schema::TYPE_OUTPUT === $type ? 'normalization_context' : 'denormalization_context'; - } else { - $operation = $resourceMetadata->getOperation($operationName); - } - - if (null === $operationType || null === $operationName) { - if ($resourceMetadata instanceof ResourceMetadata) { - return $resourceMetadata->getAttribute($attribute, []); - } - - return Schema::TYPE_OUTPUT === $type ? ($operation->getNormalizationContext() ?? []) : ($operation->getDenormalizationContext() ?? []); - } - - if ($resourceMetadata instanceof ResourceMetadata) { - return $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, $attribute, [], true); - } - - return Schema::TYPE_OUTPUT === $type ? ($operation->getNormalizationContext() ?? []) : ($operation->getDenormalizationContext() ?? []); - } - - /** - * @param HttpOperation|ResourceMetadata|null $resourceMetadata - */ - private function getValidationGroups($resourceMetadata, ?string $operationType, ?string $operationName): array - { - if ($resourceMetadata instanceof ResourceMetadata) { - $attribute = 'validation_groups'; - - if (null === $operationType || null === $operationName) { - return \is_array($validationGroups = $resourceMetadata->getAttribute($attribute, [])) ? $validationGroups : []; - } - - return \is_array($validationGroups = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, $attribute, [], true)) ? $validationGroups : []; - } - - $groups = $resourceMetadata ? ($resourceMetadata->getValidationContext()['groups'] ?? []) : []; - - return \is_array($groups) ? $groups : [$groups]; - } - - /** - * Gets the options for the property name collection / property metadata factories. - */ - private function getFactoryOptions(array $serializerContext, array $validationGroups, ?string $operationType, ?string $operationName, ?HttpOperation $operation = null): array - { - $options = [ - /* @see https://github.com/symfony/symfony/blob/v5.1.0/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php */ - 'enable_getter_setter_extraction' => true, - ]; - - if (isset($serializerContext[AbstractNormalizer::GROUPS])) { - /* @see https://github.com/symfony/symfony/blob/v4.2.6/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php */ - $options['serializer_groups'] = (array) $serializerContext[AbstractNormalizer::GROUPS]; - } - - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && $operation) { - $options['normalization_groups'] = $operation->getNormalizationContext()['groups'] ?? null; - $options['denormalization_groups'] = $operation->getDenormalizationContext()['groups'] ?? null; - } - - if (null !== $operationType && null !== $operationName) { - switch ($operationType) { - case OperationType::COLLECTION: - $options['collection_operation_name'] = $operationName; - break; - case OperationType::ITEM: - $options['item_operation_name'] = $operationName; - break; - default: - break; - } - } - - if ($validationGroups) { - $options['validation_groups'] = $validationGroups; - } - - return $options; - } -} diff --git a/src/Core/JsonSchema/SchemaFactoryInterface.php b/src/Core/JsonSchema/SchemaFactoryInterface.php deleted file mode 100644 index 70d79c4439f..00000000000 --- a/src/Core/JsonSchema/SchemaFactoryInterface.php +++ /dev/null @@ -1,31 +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\JsonSchema; - -use ApiPlatform\JsonSchema\Schema; - -/** - * Factory for creating the JSON Schema document corresponding to a PHP class. - * - * @experimental - * - * @author Kévin Dunglas - */ -interface SchemaFactoryInterface -{ - /** - * Builds the JSON Schema document corresponding to the given PHP class. - */ - public function buildSchema(string $className, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema; -} diff --git a/src/Core/JsonSchema/TypeFactory.php b/src/Core/JsonSchema/TypeFactory.php deleted file mode 100644 index 11a4af3f803..00000000000 --- a/src/Core/JsonSchema/TypeFactory.php +++ /dev/null @@ -1,22 +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\JsonSchema; - -class_exists(\ApiPlatform\JsonSchema\TypeFactory::class); - -if (false) { - final class TypeFactory extends \ApiPlatform\JsonSchema\TypeFactory - { - } -} diff --git a/src/Core/Mercure/EventListener/AddLinkHeaderListener.php b/src/Core/Mercure/EventListener/AddLinkHeaderListener.php deleted file mode 100644 index d629f8e9601..00000000000 --- a/src/Core/Mercure/EventListener/AddLinkHeaderListener.php +++ /dev/null @@ -1,22 +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\Mercure\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\AddLinkHeaderListener::class); - -if (false) { - final class AddLinkHeaderListener extends \ApiPlatform\Symfony\EventListener\AddLinkHeaderListener - { - } -} diff --git a/src/Core/Metadata/Extractor/XmlExtractor.php b/src/Core/Metadata/Extractor/XmlExtractor.php deleted file mode 100644 index e852c9472d4..00000000000 --- a/src/Core/Metadata/Extractor/XmlExtractor.php +++ /dev/null @@ -1,212 +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\Metadata\Extractor; - -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Metadata\Extractor\AbstractResourceExtractor; -use ApiPlatform\Metadata\Extractor\PropertyExtractorInterface; -use ApiPlatform\Metadata\Extractor\XmlPropertyExtractor; -use ApiPlatform\Metadata\Extractor\XmlResourceExtractor; -use Symfony\Component\Config\Util\XmlUtils; - -/** - * Extracts an array of metadata from a list of XML files. - * - * @author Kévin Dunglas - * @author Antoine Bluchet - * @author Baptiste Meyer - * @author Vincent Chalamon - * - * @deprecated since 2.7, to remove in 3.0 (replaced by ApiPlatform\Metadata\Extractor\XmlExtractor) - */ -final class XmlExtractor extends AbstractResourceExtractor implements PropertyExtractorInterface -{ - public const RESOURCE_SCHEMA = __DIR__.'/../schema/metadata.xsd'; - - private $properties; - - /** - * {@inheritdoc} - */ - public function getProperties(): array - { - if (null !== $this->properties) { - return $this->properties; - } - - $this->properties = []; - foreach ($this->paths as $path) { - $this->extractPath($path); - } - - return $this->properties; - } - - /** - * {@inheritdoc} - */ - protected function extractPath(string $path) - { - try { - /** @var \SimpleXMLElement $xml */ - $xml = simplexml_import_dom(XmlUtils::loadFile($path, self::RESOURCE_SCHEMA)); - } catch (\InvalidArgumentException $e) { - // Test if this is a new resource - try { - $xml = XmlUtils::loadFile($path, XmlResourceExtractor::SCHEMA); - - return; - } catch (\InvalidArgumentException $newResourceException) { - try { - $xml = XmlUtils::loadFile($path, XmlPropertyExtractor::SCHEMA); - - return; - } catch (\InvalidArgumentException $newPropertyException) { - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); - } - } - } - - foreach ($xml->resource as $resource) { - $resourceClass = $this->resolve((string) $resource['class']); - - $this->resources[$resourceClass] = [ - 'shortName' => $this->phpizeAttribute($resource, 'shortName', 'string'), - 'description' => $this->phpizeAttribute($resource, 'description', 'string'), - 'iri' => $this->phpizeAttribute($resource, 'iri', 'string'), - 'itemOperations' => $this->extractOperations($resource, 'itemOperation'), - 'collectionOperations' => $this->extractOperations($resource, 'collectionOperation'), - 'subresourceOperations' => $this->extractOperations($resource, 'subresourceOperation'), - 'graphql' => $this->extractOperations($resource, 'operation'), - 'attributes' => $this->extractAttributes($resource, 'attribute') ?: null, - 'properties' => $this->extractProperties($resource) ?: null, - ]; - $this->properties[$resourceClass] = $this->resources[$resourceClass]['properties']; - } - } - - /** - * Returns the array containing configured operations. Returns NULL if there is no operation configuration. - */ - private function extractOperations(\SimpleXMLElement $resource, string $operationType): ?array - { - $graphql = 'operation' === $operationType; - if (!$graphql && $legacyOperations = $this->extractAttributes($resource, $operationType)) { - @trigger_error( - sprintf('Configuring "%1$s" tags without using a parent "%1$ss" tag is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3', $operationType), - \E_USER_DEPRECATED - ); - - return $legacyOperations; - } - - $operationsParent = $graphql ? 'graphql' : "{$operationType}s"; - if (!isset($resource->{$operationsParent})) { - return null; - } - - return $this->extractAttributes($resource->{$operationsParent}, $operationType, true); - } - - /** - * Recursively transforms an attribute structure into an associative array. - */ - private function extractAttributes(\SimpleXMLElement $resource, string $elementName, bool $topLevel = false): array - { - $attributes = []; - foreach ($resource->{$elementName} as $attribute) { - $value = isset($attribute->attribute[0]) ? $this->extractAttributes($attribute, 'attribute') : $this->phpizeContent($attribute); - // allow empty operations definition, like - if ($topLevel && '' === $value) { - $value = []; - } - if (isset($attribute['name'])) { - $attributes[(string) $attribute['name']] = $value; - } else { - $attributes[] = $value; - } - } - - return $attributes; - } - - /** - * Gets metadata of a property. - */ - private function extractProperties(\SimpleXMLElement $resource): array - { - $properties = []; - foreach ($resource->property as $property) { - $properties[(string) $property['name']] = [ - 'description' => $this->phpizeAttribute($property, 'description', 'string'), - 'readable' => $this->phpizeAttribute($property, 'readable', 'bool'), - 'writable' => $this->phpizeAttribute($property, 'writable', 'bool'), - 'readableLink' => $this->phpizeAttribute($property, 'readableLink', 'bool'), - 'writableLink' => $this->phpizeAttribute($property, 'writableLink', 'bool'), - 'required' => $this->phpizeAttribute($property, 'required', 'bool'), - 'identifier' => $this->phpizeAttribute($property, 'identifier', 'bool'), - 'iri' => $this->phpizeAttribute($property, 'iri', 'string'), - 'attributes' => $this->extractAttributes($property, 'attribute'), - 'subresource' => $property->subresource ? [ - 'collection' => $this->phpizeAttribute($property->subresource, 'collection', 'bool'), - 'resourceClass' => $this->resolve($this->phpizeAttribute($property->subresource, 'resourceClass', 'string')), - 'maxDepth' => $this->phpizeAttribute($property->subresource, 'maxDepth', 'integer'), - ] : null, - ]; - } - - return $properties; - } - - /** - * Transforms an XML attribute's value in a PHP value. - * - * @return string|int|bool|null - */ - private function phpizeAttribute(\SimpleXMLElement $array, string $key, string $type) - { - if (!isset($array[$key])) { - return null; - } - - switch ($type) { - case 'string': - return (string) $array[$key]; - case 'integer': - return (int) $array[$key]; - case 'bool': - return (bool) XmlUtils::phpize($array[$key]); - } - - return null; - } - - /** - * Transforms an XML element's content in a PHP value. - */ - private function phpizeContent(\SimpleXMLElement $array) - { - $type = $array['type'] ?? null; - $value = (string) $array; - - switch ($type) { - case 'string': - return $value; - case 'constant': - return \constant($value); - default: - return XmlUtils::phpize($value); - } - } -} diff --git a/src/Core/Metadata/Extractor/YamlExtractor.php b/src/Core/Metadata/Extractor/YamlExtractor.php deleted file mode 100644 index cdd77476094..00000000000 --- a/src/Core/Metadata/Extractor/YamlExtractor.php +++ /dev/null @@ -1,183 +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\Metadata\Extractor; - -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Metadata\Extractor\AbstractResourceExtractor; -use ApiPlatform\Metadata\Extractor\PropertyExtractorInterface; -use Symfony\Component\Yaml\Exception\ParseException; -use Symfony\Component\Yaml\Yaml; - -/** - * Extracts an array of metadata from a list of YAML files. - * - * @author Antoine Bluchet - * @author Baptiste Meyer - * @author Kévin Dunglas - * @author Vincent Chalamon - * - * @deprecated since 2.7, to remove in 3.0 (replaced by ApiPlatform\Metadata\Extractor\YamlExtractor) - */ -final class YamlExtractor extends AbstractResourceExtractor implements PropertyExtractorInterface -{ - private $properties; - - /** - * {@inheritdoc} - */ - public function getProperties(): array - { - if (null !== $this->properties) { - return $this->properties; - } - - $this->properties = []; - foreach ($this->paths as $path) { - $this->extractPath($path); - } - - return $this->properties; - } - - /** - * {@inheritdoc} - */ - protected function extractPath(string $path) - { - try { - $resourcesYaml = Yaml::parse((string) file_get_contents($path), Yaml::PARSE_CONSTANT); - } catch (ParseException $e) { - $e->setParsedFile($path); - - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); - } - - if (null === $resourcesYaml = $resourcesYaml['resources'] ?? $resourcesYaml) { - return; - } - - if (!\is_array($resourcesYaml)) { - throw new InvalidArgumentException(sprintf('"resources" setting is expected to be null or an array, %s given in "%s".', \gettype($resourcesYaml), $path)); - } - - $this->extractResources($resourcesYaml, $path); - } - - private function extractResources(array $resourcesYaml, string $path): void - { - foreach ($resourcesYaml as $resourceName => $resourceYaml) { - $resourceName = $this->resolve($resourceName); - - if (null === $resourceYaml) { - $resourceYaml = []; - } - - if (!\is_array($resourceYaml)) { - throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $resourceName, \gettype($resourceYaml), $path)); - } - - $this->resources[$resourceName] = [ - 'shortName' => $this->phpize($resourceYaml, 'shortName', 'string'), - 'description' => $this->phpize($resourceYaml, 'description', 'string'), - 'iri' => $this->phpize($resourceYaml, 'iri', 'string'), - 'itemOperations' => $resourceYaml['itemOperations'] ?? null, - 'collectionOperations' => $resourceYaml['collectionOperations'] ?? null, - 'subresourceOperations' => $resourceYaml['subresourceOperations'] ?? null, - 'graphql' => $resourceYaml['graphql'] ?? null, - 'attributes' => $resourceYaml['attributes'] ?? null, - ]; - - if (!isset($resourceYaml['properties'])) { - $this->properties[$resourceName] = $this->resources[$resourceName]['properties'] = null; - - continue; - } - - if (!\is_array($resourceYaml['properties'])) { - throw new InvalidArgumentException(sprintf('"properties" setting is expected to be null or an array, %s given in "%s".', \gettype($resourceYaml['properties']), $path)); - } - - $this->extractProperties($resourceYaml, $resourceName, $path); - } - } - - private function extractProperties(array $resourceYaml, string $resourceName, string $path): void - { - foreach ($resourceYaml['properties'] as $propertyName => $propertyValues) { - if (null === $propertyValues) { - $this->properties[$resourceName][$propertyName] = $this->resources[$resourceName]['properties'][$propertyName] = null; - - continue; - } - - if (!\is_array($propertyValues)) { - throw new InvalidArgumentException(sprintf('"%s" setting is expected to be null or an array, %s given in "%s".', $propertyName, \gettype($propertyValues), $path)); - } - if (isset($propertyValues['subresource']['resourceClass'])) { - $propertyValues['subresource']['resourceClass'] = $this->resolve($propertyValues['subresource']['resourceClass']); - } - - $this->properties[$resourceName][$propertyName] = $this->resources[$resourceName]['properties'][$propertyName] = [ - 'description' => $this->phpize($propertyValues, 'description', 'string'), - 'readable' => $this->phpize($propertyValues, 'readable', 'bool'), - 'writable' => $this->phpize($propertyValues, 'writable', 'bool'), - 'readableLink' => $this->phpize($propertyValues, 'readableLink', 'bool'), - 'writableLink' => $this->phpize($propertyValues, 'writableLink', 'bool'), - 'required' => $this->phpize($propertyValues, 'required', 'bool'), - 'identifier' => $this->phpize($propertyValues, 'identifier', 'bool'), - 'iri' => $this->phpize($propertyValues, 'iri', 'string'), - 'attributes' => $propertyValues['attributes'] ?? [], - 'subresource' => isset($propertyValues['subresource']) ? [ - 'collection' => $this->phpize($propertyValues['subresource'], 'collection', 'bool'), - 'resourceClass' => $this->phpize($propertyValues['subresource'], 'resourceClass', 'string'), - 'maxDepth' => $this->phpize($propertyValues['subresource'], 'maxDepth', 'integer'), - ] : null, - ]; - } - } - - /** - * Transforms a YAML attribute's value in PHP value. - * - * @throws InvalidArgumentException - * - * @return bool|int|string|null - */ - private function phpize(array $array, string $key, string $type) - { - if (!isset($array[$key])) { - return null; - } - - switch ($type) { - case 'bool': - if (\is_bool($array[$key])) { - return $array[$key]; - } - break; - case 'integer': - if (\is_int($array[$key])) { - return $array[$key]; - } - break; - case 'string': - if (\is_string($array[$key])) { - return $array[$key]; - } - break; - } - - throw new InvalidArgumentException(sprintf('The property "%s" must be a "%s", "%s" given.', $key, $type, \gettype($array[$key]))); - } -} diff --git a/src/Core/Metadata/Property/Factory/AnnotationPropertyMetadataFactory.php b/src/Core/Metadata/Property/Factory/AnnotationPropertyMetadataFactory.php deleted file mode 100644 index 255b24fb7c2..00000000000 --- a/src/Core/Metadata/Property/Factory/AnnotationPropertyMetadataFactory.php +++ /dev/null @@ -1,154 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Annotation\ApiProperty; -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Util\Reflection; -use Doctrine\Common\Annotations\Reader; - -/** - * Creates a property metadata from {@see ApiProperty} annotations. - * - * @author Kévin Dunglas - */ -final class AnnotationPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - private $reader; - private $decorated; - - public function __construct(Reader $reader = null, PropertyMetadataFactoryInterface $decorated = null) - { - $this->reader = $reader; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - if (null === ($options['deprecate'] ?? null)) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Decorating the legacy %s is deprecated, use %s instead.', PropertyMetadataFactoryInterface::class, \ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface::class)); - } - - $parentPropertyMetadata = null; - if ($this->decorated) { - try { - $parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options); - } catch (PropertyNotFoundException $propertyNotFoundException) { - // Ignore not found exception from decorated factories - } - } - - try { - $reflectionClass = new \ReflectionClass($resourceClass); - } catch (\ReflectionException $reflectionException) { - return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property); - } - - if ($reflectionClass->hasProperty($property)) { - $annotation = null; - $reflectionProperty = $reflectionClass->getProperty($property); - if (\PHP_VERSION_ID >= 80000 && $attributes = $reflectionProperty->getAttributes(ApiProperty::class)) { - $annotation = $attributes[0]->newInstance(); - } elseif (null !== $this->reader) { - $annotation = $this->reader->getPropertyAnnotation($reflectionProperty, ApiProperty::class); - } - - if ($annotation instanceof ApiProperty) { - return $this->createMetadata($annotation, $parentPropertyMetadata); - } - } - - foreach (array_merge(Reflection::ACCESSOR_PREFIXES, Reflection::MUTATOR_PREFIXES) as $prefix) { - $methodName = $prefix.ucfirst($property); - if (!$reflectionClass->hasMethod($methodName)) { - continue; - } - - $reflectionMethod = $reflectionClass->getMethod($methodName); - if (!$reflectionMethod->isPublic()) { - continue; - } - - $annotation = null; - if (\PHP_VERSION_ID >= 80000 && $attributes = $reflectionMethod->getAttributes(ApiProperty::class)) { - $annotation = $attributes[0]->newInstance(); - } elseif (null !== $this->reader) { - $annotation = $this->reader->getMethodAnnotation($reflectionMethod, ApiProperty::class); - } - - if ($annotation instanceof ApiProperty) { - return $this->createMetadata($annotation, $parentPropertyMetadata); - } - } - - return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property); - } - - /** - * Returns the metadata from the decorated factory if available or throws an exception. - * - * @throws PropertyNotFoundException - */ - private function handleNotFound(?PropertyMetadata $parentPropertyMetadata, string $resourceClass, string $property): PropertyMetadata - { - if (null !== $parentPropertyMetadata) { - return $parentPropertyMetadata; - } - - throw new PropertyNotFoundException(sprintf('Property "%s" of class "%s" not found.', $property, $resourceClass)); - } - - private function createMetadata(ApiProperty $annotation, PropertyMetadata $parentPropertyMetadata = null): PropertyMetadata - { - if (null === $parentPropertyMetadata) { - return new PropertyMetadata( - null, - $annotation->description, - $annotation->readable, - $annotation->writable, - $annotation->readableLink, - $annotation->writableLink, - $annotation->required, - $annotation->identifier, - $annotation->iri, - null, - $annotation->attributes, - null, - null, - $annotation->default, - $annotation->example - ); - } - - $propertyMetadata = $parentPropertyMetadata; - foreach ([['get', 'description'], ['is', 'readable'], ['is', 'writable'], ['is', 'readableLink'], ['is', 'writableLink'], ['is', 'required'], ['get', 'iri'], ['is', 'identifier'], ['get', 'attributes'], ['get', 'default'], ['get', 'example']] as $property) { - if (null !== $value = $annotation->{$property[1]}) { - $propertyMetadata = $this->createWith($propertyMetadata, $property, $value); - } - } - - return $propertyMetadata; - } - - private function createWith(PropertyMetadata $propertyMetadata, array $property, $value): PropertyMetadata - { - $wither = 'with'.ucfirst($property[1]); - - return $propertyMetadata->{$wither}($value); - } -} diff --git a/src/Core/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactory.php b/src/Core/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactory.php deleted file mode 100644 index 1caa639bae8..00000000000 --- a/src/Core/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactory.php +++ /dev/null @@ -1,109 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Annotation\ApiProperty; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Util\Reflection; -use Doctrine\Common\Annotations\Reader; - -/** - * Creates a property name collection from {@see ApiProperty} annotations. - * - * @author Kévin Dunglas - */ -final class AnnotationPropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface -{ - private $reader; - private $decorated; - private $reflection; - - public function __construct(Reader $reader = null, PropertyNameCollectionFactoryInterface $decorated = null) - { - $this->reader = $reader; - $this->decorated = $decorated; - $this->reflection = new Reflection(); - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, array $options = []): PropertyNameCollection - { - $propertyNameCollection = null; - - if ($this->decorated) { - try { - /** @var PropertyNameCollection */ - $propertyNameCollection = $this->decorated->create($resourceClass, $options); - } catch (ResourceClassNotFoundException $resourceClassNotFoundException) { - // Ignore not found exceptions from decorated factory - } - } - - try { - $reflectionClass = new \ReflectionClass($resourceClass); - } catch (\ReflectionException $reflectionException) { - if (null !== $propertyNameCollection) { - return $propertyNameCollection; - } - - throw new ResourceClassNotFoundException(sprintf('The resource class "%s" does not exist.', $resourceClass)); - } - - $propertyNames = []; - - // Properties - foreach ($reflectionClass->getProperties() as $reflectionProperty) { - if ( - (\PHP_VERSION_ID >= 80000 && $reflectionProperty->getAttributes(ApiProperty::class)) || - (null !== $this->reader && null !== $this->reader->getPropertyAnnotation($reflectionProperty, ApiProperty::class)) - ) { - $propertyNames[$reflectionProperty->name] = $reflectionProperty->name; - } - } - - // Methods - foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { - if ($reflectionMethod->isStatic()) { - continue; - } - - $propertyName = $this->reflection->getProperty($reflectionMethod->name); - if (null !== $propertyName && !$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/', $propertyName)) { - $propertyName = lcfirst($propertyName); - } - - if ( - null !== $propertyName && - ( - (\PHP_VERSION_ID >= 80000 && $reflectionMethod->getAttributes(ApiProperty::class)) || - (null !== $this->reader && null !== $this->reader->getMethodAnnotation($reflectionMethod, ApiProperty::class)) - ) - ) { - $propertyNames[$propertyName] = $propertyName; - } - } - - // add property names from decorated factory - if (null !== $propertyNameCollection) { - foreach ($propertyNameCollection as $propertyName) { - $propertyNames[$propertyName] = $propertyName; - } - } - - return new PropertyNameCollection(array_values($propertyNames)); - } -} diff --git a/src/Core/Metadata/Property/Factory/AnnotationSubresourceMetadataFactory.php b/src/Core/Metadata/Property/Factory/AnnotationSubresourceMetadataFactory.php deleted file mode 100644 index a5a0bad390e..00000000000 --- a/src/Core/Metadata/Property/Factory/AnnotationSubresourceMetadataFactory.php +++ /dev/null @@ -1,118 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Annotation\ApiSubresource; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Exception\InvalidResourceException; -use ApiPlatform\Util\Reflection; -use Doctrine\Common\Annotations\Reader; -use Symfony\Component\PropertyInfo\Type; - -/** - * Adds subresources to the properties metadata from {@see ApiResource} annotations. - * - * @author Antoine Bluchet - */ -final class AnnotationSubresourceMetadataFactory implements PropertyMetadataFactoryInterface -{ - private $reader; - private $decorated; - - public function __construct(Reader $reader, PropertyMetadataFactoryInterface $decorated) - { - $this->reader = $reader; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); - - try { - $reflectionClass = new \ReflectionClass($resourceClass); - } catch (\ReflectionException $reflectionException) { - return $propertyMetadata; - } - - if ($reflectionClass->hasProperty($property)) { - $reflectionProperty = $reflectionClass->getProperty($property); - if (\PHP_VERSION_ID >= 80000 && $attributes = $reflectionProperty->getAttributes(ApiSubresource::class)) { - return $this->updateMetadata($attributes[0]->newInstance(), $propertyMetadata, $resourceClass, $property); - } - - $annotation = $this->reader->getPropertyAnnotation($reflectionProperty, ApiSubresource::class); - if ($annotation instanceof ApiSubresource) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Declare a new resource instead of using ApiSubresource on the property "%s".', $property)); - - return $this->updateMetadata($annotation, $propertyMetadata, $resourceClass, $property); - } - } - - foreach (array_merge(Reflection::ACCESSOR_PREFIXES, Reflection::MUTATOR_PREFIXES) as $prefix) { - $methodName = $prefix.ucfirst($property); - if (!$reflectionClass->hasMethod($methodName)) { - continue; - } - - $reflectionMethod = $reflectionClass->getMethod($methodName); - if (!$reflectionMethod->isPublic()) { - continue; - } - - if (\PHP_VERSION_ID >= 80000 && $attributes = $reflectionMethod->getAttributes(ApiSubresource::class)) { - return $this->updateMetadata($attributes[0]->newInstance(), $propertyMetadata, $resourceClass, $property); - } - - $annotation = $this->reader->getMethodAnnotation($reflectionMethod, ApiSubresource::class); - if ($annotation instanceof ApiSubresource) { - return $this->updateMetadata($annotation, $propertyMetadata, $resourceClass, $property); - } - } - - return $propertyMetadata; - } - - private function updateMetadata(ApiSubresource $annotation, PropertyMetadata $propertyMetadata, string $originResourceClass, string $propertyName): PropertyMetadata - { - // TODO: 3.0 support multiple types, default value of types will be [] instead of null - $type = $propertyMetadata->getType(); - if (null === $type) { - throw new InvalidResourceException(sprintf('Property "%s" on resource "%s" is declared as a subresource, but its type could not be determined.', $propertyName, $originResourceClass)); - } - $isCollection = $type->isCollection(); - - if ( - $isCollection && - $collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType() - ) { - $resourceClass = $collectionValueType->getClassName(); - } else { - $resourceClass = $type->getClassName(); - } - - $maxDepth = $annotation->maxDepth; - // @ApiSubresource is on the class identifier (/collection/{id}/subcollection/{subcollectionId}) - if (null === $resourceClass) { - $resourceClass = $originResourceClass; - $isCollection = false; - } - - return $propertyMetadata->withSubresource(new SubresourceMetadata($resourceClass, $isCollection, $maxDepth)); - } -} diff --git a/src/Core/Metadata/Property/Factory/CachedPropertyMetadataFactory.php b/src/Core/Metadata/Property/Factory/CachedPropertyMetadataFactory.php deleted file mode 100644 index ae3ff9c4f77..00000000000 --- a/src/Core/Metadata/Property/Factory/CachedPropertyMetadataFactory.php +++ /dev/null @@ -1,50 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Util\CachedTrait; -use Psr\Cache\CacheItemPoolInterface; - -/** - * Caches property metadata. - * - * @author Teoh Han Hui - */ -final class CachedPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - use CachedTrait; - - public const CACHE_KEY_PREFIX = 'property_metadata_'; - - private $decorated; - - public function __construct(CacheItemPoolInterface $cacheItemPool, PropertyMetadataFactoryInterface $decorated) - { - $this->cacheItemPool = $cacheItemPool; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - $cacheKey = self::CACHE_KEY_PREFIX.md5(serialize([$resourceClass, $property, $options])); - - return $this->getCached($cacheKey, function () use ($resourceClass, $property, $options) { - return $this->decorated->create($resourceClass, $property, $options); - }); - } -} diff --git a/src/Core/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php b/src/Core/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php deleted file mode 100644 index 89b20801939..00000000000 --- a/src/Core/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php +++ /dev/null @@ -1,22 +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\Metadata\Property\Factory; - -class_exists(\ApiPlatform\Metadata\Property\Factory\CachedPropertyNameCollectionFactory::class); - -if (false) { - final class CachedPropertyNameCollectionFactory extends \ApiPlatform\Metadata\Property\Factory\CachedPropertyNameCollectionFactory - { - } -} diff --git a/src/Core/Metadata/Property/Factory/DefaultPropertyMetadataFactory.php b/src/Core/Metadata/Property/Factory/DefaultPropertyMetadataFactory.php deleted file mode 100644 index 9c951077753..00000000000 --- a/src/Core/Metadata/Property/Factory/DefaultPropertyMetadataFactory.php +++ /dev/null @@ -1,57 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; - -/** - * Populates defaults values of the ressource properties using the default PHP values of properties. - */ -final class DefaultPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - private $decorated; - - public function __construct(PropertyMetadataFactoryInterface $decorated = null) - { - $this->decorated = $decorated; - } - - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - if (null === $this->decorated) { - $propertyMetadata = new PropertyMetadata(); - } else { - try { - $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); - } catch (PropertyNotFoundException $propertyNotFoundException) { - $propertyMetadata = new PropertyMetadata(); - } - } - - try { - $reflectionClass = new \ReflectionClass($resourceClass); - } catch (\ReflectionException $reflectionException) { - return $propertyMetadata; - } - - $defaultProperties = $reflectionClass->getDefaultProperties(); - - if (!\array_key_exists($property, $defaultProperties) || null === ($defaultProperty = $defaultProperties[$property])) { - return $propertyMetadata; - } - - return $propertyMetadata->withDefault($defaultProperty); - } -} diff --git a/src/Core/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php b/src/Core/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php deleted file mode 100644 index 74d81229a62..00000000000 --- a/src/Core/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php +++ /dev/null @@ -1,164 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Metadata\Extractor\ExtractorInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Metadata\Extractor\ResourceExtractorInterface; -use Symfony\Component\PropertyInfo\Type; - -/** - * Creates properties's metadata using an extractor. - * - * @author Kévin Dunglas - */ -final class ExtractorPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - private $extractor; - private $decorated; - - /** - * @param ResourceExtractorInterface|ExtractorInterface $extractor - */ - public function __construct($extractor, PropertyMetadataFactoryInterface $decorated = null) - { - $this->extractor = $extractor; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - $parentPropertyMetadata = null; - if ($this->decorated) { - try { - $parentPropertyMetadata = $this->decorated->create($resourceClass, $property, $options); - } catch (PropertyNotFoundException $propertyNotFoundException) { - // Ignore not found exception from decorated factories - } - } - - $isInterface = interface_exists($resourceClass); - - if ( - !property_exists($resourceClass, $property) && !$isInterface || - null === ($propertyMetadata = $this->extractor->getResources()[$resourceClass]['properties'][$property] ?? null) - ) { - return $this->handleNotFound($parentPropertyMetadata, $resourceClass, $property); - } - - if ($parentPropertyMetadata) { - return $this->update($parentPropertyMetadata, $propertyMetadata); - } - - return ($metadata = new PropertyMetadata( - null, - $propertyMetadata['description'], - $propertyMetadata['readable'], - $propertyMetadata['writable'], - $propertyMetadata['readableLink'], - $propertyMetadata['writableLink'], - $propertyMetadata['required'], - $propertyMetadata['identifier'], - $propertyMetadata['iri'], - null, - $propertyMetadata['attributes'] - ))->withSubresource($this->createSubresourceMetadata($propertyMetadata['subresource'], $metadata)); - } - - /** - * Returns the metadata from the decorated factory if available or throws an exception. - * - * @throws PropertyNotFoundException - */ - private function handleNotFound(?PropertyMetadata $parentPropertyMetadata, string $resourceClass, string $property): PropertyMetadata - { - if ($parentPropertyMetadata) { - return $parentPropertyMetadata; - } - - throw new PropertyNotFoundException(sprintf('Property "%s" of the resource class "%s" not found.', $property, $resourceClass)); - } - - /** - * Creates a new instance of metadata if the property is not already set. - */ - private function update(PropertyMetadata $propertyMetadata, array $metadata): PropertyMetadata - { - $metadataAccessors = [ - 'description' => 'get', - 'readable' => 'is', - 'writable' => 'is', - 'writableLink' => 'is', - 'readableLink' => 'is', - 'required' => 'is', - 'identifier' => 'is', - 'iri' => 'get', - 'attributes' => 'get', - ]; - - foreach ($metadataAccessors as $metadataKey => $accessorPrefix) { - if (null === $metadata[$metadataKey]) { - continue; - } - - $propertyMetadata = $propertyMetadata->{'with'.ucfirst($metadataKey)}($metadata[$metadataKey]); - } - - if ($propertyMetadata->hasSubresource()) { - return $propertyMetadata; - } - - return $propertyMetadata->withSubresource($this->createSubresourceMetadata($metadata['subresource'], $propertyMetadata)); - } - - /** - * Creates a SubresourceMetadata. - * - * @param bool|array|null $subresource the subresource metadata coming from XML or YAML - * @param PropertyMetadata $propertyMetadata the current property metadata - */ - private function createSubresourceMetadata($subresource, PropertyMetadata $propertyMetadata): ?SubresourceMetadata - { - if (!$subresource) { - return null; - } - - $type = $propertyMetadata->getType(); - $maxDepth = \is_array($subresource) ? $subresource['maxDepth'] ?? null : null; - - if (null !== $type) { - $isCollection = $type->isCollection(); - if ( - $isCollection && - $collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType() - ) { - $resourceClass = $collectionValueType->getClassName(); - } else { - $resourceClass = $type->getClassName(); - } - } elseif (\is_array($subresource) && isset($subresource['resourceClass'])) { - $resourceClass = $subresource['resourceClass']; - $isCollection = $subresource['collection'] ?? true; - } else { - return null; - } - - return new SubresourceMetadata($resourceClass, $isCollection, $maxDepth); - } -} diff --git a/src/Core/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php b/src/Core/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php deleted file mode 100644 index 000f49f61f7..00000000000 --- a/src/Core/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php +++ /dev/null @@ -1,22 +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\Metadata\Property\Factory; - -class_exists(\ApiPlatform\Metadata\Property\Factory\ExtractorPropertyNameCollectionFactory::class); - -if (false) { - final class ExtractorPropertyNameCollectionFactory extends \ApiPlatform\Metadata\Property\Factory\ExtractorPropertyNameCollectionFactory - { - } -} diff --git a/src/Core/Metadata/Property/Factory/InheritedPropertyMetadataFactory.php b/src/Core/Metadata/Property/Factory/InheritedPropertyMetadataFactory.php deleted file mode 100644 index 577abbc45d6..00000000000 --- a/src/Core/Metadata/Property/Factory/InheritedPropertyMetadataFactory.php +++ /dev/null @@ -1,58 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; - -/** - * @deprecated since 2.6, to be removed in 3.0 - */ -final class InheritedPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - private $resourceNameCollectionFactory; - private $decorated; - - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, PropertyMetadataFactoryInterface $decorated = null) - { - @trigger_error(sprintf('"%s" is deprecated since 2.6 and will be removed in 3.0.', __CLASS__), \E_USER_DEPRECATED); - - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - @trigger_error(sprintf('"%s" is deprecated since 2.6 and will be removed in 3.0.', __CLASS__), \E_USER_DEPRECATED); - - $propertyMetadata = $this->decorated ? $this->decorated->create($resourceClass, $property, $options) : new PropertyMetadata(); - - foreach ($this->resourceNameCollectionFactory->create() as $knownResourceClass) { - if ($resourceClass === $knownResourceClass) { - continue; - } - - if (is_subclass_of($knownResourceClass, $resourceClass)) { - $propertyMetadata = $this->create($knownResourceClass, $property, $options); - - return $propertyMetadata->withChildInherited($knownResourceClass); - } - } - - return $propertyMetadata; - } -} diff --git a/src/Core/Metadata/Property/Factory/InheritedPropertyNameCollectionFactory.php b/src/Core/Metadata/Property/Factory/InheritedPropertyNameCollectionFactory.php deleted file mode 100644 index 6c38c542420..00000000000 --- a/src/Core/Metadata/Property/Factory/InheritedPropertyNameCollectionFactory.php +++ /dev/null @@ -1,65 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; - -/** - * @deprecated since 2.6, to be removed in 3.0 - */ -final class InheritedPropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface -{ - private $resourceNameCollectionFactory; - private $decorated; - - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, PropertyNameCollectionFactoryInterface $decorated = null) - { - @trigger_error(sprintf('"%s" is deprecated since 2.6 and will be removed in 3.0.', __CLASS__), \E_USER_DEPRECATED); - - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, array $options = []): PropertyNameCollection - { - @trigger_error(sprintf('"%s" is deprecated since 2.6 and will be removed in 3.0.', __CLASS__), \E_USER_DEPRECATED); - - $propertyNames = []; - - // Inherited from parent - if ($this->decorated) { - foreach ($this->decorated->create($resourceClass, $options) as $propertyName) { - $propertyNames[$propertyName] = (string) $propertyName; - } - } - - foreach ($this->resourceNameCollectionFactory->create() as $knownResourceClass) { - if ($resourceClass === $knownResourceClass) { - continue; - } - - if (is_subclass_of($resourceClass, $knownResourceClass)) { - foreach ($this->create($knownResourceClass) as $propertyName) { - $propertyNames[$propertyName] = $propertyName; - } - } - } - - return new PropertyNameCollection(array_values($propertyNames)); - } -} diff --git a/src/Core/Metadata/Property/Factory/PropertyMetadataFactoryInterface.php b/src/Core/Metadata/Property/Factory/PropertyMetadataFactoryInterface.php deleted file mode 100644 index 5cb5cc1f76f..00000000000 --- a/src/Core/Metadata/Property/Factory/PropertyMetadataFactoryInterface.php +++ /dev/null @@ -1,32 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; - -/** - * Creates a property metadata value object. - * - * @author Kévin Dunglas - */ -interface PropertyMetadataFactoryInterface -{ - /** - * Creates a property metadata. - * - * @throws PropertyNotFoundException - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata; -} diff --git a/src/Core/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php b/src/Core/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php deleted file mode 100644 index 9c93b2051a8..00000000000 --- a/src/Core/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php +++ /dev/null @@ -1,233 +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\Metadata\Property\Factory; - -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Util\ResourceClassInfoTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface as SerializerClassMetadataFactoryInterface; - -/** - * Populates read/write and link status using serialization groups. - * - * @author Kévin Dunglas - * @author Teoh Han Hui - */ -final class SerializerPropertyMetadataFactory implements PropertyMetadataFactoryInterface -{ - use ResourceClassInfoTrait; - - private $serializerClassMetadataFactory; - private $decorated; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, SerializerClassMetadataFactoryInterface $serializerClassMetadataFactory, PropertyMetadataFactoryInterface $decorated, ResourceClassResolverInterface $resourceClassResolver = null) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->serializerClassMetadataFactory = $serializerClassMetadataFactory; - $this->decorated = $decorated; - $this->resourceClassResolver = $resourceClassResolver; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass, string $property, array $options = []): PropertyMetadata - { - $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); - - // BC to be removed in 3.0 - if (null !== ($childResourceClass = $propertyMetadata->getChildInherited())) { - $resourceClass = $childResourceClass; - } - - try { - [$normalizationGroups, $denormalizationGroups] = $this->getEffectiveSerializerGroups($options, $resourceClass); - } catch (ResourceClassNotFoundException $e) { - // TODO: for input/output classes, the serializer groups must be read from the actual resource class - return $propertyMetadata; - } - - $propertyMetadata = $this->transformReadWrite($propertyMetadata, $resourceClass, $property, $normalizationGroups, $denormalizationGroups); - - return $this->transformLinkStatus($propertyMetadata, $normalizationGroups, $denormalizationGroups); - } - - /** - * Sets readable/writable based on matching normalization/denormalization groups and property's ignorance. - * - * A false value is never reset as it could be unreadable/unwritable for other reasons. - * If normalization/denormalization groups are not specified and the property is not ignored, the property is implicitly readable/writable. - * - * @param string[]|null $normalizationGroups - * @param string[]|null $denormalizationGroups - */ - private function transformReadWrite(PropertyMetadata $propertyMetadata, string $resourceClass, string $propertyName, array $normalizationGroups = null, array $denormalizationGroups = null): PropertyMetadata - { - $serializerAttributeMetadata = $this->getSerializerAttributeMetadata($resourceClass, $propertyName); - $groups = $serializerAttributeMetadata ? $serializerAttributeMetadata->getGroups() : []; - $ignored = $serializerAttributeMetadata && method_exists($serializerAttributeMetadata, 'isIgnored') ? $serializerAttributeMetadata->isIgnored() : false; - - if (false !== $propertyMetadata->isReadable()) { - $propertyMetadata = $propertyMetadata->withReadable(!$ignored && (null === $normalizationGroups || array_intersect($normalizationGroups, $groups))); - } - - if (false !== $propertyMetadata->isWritable()) { - $propertyMetadata = $propertyMetadata->withWritable(!$ignored && (null === $denormalizationGroups || array_intersect($denormalizationGroups, $groups))); - } - - return $propertyMetadata; - } - - /** - * Sets readableLink/writableLink based on matching normalization/denormalization groups. - * - * If normalization/denormalization groups are not specified, - * set link status to false since embedding of resource must be explicitly enabled - * - * @param string[]|null $normalizationGroups - * @param string[]|null $denormalizationGroups - */ - private function transformLinkStatus(PropertyMetadata $propertyMetadata, array $normalizationGroups = null, array $denormalizationGroups = null): PropertyMetadata - { - // No need to check link status if property is not readable and not writable - if (false === $propertyMetadata->isReadable() && false === $propertyMetadata->isWritable()) { - return $propertyMetadata; - } - - $type = $propertyMetadata->getType(); - if (null === $type) { - return $propertyMetadata; - } - - if ( - $type->isCollection() && - $collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType() - ) { - $relatedClass = $collectionValueType->getClassName(); - } else { - $relatedClass = $type->getClassName(); - } - - // if property is not a resource relation, don't set link status (as it would have no meaning) - if (null === $relatedClass || !$this->isResourceClass($relatedClass)) { - return $propertyMetadata; - } - - // find the resource class - // this prevents serializer groups on non-resource child class from incorrectly influencing the decision - if (null !== $this->resourceClassResolver) { - $relatedClass = $this->resourceClassResolver->getResourceClass(null, $relatedClass); - } - - $relatedGroups = $this->getClassSerializerGroups($relatedClass); - - if (null === $propertyMetadata->isReadableLink()) { - $propertyMetadata = $propertyMetadata->withReadableLink(null !== $normalizationGroups && !empty(array_intersect($normalizationGroups, $relatedGroups))); - } - - if (null === $propertyMetadata->isWritableLink()) { - $propertyMetadata = $propertyMetadata->withWritableLink(null !== $denormalizationGroups && !empty(array_intersect($denormalizationGroups, $relatedGroups))); - } - - return $propertyMetadata; - } - - /** - * Gets the effective serializer groups used in normalization/denormalization. - * - * Groups are extracted in the following order: - * - * - From the "serializer_groups" key of the $options array. - * - From metadata of the given operation ("collection_operation_name" and "item_operation_name" keys). - * - From metadata of the current resource. - * - * @throws ResourceClassNotFoundException - * - * @return (string[]|null)[] - */ - private function getEffectiveSerializerGroups(array $options, string $resourceClass): array - { - if (isset($options['serializer_groups'])) { - $groups = (array) $options['serializer_groups']; - - return [$groups, $groups]; - } - - if (\array_key_exists('normalization_groups', $options) && \array_key_exists('denormalization_groups', $options)) { - return [$options['normalization_groups'] ?? null, $options['denormalization_groups'] ?? null]; - } - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (isset($options['collection_operation_name'])) { - $normalizationContext = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], 'normalization_context', null, true); - $denormalizationContext = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], 'denormalization_context', null, true); - } elseif (isset($options['item_operation_name'])) { - $normalizationContext = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], 'normalization_context', null, true); - $denormalizationContext = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], 'denormalization_context', null, true); - } elseif (isset($options['graphql_operation_name'])) { - $normalizationContext = $resourceMetadata->getGraphqlAttribute($options['graphql_operation_name'], 'normalization_context', null, true); - $denormalizationContext = $resourceMetadata->getGraphqlAttribute($options['graphql_operation_name'], 'denormalization_context', null, true); - } else { - $normalizationContext = $resourceMetadata->getAttribute('normalization_context'); - $denormalizationContext = $resourceMetadata->getAttribute('denormalization_context'); - } - - return [ - isset($normalizationContext['groups']) ? (array) $normalizationContext['groups'] : null, - isset($denormalizationContext['groups']) ? (array) $denormalizationContext['groups'] : null, - ]; - } - - private function getSerializerAttributeMetadata(string $class, string $attribute): ?AttributeMetadataInterface - { - $serializerClassMetadata = $this->serializerClassMetadataFactory->getMetadataFor($class); - - foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { - if ($attribute === $serializerAttributeMetadata->getName()) { - return $serializerAttributeMetadata; - } - } - - return null; - } - - /** - * Gets all serializer groups used in a class. - * - * @return string[] - */ - private function getClassSerializerGroups(string $class): array - { - try { - $resourceMetadata = $this->resourceMetadataFactory->create($class); - if ($outputClass = $resourceMetadata->getAttribute('output')['class'] ?? null) { - $class = $outputClass; - } - } catch (ResourceClassNotFoundException $e) { - } - - $serializerClassMetadata = $this->serializerClassMetadataFactory->getMetadataFor($class); - - $groups = []; - foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { - $groups = array_merge($groups, $serializerAttributeMetadata->getGroups()); - } - - return array_unique($groups); - } -} diff --git a/src/Core/Metadata/Property/PropertyMetadata.php b/src/Core/Metadata/Property/PropertyMetadata.php deleted file mode 100644 index b547831720f..00000000000 --- a/src/Core/Metadata/Property/PropertyMetadata.php +++ /dev/null @@ -1,435 +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\Metadata\Property; - -use Symfony\Component\PropertyInfo\Type; - -/** - * Property metadata. - * - * @author Kévin Dunglas - */ -final class PropertyMetadata -{ - /** - * @deprecated since 2.7, to be removed in 3.0, renamed as builtinTypes - */ - private $type; - private $description; - private $readable; - private $writable; - private $readableLink; - private $writableLink; - private $required; - /** - * @deprecated since 2.7, to be removed in 3.0, renamed as types - */ - private $iri; - private $identifier; - /** - * @deprecated since 2.6, to be removed in 3.0 - */ - private $childInherited; - private $attributes; - private $subresource; - private $initializable; - /** - * @var null - */ - private $default; - /** - * @var null - */ - private $example; - private $schema; - - public function __construct(Type $type = null, string $description = null, bool $readable = null, bool $writable = null, bool $readableLink = null, bool $writableLink = null, bool $required = null, bool $identifier = null, string $iri = null, $childInherited = null, array $attributes = null, SubresourceMetadata $subresource = null, bool $initializable = null, $default = null, $example = null, array $schema = null) - { - $this->type = $type; - $this->description = $description; - $this->readable = $readable; - $this->writable = $writable; - $this->readableLink = $readableLink; - $this->writableLink = $writableLink; - $this->required = $required; - $this->identifier = $identifier; - $this->iri = $iri; - if (null !== $childInherited) { - @trigger_error(sprintf('Providing a non-null value for the 10th argument ($childInherited) of the "%s" constructor is deprecated since 2.6 and will not be supported in 3.0.', __CLASS__), \E_USER_DEPRECATED); - } - $this->childInherited = $childInherited; - $this->attributes = $attributes; - $this->subresource = $subresource; - $this->initializable = $initializable; - $this->default = $default; - $this->example = $example; - $this->schema = $schema; - } - - /** - * Gets type. - * - * @deprecated since 2.7, to be removed in 3.0, renamed as getBuiltinTypes - */ - public function getType(): ?Type - { - return $this->type; - } - - /** - * Returns a new instance with the given type. - * - * @deprecated since 2.7, to be removed in 3.0, renamed as withBuiltinTypes - */ - public function withType(Type $type): self - { - $metadata = clone $this; - $metadata->type = $type; - - return $metadata; - } - - /** - * Gets description. - */ - public function getDescription(): ?string - { - return $this->description; - } - - /** - * Returns a new instance with the given description. - */ - public function withDescription(string $description): self - { - $metadata = clone $this; - $metadata->description = $description; - - return $metadata; - } - - /** - * Is readable? - */ - public function isReadable(): ?bool - { - return $this->readable; - } - - /** - * Returns a new instance of Metadata with the given readable flag. - */ - public function withReadable(bool $readable): self - { - $metadata = clone $this; - $metadata->readable = $readable; - - return $metadata; - } - - /** - * Is writable? - */ - public function isWritable(): ?bool - { - return $this->writable; - } - - /** - * Returns a new instance with the given writable flag. - */ - public function withWritable(bool $writable): self - { - $metadata = clone $this; - $metadata->writable = $writable; - - return $metadata; - } - - /** - * Is required? - */ - public function isRequired(): ?bool - { - if (true === $this->required && false === $this->writable) { - return false; - } - - return $this->required; - } - - /** - * Returns a new instance with the given required flag. - */ - public function withRequired(bool $required): self - { - $metadata = clone $this; - $metadata->required = $required; - - return $metadata; - } - - /** - * Should an IRI or an object be provided in write context? - */ - public function isWritableLink(): ?bool - { - return $this->writableLink; - } - - /** - * Returns a new instance with the given writable link flag. - */ - public function withWritableLink(bool $writableLink): self - { - $metadata = clone $this; - $metadata->writableLink = $writableLink; - - return $metadata; - } - - /** - * Is an IRI or an object generated in read context? - */ - public function isReadableLink(): ?bool - { - return $this->readableLink; - } - - /** - * Returns a new instance with the given readable link flag. - */ - public function withReadableLink(bool $readableLink): self - { - $metadata = clone $this; - $metadata->readableLink = $readableLink; - - return $metadata; - } - - /** - * Gets IRI of this property. - */ - public function getIri(): ?string - { - return $this->iri; - } - - /** - * Returns a new instance with the given IRI. - */ - public function withIri(string $iri = null): self - { - $metadata = clone $this; - $metadata->iri = $iri; - - return $metadata; - } - - /** - * Is this attribute an identifier? - */ - public function isIdentifier(): ?bool - { - return $this->identifier; - } - - /** - * Returns a new instance with the given identifier flag. - */ - public function withIdentifier(bool $identifier): self - { - $metadata = clone $this; - $metadata->identifier = $identifier; - - return $metadata; - } - - /** - * Gets attributes. - */ - public function getAttributes(): ?array - { - return $this->attributes; - } - - /** - * Gets an attribute. - * - * @param mixed|null $defaultValue - */ - public function getAttribute(string $key, $defaultValue = null) - { - return $this->attributes[$key] ?? $defaultValue; - } - - /** - * Returns a new instance with the given attribute. - */ - public function withAttributes(array $attributes): self - { - $metadata = clone $this; - $metadata->attributes = $attributes; - - return $metadata; - } - - /** - * @deprecated since 2.6, to be removed in 3.0 - */ - public function getChildInherited(): ?string - { - return $this->childInherited; - } - - /** - * @deprecated since 2.6, to be removed in 3.0 - */ - public function hasChildInherited(): bool - { - return null !== $this->childInherited; - } - - /** - * @deprecated since 2.4, to be removed in 3.0 - */ - public function isChildInherited(): ?string - { - @trigger_error(sprintf('"%s::%s" is deprecated since 2.4 and will be removed in 3.0.', __CLASS__, __METHOD__), \E_USER_DEPRECATED); - - return $this->getChildInherited(); - } - - /** - * @deprecated since 2.6, to be removed in 3.0 - */ - public function withChildInherited(string $childInherited): self - { - @trigger_error(sprintf('"%s::%s" is deprecated since 2.6 and will be removed in 3.0.', __CLASS__, __METHOD__), \E_USER_DEPRECATED); - - $metadata = clone $this; - $metadata->childInherited = $childInherited; - - return $metadata; - } - - /** - * Represents whether the property has a subresource. - */ - public function hasSubresource(): bool - { - return null !== $this->subresource; - } - - /** - * Gets the subresource metadata. - */ - public function getSubresource(): ?SubresourceMetadata - { - return $this->subresource; - } - - /** - * Returns a new instance with the given subresource. - * - * @param SubresourceMetadata $subresource - */ - public function withSubresource(SubresourceMetadata $subresource = null): self - { - $metadata = clone $this; - $metadata->subresource = $subresource; - - return $metadata; - } - - /** - * Is initializable? - */ - public function isInitializable(): ?bool - { - return $this->initializable; - } - - /** - * Returns a new instance with the given initializable flag. - */ - public function withInitializable(bool $initializable): self - { - $metadata = clone $this; - $metadata->initializable = $initializable; - - return $metadata; - } - - /** - * Returns the default value of the property or NULL if the property doesn't have a default value. - */ - public function getDefault() - { - return $this->default; - } - - /** - * Returns a new instance with the given default value for the property. - * - * @param mixed $default - */ - public function withDefault($default): self - { - $metadata = clone $this; - $metadata->default = $default; - - return $metadata; - } - - /** - * Returns an example of the value of the property. - */ - public function getExample() - { - return $this->example; - } - - /** - * Returns a new instance with the given example. - * - * @param mixed $example - */ - public function withExample($example): self - { - $metadata = clone $this; - $metadata->example = $example; - - return $metadata; - } - - /** - * @return array - */ - public function getSchema(): ?array - { - return $this->schema; - } - - /** - * Returns a new instance with the given schema. - */ - public function withSchema(array $schema = null): self - { - $metadata = clone $this; - $metadata->schema = $schema; - - return $metadata; - } -} diff --git a/src/Core/Metadata/Property/PropertyNameCollection.php b/src/Core/Metadata/Property/PropertyNameCollection.php deleted file mode 100644 index 4da85d2d1b5..00000000000 --- a/src/Core/Metadata/Property/PropertyNameCollection.php +++ /dev/null @@ -1,22 +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\Metadata\Property; - -class_exists(\ApiPlatform\Metadata\Property\PropertyNameCollection::class); - -if (false) { - final class PropertyNameCollection extends \ApiPlatform\Metadata\Property\PropertyNameCollection - { - } -} diff --git a/src/Core/Metadata/Property/SubresourceMetadata.php b/src/Core/Metadata/Property/SubresourceMetadata.php deleted file mode 100644 index f5d1c9e35aa..00000000000 --- a/src/Core/Metadata/Property/SubresourceMetadata.php +++ /dev/null @@ -1,64 +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\Metadata\Property; - -/** - * Subresource metadata. - * - * @author Antoine Bluchet - */ -final class SubresourceMetadata -{ - private $resourceClass; - private $collection; - private $maxDepth; - - public function __construct(string $resourceClass, bool $collection = false, int $maxDepth = null) - { - $this->resourceClass = $resourceClass; - $this->collection = $collection; - $this->maxDepth = $maxDepth; - } - - public function getResourceClass(): string - { - return $this->resourceClass; - } - - public function withResourceClass($resourceClass): self - { - $metadata = clone $this; - $metadata->resourceClass = $resourceClass; - - return $metadata; - } - - public function isCollection(): bool - { - return $this->collection; - } - - public function withCollection(bool $collection): self - { - $metadata = clone $this; - $metadata->collection = $collection; - - return $metadata; - } - - public function getMaxDepth(): ?int - { - return $this->maxDepth; - } -} diff --git a/src/Core/Metadata/Resource/ApiResourceToLegacyResourceMetadataTrait.php b/src/Core/Metadata/Resource/ApiResourceToLegacyResourceMetadataTrait.php deleted file mode 100644 index 482a4b2995e..00000000000 --- a/src/Core/Metadata/Resource/ApiResourceToLegacyResourceMetadataTrait.php +++ /dev/null @@ -1,126 +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\Metadata\Resource; - -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\CollectionOperationInterface; -use ApiPlatform\Metadata\HttpOperation; -use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; - -/** - * @internal - * - * @deprecated - */ -trait ApiResourceToLegacyResourceMetadataTrait -{ - private $camelCaseToSnakeCaseNameConverter; - - private function transformResourceToResourceMetadata(ApiResource $resource): ResourceMetadata - { - $collectionOperations = []; - $itemOperations = []; - foreach ($resource->getOperations() as $name => $operation) { - $arrayOperation = $this->toArray($operation); - - if (!isset($arrayOperation['openapi_context'])) { - $arrayOperation['openapi_context'] = []; - } - - $arrayOperation['openapi_context']['operationId'] = $name; - $arrayOperation['composite_identifier'] = $this->hasCompositeIdentifier($operation); - - if (HttpOperation::METHOD_POST === $operation->getMethod() && !$operation->getUriVariables()) { - $collectionOperations[$name] = $arrayOperation; - continue; - } - - if ($operation instanceof CollectionOperationInterface) { - $collectionOperations[$name] = $arrayOperation; - continue; - } - - $itemOperations[$name] = $arrayOperation; - } - - $attributes = $this->toArray($resource); - - $graphqlOperations = $resource->getGraphQlOperations() ? [] : null; - foreach ($resource->getGraphQlOperations() ?? [] as $operationName => $operation) { - $graphqlOperations[$operationName] = $this->toArray($operation); - } - - return new ResourceMetadata($resource->getShortName(), $resource->getDescription(), $resource->getTypes()[0] ?? null, $itemOperations, $collectionOperations, $attributes, null, $graphqlOperations); - } - - private function toArray($object): array - { - if (!$this->camelCaseToSnakeCaseNameConverter) { - $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter(); - } - - $arr = []; - foreach (get_class_methods($object) as $methodName) { - if ('getOperations' === $methodName || 0 !== strpos($methodName, 'get')) { - continue; - } - - if (null === $value = $object->{$methodName}()) { - continue; - } - - $arr[$this->camelCaseToSnakeCaseNameConverter->normalize(lcfirst(substr($methodName, 3)))] = $value; - } - - return $this->transformUriVariablesToIdentifiers($arr); - } - - private function transformUriVariablesToIdentifiers(array $arrayOperation): array - { - if (!isset($arrayOperation['uri_variables'])) { - return $arrayOperation; - } - - if (!\is_array($arrayOperation['uri_variables'])) { - $arrayOperation['identifiers'] = $arrayOperation['uri_variables']; - - return $arrayOperation; - } - - $arrayOperation['identifiers'] = []; - foreach ($arrayOperation['uri_variables'] as $parameterName => $identifiedBy) { - if (1 === \count($identifiedBy->getIdentifiers() ?? ['id'])) { - $arrayOperation['identifiers'][$parameterName] = [$identifiedBy->getFromClass(), $identifiedBy->getIdentifiers()[0] ?? ['id']]; - continue; - } - - foreach ($identifiedBy->getIdentifiers() as $identifier) { - $arrayOperation['identifiers'][$identifier] = [$identifiedBy->getFromClass(), $identifier]; - } - } - - return $arrayOperation; - } - - private function hasCompositeIdentifier(HttpOperation $operation): bool - { - foreach ($operation->getUriVariables() ?? [] as $parameterName => $uriVariable) { - if ($uriVariable->getCompositeIdentifier()) { - return true; - } - } - - return false; - } -} diff --git a/src/Core/Metadata/Resource/Factory/AnnotationResourceFilterMetadataFactory.php b/src/Core/Metadata/Resource/Factory/AnnotationResourceFilterMetadataFactory.php deleted file mode 100644 index bdc02f52afc..00000000000 --- a/src/Core/Metadata/Resource/Factory/AnnotationResourceFilterMetadataFactory.php +++ /dev/null @@ -1,94 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Annotation\ApiFilter; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Util\AnnotationFilterExtractorTrait; -use Doctrine\Common\Annotations\Reader; - -/** - * Adds filters to the resource metadata {@see ApiFilter} annotation. - * - * @author Antoine Bluchet - */ -final class AnnotationResourceFilterMetadataFactory implements ResourceMetadataFactoryInterface -{ - use AnnotationFilterExtractorTrait; - - private $reader; - private $decorated; - - public function __construct(?Reader $reader = null, ResourceMetadataFactoryInterface $decorated = null) - { - $this->reader = $reader; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $parentResourceMetadata = null; - if ($this->decorated) { - $parentResourceMetadata = $this->decorated->create($resourceClass); - } - - if (null === $parentResourceMetadata) { - return $this->handleNotFound($parentResourceMetadata, $resourceClass); - } - - try { - $reflectionClass = new \ReflectionClass($resourceClass); - } catch (\ReflectionException $reflectionException) { - return $this->handleNotFound($parentResourceMetadata, $resourceClass); - } - - $filters = array_keys($this->readFilterAnnotations($reflectionClass, $this->reader)); - - if (!$filters) { - return $parentResourceMetadata; - } - - $parentFilters = $parentResourceMetadata->getAttribute('filters', []); - - if ($parentFilters) { - $filters = array_merge($parentFilters, $filters); - } - - $attributes = $parentResourceMetadata->getAttributes(); - - if (!$attributes) { - $attributes = []; - } - - return $parentResourceMetadata->withAttributes(array_merge($attributes, ['filters' => $filters])); - } - - /** - * Returns the metadata from the decorated factory if available or throws an exception. - * - * @throws ResourceClassNotFoundException - */ - private function handleNotFound(?ResourceMetadata $parentPropertyMetadata, string $resourceClass): ResourceMetadata - { - if (null !== $parentPropertyMetadata) { - return $parentPropertyMetadata; - } - - throw new ResourceClassNotFoundException(sprintf('Resource "%s" not found.', $resourceClass)); - } -} diff --git a/src/Core/Metadata/Resource/Factory/AnnotationResourceMetadataFactory.php b/src/Core/Metadata/Resource/Factory/AnnotationResourceMetadataFactory.php deleted file mode 100644 index 921f9e8c4f8..00000000000 --- a/src/Core/Metadata/Resource/Factory/AnnotationResourceMetadataFactory.php +++ /dev/null @@ -1,145 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Annotation\ApiResource; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use Doctrine\Common\Annotations\Reader; - -/** - * Creates a resource metadata from {@see ApiResource} annotations. - * - * @author Kévin Dunglas - */ -final class AnnotationResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - private $reader; - private $decorated; - private $defaults; - - public function __construct(Reader $reader = null, ResourceMetadataFactoryInterface $decorated = null, array $defaults = []) - { - $this->reader = $reader; - $this->decorated = $decorated; - $this->defaults = $defaults + ['attributes' => []]; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $parentResourceMetadata = null; - if ($this->decorated) { - try { - $parentResourceMetadata = $this->decorated->create($resourceClass); - } catch (ResourceClassNotFoundException $resourceNotFoundException) { - // Ignore not found exception from decorated factories - } - } - - try { - $reflectionClass = new \ReflectionClass($resourceClass); - } catch (\ReflectionException $reflectionException) { - return $this->handleNotFound($parentResourceMetadata, $resourceClass); - } - - if (\PHP_VERSION_ID >= 80000 && $attributes = $reflectionClass->getAttributes(ApiResource::class)) { - return $this->createMetadata($attributes[0]->newInstance(), $parentResourceMetadata); - } - - if (null === $this->reader) { - $this->handleNotFound($parentResourceMetadata, $resourceClass); - } - - $resourceAnnotation = $this->reader->getClassAnnotation($reflectionClass, ApiResource::class); - - if (!$resourceAnnotation instanceof ApiResource) { - return $this->handleNotFound($parentResourceMetadata, $resourceClass); - } - - return $this->createMetadata($resourceAnnotation, $parentResourceMetadata); - } - - /** - * Returns the metadata from the decorated factory if available or throws an exception. - * - * @throws ResourceClassNotFoundException - */ - private function handleNotFound(?ResourceMetadata $parentPropertyMetadata, string $resourceClass): ResourceMetadata - { - if (null !== $parentPropertyMetadata) { - return $parentPropertyMetadata; - } - - throw new ResourceClassNotFoundException(sprintf('Resource "%s" not found.', $resourceClass)); - } - - private function createMetadata(ApiResource $annotation, ResourceMetadata $parentResourceMetadata = null): ResourceMetadata - { - $attributes = null; - if (null !== $annotation->attributes || [] !== $this->defaults['attributes']) { - $attributes = (array) $annotation->attributes; - foreach ($this->defaults['attributes'] as $key => $value) { - if (!isset($attributes[$key])) { - $attributes[$key] = $value; - } - } - } - - if (!$parentResourceMetadata) { - return new ResourceMetadata( - $annotation->shortName, - $annotation->description ?? $this->defaults['description'] ?? null, // @phpstan-ignore-line - $annotation->iri ?? $this->defaults['iri'] ?? null, // @phpstan-ignore-line - $annotation->itemOperations ?? $this->defaults['item_operations'] ?? null, // @phpstan-ignore-line - $annotation->collectionOperations ?? $this->defaults['collection_operations'] ?? null, // @phpstan-ignore-line - $attributes, - $annotation->subresourceOperations, - $annotation->graphql ?? $this->defaults['graphql'] ?? null // @phpstan-ignore-line - ); - } - - $resourceMetadata = $parentResourceMetadata; - foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'subresourceOperations', 'graphql', 'attributes'] as $property) { - $resourceMetadata = $this->createWith($resourceMetadata, $property, $annotation->{$property}); - } - - return $resourceMetadata; - } - - /** - * Creates a new instance of metadata if the property is not already set. - * - * @param mixed $value - */ - private function createWith(ResourceMetadata $resourceMetadata, string $property, $value): ResourceMetadata - { - $upperProperty = ucfirst($property); - $getter = "get$upperProperty"; - - if (null !== $resourceMetadata->{$getter}()) { - return $resourceMetadata; - } - - if (null === $value) { - return $resourceMetadata; - } - - $wither = "with$upperProperty"; - - return $resourceMetadata->{$wither}($value); - } -} diff --git a/src/Core/Metadata/Resource/Factory/AnnotationResourceNameCollectionFactory.php b/src/Core/Metadata/Resource/Factory/AnnotationResourceNameCollectionFactory.php deleted file mode 100644 index b99835902bd..00000000000 --- a/src/Core/Metadata/Resource/Factory/AnnotationResourceNameCollectionFactory.php +++ /dev/null @@ -1,67 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Annotation\ApiResource; -use ApiPlatform\Core\Util\ReflectionClassRecursiveIterator; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use Doctrine\Common\Annotations\Reader; - -/** - * Creates a resource name collection from {@see ApiResource} annotations. - * - * @author Kévin Dunglas - */ -final class AnnotationResourceNameCollectionFactory implements ResourceNameCollectionFactoryInterface -{ - private $reader; - private $paths; - private $decorated; - - /** - * @param string[] $paths - */ - public function __construct(Reader $reader = null, array $paths, ResourceNameCollectionFactoryInterface $decorated = null) - { - $this->reader = $reader; - $this->paths = $paths; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(): ResourceNameCollection - { - $classes = []; - - if ($this->decorated) { - foreach ($this->decorated->create() as $resourceClass) { - $classes[$resourceClass] = true; - } - } - - foreach (ReflectionClassRecursiveIterator::getReflectionClassesFromDirectories($this->paths) as $className => $reflectionClass) { - if ( - (\PHP_VERSION_ID >= 80000 && $reflectionClass->getAttributes(ApiResource::class)) || - (null !== $this->reader && $this->reader->getClassAnnotation($reflectionClass, ApiResource::class)) - ) { - $classes[$className] = true; - } - } - - return new ResourceNameCollection(array_keys($classes)); - } -} diff --git a/src/Core/Metadata/Resource/Factory/CachedResourceMetadataFactory.php b/src/Core/Metadata/Resource/Factory/CachedResourceMetadataFactory.php deleted file mode 100644 index 1398bb2fabb..00000000000 --- a/src/Core/Metadata/Resource/Factory/CachedResourceMetadataFactory.php +++ /dev/null @@ -1,50 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Util\CachedTrait; -use Psr\Cache\CacheItemPoolInterface; - -/** - * Caches resource metadata. - * - * @author Teoh Han Hui - */ -final class CachedResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - use CachedTrait; - - public const CACHE_KEY_PREFIX = 'resource_metadata_'; - - private $decorated; - - public function __construct(CacheItemPoolInterface $cacheItemPool, ResourceMetadataFactoryInterface $decorated) - { - $this->cacheItemPool = $cacheItemPool; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $cacheKey = self::CACHE_KEY_PREFIX.md5($resourceClass); - - return $this->getCached($cacheKey, function () use ($resourceClass) { - return $this->decorated->create($resourceClass); - }); - } -} diff --git a/src/Core/Metadata/Resource/Factory/ExtractorResourceMetadataFactory.php b/src/Core/Metadata/Resource/Factory/ExtractorResourceMetadataFactory.php deleted file mode 100644 index c5992301e46..00000000000 --- a/src/Core/Metadata/Resource/Factory/ExtractorResourceMetadataFactory.php +++ /dev/null @@ -1,104 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Exception\ResourceClassNotFoundException; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Metadata\Extractor\ResourceExtractorInterface; - -/** - * Creates resource's metadata using an extractor. - * - * @author Kévin Dunglas - * @author Antoine Bluchet - */ -final class ExtractorResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - private $extractor; - private $decorated; - private $defaults; - - public function __construct(ResourceExtractorInterface $extractor, ResourceMetadataFactoryInterface $decorated = null, array $defaults = []) - { - $this->extractor = $extractor; - $this->decorated = $decorated; - $this->defaults = $defaults + ['attributes' => []]; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $parentResourceMetadata = null; - if ($this->decorated) { - try { - $parentResourceMetadata = $this->decorated->create($resourceClass); - } catch (ResourceClassNotFoundException $resourceNotFoundException) { - // Ignore not found exception from decorated factories - } - } - - if (!(class_exists($resourceClass) || interface_exists($resourceClass)) || !$resource = $this->extractor->getResources()[$resourceClass] ?? false) { - return $this->handleNotFound($parentResourceMetadata, $resourceClass); - } - - $resource['description'] = $resource['description'] ?? $this->defaults['description'] ?? null; - $resource['iri'] = $resource['iri'] ?? $this->defaults['iri'] ?? null; - $resource['itemOperations'] = $resource['itemOperations'] ?? $this->defaults['item_operations'] ?? null; - $resource['collectionOperations'] = $resource['collectionOperations'] ?? $this->defaults['collection_operations'] ?? null; - $resource['graphql'] = $resource['graphql'] ?? $this->defaults['graphql'] ?? null; - - if (\array_key_exists('attributes', $resource) && (null !== $resource['attributes'] || [] !== $this->defaults['attributes'])) { - $resource['attributes'] = (array) $resource['attributes']; - foreach ($this->defaults['attributes'] as $key => $value) { - if (!isset($resource['attributes'][$key])) { - $resource['attributes'][$key] = $value; - } - } - } - - return $this->update($parentResourceMetadata ?: new ResourceMetadata(), $resource); - } - - /** - * Returns the metadata from the decorated factory if available or throws an exception. - * - * @throws ResourceClassNotFoundException - */ - private function handleNotFound(?ResourceMetadata $parentPropertyMetadata, string $resourceClass): ResourceMetadata - { - if (null !== $parentPropertyMetadata) { - return $parentPropertyMetadata; - } - - throw new ResourceClassNotFoundException(sprintf('Resource "%s" not found.', $resourceClass)); - } - - /** - * Creates a new instance of metadata if the property is not already set. - */ - private function update(ResourceMetadata $resourceMetadata, array $metadata): ResourceMetadata - { - foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'subresourceOperations', 'graphql', 'attributes'] as $property) { - if (!\array_key_exists($property, $metadata) || null === $metadata[$property] || null !== $resourceMetadata->{'get'.ucfirst($property)}()) { - continue; - } - - $resourceMetadata = $resourceMetadata->{'with'.ucfirst($property)}($metadata[$property]); - } - - return $resourceMetadata; - } -} diff --git a/src/Core/Metadata/Resource/Factory/FormatsResourceMetadataFactory.php b/src/Core/Metadata/Resource/Factory/FormatsResourceMetadataFactory.php deleted file mode 100644 index 9b34ba45c43..00000000000 --- a/src/Core/Metadata/Resource/Factory/FormatsResourceMetadataFactory.php +++ /dev/null @@ -1,127 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Exception\ResourceClassNotFoundException; - -/** - * Normalizes enabled formats. - * - * Formats hierarchy: - * * resource formats - * * resource input/output formats - * * operation formats - * * operation input/output formats - * - * @author Kévin Dunglas - */ -final class FormatsResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - private $decorated; - private $formats; - private $patchFormats; - - public function __construct(ResourceMetadataFactoryInterface $decorated, array $formats, array $patchFormats) - { - $this->decorated = $decorated; - $this->formats = $formats; - $this->patchFormats = $patchFormats; - } - - /** - * Adds the formats attributes. - * - * @see OperationResourceMetadataFactory - * - * @throws ResourceClassNotFoundException - */ - public function create(string $resourceClass): ResourceMetadata - { - $resourceMetadata = $this->decorated->create($resourceClass); - $rawResourceFormats = $resourceMetadata->getAttribute('formats'); - $resourceFormats = null === $rawResourceFormats ? $this->formats : $this->normalizeFormats($rawResourceFormats); - - $rawResourceInputFormats = $resourceMetadata->getAttribute('input_formats'); - $rawResourceOutputFormats = $resourceMetadata->getAttribute('output_formats'); - - $resourceInputFormats = $rawResourceInputFormats ? $this->normalizeFormats($rawResourceInputFormats) : $resourceFormats; - $resourceOutputFormats = $rawResourceOutputFormats ? $this->normalizeFormats($rawResourceOutputFormats) : $resourceFormats; - - if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) { - $resourceMetadata = $resourceMetadata->withCollectionOperations($this->normalize($resourceInputFormats, $resourceOutputFormats, $collectionOperations)); - } - - if (null !== $itemOperations = $resourceMetadata->getItemOperations()) { - $resourceMetadata = $resourceMetadata->withItemOperations($this->normalize($resourceInputFormats, $resourceOutputFormats, $itemOperations)); - } - - if (null !== $subresourceOperations = $resourceMetadata->getSubresourceOperations()) { - $resourceMetadata = $resourceMetadata->withSubresourceOperations($this->normalize($resourceInputFormats, $resourceOutputFormats, $subresourceOperations)); - } - - return $resourceMetadata; - } - - private function normalize(array $resourceInputFormats, array $resourceOutputFormats, array $operations): array - { - $newOperations = []; - foreach ($operations as $operationName => $operation) { - if ('PATCH' === ($operation['method'] ?? '') && !isset($operation['formats']) && !isset($operation['input_formats'])) { - $operation['input_formats'] = $this->patchFormats; - } - - if (isset($operation['formats'])) { - $operation['formats'] = $this->normalizeFormats($operation['formats']); - } - - $operation['input_formats'] = isset($operation['input_formats']) ? $this->normalizeFormats($operation['input_formats']) : $operation['formats'] ?? $resourceInputFormats; - $operation['output_formats'] = isset($operation['output_formats']) ? $this->normalizeFormats($operation['output_formats']) : $operation['formats'] ?? $resourceOutputFormats; - - $newOperations[$operationName] = $operation; - } - - return $newOperations; - } - - /** - * @param array|string $currentFormats - * - * @throws InvalidArgumentException - */ - private function normalizeFormats($currentFormats): array - { - $currentFormats = (array) $currentFormats; - - $normalizedFormats = []; - foreach ($currentFormats as $format => $value) { - if (!is_numeric($format)) { - $normalizedFormats[$format] = (array) $value; - continue; - } - if (!\is_string($value)) { - throw new InvalidArgumentException(sprintf("The 'formats' attributes value must be a string when trying to include an already configured format, %s given.", \gettype($value))); - } - if (\array_key_exists($value, $this->formats)) { - $normalizedFormats[$value] = $this->formats[$value]; - continue; - } - - throw new InvalidArgumentException(sprintf("You either need to add the format '%s' to your project configuration or declare a mime type for it in your annotation.", $value)); - } - - return $normalizedFormats; - } -} diff --git a/src/Core/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php b/src/Core/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php deleted file mode 100644 index 24e7b997d72..00000000000 --- a/src/Core/Metadata/Resource/Factory/InputOutputResourceMetadataFactory.php +++ /dev/null @@ -1,109 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; - -/** - * Transforms the given input/output metadata to a normalized one. - * - * @author Antoine Bluchet - */ -final class InputOutputResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - private $decorated; - - public function __construct(ResourceMetadataFactoryInterface $decorated) - { - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $resourceMetadata = $this->decorated->create($resourceClass); - - $attributes = $resourceMetadata->getAttributes() ?: []; - $attributes['input'] = isset($attributes['input']) ? $this->transformInputOutput($attributes['input']) : null; - $attributes['output'] = isset($attributes['output']) ? $this->transformInputOutput($attributes['output']) : null; - - if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) { - $resourceMetadata = $resourceMetadata->withCollectionOperations($this->getTransformedOperations($collectionOperations, $attributes)); - } - - if (null !== $itemOperations = $resourceMetadata->getItemOperations()) { - $resourceMetadata = $resourceMetadata->withItemOperations($this->getTransformedOperations($itemOperations, $attributes)); - } - - if (null !== $graphQlAttributes = $resourceMetadata->getGraphql()) { - $resourceMetadata = $resourceMetadata->withGraphql($this->getTransformedOperations($graphQlAttributes, $attributes)); - } - - return $resourceMetadata->withAttributes($attributes); - } - - private function getTransformedOperations(array $operations, array $resourceAttributes): array - { - foreach ($operations as $key => &$operation) { - if (!\is_array($operation)) { - continue; - } - - $operation['input'] = isset($operation['input']) ? $this->transformInputOutput($operation['input']) : $resourceAttributes['input']; - $operation['output'] = isset($operation['output']) ? $this->transformInputOutput($operation['output']) : $resourceAttributes['output']; - - if ( - isset($operation['input']) - && \array_key_exists('class', $operation['input']) - && null === $operation['input']['class'] - ) { - $operation['deserialize'] ?? $operation['deserialize'] = false; - $operation['validate'] ?? $operation['validate'] = false; - } - - if ( - isset($operation['output']) - && \array_key_exists('class', $operation['output']) - && null === $operation['output']['class'] - ) { - $operation['status'] ?? $operation['status'] = 204; - } - } - - return $operations; - } - - private function transformInputOutput($attribute): ?array - { - if (null === $attribute) { - return null; - } - - if (false === $attribute) { - return ['class' => null]; - } - - if (\is_string($attribute)) { - $attribute = ['class' => $attribute]; - } - - if (!isset($attribute['name']) && isset($attribute['class'])) { - $attribute['name'] = (new \ReflectionClass($attribute['class']))->getShortName(); - } - - return $attribute; - } -} diff --git a/src/Core/Metadata/Resource/Factory/OperationResourceMetadataFactory.php b/src/Core/Metadata/Resource/Factory/OperationResourceMetadataFactory.php deleted file mode 100644 index 52f65fde23c..00000000000 --- a/src/Core/Metadata/Resource/Factory/OperationResourceMetadataFactory.php +++ /dev/null @@ -1,153 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; - -/** - * Creates or completes operations. - * - * @author Kévin Dunglas - */ -final class OperationResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - /** - * @internal - */ - public const SUPPORTED_COLLECTION_OPERATION_METHODS = [ - 'GET' => true, - 'POST' => true, - ]; - - /** - * @internal - */ - public const SUPPORTED_ITEM_OPERATION_METHODS = [ - 'GET' => true, - 'PUT' => true, - // PATCH is automatically supported if at least one patch format has been configured - 'DELETE' => true, - ]; - - private $decorated; - private $patchFormats; - - public function __construct(ResourceMetadataFactoryInterface $decorated, array $patchFormats = []) - { - $this->decorated = $decorated; - $this->patchFormats = $patchFormats; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $resourceMetadata = $this->decorated->create($resourceClass); - $isAbstract = (new \ReflectionClass($resourceClass))->isAbstract(); - - $collectionOperations = $resourceMetadata->getCollectionOperations(); - if (null === $collectionOperations) { - $resourceMetadata = $resourceMetadata->withCollectionOperations($this->createOperations($isAbstract ? ['GET'] : ['GET', 'POST'], $resourceMetadata)); - } else { - $resourceMetadata = $this->normalize(true, $resourceClass, $resourceMetadata, $collectionOperations); - } - - $itemOperations = $resourceMetadata->getItemOperations(); - if (null === $itemOperations) { - $methods = ['GET', 'DELETE']; - - if (!$isAbstract) { - $methods[] = 'PUT'; - - if ($this->patchFormats) { - $methods[] = 'PATCH'; - } - } - - $resourceMetadata = $resourceMetadata->withItemOperations($this->createOperations($methods, $resourceMetadata)); - } else { - $resourceMetadata = $this->normalize(false, $resourceClass, $resourceMetadata, $itemOperations); - } - - $graphql = $resourceMetadata->getGraphql(); - if (null === $graphql) { - $resourceMetadata = $resourceMetadata->withGraphql(['item_query' => [], 'collection_query' => [], 'delete' => [], 'update' => [], 'create' => []]); - } else { - $resourceMetadata = $this->normalizeGraphQl($resourceMetadata, $graphql); - } - - return $resourceMetadata; - } - - private function createOperations(array $methods, ResourceMetadata $resourceMetadata): array - { - $operations = []; - foreach ($methods as $method) { - $operations[strtolower($method)] = ['method' => $method, 'stateless' => $resourceMetadata->getAttribute('stateless')]; - } - - return $operations; - } - - private function normalize(bool $collection, string $resourceClass, ResourceMetadata $resourceMetadata, array $operations): ResourceMetadata - { - $newOperations = []; - foreach ($operations as $operationName => $operation) { - // e.g.: @ApiResource(itemOperations={"get"}) - if (\is_int($operationName) && \is_string($operation)) { - $operationName = $operation; - $operation = []; - } - - $upperOperationName = strtoupper((string) $operationName); - if ($collection) { - $supported = isset(self::SUPPORTED_COLLECTION_OPERATION_METHODS[$upperOperationName]); - } else { - $supported = isset(self::SUPPORTED_ITEM_OPERATION_METHODS[$upperOperationName]) || ($this->patchFormats && 'PATCH' === $upperOperationName); - } - - if (!isset($operation['method']) && !isset($operation['route_name'])) { - if ($supported) { - $operation['method'] = $upperOperationName; - } else { - @trigger_error(sprintf('The "route_name" attribute will not be set automatically again in API Platform 3.0, set it for the %s operation "%s" of the class "%s".', $collection ? 'collection' : 'item', $operationName, $resourceClass), \E_USER_DEPRECATED); - $operation['route_name'] = $operationName; - } - } - - if (isset($operation['method'])) { - $operation['method'] = strtoupper($operation['method']); - } - - $operation['stateless'] = $operation['stateless'] ?? $resourceMetadata->getAttribute('stateless'); - - $newOperations[$operationName] = $operation; - } - - return $collection ? $resourceMetadata->withCollectionOperations($newOperations) : $resourceMetadata->withItemOperations($newOperations); - } - - private function normalizeGraphQl(ResourceMetadata $resourceMetadata, array $operations): ResourceMetadata - { - foreach ($operations as $operationName => $operation) { - if (\is_int($operationName) && \is_string($operation)) { - unset($operations[$operationName]); - $operations[$operation] = []; - } - } - - return $resourceMetadata->withGraphql($operations); - } -} diff --git a/src/Core/Metadata/Resource/Factory/PhpDocResourceMetadataFactory.php b/src/Core/Metadata/Resource/Factory/PhpDocResourceMetadataFactory.php deleted file mode 100644 index f2257d24efd..00000000000 --- a/src/Core/Metadata/Resource/Factory/PhpDocResourceMetadataFactory.php +++ /dev/null @@ -1,61 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use phpDocumentor\Reflection\DocBlockFactory; -use phpDocumentor\Reflection\DocBlockFactoryInterface; -use phpDocumentor\Reflection\Types\ContextFactory; - -/** - * Extracts descriptions from PHPDoc. - * - * @author Kévin Dunglas - */ -final class PhpDocResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - private $decorated; - private $docBlockFactory; - private $contextFactory; - - public function __construct(ResourceMetadataFactoryInterface $decorated, DocBlockFactoryInterface $docBlockFactory = null) - { - $this->decorated = $decorated; - $this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance(); - $this->contextFactory = new ContextFactory(); - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $resourceMetadata = $this->decorated->create($resourceClass); - - if (null !== $resourceMetadata->getDescription()) { - return $resourceMetadata; - } - - $reflectionClass = new \ReflectionClass($resourceClass); - - try { - $docBlock = $this->docBlockFactory->create($reflectionClass, $this->contextFactory->createFromReflector($reflectionClass)); - $resourceMetadata = $resourceMetadata->withDescription($docBlock->getSummary()); - } catch (\InvalidArgumentException $e) { - // Ignore empty DocBlocks - } - - return $resourceMetadata; - } -} diff --git a/src/Core/Metadata/Resource/Factory/ResourceMetadataFactoryInterface.php b/src/Core/Metadata/Resource/Factory/ResourceMetadataFactoryInterface.php deleted file mode 100644 index 8032e73377e..00000000000 --- a/src/Core/Metadata/Resource/Factory/ResourceMetadataFactoryInterface.php +++ /dev/null @@ -1,32 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Exception\ResourceClassNotFoundException; - -/** - * Creates a resource metadata value object. - * - * @author Kévin Dunglas - */ -interface ResourceMetadataFactoryInterface -{ - /** - * Creates a resource metadata. - * - * @throws ResourceClassNotFoundException - */ - public function create(string $resourceClass): ResourceMetadata; -} diff --git a/src/Core/Metadata/Resource/Factory/ShortNameResourceMetadataFactory.php b/src/Core/Metadata/Resource/Factory/ShortNameResourceMetadataFactory.php deleted file mode 100644 index c7f9e898f63..00000000000 --- a/src/Core/Metadata/Resource/Factory/ShortNameResourceMetadataFactory.php +++ /dev/null @@ -1,49 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; - -/** - * Guesses the short name from the class name if not already set. - * - * @author Kévin Dunglas - */ -final class ShortNameResourceMetadataFactory implements ResourceMetadataFactoryInterface -{ - private $decorated; - - public function __construct(ResourceMetadataFactoryInterface $decorated) - { - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): ResourceMetadata - { - $resourceMetadata = $this->decorated->create($resourceClass); - - if (null !== $resourceMetadata->getShortName()) { - return $resourceMetadata; - } - - if (false !== $pos = strrpos($resourceClass, '\\')) { - return $resourceMetadata->withShortName(substr($resourceClass, $pos + 1)); - } - - return $resourceMetadata->withShortName($resourceClass); - } -} diff --git a/src/Core/Metadata/Resource/ResourceMetadata.php b/src/Core/Metadata/Resource/ResourceMetadata.php deleted file mode 100644 index cc226f6c201..00000000000 --- a/src/Core/Metadata/Resource/ResourceMetadata.php +++ /dev/null @@ -1,311 +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\Metadata\Resource; - -use ApiPlatform\Core\Api\OperationType; - -/** - * Resource metadata. - * - * @author Kévin Dunglas - */ -final class ResourceMetadata -{ - private $shortName; - private $description; - private $iri; - private $itemOperations; - private $collectionOperations; - private $subresourceOperations; - private $graphql; - private $attributes; - - public function __construct(string $shortName = null, string $description = null, string $iri = null, array $itemOperations = null, array $collectionOperations = null, array $attributes = null, array $subresourceOperations = null, array $graphql = null) - { - $this->shortName = $shortName; - $this->description = $description; - $this->iri = $iri; - $this->itemOperations = $itemOperations; - $this->collectionOperations = $collectionOperations; - $this->subresourceOperations = $subresourceOperations; - $this->graphql = $graphql; - $this->attributes = $attributes; - } - - /** - * Gets the short name. - */ - public function getShortName(): ?string - { - return $this->shortName; - } - - /** - * Returns a new instance with the given short name. - */ - public function withShortName(string $shortName): self - { - $metadata = clone $this; - $metadata->shortName = $shortName; - - return $metadata; - } - - /** - * Gets the description. - */ - public function getDescription(): ?string - { - return $this->description; - } - - /** - * Returns a new instance with the given description. - */ - public function withDescription(string $description): self - { - $metadata = clone $this; - $metadata->description = $description; - - return $metadata; - } - - /** - * Gets the associated IRI. - */ - public function getIri(): ?string - { - return $this->iri; - } - - /** - * Returns a new instance with the given IRI. - */ - public function withIri(string $iri): self - { - $metadata = clone $this; - $metadata->iri = $iri; - - return $metadata; - } - - /** - * Gets item operations. - */ - public function getItemOperations(): ?array - { - return $this->itemOperations; - } - - /** - * Returns a new instance with the given item operations. - */ - public function withItemOperations(array $itemOperations): self - { - $metadata = clone $this; - $metadata->itemOperations = $itemOperations; - - return $metadata; - } - - /** - * Gets collection operations. - */ - public function getCollectionOperations(): ?array - { - return $this->collectionOperations; - } - - /** - * Returns a new instance with the given collection operations. - */ - public function withCollectionOperations(array $collectionOperations): self - { - $metadata = clone $this; - $metadata->collectionOperations = $collectionOperations; - - return $metadata; - } - - /** - * Gets subresource operations. - */ - public function getSubresourceOperations(): ?array - { - return $this->subresourceOperations; - } - - /** - * Returns a new instance with the given subresource operations. - */ - public function withSubresourceOperations(array $subresourceOperations): self - { - $metadata = clone $this; - $metadata->subresourceOperations = $subresourceOperations; - - return $metadata; - } - - /** - * Gets a collection operation attribute, optionally fallback to a resource attribute. - * - * @param mixed|null $defaultValue - */ - public function getCollectionOperationAttribute(?string $operationName, string $key, $defaultValue = null, bool $resourceFallback = false) - { - return $this->findOperationAttribute($this->collectionOperations, $operationName, $key, $defaultValue, $resourceFallback); - } - - /** - * Gets an item operation attribute, optionally fallback to a resource attribute. - * - * @param mixed|null $defaultValue - */ - public function getItemOperationAttribute(?string $operationName, string $key, $defaultValue = null, bool $resourceFallback = false) - { - return $this->findOperationAttribute($this->itemOperations, $operationName, $key, $defaultValue, $resourceFallback); - } - - /** - * Gets a subresource operation attribute, optionally fallback to a resource attribute. - * - * @param mixed|null $defaultValue - */ - public function getSubresourceOperationAttribute(?string $operationName, string $key, $defaultValue = null, bool $resourceFallback = false) - { - return $this->findOperationAttribute($this->subresourceOperations, $operationName, $key, $defaultValue, $resourceFallback); - } - - public function getGraphqlAttribute(string $operationName, string $key, $defaultValue = null, bool $resourceFallback = false) - { - if (isset($this->graphql[$operationName][$key])) { - return $this->graphql[$operationName][$key]; - } - - if ($resourceFallback && isset($this->attributes[$key])) { - return $this->attributes[$key]; - } - - return $defaultValue; - } - - /** - * Gets the first available operation attribute according to the following order: collection, item, subresource, optionally fallback to a default value. - * - * @param mixed|null $defaultValue - */ - public function getOperationAttribute(array $attributes, string $key, $defaultValue = null, bool $resourceFallback = false) - { - if (isset($attributes['collection_operation_name'])) { - return $this->getCollectionOperationAttribute($attributes['collection_operation_name'], $key, $defaultValue, $resourceFallback); - } - - if (isset($attributes['item_operation_name'])) { - return $this->getItemOperationAttribute($attributes['item_operation_name'], $key, $defaultValue, $resourceFallback); - } - - if (isset($attributes['subresource_operation_name'])) { - return $this->getSubresourceOperationAttribute($attributes['subresource_operation_name'], $key, $defaultValue, $resourceFallback); - } - - if ($resourceFallback && isset($this->attributes[$key])) { - return $this->attributes[$key]; - } - - return $defaultValue; - } - - /** - * Gets an attribute for a given operation type and operation name. - * - * @param mixed|null $defaultValue - */ - public function getTypedOperationAttribute(string $operationType, string $operationName, string $key, $defaultValue = null, bool $resourceFallback = false) - { - switch ($operationType) { - case OperationType::COLLECTION: - return $this->getCollectionOperationAttribute($operationName, $key, $defaultValue, $resourceFallback); - case OperationType::ITEM: - return $this->getItemOperationAttribute($operationName, $key, $defaultValue, $resourceFallback); - default: - return $this->getSubresourceOperationAttribute($operationName, $key, $defaultValue, $resourceFallback); - } - } - - /** - * Gets attributes. - */ - public function getAttributes(): ?array - { - return $this->attributes; - } - - /** - * Gets an attribute. - * - * @param mixed|null $defaultValue - */ - public function getAttribute(string $key, $defaultValue = null) - { - return $this->attributes[$key] ?? $defaultValue; - } - - /** - * Returns a new instance with the given attribute. - */ - public function withAttributes(array $attributes): self - { - $metadata = clone $this; - $metadata->attributes = $attributes; - - return $metadata; - } - - /** - * Gets options of for the GraphQL query. - */ - public function getGraphql(): ?array - { - return $this->graphql; - } - - /** - * Returns a new instance with the given GraphQL options. - */ - public function withGraphql(array $graphql): self - { - $metadata = clone $this; - $metadata->graphql = $graphql; - - return $metadata; - } - - /** - * Gets an operation attribute, optionally fallback to a resource attribute. - * - * @param mixed|null $defaultValue - */ - private function findOperationAttribute(?array $operations, ?string $operationName, string $key, $defaultValue, bool $resourceFallback) - { - if (null !== $operationName && isset($operations[$operationName][$key])) { - return $operations[$operationName][$key]; - } - - if ($resourceFallback && isset($this->attributes[$key])) { - return $this->attributes[$key]; - } - - return $defaultValue; - } -} diff --git a/src/Core/Metadata/Resource/ToggleableOperationAttributeTrait.php b/src/Core/Metadata/Resource/ToggleableOperationAttributeTrait.php deleted file mode 100644 index b1aa81da947..00000000000 --- a/src/Core/Metadata/Resource/ToggleableOperationAttributeTrait.php +++ /dev/null @@ -1,44 +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\Metadata\Resource; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; - -/** - * @internal - * TODO: 3.0 remove the trait - */ -trait ToggleableOperationAttributeTrait -{ - /** - * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface|null - */ - private $resourceMetadataFactory; - - private function isOperationAttributeDisabled(array $attributes, string $attribute, bool $default = false, bool $resourceFallback = true): bool - { - if (null === $this->resourceMetadataFactory) { - return !($attributes[$attribute] ?? !$default); - } - - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - return !$default; - } - - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - - return !((bool) $resourceMetadata->getOperationAttribute($attributes, $attribute, !$default, $resourceFallback)); - } -} diff --git a/src/Core/Metadata/schema/metadata.xsd b/src/Core/Metadata/schema/metadata.xsd deleted file mode 100644 index 2515d2440fc..00000000000 --- a/src/Core/Metadata/schema/metadata.xsd +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Core/OpenApi/Factory/OpenApiFactory.php b/src/Core/OpenApi/Factory/OpenApiFactory.php deleted file mode 100644 index 4937f73cd08..00000000000 --- a/src/Core/OpenApi/Factory/OpenApiFactory.php +++ /dev/null @@ -1,539 +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\OpenApi\Factory; - -use ApiPlatform\Core\Api\FilterLocatorTrait; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; -use ApiPlatform\JsonSchema\Schema; -use ApiPlatform\JsonSchema\TypeFactoryInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface; -use ApiPlatform\OpenApi\Model; -use ApiPlatform\OpenApi\Model\ExternalDocumentation; -use ApiPlatform\OpenApi\Model\PathItem; -use ApiPlatform\OpenApi\OpenApi; -use ApiPlatform\OpenApi\Options; -use ApiPlatform\PathResolver\OperationPathResolverInterface; -use ApiPlatform\State\Pagination\PaginationOptions; -use Psr\Container\ContainerInterface; -use Symfony\Component\PropertyInfo\Type; - -/** - * Generates an Open API v3 specification. - */ -final class OpenApiFactory implements OpenApiFactoryInterface -{ - use FilterLocatorTrait; - - public const BASE_URL = 'base_url'; - public const OPENAPI_DEFINITION_NAME = 'openapi_definition_name'; - - private $resourceNameCollectionFactory; - private $resourceMetadataFactory; - private $propertyNameCollectionFactory; - private $propertyMetadataFactory; - private $operationPathResolver; - private $subresourceOperationFactory; - private $formats; - private $jsonSchemaFactory; - private $jsonSchemaTypeFactory; - private $openApiOptions; - private $paginationOptions; - private $identifiersExtractor; - - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, SchemaFactoryInterface $jsonSchemaFactory, TypeFactoryInterface $jsonSchemaTypeFactory, OperationPathResolverInterface $operationPathResolver, ContainerInterface $filterLocator, SubresourceOperationFactoryInterface $subresourceOperationFactory, IdentifiersExtractorInterface $identifiersExtractor = null, array $formats = [], Options $openApiOptions = null, PaginationOptions $paginationOptions = null) - { - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->jsonSchemaFactory = $jsonSchemaFactory; - $this->jsonSchemaTypeFactory = $jsonSchemaTypeFactory; - $this->formats = $formats; - $this->setFilterLocator($filterLocator, true); - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->operationPathResolver = $operationPathResolver; - $this->subresourceOperationFactory = $subresourceOperationFactory; - $this->identifiersExtractor = $identifiersExtractor; - $this->openApiOptions = $openApiOptions ?: new Options('API Platform'); - $this->paginationOptions = $paginationOptions ?: new PaginationOptions(); - } - - /** - * {@inheritdoc} - */ - public function __invoke(array $context = []): OpenApi - { - $baseUrl = $context[self::BASE_URL] ?? '/'; - $contact = null === $this->openApiOptions->getContactUrl() || null === $this->openApiOptions->getContactEmail() ? null : new Model\Contact($this->openApiOptions->getContactName(), $this->openApiOptions->getContactUrl(), $this->openApiOptions->getContactEmail()); - $license = null === $this->openApiOptions->getLicenseName() ? null : new Model\License($this->openApiOptions->getLicenseName(), $this->openApiOptions->getLicenseUrl()); - $info = new Model\Info($this->openApiOptions->getTitle(), $this->openApiOptions->getVersion(), trim($this->openApiOptions->getDescription()), $this->openApiOptions->getTermsOfService(), $contact, $license); - $servers = '/' === $baseUrl || '' === $baseUrl ? [new Model\Server('/')] : [new Model\Server($baseUrl)]; - $paths = new Model\Paths(); - $links = []; - $schemas = new \ArrayObject(); - - foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - // Items needs to be parsed first to be able to reference the lines from the collection operation - $this->collectPaths($resourceMetadata, $resourceClass, OperationType::ITEM, $context, $paths, $links, $schemas); - $this->collectPaths($resourceMetadata, $resourceClass, OperationType::COLLECTION, $context, $paths, $links, $schemas); - - $this->collectPaths($resourceMetadata, $resourceClass, OperationType::SUBRESOURCE, $context, $paths, $links, $schemas); - } - - $securitySchemes = $this->getSecuritySchemes(); - $securityRequirements = []; - - foreach (array_keys($securitySchemes) as $key) { - $securityRequirements[] = [$key => []]; - } - - return new OpenApi( - $info, - $servers, - $paths, - new Model\Components( - $schemas, - new \ArrayObject(), - new \ArrayObject(), - new \ArrayObject(), - new \ArrayObject(), - new \ArrayObject(), - new \ArrayObject($securitySchemes) - ), - $securityRequirements - ); - } - - private function collectPaths(ResourceMetadata $resourceMetadata, string $resourceClass, string $operationType, array $context, Model\Paths $paths, array &$links, \ArrayObject $schemas): void - { - $resourceShortName = $resourceMetadata->getShortName(); - $operations = OperationType::COLLECTION === $operationType ? $resourceMetadata->getCollectionOperations() : (OperationType::ITEM === $operationType ? $resourceMetadata->getItemOperations() : $this->subresourceOperationFactory->create($resourceClass)); - if (!$operations) { - return; - } - - $rootResourceClass = $resourceClass; - foreach ($operations as $operationName => $operation) { - if (OperationType::COLLECTION === $operationType && !$resourceMetadata->getItemOperations()) { - $identifiers = []; - } else { - $identifiers = (array) ($operation['identifiers'] ?? $resourceMetadata->getAttribute('identifiers', null === $this->identifiersExtractor ? ['id'] : $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass))); - } - if (\count($identifiers) > 1 ? $resourceMetadata->getAttribute('composite_identifier', true) : false) { - $identifiers = ['id']; - } - - $resourceClass = $operation['resource_class'] ?? $rootResourceClass; - $path = $this->getPath($resourceShortName, $operationName, $operation, $operationType); - $method = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method', 'GET'); - - if (!\in_array($method, PathItem::$methods, true)) { - continue; - } - - [$requestMimeTypes, $responseMimeTypes] = $this->getMimeTypes($resourceClass, $operationName, $operationType, $resourceMetadata); - $operationId = $operation['openapi_context']['operationId'] ?? lcfirst($operationName).ucfirst($resourceShortName).ucfirst($operationType); - $linkedOperationId = 'get'.ucfirst($resourceShortName).ucfirst(OperationType::ITEM); - $pathItem = $paths->getPath($path) ?: new Model\PathItem(); - $forceSchemaCollection = OperationType::SUBRESOURCE === $operationType ? ($operation['collection'] ?? false) : false; - - $schema = new Schema('openapi'); - $schema->setDefinitions($schemas); - - $operationOutputSchemas = []; - foreach ($responseMimeTypes as $operationFormat) { - $operationOutputSchema = $this->jsonSchemaFactory->buildSchema($resourceClass, $operationFormat, Schema::TYPE_OUTPUT, $operationType, $operationName, $schema, null, $forceSchemaCollection); - $operationOutputSchemas[$operationFormat] = $operationOutputSchema; - $this->appendSchemaDefinitions($schemas, $operationOutputSchema->getDefinitions()); - } - - $parameters = []; - $responses = []; - - if ($operation['openapi_context']['parameters'] ?? false) { - foreach ($operation['openapi_context']['parameters'] as $parameter) { - $parameters[] = new Model\Parameter($parameter['name'], $parameter['in'], $parameter['description'] ?? '', $parameter['required'] ?? false, $parameter['deprecated'] ?? false, $parameter['allowEmptyValue'] ?? false, $parameter['schema'] ?? [], $parameter['style'] ?? null, $parameter['explode'] ?? false, $parameter['allowReserved '] ?? false, $parameter['example'] ?? null, isset($parameter['examples']) ? new \ArrayObject($parameter['examples']) : null, isset($parameter['content']) ? new \ArrayObject($parameter['content']) : null); - } - } - - // Set up parameters - if (OperationType::ITEM === $operationType) { - foreach ($identifiers as $parameterName => $identifier) { - $parameterName = \is_string($parameterName) ? $parameterName : $identifier; - $parameter = new Model\Parameter($parameterName, 'path', 'Resource identifier', true, false, false, ['type' => 'string']); - if ($this->hasParameter($parameter, $parameters)) { - continue; - } - - $parameters[] = $parameter; - } - $links[$operationId] = $this->getLink($resourceClass, $operationId, $path); - } elseif (OperationType::COLLECTION === $operationType && 'GET' === $method) { - foreach (array_merge($this->getPaginationParameters($resourceMetadata, $operationName), $this->getFiltersParameters($resourceMetadata, $operationName, $resourceClass)) as $parameter) { - if ($this->hasParameter($parameter, $parameters)) { - continue; - } - - $parameters[] = $parameter; - } - } elseif (OperationType::SUBRESOURCE === $operationType) { - foreach ($operation['identifiers'] as $parameterName => [$class, $property]) { - $parameter = new Model\Parameter($parameterName, 'path', $this->resourceMetadataFactory->create($class)->getShortName().' identifier', true, false, false, ['type' => 'string']); - if ($this->hasParameter($parameter, $parameters)) { - continue; - } - - $parameters[] = $parameter; - } - - if ($operation['collection']) { - $subresourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - foreach (array_merge($this->getPaginationParameters($resourceMetadata, $operationName), $this->getFiltersParameters($subresourceMetadata, $operationName, $resourceClass)) as $parameter) { - if ($this->hasParameter($parameter, $parameters)) { - continue; - } - - $parameters[] = $parameter; - } - } - } - - // Create responses - switch ($method) { - case 'GET': - $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '200'); - $responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas); - $responses[$successStatus] = new Model\Response(sprintf('%s %s', $resourceShortName, OperationType::COLLECTION === $operationType ? 'collection' : 'resource'), $responseContent); - break; - case 'POST': - $responseLinks = new \ArrayObject(isset($links[$linkedOperationId]) ? [ucfirst($linkedOperationId) => $links[$linkedOperationId]] : []); - $responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas); - $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '201'); - $responses[$successStatus] = new Model\Response(sprintf('%s resource created', $resourceShortName), $responseContent, null, $responseLinks); - $responses['400'] = new Model\Response('Invalid input'); - $responses['422'] = new Model\Response('Unprocessable entity'); - break; - case 'PATCH': - case 'PUT': - $responseLinks = new \ArrayObject(isset($links[$linkedOperationId]) ? [ucfirst($linkedOperationId) => $links[$linkedOperationId]] : []); - $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '200'); - $responseContent = $this->buildContent($responseMimeTypes, $operationOutputSchemas); - $responses[$successStatus] = new Model\Response(sprintf('%s resource updated', $resourceShortName), $responseContent, null, $responseLinks); - $responses['400'] = new Model\Response('Invalid input'); - $responses['422'] = new Model\Response('Unprocessable entity'); - break; - case 'DELETE': - $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '204'); - $responses[$successStatus] = new Model\Response(sprintf('%s resource deleted', $resourceShortName)); - break; - } - - if (OperationType::ITEM === $operationType) { - $responses['404'] = new Model\Response('Resource not found'); - } - - if (!$responses) { - $responses['default'] = new Model\Response('Unexpected error'); - } - - if ($contextResponses = $operation['openapi_context']['responses'] ?? false) { - foreach ($contextResponses as $statusCode => $contextResponse) { - $responses[$statusCode] = new Model\Response($contextResponse['description'] ?? '', isset($contextResponse['content']) ? new \ArrayObject($contextResponse['content']) : null, isset($contextResponse['headers']) ? new \ArrayObject($contextResponse['headers']) : null, isset($contextResponse['links']) ? new \ArrayObject($contextResponse['links']) : null); - } - } - - $requestBody = null; - if ($contextRequestBody = $operation['openapi_context']['requestBody'] ?? false) { - $requestBody = new Model\RequestBody($contextRequestBody['description'] ?? '', new \ArrayObject($contextRequestBody['content']), $contextRequestBody['required'] ?? false); - } elseif ('PUT' === $method || 'POST' === $method || 'PATCH' === $method) { - $operationInputSchemas = []; - foreach ($requestMimeTypes as $operationFormat) { - $operationInputSchema = $this->jsonSchemaFactory->buildSchema($resourceClass, $operationFormat, Schema::TYPE_INPUT, $operationType, $operationName, $schema, null, $forceSchemaCollection); - $operationInputSchemas[$operationFormat] = $operationInputSchema; - $this->appendSchemaDefinitions($schemas, $operationInputSchema->getDefinitions()); - } - - $requestBody = new Model\RequestBody(sprintf('The %s %s resource', 'POST' === $method ? 'new' : 'updated', $resourceShortName), $this->buildContent($requestMimeTypes, $operationInputSchemas), true); - } - - $pathItem = $pathItem->{'with'.ucfirst($method)}(new Model\Operation( - $operationId, - $operation['openapi_context']['tags'] ?? (OperationType::SUBRESOURCE === $operationType ? $operation['shortNames'] : [$resourceShortName]), - $responses, - $operation['openapi_context']['summary'] ?? $this->getPathDescription($resourceShortName, $method, $operationType), - $operation['openapi_context']['description'] ?? $this->getPathDescription($resourceShortName, $method, $operationType), - isset($operation['openapi_context']['externalDocs']) ? new ExternalDocumentation($operation['openapi_context']['externalDocs']['description'] ?? null, $operation['openapi_context']['externalDocs']['url']) : null, - $parameters, - $requestBody, - isset($operation['openapi_context']['callbacks']) ? new \ArrayObject($operation['openapi_context']['callbacks']) : null, - $operation['openapi_context']['deprecated'] ?? (bool) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', false, true), - $operation['openapi_context']['security'] ?? null, - $operation['openapi_context']['servers'] ?? null, - array_filter($operation['openapi_context'] ?? [], static function ($item) { - return preg_match('/^x-.*$/i', $item); - }, \ARRAY_FILTER_USE_KEY) - )); - - $paths->addPath($path, $pathItem); - } - } - - private function buildContent(array $responseMimeTypes, array $operationSchemas): \ArrayObject - { - /** @var \ArrayObject */ - $content = new \ArrayObject(); - - foreach ($responseMimeTypes as $mimeType => $format) { - $content[$mimeType] = new Model\MediaType(new \ArrayObject($operationSchemas[$format]->getArrayCopy(false))); - } - - return $content; - } - - private function getMimeTypes(string $resourceClass, string $operationName, string $operationType, ResourceMetadata $resourceMetadata = null): array - { - $requestFormats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'input_formats', $this->formats, true); - $responseFormats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'output_formats', $this->formats, true); - - $requestMimeTypes = $this->flattenMimeTypes($requestFormats); - $responseMimeTypes = $this->flattenMimeTypes($responseFormats); - - return [$requestMimeTypes, $responseMimeTypes]; - } - - private function flattenMimeTypes(array $responseFormats): array - { - $responseMimeTypes = []; - foreach ($responseFormats as $responseFormat => $mimeTypes) { - foreach ($mimeTypes as $mimeType) { - $responseMimeTypes[$mimeType] = $responseFormat; - } - } - - return $responseMimeTypes; - } - - /** - * Gets the path for an operation. - * - * If the path ends with the optional _format parameter, it is removed - * as optional path parameters are not yet supported. - * - * @see https://github.com/OAI/OpenAPI-Specification/issues/93 - */ - private function getPath(string $resourceShortName, string $operationName, array $operation, string $operationType): string - { - $path = $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName); - if ('.{_format}' === substr($path, -10)) { - $path = substr($path, 0, -10); - } - - return 0 === strpos($path, '/') ? $path : '/'.$path; - } - - private function getPathDescription(string $resourceShortName, string $method, string $operationType): string - { - switch ($method) { - case 'GET': - $pathSummary = OperationType::COLLECTION === $operationType ? 'Retrieves the collection of %s resources.' : 'Retrieves a %s resource.'; - break; - case 'POST': - $pathSummary = 'Creates a %s resource.'; - break; - case 'PATCH': - $pathSummary = 'Updates the %s resource.'; - break; - case 'PUT': - $pathSummary = 'Replaces the %s resource.'; - break; - case 'DELETE': - $pathSummary = 'Removes the %s resource.'; - break; - default: - return $resourceShortName; - } - - return sprintf($pathSummary, $resourceShortName); - } - - /** - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#linkObject. - */ - private function getLink(string $resourceClass, string $operationId, string $path): Model\Link - { - $parameters = []; - - foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); - if (!$propertyMetadata->isIdentifier()) { - continue; - } - - $parameters[$propertyName] = sprintf('$response.body#/%s', $propertyName); - } - - return new Model\Link( - $operationId, - new \ArrayObject($parameters), - null, - 1 === \count($parameters) ? sprintf('The `%1$s` value returned in the response can be used as the `%1$s` parameter in `GET %2$s`.', key($parameters), $path) : sprintf('The values returned in the response can be used in `GET %s`.', $path) - ); - } - - /** - * Gets parameters corresponding to enabled filters. - */ - private function getFiltersParameters(ResourceMetadata $resourceMetadata, string $operationName, string $resourceClass): array - { - $parameters = []; - $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); - foreach ($resourceFilters as $filterId) { - if (!$filter = $this->getFilter($filterId)) { - continue; - } - - foreach ($filter->getDescription($resourceClass) as $name => $data) { - $schema = $data['schema'] ?? (\in_array($data['type'], Type::$builtinTypes, true) ? $this->jsonSchemaTypeFactory->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false)) : ['type' => 'string']); - - $parameters[] = new Model\Parameter( - $name, - 'query', - $data['description'] ?? '', - $data['required'] ?? false, - $data['openapi']['deprecated'] ?? false, - $data['openapi']['allowEmptyValue'] ?? true, - $schema, - 'array' === $schema['type'] && \in_array($data['type'], - [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_OBJECT], true) ? 'deepObject' : 'form', - $data['openapi']['explode'] ?? ('array' === $schema['type']), - $data['openapi']['allowReserved'] ?? false, - $data['openapi']['example'] ?? null, - isset($data['openapi']['examples'] - ) ? new \ArrayObject($data['openapi']['examples']) : null); - } - } - - return $parameters; - } - - private function getPaginationParameters(ResourceMetadata $resourceMetadata, string $operationName): array - { - if (!$this->paginationOptions->isPaginationEnabled()) { - return []; - } - - $parameters = []; - - if ($resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_enabled', true, true)) { - $parameters[] = new Model\Parameter($this->paginationOptions->getPaginationPageParameterName(), 'query', 'The collection page number', false, false, true, ['type' => 'integer', 'default' => 1]); - - if ($resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_items_per_page', $this->paginationOptions->getClientItemsPerPage(), true)) { - $schema = [ - 'type' => 'integer', - 'default' => $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_items_per_page', 30, true), - 'minimum' => 0, - ]; - - if (null !== $maxItemsPerPage = $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_maximum_items_per_page', null, true)) { - $schema['maximum'] = $maxItemsPerPage; - } - - $parameters[] = new Model\Parameter($this->paginationOptions->getItemsPerPageParameterName(), 'query', 'The number of items per page', false, false, true, $schema); - } - } - - if ($resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_client_enabled', $this->paginationOptions->getPaginationClientEnabled(), true)) { - $parameters[] = new Model\Parameter($this->paginationOptions->getPaginationClientEnabledParameterName(), 'query', 'Enable or disable pagination', false, false, true, ['type' => 'boolean']); - } - - return $parameters; - } - - private function getOauthSecurityScheme(): Model\SecurityScheme - { - $oauthFlow = new Model\OAuthFlow($this->openApiOptions->getOAuthAuthorizationUrl(), $this->openApiOptions->getOAuthTokenUrl(), $this->openApiOptions->getOAuthRefreshUrl(), new \ArrayObject($this->openApiOptions->getOAuthScopes())); - $description = sprintf( - 'OAuth 2.0 %s Grant', - strtolower(preg_replace('/[A-Z]/', ' \\0', lcfirst($this->openApiOptions->getOAuthFlow()))) - ); - $implicit = $password = $clientCredentials = $authorizationCode = null; - - switch ($this->openApiOptions->getOAuthFlow()) { - case 'implicit': - $implicit = $oauthFlow; - break; - case 'password': - $password = $oauthFlow; - break; - case 'application': - case 'clientCredentials': - $clientCredentials = $oauthFlow; - break; - case 'accessCode': - case 'authorizationCode': - $authorizationCode = $oauthFlow; - break; - default: - throw new \LogicException('OAuth flow must be one of: implicit, password, clientCredentials, authorizationCode'); - } - - return new Model\SecurityScheme($this->openApiOptions->getOAuthType(), $description, null, null, null, null, new Model\OAuthFlows($implicit, $password, $clientCredentials, $authorizationCode), null); - } - - private function getSecuritySchemes(): array - { - $securitySchemes = []; - - if ($this->openApiOptions->getOAuthEnabled()) { - $securitySchemes['oauth'] = $this->getOauthSecurityScheme(); - } - - foreach ($this->openApiOptions->getApiKeys() as $key => $apiKey) { - $description = sprintf('Value for the %s %s parameter.', $apiKey['name'], $apiKey['type']); - $securitySchemes[$key] = new Model\SecurityScheme('apiKey', $description, $apiKey['name'], $apiKey['type']); - } - - return $securitySchemes; - } - - private function appendSchemaDefinitions(\ArrayObject $schemas, \ArrayObject $definitions): void - { - foreach ($definitions as $key => $value) { - $schemas[$key] = $value; - } - } - - /** - * @param Model\Parameter[] $parameters - */ - private function hasParameter(Model\Parameter $parameter, array $parameters): bool - { - foreach ($parameters as $existingParameter) { - if ($existingParameter->getName() === $parameter->getName() && $existingParameter->getIn() === $parameter->getIn()) { - return true; - } - } - - return false; - } -} diff --git a/src/Core/OpenApi/Model/Components.php b/src/Core/OpenApi/Model/Components.php deleted file mode 100644 index 0d67b0427f7..00000000000 --- a/src/Core/OpenApi/Model/Components.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Components::class); - -if (false) { - final class Components extends \ApiPlatform\OpenApi\Model\Components - { - } -} diff --git a/src/Core/OpenApi/Model/Contact.php b/src/Core/OpenApi/Model/Contact.php deleted file mode 100644 index 9d581ea108f..00000000000 --- a/src/Core/OpenApi/Model/Contact.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Contact::class); - -if (false) { - final class Contact extends \ApiPlatform\OpenApi\Model\Contact - { - } -} diff --git a/src/Core/OpenApi/Model/Encoding.php b/src/Core/OpenApi/Model/Encoding.php deleted file mode 100644 index bd8e3bcd106..00000000000 --- a/src/Core/OpenApi/Model/Encoding.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Encoding::class); - -if (false) { - final class Encoding extends \ApiPlatform\OpenApi\Model\Encoding - { - } -} diff --git a/src/Core/OpenApi/Model/ExtensionTrait.php b/src/Core/OpenApi/Model/ExtensionTrait.php deleted file mode 100644 index 1c14b40fc2e..00000000000 --- a/src/Core/OpenApi/Model/ExtensionTrait.php +++ /dev/null @@ -1,23 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\ExtensionTrait::class); - -if (false) { - trait ExtensionTrait - { - use \ApiPlatform\OpenApi\Model\ExtensionTrait; - } -} diff --git a/src/Core/OpenApi/Model/ExternalDocumentation.php b/src/Core/OpenApi/Model/ExternalDocumentation.php deleted file mode 100644 index e5b093f74e1..00000000000 --- a/src/Core/OpenApi/Model/ExternalDocumentation.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\ExternalDocumentation::class); - -if (false) { - final class ExternalDocumentation extends \ApiPlatform\OpenApi\Model\ExternalDocumentation - { - } -} diff --git a/src/Core/OpenApi/Model/Info.php b/src/Core/OpenApi/Model/Info.php deleted file mode 100644 index 5a1bcad4f44..00000000000 --- a/src/Core/OpenApi/Model/Info.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Info::class); - -if (false) { - final class Info extends \ApiPlatform\OpenApi\Model\Info - { - } -} diff --git a/src/Core/OpenApi/Model/License.php b/src/Core/OpenApi/Model/License.php deleted file mode 100644 index 294f1e3ba1e..00000000000 --- a/src/Core/OpenApi/Model/License.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\License::class); - -if (false) { - final class License extends \ApiPlatform\OpenApi\Model\License - { - } -} diff --git a/src/Core/OpenApi/Model/Link.php b/src/Core/OpenApi/Model/Link.php deleted file mode 100644 index 74eeb0cd700..00000000000 --- a/src/Core/OpenApi/Model/Link.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Link::class); - -if (false) { - final class Link extends \ApiPlatform\OpenApi\Model\Link - { - } -} diff --git a/src/Core/OpenApi/Model/MediaType.php b/src/Core/OpenApi/Model/MediaType.php deleted file mode 100644 index 0e097e418f3..00000000000 --- a/src/Core/OpenApi/Model/MediaType.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\MediaType::class); - -if (false) { - final class MediaType extends \ApiPlatform\OpenApi\Model\MediaType - { - } -} diff --git a/src/Core/OpenApi/Model/OAuthFlow.php b/src/Core/OpenApi/Model/OAuthFlow.php deleted file mode 100644 index e87a3070be6..00000000000 --- a/src/Core/OpenApi/Model/OAuthFlow.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\OAuthFlow::class); - -if (false) { - final class OAuthFlow extends \ApiPlatform\OpenApi\Model\OAuthFlow - { - } -} diff --git a/src/Core/OpenApi/Model/OAuthFlows.php b/src/Core/OpenApi/Model/OAuthFlows.php deleted file mode 100644 index 6ad827f3b47..00000000000 --- a/src/Core/OpenApi/Model/OAuthFlows.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\OAuthFlows::class); - -if (false) { - final class OAuthFlows extends \ApiPlatform\OpenApi\Model\OAuthFlows - { - } -} diff --git a/src/Core/OpenApi/Model/Operation.php b/src/Core/OpenApi/Model/Operation.php deleted file mode 100644 index 438d1babbcf..00000000000 --- a/src/Core/OpenApi/Model/Operation.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Operation::class); - -if (false) { - final class Operation extends \ApiPlatform\OpenApi\Model\Operation - { - } -} diff --git a/src/Core/OpenApi/Model/Parameter.php b/src/Core/OpenApi/Model/Parameter.php deleted file mode 100644 index 15cf20105ea..00000000000 --- a/src/Core/OpenApi/Model/Parameter.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Parameter::class); - -if (false) { - final class Parameter extends \ApiPlatform\OpenApi\Model\Parameter - { - } -} diff --git a/src/Core/OpenApi/Model/PathItem.php b/src/Core/OpenApi/Model/PathItem.php deleted file mode 100644 index 620b550d86e..00000000000 --- a/src/Core/OpenApi/Model/PathItem.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\PathItem::class); - -if (false) { - final class PathItem extends \ApiPlatform\OpenApi\Model\PathItem - { - } -} diff --git a/src/Core/OpenApi/Model/Paths.php b/src/Core/OpenApi/Model/Paths.php deleted file mode 100644 index 71b34eb4cd5..00000000000 --- a/src/Core/OpenApi/Model/Paths.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Paths::class); - -if (false) { - final class Paths extends \ApiPlatform\OpenApi\Model\Paths - { - } -} diff --git a/src/Core/OpenApi/Model/RequestBody.php b/src/Core/OpenApi/Model/RequestBody.php deleted file mode 100644 index bddb2198353..00000000000 --- a/src/Core/OpenApi/Model/RequestBody.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\RequestBody::class); - -if (false) { - final class RequestBody extends \ApiPlatform\OpenApi\Model\RequestBody - { - } -} diff --git a/src/Core/OpenApi/Model/Response.php b/src/Core/OpenApi/Model/Response.php deleted file mode 100644 index eea31238412..00000000000 --- a/src/Core/OpenApi/Model/Response.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Response::class); - -if (false) { - final class Response extends \ApiPlatform\OpenApi\Model\Response - { - } -} diff --git a/src/Core/OpenApi/Model/Schema.php b/src/Core/OpenApi/Model/Schema.php deleted file mode 100644 index 73a32a49a0c..00000000000 --- a/src/Core/OpenApi/Model/Schema.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Schema::class); - -if (false) { - final class Schema extends \ApiPlatform\OpenApi\Model\Schema - { - } -} diff --git a/src/Core/OpenApi/Model/SecurityScheme.php b/src/Core/OpenApi/Model/SecurityScheme.php deleted file mode 100644 index 8faffa45d28..00000000000 --- a/src/Core/OpenApi/Model/SecurityScheme.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\SecurityScheme::class); - -if (false) { - final class SecurityScheme extends \ApiPlatform\OpenApi\Model\SecurityScheme - { - } -} diff --git a/src/Core/OpenApi/Model/Server.php b/src/Core/OpenApi/Model/Server.php deleted file mode 100644 index a63c045560e..00000000000 --- a/src/Core/OpenApi/Model/Server.php +++ /dev/null @@ -1,22 +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\OpenApi\Model; - -class_exists(\ApiPlatform\OpenApi\Model\Server::class); - -if (false) { - final class Server extends \ApiPlatform\OpenApi\Model\Server - { - } -} diff --git a/src/Core/OpenApi/OpenApi.php b/src/Core/OpenApi/OpenApi.php deleted file mode 100644 index 0bd7f40b086..00000000000 --- a/src/Core/OpenApi/OpenApi.php +++ /dev/null @@ -1,22 +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\OpenApi; - -class_exists(\ApiPlatform\OpenApi\OpenApi::class); - -if (false) { - final class OpenApi extends \ApiPlatform\OpenApi\OpenApi - { - } -} diff --git a/src/Core/OpenApi/Options.php b/src/Core/OpenApi/Options.php deleted file mode 100644 index c82e2176249..00000000000 --- a/src/Core/OpenApi/Options.php +++ /dev/null @@ -1,22 +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\OpenApi; - -class_exists(\ApiPlatform\OpenApi\Options::class); - -if (false) { - final class Options extends \ApiPlatform\OpenApi\Options - { - } -} diff --git a/src/Core/OpenApi/Serializer/OpenApiNormalizer.php b/src/Core/OpenApi/Serializer/OpenApiNormalizer.php deleted file mode 100644 index 22f696d0643..00000000000 --- a/src/Core/OpenApi/Serializer/OpenApiNormalizer.php +++ /dev/null @@ -1,22 +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\OpenApi\Serializer; - -class_exists(\ApiPlatform\OpenApi\Serializer\OpenApiNormalizer::class); - -if (false) { - final class OpenApiNormalizer extends \ApiPlatform\OpenApi\Serializer\OpenApiNormalizer - { - } -} diff --git a/src/Core/Operation/DashPathSegmentNameGenerator.php b/src/Core/Operation/DashPathSegmentNameGenerator.php deleted file mode 100644 index f7bfa15455d..00000000000 --- a/src/Core/Operation/DashPathSegmentNameGenerator.php +++ /dev/null @@ -1,22 +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\Operation; - -class_exists(\ApiPlatform\Operation\DashPathSegmentNameGenerator::class); - -if (false) { - final class DashPathSegmentNameGenerator extends \ApiPlatform\Operation\DashPathSegmentNameGenerator - { - } -} diff --git a/src/Core/Operation/Factory/CachedSubresourceOperationFactory.php b/src/Core/Operation/Factory/CachedSubresourceOperationFactory.php deleted file mode 100644 index fa5ced37a34..00000000000 --- a/src/Core/Operation/Factory/CachedSubresourceOperationFactory.php +++ /dev/null @@ -1,47 +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\Operation\Factory; - -use ApiPlatform\Util\CachedTrait; -use Psr\Cache\CacheItemPoolInterface; - -/** - * @internal - */ -final class CachedSubresourceOperationFactory implements SubresourceOperationFactoryInterface -{ - use CachedTrait; - - public const CACHE_KEY_PREFIX = 'subresource_operations_'; - - private $decorated; - - public function __construct(CacheItemPoolInterface $cacheItemPool, SubresourceOperationFactoryInterface $decorated) - { - $this->cacheItemPool = $cacheItemPool; - $this->decorated = $decorated; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): array - { - $cacheKey = self::CACHE_KEY_PREFIX.md5($resourceClass); - - return $this->getCached($cacheKey, function () use ($resourceClass) { - return $this->decorated->create($resourceClass); - }); - } -} diff --git a/src/Core/Operation/Factory/SubresourceOperationFactory.php b/src/Core/Operation/Factory/SubresourceOperationFactory.php deleted file mode 100644 index 2b442ff4806..00000000000 --- a/src/Core/Operation/Factory/SubresourceOperationFactory.php +++ /dev/null @@ -1,214 +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\Operation\Factory; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameGenerator; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Operation\PathSegmentNameGeneratorInterface; - -/** - * @internal - */ -final class SubresourceOperationFactory implements SubresourceOperationFactoryInterface -{ - public const SUBRESOURCE_SUFFIX = '_subresource'; - public const FORMAT_SUFFIX = '.{_format}'; - public const ROUTE_OPTIONS = ['defaults' => [], 'requirements' => [], 'options' => [], 'host' => '', 'schemes' => [], 'condition' => '', 'controller' => null, 'stateless' => null]; - - private $resourceMetadataFactory; - private $propertyNameCollectionFactory; - private $propertyMetadataFactory; - private $pathSegmentNameGenerator; - private $identifiersExtractor; - - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PathSegmentNameGeneratorInterface $pathSegmentNameGenerator, IdentifiersExtractorInterface $identifiersExtractor = null) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->pathSegmentNameGenerator = $pathSegmentNameGenerator; - $this->identifiersExtractor = $identifiersExtractor; - } - - /** - * {@inheritdoc} - */ - public function create(string $resourceClass): array - { - $tree = []; - - try { - $this->computeSubresourceOperations($resourceClass, $tree); - } catch (ResourceClassNotFoundException $e) { - return []; - } - - return $tree; - } - - /** - * Handles subresource operations recursively and declare their corresponding routes. - * - * @param string $rootResourceClass null on the first iteration, it then keeps track of the origin resource class - * @param array $parentOperation the previous call operation - * @param int $depth the number of visited - * @param int $maxDepth - */ - private function computeSubresourceOperations(string $resourceClass, array &$tree, string $rootResourceClass = null, array $parentOperation = null, array $visited = [], int $depth = 0, int $maxDepth = null): void - { - if (null === $rootResourceClass) { - $rootResourceClass = $resourceClass; - } - - foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, ['deprecate' => false]); - - if (!$subresource = $propertyMetadata->getSubresource()) { - continue; - } - - trigger_deprecation('api-platform/core', '2.7', sprintf('A subresource is declared on "%s::%s". Subresources are deprecated, use another #[ApiResource] instead.', $resourceClass, $property)); - $subresourceClass = $subresource->getResourceClass(); - $subresourceMetadata = $this->resourceMetadataFactory->create($subresourceClass); - $subresourceMetadata = $subresourceMetadata->withAttributes(($subresourceMetadata->getAttributes() ?: []) + ['identifiers' => !$this->identifiersExtractor ? [$property] : $this->identifiersExtractor->getIdentifiersFromResourceClass($subresourceClass)]); - $isLastItem = ($parentOperation['resource_class'] ?? null) === $resourceClass && $propertyMetadata->isIdentifier(); - - // A subresource that is also an identifier can't be a start point - if ($isLastItem && (null === $parentOperation || false === $parentOperation['collection'])) { - continue; - } - - $visiting = "$resourceClass $property $subresourceClass"; - - // Handle maxDepth - if (null !== $maxDepth && $depth >= $maxDepth) { - break; - } - if (isset($visited[$visiting])) { - continue; - } - - $rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass); - $rootResourceMetadata = $rootResourceMetadata->withAttributes(($rootResourceMetadata->getAttributes() ?: []) + ['identifiers' => !$this->identifiersExtractor ? ['id'] : $this->identifiersExtractor->getIdentifiersFromResourceClass($rootResourceClass)]); - $operationName = 'get'; - $operation = [ - 'property' => $property, - 'collection' => $subresource->isCollection(), - 'resource_class' => $subresourceClass, - 'shortNames' => [$subresourceMetadata->getShortName()], - 'legacy_filters' => $subresourceMetadata->getAttribute('filters', []), - 'legacy_normalization_context' => $subresourceMetadata->getAttribute('normalization_context', []), - 'legacy_type' => $subresourceMetadata->getIri(), - ]; - - if (null === $parentOperation) { - $identifiers = (array) $rootResourceMetadata->getAttribute('identifiers'); - $rootShortname = $rootResourceMetadata->getShortName(); - $identifier = \is_string($key = array_key_first($identifiers)) ? $key : $identifiers[0]; - $operation['identifiers'][$identifier] = [$rootResourceClass, $identifiers[$identifier][1] ?? $identifier, true]; - $operation['operation_name'] = sprintf( - '%s_%s%s', - RouteNameGenerator::inflector($operation['property'], $operation['collection']), - $operationName, - self::SUBRESOURCE_SUFFIX - ); - - $subresourceOperation = $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? []; - - $operation['route_name'] = sprintf( - '%s%s_%s', - RouteNameGenerator::ROUTE_NAME_PREFIX, - RouteNameGenerator::inflector($rootShortname), - $operation['operation_name'] - ); - - $prefix = trim(trim($rootResourceMetadata->getAttribute('route_prefix', '')), '/'); - if ('' !== $prefix) { - $prefix .= '/'; - } - - $operation['path'] = $subresourceOperation['path'] ?? sprintf( - '/%s%s/{%s}/%s%s', - $prefix, - $this->pathSegmentNameGenerator->getSegmentName($rootShortname), - $identifier, - $this->pathSegmentNameGenerator->getSegmentName($operation['property'], $operation['collection']), - self::FORMAT_SUFFIX - ); - - if (!\in_array($rootShortname, $operation['shortNames'], true)) { - $operation['shortNames'][] = $rootShortname; - } - } else { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $identifiers = (array) $resourceMetadata->getAttribute('identifiers', null === $this->identifiersExtractor ? ['id'] : $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass)); - $identifier = \is_string($key = array_key_first($identifiers)) ? $key : $identifiers[0]; - $operation['identifiers'] = $parentOperation['identifiers']; - - if (!isset($operation['identifiers'][$parentOperation['property']])) { - $operation['identifiers'][$parentOperation['property']] = [$resourceClass, $identifiers[$identifier][1] ?? $identifier, $isLastItem ? true : $parentOperation['collection']]; - } - - $operation['operation_name'] = str_replace( - 'get'.self::SUBRESOURCE_SUFFIX, - RouteNameGenerator::inflector($isLastItem ? 'item' : $property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX, - $parentOperation['operation_name'] - ); - $operation['route_name'] = str_replace($parentOperation['operation_name'], $operation['operation_name'], $parentOperation['route_name']); - - if (!\in_array($resourceMetadata->getShortName(), $operation['shortNames'], true)) { - $operation['shortNames'][] = $resourceMetadata->getShortName(); - } - - $subresourceOperation = $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? []; - - if (isset($subresourceOperation['path'])) { - $operation['path'] = $subresourceOperation['path']; - } else { - $operation['path'] = str_replace(self::FORMAT_SUFFIX, '', (string) $parentOperation['path']); - - if ($parentOperation['collection']) { - $operation['path'] .= sprintf('/{%s}', array_key_last($operation['identifiers'])); - } - - if ($isLastItem) { - $operation['path'] .= self::FORMAT_SUFFIX; - } else { - $operation['path'] .= sprintf('/%s%s', $this->pathSegmentNameGenerator->getSegmentName($property, $operation['collection']), self::FORMAT_SUFFIX); - } - } - } - - if (isset($subresourceOperation['openapi_context'])) { - $operation['openapi_context'] = $subresourceOperation['openapi_context']; - } - - foreach (self::ROUTE_OPTIONS as $routeOption => $defaultValue) { - $operation[$routeOption] = $subresourceOperation[$routeOption] ?? $defaultValue; - } - - $tree[$operation['route_name']] = $operation; - - // Get the minimum maxDepth between the rootMaxDepth and the maxDepth of the to be visited Subresource - $currentMaxDepth = array_filter([$maxDepth, $subresource->getMaxDepth()], 'is_int'); - $currentMaxDepth = empty($currentMaxDepth) ? null : min($currentMaxDepth); - - $this->computeSubresourceOperations($subresourceClass, $tree, $rootResourceClass, $operation, $visited + [$visiting => true], $depth + 1, $currentMaxDepth); - } - } -} diff --git a/src/Core/Operation/Factory/SubresourceOperationFactoryInterface.php b/src/Core/Operation/Factory/SubresourceOperationFactoryInterface.php deleted file mode 100644 index 030c81a07a7..00000000000 --- a/src/Core/Operation/Factory/SubresourceOperationFactoryInterface.php +++ /dev/null @@ -1,27 +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\Operation\Factory; - -/** - * Computes subresource operation for a given resource. - * - * @author Antoine Bluchet - */ -interface SubresourceOperationFactoryInterface -{ - /** - * Creates subresource operations. - */ - public function create(string $resourceClass): array; -} diff --git a/src/Core/Operation/UnderscorePathSegmentNameGenerator.php b/src/Core/Operation/UnderscorePathSegmentNameGenerator.php deleted file mode 100644 index 854d0b08074..00000000000 --- a/src/Core/Operation/UnderscorePathSegmentNameGenerator.php +++ /dev/null @@ -1,22 +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\Operation; - -class_exists(\ApiPlatform\Operation\UnderscorePathSegmentNameGenerator::class); - -if (false) { - final class UnderscorePathSegmentNameGenerator extends \ApiPlatform\Operation\UnderscorePathSegmentNameGenerator - { - } -} diff --git a/src/Core/PathResolver/CustomOperationPathResolver.php b/src/Core/PathResolver/CustomOperationPathResolver.php deleted file mode 100644 index 6aa963d40fe..00000000000 --- a/src/Core/PathResolver/CustomOperationPathResolver.php +++ /dev/null @@ -1,22 +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\PathResolver; - -class_exists(\ApiPlatform\PathResolver\CustomOperationPathResolver::class); - -if (false) { - final class CustomOperationPathResolver extends \ApiPlatform\PathResolver\CustomOperationPathResolver - { - } -} diff --git a/src/Core/PathResolver/DashOperationPathResolver.php b/src/Core/PathResolver/DashOperationPathResolver.php deleted file mode 100644 index 25810ebea45..00000000000 --- a/src/Core/PathResolver/DashOperationPathResolver.php +++ /dev/null @@ -1,46 +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\PathResolver; - -use ApiPlatform\Core\Operation\DashPathSegmentNameGenerator; -use ApiPlatform\PathResolver\OperationPathResolverInterface; - -/** - * Generates a path with words separated by underscores. - * - * @author Paul Le Corre - * - * @deprecated since version 2.1, to be removed in 3.0. Use {@see \ApiPlatform\Core\Operation\DashPathSegmentNameGenerator} instead. - */ -final class DashOperationPathResolver implements OperationPathResolverInterface -{ - public function __construct() - { - @trigger_error(sprintf('The use of %s is deprecated since 2.1. Please use %s instead.', __CLASS__, DashPathSegmentNameGenerator::class), \E_USER_DEPRECATED); - } - - /** - * {@inheritdoc} - */ - public function resolveOperationPath(string $resourceShortName, array $operation, $operationType/* , string $operationName = null */): string - { - if (\func_num_args() >= 4) { - $operationName = func_get_arg(3); - } else { - $operationName = null; - } - - return (new OperationPathResolver(new DashPathSegmentNameGenerator()))->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName); - } -} diff --git a/src/Core/PathResolver/OperationPathResolver.php b/src/Core/PathResolver/OperationPathResolver.php deleted file mode 100644 index 7ff7f7c7da7..00000000000 --- a/src/Core/PathResolver/OperationPathResolver.php +++ /dev/null @@ -1,22 +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\PathResolver; - -class_exists(\ApiPlatform\PathResolver\OperationPathResolver::class); - -if (false) { - final class OperationPathResolver extends \ApiPlatform\PathResolver\OperationPathResolver - { - } -} diff --git a/src/Core/PathResolver/UnderscoreOperationPathResolver.php b/src/Core/PathResolver/UnderscoreOperationPathResolver.php deleted file mode 100644 index 99615c74adc..00000000000 --- a/src/Core/PathResolver/UnderscoreOperationPathResolver.php +++ /dev/null @@ -1,46 +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\PathResolver; - -use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator; -use ApiPlatform\PathResolver\OperationPathResolverInterface; - -/** - * Generates a path with words separated by underscores. - * - * @author Paul Le Corre - * - * @deprecated since version 2.1, to be removed in 3.0. Use {@see \ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator} instead. - */ -final class UnderscoreOperationPathResolver implements OperationPathResolverInterface -{ - public function __construct() - { - @trigger_error(sprintf('The use of %s is deprecated since 2.1. Please use %s instead.', __CLASS__, UnderscorePathSegmentNameGenerator::class), \E_USER_DEPRECATED); - } - - /** - * {@inheritdoc} - */ - public function resolveOperationPath(string $resourceShortName, array $operation, $operationType/* , string $operationName = null */): string - { - if (\func_num_args() >= 4) { - $operationName = func_get_arg(3); - } else { - $operationName = null; - } - - return (new OperationPathResolver(new UnderscorePathSegmentNameGenerator()))->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName); - } -} diff --git a/src/Core/Problem/Serializer/ConstraintViolationListNormalizer.php b/src/Core/Problem/Serializer/ConstraintViolationListNormalizer.php deleted file mode 100644 index 9b8ff0b88c5..00000000000 --- a/src/Core/Problem/Serializer/ConstraintViolationListNormalizer.php +++ /dev/null @@ -1,22 +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\Problem\Serializer; - -class_exists(\ApiPlatform\Problem\Serializer\ConstraintViolationListNormalizer::class); - -if (false) { - final class ConstraintViolationListNormalizer extends \ApiPlatform\Problem\Serializer\ConstraintViolationListNormalizer - { - } -} diff --git a/src/Core/Problem/Serializer/ErrorNormalizer.php b/src/Core/Problem/Serializer/ErrorNormalizer.php deleted file mode 100644 index fcd78ee5356..00000000000 --- a/src/Core/Problem/Serializer/ErrorNormalizer.php +++ /dev/null @@ -1,22 +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\Problem\Serializer; - -class_exists(\ApiPlatform\Problem\Serializer\ErrorNormalizer::class); - -if (false) { - final class ErrorNormalizer extends \ApiPlatform\Problem\Serializer\ErrorNormalizer - { - } -} diff --git a/src/Core/Problem/Serializer/ErrorNormalizerTrait.php b/src/Core/Problem/Serializer/ErrorNormalizerTrait.php deleted file mode 100644 index 767688c159f..00000000000 --- a/src/Core/Problem/Serializer/ErrorNormalizerTrait.php +++ /dev/null @@ -1,23 +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\Problem\Serializer; - -class_exists(\ApiPlatform\Problem\Serializer\ErrorNormalizerTrait::class); - -if (false) { - trait ErrorNormalizerTrait - { - use \ApiPlatform\Problem\Serializer\ErrorNormalizerTrait; - } -} diff --git a/src/Core/Security/Core/Authorization/ExpressionLanguageProvider.php b/src/Core/Security/Core/Authorization/ExpressionLanguageProvider.php deleted file mode 100644 index 51e66c90598..00000000000 --- a/src/Core/Security/Core/Authorization/ExpressionLanguageProvider.php +++ /dev/null @@ -1,22 +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\Security\Core\Authorization; - -class_exists(\ApiPlatform\Symfony\Security\Core\Authorization\ExpressionLanguageProvider::class); - -if (false) { - final class ExpressionLanguageProvider extends \ApiPlatform\Symfony\Security\Core\Authorization\ExpressionLanguageProvider - { - } -} diff --git a/src/Core/Security/EventListener/DenyAccessListener.php b/src/Core/Security/EventListener/DenyAccessListener.php deleted file mode 100644 index db2a9e059bf..00000000000 --- a/src/Core/Security/EventListener/DenyAccessListener.php +++ /dev/null @@ -1,22 +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\Security\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\DenyAccessListener::class); - -if (false) { - final class DenyAccessListener extends \ApiPlatform\Symfony\EventListener\DenyAccessListener - { - } -} diff --git a/src/Core/Security/ExpressionLanguage.php b/src/Core/Security/ExpressionLanguage.php deleted file mode 100644 index 2fb29d73851..00000000000 --- a/src/Core/Security/ExpressionLanguage.php +++ /dev/null @@ -1,22 +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\Security; - -class_exists(\ApiPlatform\Symfony\Security\ExpressionLanguage::class); - -if (false) { - class ExpressionLanguage extends \ApiPlatform\Symfony\Security\ExpressionLanguage - { - } -} diff --git a/src/Core/Security/ResourceAccessChecker.php b/src/Core/Security/ResourceAccessChecker.php deleted file mode 100644 index c9990d2ae6e..00000000000 --- a/src/Core/Security/ResourceAccessChecker.php +++ /dev/null @@ -1,22 +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\Security; - -class_exists(\ApiPlatform\Symfony\Security\ResourceAccessChecker::class); - -if (false) { - final class ResourceAccessChecker extends \ApiPlatform\Symfony\Security\ResourceAccessChecker - { - } -} diff --git a/src/Core/Serializer/AbstractCollectionNormalizer.php b/src/Core/Serializer/AbstractCollectionNormalizer.php deleted file mode 100644 index a4c82b907a9..00000000000 --- a/src/Core/Serializer/AbstractCollectionNormalizer.php +++ /dev/null @@ -1,22 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\AbstractCollectionNormalizer::class); - -if (false) { - class AbstractCollectionNormalizer extends \ApiPlatform\Serializer\AbstractCollectionNormalizer - { - } -} diff --git a/src/Core/Serializer/AbstractConstraintViolationListNormalizer.php b/src/Core/Serializer/AbstractConstraintViolationListNormalizer.php deleted file mode 100644 index 106dc418511..00000000000 --- a/src/Core/Serializer/AbstractConstraintViolationListNormalizer.php +++ /dev/null @@ -1,22 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\AbstractConstraintViolationListNormalizer::class); - -if (false) { - class AbstractConstraintViolationListNormalizer extends \ApiPlatform\Serializer\AbstractConstraintViolationListNormalizer - { - } -} diff --git a/src/Core/Serializer/AbstractItemNormalizer.php b/src/Core/Serializer/AbstractItemNormalizer.php deleted file mode 100644 index fcec2177f32..00000000000 --- a/src/Core/Serializer/AbstractItemNormalizer.php +++ /dev/null @@ -1,22 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\AbstractItemNormalizer::class); - -if (false) { - class AbstractItemNormalizer extends \ApiPlatform\Serializer\AbstractItemNormalizer - { - } -} diff --git a/src/Core/Serializer/CacheKeyTrait.php b/src/Core/Serializer/CacheKeyTrait.php deleted file mode 100644 index e047229da1d..00000000000 --- a/src/Core/Serializer/CacheKeyTrait.php +++ /dev/null @@ -1,23 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\CacheKeyTrait::class); - -if (false) { - trait CacheKeyTrait - { - use \ApiPlatform\Serializer\CacheKeyTrait; - } -} diff --git a/src/Core/Serializer/ContextTrait.php b/src/Core/Serializer/ContextTrait.php deleted file mode 100644 index e173d5ae011..00000000000 --- a/src/Core/Serializer/ContextTrait.php +++ /dev/null @@ -1,23 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\ContextTrait::class); - -if (false) { - trait ContextTrait - { - use \ApiPlatform\Serializer\ContextTrait; - } -} diff --git a/src/Core/Serializer/Filter/GroupFilter.php b/src/Core/Serializer/Filter/GroupFilter.php deleted file mode 100644 index 25abf1d0fd9..00000000000 --- a/src/Core/Serializer/Filter/GroupFilter.php +++ /dev/null @@ -1,22 +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\Serializer\Filter; - -class_exists(\ApiPlatform\Serializer\Filter\GroupFilter::class); - -if (false) { - final class GroupFilter extends \ApiPlatform\Serializer\Filter\GroupFilter - { - } -} diff --git a/src/Core/Serializer/Filter/PropertyFilter.php b/src/Core/Serializer/Filter/PropertyFilter.php deleted file mode 100644 index 7e0cc0c46e2..00000000000 --- a/src/Core/Serializer/Filter/PropertyFilter.php +++ /dev/null @@ -1,22 +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\Serializer\Filter; - -class_exists(\ApiPlatform\Serializer\Filter\PropertyFilter::class); - -if (false) { - final class PropertyFilter extends \ApiPlatform\Serializer\Filter\PropertyFilter - { - } -} diff --git a/src/Core/Serializer/InputOutputMetadataTrait.php b/src/Core/Serializer/InputOutputMetadataTrait.php deleted file mode 100644 index 0073cc00912..00000000000 --- a/src/Core/Serializer/InputOutputMetadataTrait.php +++ /dev/null @@ -1,23 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\InputOutputMetadataTrait::class); - -if (false) { - trait InputOutputMetadataTrait - { - use \ApiPlatform\Serializer\InputOutputMetadataTrait; - } -} diff --git a/src/Core/Serializer/ItemNormalizer.php b/src/Core/Serializer/ItemNormalizer.php deleted file mode 100644 index b9ff47a2f9e..00000000000 --- a/src/Core/Serializer/ItemNormalizer.php +++ /dev/null @@ -1,22 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\ItemNormalizer::class); - -if (false) { - class ItemNormalizer extends \ApiPlatform\Serializer\ItemNormalizer - { - } -} diff --git a/src/Core/Serializer/JsonEncoder.php b/src/Core/Serializer/JsonEncoder.php deleted file mode 100644 index 3bc790a7429..00000000000 --- a/src/Core/Serializer/JsonEncoder.php +++ /dev/null @@ -1,22 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\JsonEncoder::class); - -if (false) { - final class JsonEncoder extends \ApiPlatform\Serializer\JsonEncoder - { - } -} diff --git a/src/Core/Serializer/Mapping/Factory/ClassMetadataFactory.php b/src/Core/Serializer/Mapping/Factory/ClassMetadataFactory.php deleted file mode 100644 index 88245f55bfd..00000000000 --- a/src/Core/Serializer/Mapping/Factory/ClassMetadataFactory.php +++ /dev/null @@ -1,22 +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\Serializer\Mapping\Factory; - -class_exists(\ApiPlatform\Serializer\Mapping\Factory\ClassMetadataFactory::class); - -if (false) { - final class ClassMetadataFactory extends \ApiPlatform\Serializer\Mapping\Factory\ClassMetadataFactory - { - } -} diff --git a/src/Core/Serializer/ResourceList.php b/src/Core/Serializer/ResourceList.php deleted file mode 100644 index 3c992b93f7c..00000000000 --- a/src/Core/Serializer/ResourceList.php +++ /dev/null @@ -1,22 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\ResourceList::class); - -if (false) { - class ResourceList extends \ApiPlatform\Serializer\ResourceList - { - } -} diff --git a/src/Core/Serializer/SerializerContextBuilder.php b/src/Core/Serializer/SerializerContextBuilder.php deleted file mode 100644 index aaf4f86cc48..00000000000 --- a/src/Core/Serializer/SerializerContextBuilder.php +++ /dev/null @@ -1,22 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\SerializerContextBuilder::class); - -if (false) { - final class SerializerContextBuilder extends \ApiPlatform\Serializer\SerializerContextBuilder - { - } -} diff --git a/src/Core/Serializer/SerializerFilterContextBuilder.php b/src/Core/Serializer/SerializerFilterContextBuilder.php deleted file mode 100644 index 538503c8baf..00000000000 --- a/src/Core/Serializer/SerializerFilterContextBuilder.php +++ /dev/null @@ -1,22 +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\Serializer; - -class_exists(\ApiPlatform\Serializer\SerializerFilterContextBuilder::class); - -if (false) { - final class SerializerFilterContextBuilder extends \ApiPlatform\Serializer\SerializerFilterContextBuilder - { - } -} diff --git a/src/Core/Swagger/Serializer/ApiGatewayNormalizer.php b/src/Core/Swagger/Serializer/ApiGatewayNormalizer.php deleted file mode 100644 index 9d359d15391..00000000000 --- a/src/Core/Swagger/Serializer/ApiGatewayNormalizer.php +++ /dev/null @@ -1,22 +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\Swagger\Serializer; - -class_exists(\ApiPlatform\OpenApi\Serializer\ApiGatewayNormalizer::class); - -if (false) { - final class ApiGatewayNormalizer extends \ApiPlatform\OpenApi\Serializer\ApiGatewayNormalizer - { - } -} diff --git a/src/Core/Swagger/Serializer/DocumentationNormalizer.php b/src/Core/Swagger/Serializer/DocumentationNormalizer.php deleted file mode 100644 index 5d0a6bad5f8..00000000000 --- a/src/Core/Swagger/Serializer/DocumentationNormalizer.php +++ /dev/null @@ -1,955 +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\Swagger\Serializer; - -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\FilterLocatorTrait; -use ApiPlatform\Core\Api\FormatsProviderInterface; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\OperationAwareFormatsProviderInterface; -use ApiPlatform\Core\Api\OperationMethodResolverInterface; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Api\UrlGeneratorInterface; -use ApiPlatform\Core\JsonSchema\SchemaFactory as LegacySchemaFactory; -use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface as LegacySchemaFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ApiResourceToLegacyResourceMetadataTrait; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; -use ApiPlatform\Documentation\Documentation; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\JsonSchema\Schema; -use ApiPlatform\JsonSchema\SchemaFactory; -use ApiPlatform\JsonSchema\SchemaFactoryInterface; -use ApiPlatform\JsonSchema\TypeFactory; -use ApiPlatform\JsonSchema\TypeFactoryInterface; -use ApiPlatform\Metadata\HttpOperation; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\OpenApi\OpenApi; -use ApiPlatform\OpenApi\Serializer\ApiGatewayNormalizer; -use ApiPlatform\PathResolver\OperationPathResolverInterface; -use Psr\Container\ContainerInterface; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * Generates an OpenAPI specification (formerly known as Swagger). OpenAPI v2 and v3 are supported. - * - * @author Amrouche Hamza - * @author Teoh Han Hui - * @author Kévin Dunglas - * @author Anthony GRASSIOT - */ -final class DocumentationNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface -{ - use ApiResourceToLegacyResourceMetadataTrait; - use FilterLocatorTrait; - - public const FORMAT = 'json'; - public const BASE_URL = 'base_url'; - public const SPEC_VERSION = 'spec_version'; - public const OPENAPI_VERSION = '3.0.2'; - public const SWAGGER_DEFINITION_NAME = 'swagger_definition_name'; - public const SWAGGER_VERSION = '2.0'; - - /** - * @deprecated - */ - public const ATTRIBUTE_NAME = 'swagger_context'; - - private $resourceMetadataFactory; - private $propertyNameCollectionFactory; - private $propertyMetadataFactory; - private $operationMethodResolver; - private $operationPathResolver; - private $oauthEnabled; - private $oauthType; - private $oauthFlow; - private $oauthTokenUrl; - private $oauthAuthorizationUrl; - private $oauthScopes; - private $apiKeys; - private $subresourceOperationFactory; - private $paginationEnabled; - private $paginationPageParameterName; - private $clientItemsPerPage; - private $itemsPerPageParameterName; - private $paginationClientEnabled; - private $paginationClientEnabledParameterName; - private $formats; - private $formatsProvider; - - /** - * @var SchemaFactoryInterface|LegacySchemaFactoryInterface - */ - private $jsonSchemaFactory; - /** - * @var TypeFactoryInterface - */ - private $jsonSchemaTypeFactory; - private $defaultContext = [ - self::BASE_URL => '/', - ApiGatewayNormalizer::API_GATEWAY => false, - ]; - - private $identifiersExtractor; - - private $openApiNormalizer; - private $legacyMode; - - /** - * @param LegacySchemaFactoryInterface|SchemaFactoryInterface|ResourceClassResolverInterface|null $jsonSchemaFactory - * @param ContainerInterface|FilterCollection|null $filterLocator - * @param array|OperationAwareFormatsProviderInterface $formats - * @param mixed|null $jsonSchemaTypeFactory - * @param int[] $swaggerVersions - * @param mixed $resourceMetadataFactory - */ - public function __construct($resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, $jsonSchemaFactory = null, $jsonSchemaTypeFactory = null, OperationPathResolverInterface $operationPathResolver = null, UrlGeneratorInterface $urlGenerator = null, $filterLocator = null, NameConverterInterface $nameConverter = null, bool $oauthEnabled = false, string $oauthType = '', string $oauthFlow = '', string $oauthTokenUrl = '', string $oauthAuthorizationUrl = '', array $oauthScopes = [], array $apiKeys = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, bool $paginationEnabled = true, string $paginationPageParameterName = 'page', bool $clientItemsPerPage = false, string $itemsPerPageParameterName = 'itemsPerPage', $formats = [], bool $paginationClientEnabled = false, string $paginationClientEnabledParameterName = 'pagination', array $defaultContext = [], array $swaggerVersions = [2, 3], IdentifiersExtractorInterface $identifiersExtractor = null, NormalizerInterface $openApiNormalizer = null, bool $legacyMode = false) - { - if ($jsonSchemaTypeFactory instanceof OperationMethodResolverInterface) { - @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', OperationMethodResolverInterface::class, __METHOD__), \E_USER_DEPRECATED); - - $this->operationMethodResolver = $jsonSchemaTypeFactory; - $this->jsonSchemaTypeFactory = new TypeFactory(); - } else { - $this->jsonSchemaTypeFactory = $jsonSchemaTypeFactory ?? new TypeFactory(); - } - - if ($jsonSchemaFactory instanceof ResourceClassResolverInterface) { - @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', ResourceClassResolverInterface::class, __METHOD__), \E_USER_DEPRECATED); - } - - if (null === $jsonSchemaFactory || $jsonSchemaFactory instanceof ResourceClassResolverInterface) { - if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $jsonSchemaFactory = new LegacySchemaFactory($this->jsonSchemaTypeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $nameConverter); - } else { - $jsonSchemaFactory = new SchemaFactory($this->jsonSchemaTypeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $nameConverter); - } - - $this->jsonSchemaTypeFactory->setSchemaFactory($jsonSchemaFactory); - } - $this->jsonSchemaFactory = $jsonSchemaFactory; - - if ($nameConverter) { - @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', NameConverterInterface::class, __METHOD__), \E_USER_DEPRECATED); - } - - if ($urlGenerator) { - @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.1 and will be removed in 3.0.', UrlGeneratorInterface::class, __METHOD__), \E_USER_DEPRECATED); - } - - if ($formats instanceof FormatsProviderInterface) { - @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0, pass an array instead.', FormatsProviderInterface::class, __METHOD__), \E_USER_DEPRECATED); - - $this->formatsProvider = $formats; - } else { - $this->formats = $formats; - } - - $this->setFilterLocator($filterLocator, true); - - if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - 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; - $this->operationPathResolver = $operationPathResolver; - $this->oauthEnabled = $oauthEnabled; - $this->oauthType = $oauthType; - $this->oauthFlow = $oauthFlow; - $this->oauthTokenUrl = $oauthTokenUrl; - $this->oauthAuthorizationUrl = $oauthAuthorizationUrl; - $this->oauthScopes = $oauthScopes; - $this->subresourceOperationFactory = $subresourceOperationFactory; - $this->paginationEnabled = $paginationEnabled; - $this->paginationPageParameterName = $paginationPageParameterName; - $this->apiKeys = $apiKeys; - $this->clientItemsPerPage = $clientItemsPerPage; - $this->itemsPerPageParameterName = $itemsPerPageParameterName; - $this->paginationClientEnabled = $paginationClientEnabled; - $this->paginationClientEnabledParameterName = $paginationClientEnabledParameterName; - $this->defaultContext[self::SPEC_VERSION] = $swaggerVersions[0] ?? 2; - $this->defaultContext = array_merge($this->defaultContext, $defaultContext); - $this->identifiersExtractor = $identifiersExtractor; - $this->openApiNormalizer = $openApiNormalizer; - $this->legacyMode = $legacyMode; - } - - /** - * {@inheritdoc} - * - * @return array|string|int|float|bool|\ArrayObject|null - */ - public function normalize($object, $format = null, array $context = []) - { - if ($object instanceof OpenApi) { - @trigger_error('Using the swagger DocumentationNormalizer is deprecated in favor of decorating the OpenApiFactory, use the "openapi.backward_compatibility_layer" configuration to change this behavior.', \E_USER_DEPRECATED); - - return $this->openApiNormalizer->normalize($object, $format, $context); - } - - $v3 = 3 === ($context['spec_version'] ?? $this->defaultContext['spec_version']) && !($context['api_gateway'] ?? $this->defaultContext['api_gateway']); - - $definitions = new \ArrayObject(); - $paths = new \ArrayObject(); - $links = new \ArrayObject(); - - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - foreach ($object->getResourceNameCollection() as $resourceClass) { - $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass); - foreach ($resourceMetadataCollection as $i => $resourceMetadata) { - $resourceMetadata = $this->transformResourceToResourceMetadata($resourceMetadata); - // Items needs to be parsed first to be able to reference the lines from the collection operation - $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceMetadata->getShortName(), $resourceMetadata, OperationType::ITEM, $links); - $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceMetadata->getShortName(), $resourceMetadata, OperationType::COLLECTION, $links); - } - } - - $definitions->ksort(); - $paths->ksort(); - - return $this->computeDoc($v3, $object, $definitions, $paths, $context); - } - - foreach ($object->getResourceNameCollection() as $resourceClass) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if ($this->identifiersExtractor) { - $identifiers = []; - if ($resourceMetadata->getItemOperations()) { - $identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass); - } - - $resourceMetadata = $resourceMetadata->withAttributes(($resourceMetadata->getAttributes() ?: []) + ['identifiers' => $identifiers]); - } - $resourceShortName = $resourceMetadata->getShortName(); - - // Items needs to be parsed first to be able to reference the lines from the collection operation - $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, OperationType::ITEM, $links); - $this->addPaths($v3, $paths, $definitions, $resourceClass, $resourceShortName, $resourceMetadata, OperationType::COLLECTION, $links); - - if (null === $this->subresourceOperationFactory) { - continue; - } - - foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $subresourceOperation) { - $method = $resourceMetadata->getTypedOperationAttribute(OperationType::SUBRESOURCE, $subresourceOperation['operation_name'], 'method', 'GET'); - $paths[$this->getPath($subresourceOperation['shortNames'][0], $subresourceOperation['route_name'], $subresourceOperation, OperationType::SUBRESOURCE)][strtolower($method)] = $this->addSubresourceOperation($v3, $subresourceOperation, $definitions, $operationId, $resourceMetadata); - } - } - - $definitions->ksort(); - $paths->ksort(); - - return $this->computeDoc($v3, $object, $definitions, $paths, $context); - } - - /** - * Updates the list of entries in the paths collection. - */ - private function addPaths(bool $v3, \ArrayObject $paths, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, ResourceMetadata $resourceMetadata, string $operationType, \ArrayObject $links) - { - if (null === $operations = OperationType::COLLECTION === $operationType ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) { - return; - } - - foreach ($operations as $operationName => $operation) { - if (isset($operation['uri_template'])) { - $path = str_replace('.{_format}', '', $operation['uri_template']); - if (0 !== strpos($path, '/')) { - $path = '/'.$path; - } - } else { - $path = $this->getPath($resourceShortName, $operationName, $operation, $operationType); - } - - if ($this->operationMethodResolver) { - $method = OperationType::ITEM === $operationType ? $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName) : $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); - } else { - $method = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method', 'GET'); - } - - $paths[$path][strtolower($method)] = $this->getPathOperation($v3, $operationName, $operation, $method, $operationType, $resourceClass, $resourceMetadata, $definitions, $links); - } - } - - /** - * Gets the path for an operation. - * - * If the path ends with the optional _format parameter, it is removed - * as optional path parameters are not yet supported. - * - * @see https://github.com/OAI/OpenAPI-Specification/issues/93 - */ - private function getPath(string $resourceShortName, string $operationName, array $operation, string $operationType): string - { - $path = $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName); - - if ('.{_format}' === substr($path, -10)) { - $path = substr($path, 0, -10); - } - - return $path; - } - - /** - * Gets a path Operation Object. - * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object - */ - private function getPathOperation(bool $v3, string $operationName, array $operation, string $method, string $operationType, string $resourceClass, ResourceMetadata $resourceMetadata, \ArrayObject $definitions, \ArrayObject $links): \ArrayObject - { - $pathOperation = new \ArrayObject($operation[$v3 ? 'openapi_context' : 'swagger_context'] ?? []); - $resourceShortName = $resourceMetadata->getShortName(); - $pathOperation['tags'] ?? $pathOperation['tags'] = [$resourceShortName]; - $pathOperation['operationId'] ?? $pathOperation['operationId'] = lcfirst($operationName).ucfirst($resourceShortName).ucfirst($operationType); - if ($v3 && 'GET' === $method && OperationType::ITEM === $operationType && $link = $this->getLinkObject($resourceClass, $pathOperation['operationId'], $this->getPath($resourceShortName, $operationName, $operation, $operationType))) { - $links[$pathOperation['operationId']] = $link; - } - if ($resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true)) { - $pathOperation['deprecated'] = true; - } - - if (null === $this->formatsProvider) { - $requestFormats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'input_formats', [], true); - $responseFormats = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'output_formats', [], true); - } else { - $requestFormats = $responseFormats = $this->formatsProvider->getFormatsFromOperation($resourceClass, $operationName, $operationType); - } - - $requestMimeTypes = $this->flattenMimeTypes($requestFormats); - $responseMimeTypes = $this->flattenMimeTypes($responseFormats); - switch ($method) { - case 'GET': - return $this->updateGetOperation($v3, $pathOperation, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); - case 'POST': - return $this->updatePostOperation($v3, $pathOperation, $requestMimeTypes, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions, $links); - case 'PATCH': - $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Updates the %s resource.', $resourceShortName); - // no break - case 'PUT': - return $this->updatePutOperation($v3, $pathOperation, $requestMimeTypes, $responseMimeTypes, $operationType, $resourceMetadata, $resourceClass, $resourceShortName, $operationName, $definitions); - case 'DELETE': - return $this->updateDeleteOperation($v3, $pathOperation, $resourceShortName, $operationType, $operationName, $resourceMetadata, $resourceClass); - } - - return $pathOperation; - } - - /** - * @return array the update message as first value, and if the schema is defined as second - */ - private function addSchemas(bool $v3, array $message, \ArrayObject $definitions, string $resourceClass, string $operationType, string $operationName, array $mimeTypes, string $type = Schema::TYPE_OUTPUT, bool $forceCollection = false): array - { - if (!$v3) { - $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $type, $operationType, $operationName, 'json', null, $forceCollection); - if (!$jsonSchema->isDefined()) { - return [$message, false]; - } - - $message['schema'] = $jsonSchema->getArrayCopy(false); - - return [$message, true]; - } - - foreach ($mimeTypes as $mimeType => $format) { - $jsonSchema = $this->getJsonSchema($v3, $definitions, $resourceClass, $type, $operationType, $operationName, $format, null, $forceCollection); - if (!$jsonSchema->isDefined()) { - return [$message, false]; - } - - $message['content'][$mimeType] = ['schema' => $jsonSchema->getArrayCopy(false)]; - } - - return [$message, true]; - } - - private function updateGetOperation(bool $v3, \ArrayObject $pathOperation, array $mimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions): \ArrayObject - { - $successStatus = (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '200'); - - if (!$v3) { - $pathOperation['produces'] ?? $pathOperation['produces'] = array_keys($mimeTypes); - } - - if (OperationType::COLLECTION === $operationType) { - $outputResourseShortName = $resourceMetadata->getCollectionOperations()[$operationName]['output']['name'] ?? $resourceShortName; - $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves the collection of %s resources.', $outputResourseShortName); - - $successResponse = ['description' => sprintf('%s collection response', $outputResourseShortName)]; - [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $mimeTypes); - - $pathOperation['responses'] ?? $pathOperation['responses'] = [$successStatus => $successResponse]; - - if ( - ($resourceMetadata->getAttributes()['extra_properties']['is_legacy_subresource'] ?? false) || - ($resourceMetadata->getAttributes()['extra_properties']['is_alternate_resource_metadata'] ?? false)) { - // Avoid duplicates parameters when there is a filter on a subresource identifier - $parametersMemory = []; - $pathOperation['parameters'] = []; - - foreach ($resourceMetadata->getAttributes()['identifiers'] as $parameterName => [$class, $identifier]) { - $parameter = ['name' => $parameterName, 'in' => 'path', 'required' => true]; - $v3 ? $parameter['schema'] = ['type' => 'string'] : $parameter['type'] = 'string'; - $pathOperation['parameters'][] = $parameter; - $parametersMemory[] = $parameterName; - } - - if ($parameters = $this->getFiltersParameters($v3, $resourceClass, $operationName, $resourceMetadata)) { - foreach ($parameters as $parameter) { - if (!\in_array($parameter['name'], $parametersMemory, true)) { - $pathOperation['parameters'][] = $parameter; - } - } - } - } else { - $pathOperation['parameters'] ?? $pathOperation['parameters'] = $this->getFiltersParameters($v3, $resourceClass, $operationName, $resourceMetadata); - } - - $this->addPaginationParameters($v3, $resourceMetadata, OperationType::COLLECTION, $operationName, $pathOperation); - - return $pathOperation; - } - - $outputResourseShortName = $resourceMetadata->getItemOperations()[$operationName]['output']['name'] ?? $resourceShortName; - $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Retrieves a %s resource.', $outputResourseShortName); - - $pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass); - - $successResponse = ['description' => sprintf('%s resource response', $outputResourseShortName)]; - [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $mimeTypes); - - $pathOperation['responses'] ?? $pathOperation['responses'] = [ - $successStatus => $successResponse, - '404' => ['description' => 'Resource not found'], - ]; - - return $pathOperation; - } - - private function addPaginationParameters(bool $v3, ResourceMetadata $resourceMetadata, string $operationType, string $operationName, \ArrayObject $pathOperation) - { - if ($this->paginationEnabled && $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_enabled', true, true)) { - $paginationParameter = [ - 'name' => $this->paginationPageParameterName, - 'in' => 'query', - 'required' => false, - 'description' => 'The collection page number', - ]; - $v3 ? $paginationParameter['schema'] = [ - 'type' => 'integer', - 'default' => 1, - ] : $paginationParameter['type'] = 'integer'; - $pathOperation['parameters'][] = $paginationParameter; - - if ($resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_client_items_per_page', $this->clientItemsPerPage, true)) { - $itemPerPageParameter = [ - 'name' => $this->itemsPerPageParameterName, - 'in' => 'query', - 'required' => false, - 'description' => 'The number of items per page', - ]; - if ($v3) { - $itemPerPageParameter['schema'] = [ - 'type' => 'integer', - 'default' => $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_items_per_page', 30, true), - 'minimum' => 0, - ]; - - $maxItemsPerPage = $resourceMetadata->getTypedOperationAttribute($operationType, $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->getTypedOperationAttribute($operationType, $operationName, 'pagination_maximum_items_per_page', $maxItemsPerPage, true); - - if (null !== $maxItemsPerPage) { - $itemPerPageParameter['schema']['maximum'] = $maxItemsPerPage; - } - } else { - $itemPerPageParameter['type'] = 'integer'; - } - - $pathOperation['parameters'][] = $itemPerPageParameter; - } - } - - if ($this->paginationEnabled && $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'pagination_client_enabled', $this->paginationClientEnabled, true)) { - $paginationEnabledParameter = [ - 'name' => $this->paginationClientEnabledParameterName, - 'in' => 'query', - 'required' => false, - 'description' => 'Enable or disable pagination', - ]; - $v3 ? $paginationEnabledParameter['schema'] = ['type' => 'boolean'] : $paginationEnabledParameter['type'] = 'boolean'; - $pathOperation['parameters'][] = $paginationEnabledParameter; - } - } - - /** - * @throws ResourceClassNotFoundException - */ - private function addSubresourceOperation(bool $v3, array $subresourceOperation, \ArrayObject $definitions, string $operationId, ResourceMetadata $resourceMetadata): \ArrayObject - { - $operationName = 'get'; // TODO: we might want to extract that at some point to also support other subresource operations - $collection = $subresourceOperation['collection'] ?? false; - - $subResourceMetadata = $this->resourceMetadataFactory->create($subresourceOperation['resource_class']); - - $pathOperation = new \ArrayObject([]); - $pathOperation['tags'] = $subresourceOperation['shortNames']; - $pathOperation['operationId'] = $operationId; - $pathOperation['summary'] = sprintf('Retrieves %s%s resource%s.', $subresourceOperation['collection'] ? 'the collection of ' : 'a ', $subresourceOperation['shortNames'][0], $subresourceOperation['collection'] ? 's' : ''); - - if (null === $this->formatsProvider) { - // TODO: Subresource operation metadata aren't available by default, for now we have to fallback on default formats. - // TODO: A better approach would be to always populate the subresource operation array. - $subResourceMetadata = $this - ->resourceMetadataFactory - ->create($subresourceOperation['resource_class']); - - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $subResourceMetadata = $this->transformResourceToResourceMetadata($subResourceMetadata[0]); - } - - $responseFormats = $subResourceMetadata->getTypedOperationAttribute(OperationType::SUBRESOURCE, $operationName, 'output_formats', $this->formats, true); - } else { - $responseFormats = $this->formatsProvider->getFormatsFromOperation($subresourceOperation['resource_class'], $operationName, OperationType::SUBRESOURCE); - } - - $mimeTypes = $this->flattenMimeTypes($responseFormats); - - if (!$v3) { - $pathOperation['produces'] = array_keys($mimeTypes); - } - - $successResponse = [ - 'description' => sprintf('%s %s response', $subresourceOperation['shortNames'][0], $collection ? 'collection' : 'resource'), - ]; - [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $subresourceOperation['resource_class'], OperationType::SUBRESOURCE, $operationName, $mimeTypes, Schema::TYPE_OUTPUT, $collection); - - $pathOperation['responses'] = ['200' => $successResponse, '404' => ['description' => 'Resource not found']]; - - // Avoid duplicates parameters when there is a filter on a subresource identifier - $parametersMemory = []; - $pathOperation['parameters'] = []; - foreach ($subresourceOperation['identifiers'] as $parameterName => [$class, $identifier, $hasIdentifier]) { - if (false === strpos($subresourceOperation['path'], sprintf('{%s}', $parameterName))) { - continue; - } - - $parameter = ['name' => $parameterName, 'in' => 'path', 'required' => true]; - $v3 ? $parameter['schema'] = ['type' => 'string'] : $parameter['type'] = 'string'; - $pathOperation['parameters'][] = $parameter; - $parametersMemory[] = $parameterName; - } - - if ($parameters = $this->getFiltersParameters($v3, $subresourceOperation['resource_class'], $operationName, $subResourceMetadata)) { - foreach ($parameters as $parameter) { - if (!\in_array($parameter['name'], $parametersMemory, true)) { - $pathOperation['parameters'][] = $parameter; - } - } - } - - if ($subresourceOperation['collection']) { - $this->addPaginationParameters($v3, $subResourceMetadata, OperationType::SUBRESOURCE, $subresourceOperation['operation_name'], $pathOperation); - } - - return $pathOperation; - } - - private function updatePostOperation(bool $v3, \ArrayObject $pathOperation, array $requestMimeTypes, array $responseMimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions, \ArrayObject $links): \ArrayObject - { - if (!$v3) { - $pathOperation['consumes'] ?? $pathOperation['consumes'] = array_keys($requestMimeTypes); - $pathOperation['produces'] ?? $pathOperation['produces'] = array_keys($responseMimeTypes); - } - - $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Creates a %s resource.', $resourceShortName); - - $identifiers = (array) $resourceMetadata - ->getTypedOperationAttribute($operationType, $operationName, 'identifiers', [], false); - - $pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass, OperationType::ITEM === $operationType ? false : true); - - $successResponse = ['description' => sprintf('%s resource created', $resourceShortName)]; - [$successResponse, $defined] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $responseMimeTypes); - - if ($defined && $v3 && ($links[$key = 'get'.ucfirst($resourceShortName).ucfirst(OperationType::ITEM)] ?? null)) { - $successResponse['links'] = [ucfirst($key) => $links[$key]]; - } - - $pathOperation['responses'] ?? $pathOperation['responses'] = [ - (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '201') => $successResponse, - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ]; - - return $this->addRequestBody($v3, $pathOperation, $definitions, $resourceClass, $resourceShortName, $operationType, $operationName, $requestMimeTypes); - } - - private function updatePutOperation(bool $v3, \ArrayObject $pathOperation, array $requestMimeTypes, array $responseMimeTypes, string $operationType, ResourceMetadata $resourceMetadata, string $resourceClass, string $resourceShortName, string $operationName, \ArrayObject $definitions): \ArrayObject - { - if (!$v3) { - $pathOperation['consumes'] ?? $pathOperation['consumes'] = array_keys($requestMimeTypes); - $pathOperation['produces'] ?? $pathOperation['produces'] = array_keys($responseMimeTypes); - } - - $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Replaces the %s resource.', $resourceShortName); - - $pathOperation = $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass); - - $successResponse = ['description' => sprintf('%s resource updated', $resourceShortName)]; - [$successResponse] = $this->addSchemas($v3, $successResponse, $definitions, $resourceClass, $operationType, $operationName, $responseMimeTypes); - - $pathOperation['responses'] ?? $pathOperation['responses'] = [ - (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '200') => $successResponse, - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ]; - - return $this->addRequestBody($v3, $pathOperation, $definitions, $resourceClass, $resourceShortName, $operationType, $operationName, $requestMimeTypes, true); - } - - private function addRequestBody(bool $v3, \ArrayObject $pathOperation, \ArrayObject $definitions, string $resourceClass, string $resourceShortName, string $operationType, string $operationName, array $requestMimeTypes, bool $put = false) - { - if (isset($pathOperation['requestBody'])) { - return $pathOperation; - } - - [$message, $defined] = $this->addSchemas($v3, [], $definitions, $resourceClass, $operationType, $operationName, $requestMimeTypes, Schema::TYPE_INPUT); - if (!$defined) { - return $pathOperation; - } - - $description = sprintf('The %s %s resource', $put ? 'updated' : 'new', $resourceShortName); - if ($v3) { - $pathOperation['requestBody'] = $message + ['description' => $description]; - - return $pathOperation; - } - - if (!$this->hasBodyParameter($pathOperation['parameters'] ?? [])) { - $pathOperation['parameters'][] = [ - 'name' => lcfirst($resourceShortName), - 'in' => 'body', - 'description' => $description, - ] + $message; - } - - return $pathOperation; - } - - private function hasBodyParameter(array $parameters): bool - { - foreach ($parameters as $parameter) { - if (\array_key_exists('in', $parameter) && 'body' === $parameter['in']) { - return true; - } - } - - return false; - } - - private function updateDeleteOperation(bool $v3, \ArrayObject $pathOperation, string $resourceShortName, string $operationType, string $operationName, ResourceMetadata $resourceMetadata, string $resourceClass): \ArrayObject - { - $pathOperation['summary'] ?? $pathOperation['summary'] = sprintf('Removes the %s resource.', $resourceShortName); - $pathOperation['responses'] ?? $pathOperation['responses'] = [ - (string) $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'status', '204') => ['description' => sprintf('%s resource deleted', $resourceShortName)], - '404' => ['description' => 'Resource not found'], - ]; - - return $this->addItemOperationParameters($v3, $pathOperation, $operationType, $operationName, $resourceMetadata, $resourceClass); - } - - private function addItemOperationParameters(bool $v3, \ArrayObject $pathOperation, string $operationType, string $operationName, ResourceMetadata $resourceMetadata, string $resourceClass, bool $isPost = false): \ArrayObject - { - $identifiers = (array) $resourceMetadata - ->getTypedOperationAttribute($operationType, $operationName, 'identifiers', [], false); - - // Auto-generated routes in API Platform < 2.7 are considered as collection, hotfix this as the OpenApi Factory supports new operations anyways. - // this also fixes a bug where we could not create POST item operations in API P 2.6 - if (OperationType::ITEM === $operationType && $isPost) { - $operationType = OperationType::COLLECTION; - } - - if (!$identifiers && OperationType::COLLECTION !== $operationType) { - try { - $identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass); - } catch (RuntimeException $e) { - // Ignore exception here - } catch (ResourceClassNotFoundException $e) { - if (false === $this->legacyMode) { - // Skipping these, swagger is not compatible with post 2.7 resource metadata - return $pathOperation; - } - throw $e; - } - } - - if (\count($identifiers) > 1 ? $resourceMetadata->getItemOperationAttribute($operationName, 'composite_identifier', true, true) : false) { - $identifiers = ['id']; - } - - if (!$identifiers && OperationType::COLLECTION === $operationType) { - return $pathOperation; - } - - if (!isset($pathOperation['parameters'])) { - $pathOperation['parameters'] = []; - } - - foreach ($identifiers as $parameterName => $identifier) { - $parameter = [ - 'name' => \is_string($parameterName) ? $parameterName : $identifier, - 'in' => 'path', - 'required' => true, - ]; - $v3 ? $parameter['schema'] = ['type' => 'string'] : $parameter['type'] = 'string'; - $pathOperation['parameters'][] = $parameter; - } - - return $pathOperation; - } - - private function getJsonSchema(bool $v3, \ArrayObject $definitions, string $resourceClass, string $type, ?string $operationType, ?string $operationName, string $format = 'json', ?array $serializerContext = null, bool $forceCollection = false): Schema - { - $schema = new Schema($v3 ? Schema::VERSION_OPENAPI : Schema::VERSION_SWAGGER); - $schema->setDefinitions($definitions); - - if ($this->jsonSchemaFactory instanceof SchemaFactoryInterface) { - $operation = $operationName ? (new class() extends HttpOperation {})->withName($operationName) : null; - - return $this->jsonSchemaFactory->buildSchema($resourceClass, $format, $type, $operation, $schema, $serializerContext, $forceCollection); - } - - return $this->jsonSchemaFactory->buildSchema($resourceClass, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection); - } - - private function computeDoc(bool $v3, Documentation $documentation, \ArrayObject $definitions, \ArrayObject $paths, array $context): array - { - $baseUrl = $context[self::BASE_URL] ?? $this->defaultContext[self::BASE_URL]; - - if ($v3) { - $docs = ['openapi' => self::OPENAPI_VERSION]; - if ('/' !== $baseUrl && '' !== $baseUrl) { - $docs['servers'] = [['url' => $baseUrl]]; - } - } else { - $docs = [ - 'swagger' => self::SWAGGER_VERSION, - 'basePath' => $baseUrl, - ]; - } - - $docs += [ - 'info' => [ - 'title' => $documentation->getTitle(), - 'version' => $documentation->getVersion(), - ], - 'paths' => $paths, - ]; - - if ('' !== $description = $documentation->getDescription()) { - $docs['info']['description'] = $description; - } - - $securityDefinitions = []; - $security = []; - - if ($this->oauthEnabled) { - $oauthAttributes = [ - 'authorizationUrl' => $this->oauthAuthorizationUrl, - 'scopes' => new \ArrayObject($this->oauthScopes), - ]; - - if ($this->oauthTokenUrl) { - $oauthAttributes['tokenUrl'] = $this->oauthTokenUrl; - } - - $securityDefinitions['oauth'] = [ - 'type' => $this->oauthType, - 'description' => sprintf( - 'OAuth 2.0 %s Grant', - strtolower(preg_replace('/[A-Z]/', ' \\0', lcfirst($this->oauthFlow))) - ), - ]; - - if ($v3) { - $securityDefinitions['oauth']['flows'] = [ - $this->oauthFlow => $oauthAttributes, - ]; - } else { - $securityDefinitions['oauth']['flow'] = $this->oauthFlow; - $securityDefinitions['oauth'] = array_merge($securityDefinitions['oauth'], $oauthAttributes); - } - - $security[] = ['oauth' => []]; - } - - foreach ($this->apiKeys as $key => $apiKey) { - $name = $apiKey['name']; - $type = $apiKey['type']; - - $securityDefinitions[$key] = [ - 'type' => 'apiKey', - 'in' => $type, - 'description' => sprintf('Value for the %s %s', $name, 'query' === $type ? sprintf('%s parameter', $type) : $type), - 'name' => $name, - ]; - - $security[] = [$key => []]; - } - - if ($securityDefinitions && $security) { // @phpstan-ignore-line false positive - $docs['security'] = $security; - if (!$v3) { - $docs['securityDefinitions'] = $securityDefinitions; - } - } - - if ($v3) { - if (\count($definitions) + \count($securityDefinitions)) { - $docs['components'] = []; - if (\count($definitions)) { - $docs['components']['schemas'] = $definitions; - } - if (\count($securityDefinitions)) { - $docs['components']['securitySchemes'] = $securityDefinitions; - } - } - } elseif (\count($definitions) > 0) { - $docs['definitions'] = $definitions; - } - - return $docs; - } - - /** - * Gets parameters corresponding to enabled filters. - */ - private function getFiltersParameters(bool $v3, string $resourceClass, string $operationName, ResourceMetadata $resourceMetadata): array - { - if (null === $this->filterLocator) { - return []; - } - - $parameters = []; - $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); - foreach ($resourceFilters as $filterId) { - if (!$filter = $this->getFilter($filterId)) { - continue; - } - - foreach ($filter->getDescription($resourceClass) as $name => $data) { - $parameter = [ - 'name' => $name, - 'in' => 'query', - 'required' => $data['required'], - ]; - - $type = \in_array($data['type'], Type::$builtinTypes, true) ? $this->jsonSchemaTypeFactory->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false)) : ['type' => 'string']; - $v3 ? $parameter['schema'] = $type : $parameter += $type; - - if ($v3 && isset($data['schema'])) { - $parameter['schema'] = $data['schema']; - } - - if ('array' === ($type['type'] ?? '')) { - $deepObject = \in_array($data['type'], [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_OBJECT], true); - - if ($v3) { - $parameter['style'] = $deepObject ? 'deepObject' : 'form'; - $parameter['explode'] = true; - } else { - $parameter['collectionFormat'] = $deepObject ? 'csv' : 'multi'; - } - } - - $key = $v3 ? 'openapi' : 'swagger'; - if (isset($data[$key])) { - $parameter = $data[$key] + $parameter; - } - - $parameters[] = $parameter; - } - } - - return $parameters; - } - - /** - * {@inheritdoc} - */ - public function supportsNormalization($data, $format = null): bool - { - return self::FORMAT === $format && ($data instanceof Documentation || $this->openApiNormalizer && $data instanceof OpenApi); - } - - /** - * {@inheritdoc} - */ - public function hasCacheableSupportsMethod(): bool - { - return true; - } - - private function flattenMimeTypes(array $responseFormats): array - { - $responseMimeTypes = []; - foreach ($responseFormats as $responseFormat => $mimeTypes) { - foreach ($mimeTypes as $mimeType) { - $responseMimeTypes[$mimeType] = $responseFormat; - } - } - - return $responseMimeTypes; - } - - /** - * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#linkObject. - */ - private function getLinkObject(string $resourceClass, string $operationId, string $path): array - { - $linkObject = $identifiers = []; - foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); - if (!$propertyMetadata->isIdentifier()) { - continue; - } - - $linkObject['parameters'][$propertyName] = sprintf('$response.body#/%s', $propertyName); - $identifiers[] = $propertyName; - } - - if (!$linkObject) { - return []; - } - $linkObject['operationId'] = $operationId; - $linkObject['description'] = 1 === \count($identifiers) ? sprintf('The `%1$s` value returned in the response can be used as the `%1$s` parameter in `GET %2$s`.', $identifiers[0], $path) : sprintf('The values returned in the response can be used in `GET %s`.', $path); - - return $linkObject; - } -} diff --git a/src/Core/Test/DoctrineMongoDbOdmFilterTestCase.php b/src/Core/Test/DoctrineMongoDbOdmFilterTestCase.php deleted file mode 100644 index 2a58a772cf8..00000000000 --- a/src/Core/Test/DoctrineMongoDbOdmFilterTestCase.php +++ /dev/null @@ -1,22 +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\Test; - -class_exists(\ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase::class); - -if (false) { - class DoctrineMongoDbOdmFilterTestCase extends \ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase - { - } -} diff --git a/src/Core/Test/DoctrineMongoDbOdmSetup.php b/src/Core/Test/DoctrineMongoDbOdmSetup.php deleted file mode 100644 index 87b4cdfd54d..00000000000 --- a/src/Core/Test/DoctrineMongoDbOdmSetup.php +++ /dev/null @@ -1,22 +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\Test; - -class_exists(\ApiPlatform\Test\DoctrineMongoDbOdmSetup::class); - -if (false) { - class DoctrineMongoDbOdmSetup extends \ApiPlatform\Test\DoctrineMongoDbOdmSetup - { - } -} diff --git a/src/Core/Test/DoctrineMongoDbOdmTestCase.php b/src/Core/Test/DoctrineMongoDbOdmTestCase.php deleted file mode 100644 index 79cbe9fc64c..00000000000 --- a/src/Core/Test/DoctrineMongoDbOdmTestCase.php +++ /dev/null @@ -1,22 +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\Test; - -class_exists(\ApiPlatform\Test\DoctrineMongoDbOdmTestCase::class); - -if (false) { - class DoctrineMongoDbOdmTestCase extends \ApiPlatform\Test\DoctrineMongoDbOdmTestCase - { - } -} diff --git a/src/Core/Test/DoctrineOrmFilterTestCase.php b/src/Core/Test/DoctrineOrmFilterTestCase.php deleted file mode 100644 index 487505038a8..00000000000 --- a/src/Core/Test/DoctrineOrmFilterTestCase.php +++ /dev/null @@ -1,22 +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\Test; - -class_exists(\ApiPlatform\Test\DoctrineOrmFilterTestCase::class); - -if (false) { - class DoctrineOrmFilterTestCase extends \ApiPlatform\Test\DoctrineOrmFilterTestCase - { - } -} diff --git a/src/Core/Upgrade/ColorConsoleDiffFormatter.php b/src/Core/Upgrade/ColorConsoleDiffFormatter.php deleted file mode 100644 index c423afaf390..00000000000 --- a/src/Core/Upgrade/ColorConsoleDiffFormatter.php +++ /dev/null @@ -1,119 +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\Upgrade; - -use Symfony\Component\Console\Formatter\OutputFormatter; - -/** - * Inspired by @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/src/Differ/DiffConsoleFormatter.php to be - * used as standalone class, without need to require whole package by Dariusz Rumiński - * Forked by @soyuka from Symplify\PackageBuilder\Console\Formatter to remove Nette\Utils\Strings dependency to be even more standalone. - * - * @see \Symplify\PackageBuilder\Tests\Console\Formatter\ColorConsoleDiffFormatterTest - * - * @internal - */ -final class ColorConsoleDiffFormatter -{ - /** - * @var string - * - * @see https://regex101.com/r/ovLMDF/1 - */ - private const PLUS_START_REGEX = '#^(\+.*)#'; - - /** - * @var string - * - * @see https://regex101.com/r/xwywpa/1 - */ - private const MINUT_START_REGEX = '#^(\-.*)#'; - - /** - * @var string - * - * @see https://regex101.com/r/CMlwa8/1 - */ - private const AT_START_REGEX = '#^(@.*)#'; - - /** - * @var string - * - * @see https://regex101.com/r/qduj2O/1 - */ - private const NEWLINES_REGEX = "#\n\r|\n#"; - - private string $template; - - public function __construct() - { - $this->template = sprintf( - ' ---------- begin diff ----------%s%%s%s ----------- end diff -----------'.\PHP_EOL, - \PHP_EOL, - \PHP_EOL - ); - } - - public function format(string $diff): string - { - return $this->formatWithTemplate($diff, $this->template); - } - - private function formatWithTemplate(string $diff, string $template): string - { - $escapedDiff = OutputFormatter::escape(rtrim($diff)); - - $escapedDiffLines = preg_split(self::NEWLINES_REGEX, $escapedDiff); - - // remove description of added + remove; obvious on diffs - foreach ($escapedDiffLines as $key => $escapedDiffLine) { - if ('--- Original' === $escapedDiffLine) { - unset($escapedDiffLines[$key]); - } - - if ('+++ New' === $escapedDiffLine) { - unset($escapedDiffLines[$key]); - } - } - - $coloredLines = array_map(function (string $string): string { - $string = $this->makePlusLinesGreen($string); - $string = $this->makeMinusLinesRed($string); - $string = $this->makeAtNoteCyan($string); - - if (' ' === $string) { - return ''; - } - - return $string; - }, $escapedDiffLines); - - return sprintf($template, implode(\PHP_EOL, $coloredLines)); - } - - private function makePlusLinesGreen(string $string): string - { - return preg_replace(self::PLUS_START_REGEX, '$1', $string); - } - - private function makeMinusLinesRed(string $string): string - { - return preg_replace(self::MINUT_START_REGEX, '$1', $string); - } - - private function makeAtNoteCyan(string $string): string - { - return preg_replace(self::AT_START_REGEX, '$1', $string); - } -} diff --git a/src/Core/Upgrade/RemoveAnnotationTrait.php b/src/Core/Upgrade/RemoveAnnotationTrait.php deleted file mode 100644 index be49d3ce0ec..00000000000 --- a/src/Core/Upgrade/RemoveAnnotationTrait.php +++ /dev/null @@ -1,34 +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\Upgrade; - -use phpDocumentor\Reflection\DocBlock\Serializer; -use phpDocumentor\Reflection\DocBlockFactory; -use PhpParser\Comment\Doc; - -trait RemoveAnnotationTrait -{ - private function removeAnnotationByTag(Doc $comment, string $tagName): Doc - { - $factory = DocBlockFactory::createInstance(); - $docBlock = $factory->create($comment->getText()); - foreach ($docBlock->getTagsByName($tagName) as $tag) { - $docBlock->removeTag($tag); - } - - $serializer = new Serializer(0, '', true, null, null, \PHP_EOL); - - return new Doc($serializer->getDocComment($docBlock)); - } -} diff --git a/src/Core/Upgrade/SubresourceTransformer.php b/src/Core/Upgrade/SubresourceTransformer.php deleted file mode 100644 index e14dd94d9e9..00000000000 --- a/src/Core/Upgrade/SubresourceTransformer.php +++ /dev/null @@ -1,112 +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\Upgrade; - -use ApiPlatform\Util\Inflector; -use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as ODMClassMetadata; -use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver as ODMAnnotationDriver; -use Doctrine\ODM\MongoDB\Mapping\MappingException as ODMMappingException; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\ORM\Mapping\Driver\AnnotationDriver; -use Doctrine\ORM\Mapping\MappingException; -use Doctrine\Persistence\Mapping\RuntimeReflectionService; - -final class SubresourceTransformer -{ - private $ormMetadataFactory; - private $odmMetadataFactory; - - public function __construct() - { - $this->ormMetadataFactory = class_exists(AnnotationDriver::class) ? new AnnotationDriver(new AnnotationReader()) : null; - $this->odmMetadataFactory = class_exists(ODMAnnotationDriver::class) ? new ODMAnnotationDriver(new AnnotationReader()) : null; - } - - public function toUriVariables(array $subresourceMetadata): array - { - $uriVariables = []; - $toClass = $subresourceMetadata['resource_class']; - $fromProperty = $subresourceMetadata['property']; - - foreach (array_reverse($subresourceMetadata['identifiers']) as $identifier => $identifiedBy) { - [$fromClass, $fromIdentifier, $fromPathVariable] = $identifiedBy; - $fromClassMetadata = $this->getDoctrineMetadata($fromClass); - $fromClassMetadataAssociationMappings = $fromClassMetadata->associationMappings; - - $uriVariables[$identifier] = [ - 'from_class' => $fromClass, - 'from_property' => null, - 'to_property' => null, - 'identifiers' => $fromPathVariable ? [$fromIdentifier] : [], - 'composite_identifier' => false, - 'expanded_value' => $fromPathVariable ? null : Inflector::tableize($identifier), - ]; - - if ($toClass === $fromClass) { - $fromProperty = $identifier; - continue; - } - - $toClass = $fromClass; - - if (isset($fromProperty, $fromClassMetadataAssociationMappings[$fromProperty])) { - $type = $fromClassMetadataAssociationMappings[$fromProperty]['type']; - if (((class_exists(ODMClassMetadata::class) && ODMClassMetadata::MANY === $type) || (\is_int($type) && $type & ClassMetadataInfo::TO_MANY)) && isset($fromClassMetadataAssociationMappings[$fromProperty]['mappedBy'])) { - $uriVariables[$identifier]['to_property'] = $fromClassMetadataAssociationMappings[$fromProperty]['mappedBy']; - $fromProperty = $identifier; - continue; - } - $uriVariables[$identifier]['from_property'] = $fromProperty; - $fromProperty = $identifier; - } - } - - return array_reverse($uriVariables); - } - - /** - * @return ODMClassMetadata|ClassMetadata - */ - private function getDoctrineMetadata(string $class) - { - if ($this->odmMetadataFactory && class_exists(ODMClassMetadata::class)) { - $isDocument = true; - $metadata = new ODMClassMetadata($class); - - try { - $this->odmMetadataFactory->loadMetadataForClass($class, $metadata); - } catch (ODMMappingException $e) { - $isDocument = false; - } - - if ($isDocument) { - return $metadata; - } - } - - $metadata = new ClassMetadata($class); - $metadata->initializeReflection(new RuntimeReflectionService()); - - try { - if ($this->ormMetadataFactory) { - $this->ormMetadataFactory->loadMetadataForClass($class, $metadata); - } - } catch (MappingException $e) { - } - - return $metadata; - } -} diff --git a/src/Core/Upgrade/UpgradeApiResourceVisitor.php b/src/Core/Upgrade/UpgradeApiResourceVisitor.php deleted file mode 100644 index f88590c74b5..00000000000 --- a/src/Core/Upgrade/UpgradeApiResourceVisitor.php +++ /dev/null @@ -1,444 +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\Upgrade; - -use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Annotation\ApiResource as LegacyApiResource; -use ApiPlatform\Core\Annotation\ApiSubresource; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Delete; -use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\GraphQl\Mutation; -use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\GraphQl\QueryCollection; -use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Patch; -use ApiPlatform\Metadata\Post; -use ApiPlatform\Metadata\Put; -use ApiPlatform\Metadata\Resource\DeprecationMetadataTrait; -use PhpParser\Node; -use PhpParser\NodeVisitorAbstract; - -final class UpgradeApiResourceVisitor extends NodeVisitorAbstract -{ - use DeprecationMetadataTrait; - use RemoveAnnotationTrait; - - private LegacyApiResource $resourceAnnotation; - private IdentifiersExtractorInterface $identifiersExtractor; - private bool $isAnnotation = false; - private string $resourceClass; - - public function __construct(LegacyApiResource $resourceAnnotation, bool $isAnnotation, IdentifiersExtractorInterface $identifiersExtractor, string $resourceClass) - { - $this->resourceAnnotation = $resourceAnnotation; - $this->isAnnotation = $isAnnotation; - $this->identifiersExtractor = $identifiersExtractor; - $this->resourceClass = $resourceClass; - } - - /** - * In API Platform 3.x there's no difference between items and collections other then a flag within the Operation - * Therefore we need to fix the behavior with an empty array. - */ - private function getLegacyOperations(bool $isCollection = false): array - { - $key = $isCollection ? 'collectionOperations' : 'itemOperations'; - if ([] === $this->resourceAnnotation->{$key}) { - return []; - } - - $default = $isCollection ? ['post', 'get'] : ['get', 'put', 'patch', 'delete']; - - return $this->resourceAnnotation->{$key} ?? $default; - } - - /** - * @return int|Node|null - */ - public function enterNode(Node $node) - { - // We don't go through every resources to remove ApiSubresource annotations, do this here as well if there are some - // @see UpgradeApiSubresourceVisitor - $comment = $node->getDocComment(); - if ($comment && preg_match('/@ApiSubresource/', $comment->getText())) { - $node->setDocComment($this->removeAnnotationByTag($comment, 'ApiSubresource')); - } - - if ($node instanceof Node\Stmt\Namespace_) { - $namespaces = array_unique(array_merge( - [ApiResource::class], - $this->getOperationsNamespaces($this->getLegacyOperations()), - $this->getOperationsNamespaces($this->getLegacyOperations(true), true), - $this->getGraphQlOperationsNamespaces($this->resourceAnnotation->graphql ?? []) - )); - - if (true === !($this->resourceAnnotation->attributes['composite_identifier'] ?? true)) { - $namespaces[] = Link::class; - } - - foreach ($node->stmts as $k => $stmt) { - if (!$stmt instanceof Node\Stmt\Use_) { - break; - } - - $useStatement = implode('\\', $stmt->uses[0]->name->parts); - - if (LegacyApiResource::class === $useStatement) { - unset($node->stmts[$k]); - continue; - } - - // There might be a use left as the UpgradeApiSubresourceVisitor doesn't go through all the resources - if (ApiSubresource::class === $useStatement) { - unset($node->stmts[$k]); - continue; - } - - if (false !== ($key = array_search($useStatement, $namespaces, true))) { - unset($namespaces[$key]); - } - } - - foreach ($namespaces as $namespace) { - array_unshift($node->stmts, new Node\Stmt\Use_([ - new Node\Stmt\UseUse( - new Node\Name( - $namespace - ) - ), - ])); - } - } - - if ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Interface_) { - if ($this->isAnnotation) { - $this->removeAnnotation($node); - } else { - $this->removeAttribute($node); - } - - $arguments = []; - $operations = null === $this->resourceAnnotation->itemOperations && null === $this->resourceAnnotation->collectionOperations ? null : array_merge( - $this->legacyOperationsToOperations($this->getLegacyOperations()), - $this->legacyOperationsToOperations($this->getLegacyOperations(true), true) - ); - - if (null !== $operations) { - $arguments['operations'] = new Node\Expr\Array_( - array_map(function ($value) { - return new Node\Expr\ArrayItem($value); - }, $operations), - [ - 'kind' => Node\Expr\Array_::KIND_SHORT, - ] - ); - } - - $graphQlOperations = null === $this->resourceAnnotation->graphql ? null : []; - foreach ($this->resourceAnnotation->graphql ?? [] as $operationName => $graphQlOperation) { - if (\is_int($operationName)) { - $ns = $this->getGraphQlOperationNamespace($graphQlOperation); - $graphQlOperations[] = new Node\Expr\New_(new Node\Name($this->getShortName($ns)), $this->arrayToArguments(['name' => $this->valueToNode($graphQlOperation)])); - continue; - } - - $ns = $this->getGraphQlOperationNamespace($operationName, $graphQlOperation); - $args = ['name' => $this->valueToNode($operationName)]; - foreach ($graphQlOperation as $key => $value) { - [$key, $value] = $this->getKeyValue($key, $value); - $args[$key] = $this->valueToNode($value); - } - - $graphQlOperations[] = new Node\Expr\New_(new Node\Name($this->getShortName($ns)), $this->arrayToArguments($args)); - } - - if (null !== $graphQlOperations) { - $arguments['graphQlOperations'] = new Node\Expr\Array_( - array_map(function ($value) { - return new Node\Expr\ArrayItem($value); - }, $graphQlOperations), - [ - 'kind' => Node\Expr\Array_::KIND_SHORT, - ] - ); - } - - foreach (['shortName', 'description', 'iri'] as $key) { - if (!($value = $this->resourceAnnotation->{$key})) { - continue; - } - - if ('iri' === $key) { - $arguments['types'] = new Node\Expr\Array_([new Node\Expr\ArrayItem( - new Node\Scalar\String_($value) - )], ['kind' => Node\Expr\Array_::KIND_SHORT]); - continue; - } - - $arguments[$key] = new Node\Scalar\String_($value); - } - - foreach ($this->resourceAnnotation->attributes ?? [] as $key => $value) { - if (null === $value) { - continue; - } - - [$key, $value] = $this->getKeyValue($key, $value); - - if ('urlGenerationStrategy' === $key) { - $urlGeneratorInterface = new \ReflectionClass(UrlGeneratorInterface::class); - $urlGeneratorConstants = array_flip($urlGeneratorInterface->getConstants()); - $currentUrlGeneratorConstant = $urlGeneratorConstants[$value]; - - $arguments[$key] = - new Node\Expr\ClassConstFetch( - new Node\Name('UrlGeneratorInterface'), - $currentUrlGeneratorConstant - ); - continue; - } - - if ('compositeIdentifier' === $key) { - if (false !== $value) { - continue; - } - - $identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($this->resourceClass); - $identifierNodeItems = []; - foreach ($identifiers as $identifier) { - $identifierNodes = [ - 'compositeIdentifier' => new Node\Expr\ConstFetch(new Node\Name('false')), - 'fromClass' => new Node\Expr\ClassConstFetch( - new Node\Name( - 'self' - ), - 'class' - ), - 'identifiers' => new Node\Expr\Array_( - [ - new Node\Expr\ArrayItem(new Node\Scalar\String_($identifier)), - ], - ['kind' => Node\Expr\Array_::KIND_SHORT] - ), - ]; - - $identifierNodeItems[] = new Node\Expr\ArrayItem( - new Node\Expr\New_(new Node\Name('Link'), $this->arrayToArguments($identifierNodes)), - new Node\Scalar\String_($identifier) - ); - } - - $arguments['uriVariables'] = new Node\Expr\Array_($identifierNodeItems, ['kind' => Node\Expr\Array_::KIND_SHORT]); - continue; - } - - $arguments[$key] = $this->valueToNode($value); - } - - $apiResourceAttribute = - new Node\AttributeGroup([ - new Node\Attribute( - new Node\Name('ApiResource'), - $this->arrayToArguments($arguments) - ), - ]); - - array_unshift($node->attrGroups, $apiResourceAttribute); - } - } - - private function getGraphQlOperationNamespace(string $operationName, array $operation = []): string - { - switch ($operationName) { - case 'item_query': - return Query::class; - case 'collection_query': - return QueryCollection::class; - case 'update': - return Mutation::class; - case 'delete': - return Mutation::class; - case 'create': - return Mutation::class; - default: - if (isset($operation['item_query'])) { - return Query::class; - } - - if (isset($operation['collection_query'])) { - return QueryCollection::class; - } - - if (isset($operation['mutation'])) { - return Mutation::class; - } - - throw new \LogicException(sprintf('The graphql operation %s is not following API Platform naming convention.', $operationName)); - } - } - - private function getOperationNamespace(string $method, bool $isCollection = false): string - { - switch ($method) { - case 'POST': - return Post::class; - case 'PUT': - return Put::class; - case 'PATCH': - return Patch::class; - case 'DELETE': - return Delete::class; - default: - return $isCollection ? GetCollection::class : Get::class; - } - } - - private function getGraphQlOperationsNamespaces(array $operations): array - { - $namespaces = []; - foreach ($operations as $operationName => $operation) { - if (\is_string($operationName)) { - $namespaces[] = $this->getGraphQlOperationNamespace($operationName, $operation); - continue; - } - - $namespaces[] = $this->getGraphQlOperationNamespace($operation); - } - - return $namespaces; - } - - private function getOperationsNamespaces(array $operations, bool $isCollection = false): array - { - $namespaces = []; - foreach ($operations as $operationName => $operation) { - if (\is_string($operationName)) { - $namespaces[] = $this->getOperationNamespace($operation['method'] ?? strtoupper($operationName), $isCollection); - continue; - } - - $method = \is_string($operation) ? strtoupper($operation) : $operation['method']; - $namespaces[] = $this->getOperationNamespace($method, $isCollection); - } - - return $namespaces; - } - - /** - * @return Node\Arg[] - */ - private function arrayToArguments(array $arguments) - { - $args = []; - foreach ($arguments as $key => $value) { - $args[] = new Node\Arg($value, false, false, [], new Node\Identifier($key)); - } - - return $args; - } - - private function removeAttribute(Node\Stmt\Class_|Node\Stmt\Interface_ $node) - { - foreach ($node->attrGroups as $k => $attrGroupNode) { - foreach ($attrGroupNode->attrs as $i => $attribute) { - if (str_ends_with(implode('\\', $attribute->name->parts), 'ApiResource')) { - unset($node->attrGroups[$k]); - break; - } - } - } - } - - private function removeAnnotation(Node\Stmt\Class_|Node\Stmt\Interface_ $node) - { - $comment = $node->getDocComment(); - - if (preg_match('/@ApiResource/', $comment->getText())) { - $node->setDocComment($this->removeAnnotationByTag($comment, 'ApiResource')); - } - } - - private function valueToNode(mixed $value) - { - if (\is_string($value)) { - if (class_exists($value)) { - return new Node\Expr\ClassConstFetch(new Node\Name($this->getShortName($value)), 'class'); - } - - return new Node\Scalar\String_($value); - } - - if (\is_bool($value)) { - return new Node\Expr\ConstFetch(new Node\Name($value ? 'true' : 'false')); - } - - if (is_numeric($value)) { - return \is_int($value) ? new Node\Scalar\LNumber($value) : new Node\Scalar\DNumber($value); - } - - if (\is_array($value)) { - return new Node\Expr\Array_( - array_map(function ($key, $value) { - return new Node\Expr\ArrayItem( - $this->valueToNode($value), - \is_string($key) ? $this->valueToNode($key) : null, - ); - }, array_keys($value), array_values($value)), - [ - 'kind' => Node\Expr\Array_::KIND_SHORT, - ] - ); - } - } - - private function getShortName(string $class): string - { - if (false !== $pos = strrpos($class, '\\')) { - return substr($class, $pos + 1); - } - - return $class; - } - - private function createOperation(string $namespace, array $arguments = []) - { - $args = []; - foreach ($arguments as $key => $value) { - [$key, $value] = $this->getKeyValue($key, $value); - $args[$key] = $this->valueToNode($value); - } - - return new Node\Expr\New_(new Node\Name($this->getShortName($namespace)), $this->arrayToArguments($args)); - } - - private function legacyOperationsToOperations($legacyOperations, bool $isCollection = false) - { - $operations = []; - foreach ($legacyOperations as $operationName => $operation) { - if (\is_int($operationName)) { - $operations[] = $this->createOperation($this->getOperationNamespace(strtoupper($operation), $isCollection)); - continue; - } - - $method = $operation['method'] ?? strtoupper($operationName); - unset($operation['method']); - $operations[] = $this->createOperation($this->getOperationNamespace($method, $isCollection), $operation); - } - - return $operations; - } -} diff --git a/src/Core/Upgrade/UpgradeApiSubresourceVisitor.php b/src/Core/Upgrade/UpgradeApiSubresourceVisitor.php deleted file mode 100644 index 087160db7d4..00000000000 --- a/src/Core/Upgrade/UpgradeApiSubresourceVisitor.php +++ /dev/null @@ -1,301 +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\Upgrade; - -use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Annotation\ApiSubresource; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\Link; -use PhpParser\Node; -use PhpParser\NodeVisitorAbstract; -use ReflectionClass; - -final class UpgradeApiSubresourceVisitor extends NodeVisitorAbstract -{ - use RemoveAnnotationTrait; - private $subresourceMetadata; - private $referenceType; - - public function __construct($subresourceMetadata, $referenceType) - { - $this->subresourceMetadata = $subresourceMetadata; - $this->referenceType = $referenceType; - } - - /** - * @return int|Node|null - */ - public function enterNode(Node $node) - { - $operationToCreate = $this->subresourceMetadata['collection'] ? GetCollection::class : Get::class; - $operationUseStatementNeeded = true; - $apiResourceUseStatementNeeded = true; - $linkUseStatementNeeded = true; - - $comment = $node->getDocComment(); - if ($comment && preg_match('/@ApiSubresource/', $comment->getText())) { - $node->setDocComment($this->removeAnnotationByTag($comment, 'ApiSubresource')); - } - - if ($node instanceof Node\Stmt\Namespace_) { - foreach ($node->stmts as $i => $stmt) { - if (!$stmt instanceof Node\Stmt\Use_) { - break; - } - - $useStatement = implode('\\', $stmt->uses[0]->name->parts); - if (ApiSubresource::class === $useStatement) { - unset($node->stmts[$i]); - } - - if (ApiResource::class === $useStatement) { - $apiResourceUseStatementNeeded = false; - continue; - } - - if (Link::class === $useStatement) { - $linkUseStatementNeeded = false; - continue; - } - - if ($useStatement === $operationToCreate) { - $operationUseStatementNeeded = false; - continue; - } - } - - if ($operationUseStatementNeeded) { - array_unshift( - $node->stmts, - new Node\Stmt\Use_([ - new Node\Stmt\UseUse( - new Node\Name( - $this->subresourceMetadata['collection'] ? GetCollection::class : Get::class - ) - ), - ]) - ); - } - - if ($apiResourceUseStatementNeeded) { - array_unshift( - $node->stmts, - new Node\Stmt\Use_([ - new Node\Stmt\UseUse( - new Node\Name( - ApiResource::class - ) - ), - ]) - ); - } - - if ($linkUseStatementNeeded) { - array_unshift( - $node->stmts, - new Node\Stmt\Use_([ - new Node\Stmt\UseUse( - new Node\Name( - Link::class - ) - ), - ]) - ); - } - } - - if ($node instanceof Node\Stmt\Class_) { - $identifiersNodeItems = []; - - foreach ($this->subresourceMetadata['uri_variables'] as $identifier => $resource) { - $identifierNodes = [ - 'fromClass' => new Node\Expr\ClassConstFetch( - new Node\Name( - ($resource['from_class'] === $this->subresourceMetadata['resource_class']) ? 'self' : '\\'.$resource['from_class'] - ), - 'class' - ), - 'identifiers' => new Node\Expr\Array_( - isset($resource['identifiers'][0]) ? [ - new Node\Expr\ArrayItem(new Node\Scalar\String_($resource['identifiers'][0])), - ] : [], - ['kind' => Node\Expr\Array_::KIND_SHORT] - ), - ]; - - if (isset($resource['expanded_value'])) { - $identifierNodes['expandedValue'] = new Node\Scalar\String_($resource['expanded_value']); - } - - if (isset($resource['from_property']) || isset($resource['to_property'])) { - $identifierNodes[isset($resource['to_property']) ? 'toProperty' : 'fromProperty'] = new Node\Scalar\String_($resource['to_property'] ?? $resource['from_property']); - } - - $identifierNodeItems[] = new Node\Expr\ArrayItem( - new Node\Expr\New_(new Node\Name('Link'), $this->arrayToArguments($identifierNodes)), - new Node\Scalar\String_($identifier) - ); - } - - $arguments = [ - new Node\Arg( - new Node\Scalar\String_($this->subresourceMetadata['path']), - false, - false, - [], - new Node\Identifier('uriTemplate') - ), - new Node\Arg( - new Node\Expr\Array_($identifierNodeItems, ['kind' => Node\Expr\Array_::KIND_SHORT]), - false, - false, - [], - new Node\Identifier('uriVariables') - ), - new Node\Arg( - new Node\Scalar\LNumber(200), - false, - false, - [], - new Node\Identifier('status') - ), - ]; - - if (null !== $this->referenceType) { - $urlGeneratorInterface = new ReflectionClass(UrlGeneratorInterface::class); - $urlGeneratorConstants = array_flip($urlGeneratorInterface->getConstants()); - $currentUrlGeneratorConstant = $urlGeneratorConstants[$this->referenceType]; - - $arguments[] = new Node\Arg( - new Node\Expr\ClassConstFetch( - new Node\Name('UrlGeneratorInterface'), - $currentUrlGeneratorConstant - ), - false, - false, - [], - new Node\Identifier('urlGenerationStrategy') - ); - } - - if ($this->subresourceMetadata['legacy_type'] ?? false) { - $arguments[] = new Node\Arg( - new Node\Expr\ArrayItem( - new Node\Expr\Array_( - [ - new Node\Expr\ArrayItem( - new Node\Scalar\String_($this->subresourceMetadata['legacy_type']) - ), - ], - [ - 'kind' => Node\Expr\Array_::KIND_SHORT, - ] - ) - ), - false, - false, - [], - new Node\Identifier('types') - ); - } - - if ($this->subresourceMetadata['legacy_filters'] ?? false) { - $arguments[] = new Node\Arg( - new Node\Expr\ArrayItem( - new Node\Expr\Array_( - array_map(function ($filter) { - return new Node\Expr\ArrayItem( - new Node\Scalar\String_($filter) - ); - }, $this->subresourceMetadata['legacy_filters']), - [ - 'kind' => Node\Expr\Array_::KIND_SHORT, - ] - ) - ), - false, - false, - [], - new Node\Identifier('filters') - ); - } - - if ($this->subresourceMetadata['legacy_normalization_context']['groups'] ?? false) { - $arguments[] = new Node\Arg( - new Node\Expr\ArrayItem( - new Node\Expr\Array_([ - new Node\Expr\ArrayItem( - new Node\Expr\Array_( - array_map(function ($group) { - return new Node\Expr\ArrayItem(new Node\Scalar\String_($group)); - }, $this->subresourceMetadata['legacy_normalization_context']['groups']), - ['kind' => Node\Expr\Array_::KIND_SHORT] - ), - new Node\Scalar\String_('groups') - ), - ], ['kind' => Node\Expr\Array_::KIND_SHORT]) - ), - false, - false, - [], - new Node\Identifier('normalizationContext') - ); - } - - $arguments[] = new Node\Arg( - new Node\Expr\Array_( - [ - new Node\Expr\ArrayItem( - new Node\Expr\New_( - new Node\Name($this->subresourceMetadata['collection'] ? 'GetCollection' : 'Get') - ), - ), - ], - [ - 'kind' => Node\Expr\Array_::KIND_SHORT, - ] - ), - false, - false, - [], - new Node\Identifier('operations') - ); - - $apiResourceAttribute = - new Node\AttributeGroup([ - new Node\Attribute( - new Node\Name('ApiResource'), - $arguments - ), - ]); - - $node->attrGroups[] = $apiResourceAttribute; - } - } - - /** - * @return Node\Arg[] - */ - private function arrayToArguments(array $arguments) - { - $args = []; - foreach ($arguments as $key => $value) { - $args[] = new Node\Arg($value, false, false, [], new Node\Identifier($key)); - } - - return $args; - } -} diff --git a/src/Core/Util/AnnotationFilterExtractorTrait.php b/src/Core/Util/AnnotationFilterExtractorTrait.php deleted file mode 100644 index 62498a53fb1..00000000000 --- a/src/Core/Util/AnnotationFilterExtractorTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\AnnotationFilterExtractorTrait::class); - -if (false) { - trait AnnotationFilterExtractorTrait - { - use \ApiPlatform\Util\AnnotationFilterExtractorTrait; - } -} diff --git a/src/Core/Util/ArrayTrait.php b/src/Core/Util/ArrayTrait.php deleted file mode 100644 index 2fc3bc81c37..00000000000 --- a/src/Core/Util/ArrayTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\ArrayTrait::class); - -if (false) { - trait ArrayTrait - { - use \ApiPlatform\Util\ArrayTrait; - } -} diff --git a/src/Core/Util/AttributesExtractor.php b/src/Core/Util/AttributesExtractor.php deleted file mode 100644 index 1ef911cca26..00000000000 --- a/src/Core/Util/AttributesExtractor.php +++ /dev/null @@ -1,22 +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\Util; - -class_exists(\ApiPlatform\Util\AttributesExtractor::class); - -if (false) { - final class AttributesExtractor extends \ApiPlatform\Util\AttributesExtractor - { - } -} diff --git a/src/Core/Util/ClassInfoTrait.php b/src/Core/Util/ClassInfoTrait.php deleted file mode 100644 index 9fc3fb7751b..00000000000 --- a/src/Core/Util/ClassInfoTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\ClassInfoTrait::class); - -if (false) { - trait ClassInfoTrait - { - use \ApiPlatform\Util\ClassInfoTrait; - } -} diff --git a/src/Core/Util/ClientTrait.php b/src/Core/Util/ClientTrait.php deleted file mode 100644 index 376776f80f7..00000000000 --- a/src/Core/Util/ClientTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\ClientTrait::class); - -if (false) { - trait ClientTrait - { - use \ApiPlatform\Util\ClientTrait; - } -} diff --git a/src/Core/Util/CloneTrait.php b/src/Core/Util/CloneTrait.php deleted file mode 100644 index a2a221b85b3..00000000000 --- a/src/Core/Util/CloneTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\CloneTrait::class); - -if (false) { - trait CloneTrait - { - use \ApiPlatform\Util\CloneTrait; - } -} diff --git a/src/Core/Util/CorsTrait.php b/src/Core/Util/CorsTrait.php deleted file mode 100644 index 7b66cb2697b..00000000000 --- a/src/Core/Util/CorsTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\CorsTrait::class); - -if (false) { - trait CorsTrait - { - use \ApiPlatform\Util\CorsTrait; - } -} diff --git a/src/Core/Util/ErrorFormatGuesser.php b/src/Core/Util/ErrorFormatGuesser.php deleted file mode 100644 index 5bc6fb11b0e..00000000000 --- a/src/Core/Util/ErrorFormatGuesser.php +++ /dev/null @@ -1,22 +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\Util; - -class_exists(\ApiPlatform\Util\ErrorFormatGuesser::class); - -if (false) { - final class ErrorFormatGuesser extends \ApiPlatform\Util\ErrorFormatGuesser - { - } -} diff --git a/src/Core/Util/Inflector.php b/src/Core/Util/Inflector.php deleted file mode 100644 index ab2ec6ca1f4..00000000000 --- a/src/Core/Util/Inflector.php +++ /dev/null @@ -1,22 +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\Util; - -class_exists(\ApiPlatform\Util\Inflector::class); - -if (false) { - final class Inflector extends \ApiPlatform\Util\Inflector - { - } -} diff --git a/src/Core/Util/IriHelper.php b/src/Core/Util/IriHelper.php deleted file mode 100644 index aca7d8dfd23..00000000000 --- a/src/Core/Util/IriHelper.php +++ /dev/null @@ -1,22 +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\Util; - -class_exists(\ApiPlatform\Util\IriHelper::class); - -if (false) { - final class IriHelper extends \ApiPlatform\Util\IriHelper - { - } -} diff --git a/src/Core/Util/Reflection.php b/src/Core/Util/Reflection.php deleted file mode 100644 index 2e4910c4321..00000000000 --- a/src/Core/Util/Reflection.php +++ /dev/null @@ -1,22 +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\Util; - -class_exists(\ApiPlatform\Util\Reflection::class); - -if (false) { - final class Reflection extends \ApiPlatform\Util\Reflection - { - } -} diff --git a/src/Core/Util/ReflectionClassRecursiveIterator.php b/src/Core/Util/ReflectionClassRecursiveIterator.php deleted file mode 100644 index 56504a2c544..00000000000 --- a/src/Core/Util/ReflectionClassRecursiveIterator.php +++ /dev/null @@ -1,22 +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\Util; - -class_exists(\ApiPlatform\Util\ReflectionClassRecursiveIterator::class); - -if (false) { - final class ReflectionClassRecursiveIterator extends \ApiPlatform\Util\ReflectionClassRecursiveIterator - { - } -} diff --git a/src/Core/Util/RequestAttributesExtractor.php b/src/Core/Util/RequestAttributesExtractor.php deleted file mode 100644 index cb834b5d9f6..00000000000 --- a/src/Core/Util/RequestAttributesExtractor.php +++ /dev/null @@ -1,22 +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\Util; - -class_exists(\ApiPlatform\Util\RequestAttributesExtractor::class); - -if (false) { - final class RequestAttributesExtractor extends \ApiPlatform\Util\RequestAttributesExtractor - { - } -} diff --git a/src/Core/Util/RequestParser.php b/src/Core/Util/RequestParser.php deleted file mode 100644 index db51ba35427..00000000000 --- a/src/Core/Util/RequestParser.php +++ /dev/null @@ -1,22 +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\Util; - -class_exists(\ApiPlatform\Util\RequestParser::class); - -if (false) { - final class RequestParser extends \ApiPlatform\Util\RequestParser - { - } -} diff --git a/src/Core/Util/ResourceClassInfoTrait.php b/src/Core/Util/ResourceClassInfoTrait.php deleted file mode 100644 index ab7bbc12c28..00000000000 --- a/src/Core/Util/ResourceClassInfoTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\ResourceClassInfoTrait::class); - -if (false) { - trait ResourceClassInfoTrait - { - use \ApiPlatform\Util\ResourceClassInfoTrait; - } -} diff --git a/src/Core/Util/ResponseTrait.php b/src/Core/Util/ResponseTrait.php deleted file mode 100644 index 8df237ceb58..00000000000 --- a/src/Core/Util/ResponseTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\ResponseTrait::class); - -if (false) { - trait ResponseTrait - { - use \ApiPlatform\Util\ResponseTrait; - } -} diff --git a/src/Core/Util/SortTrait.php b/src/Core/Util/SortTrait.php deleted file mode 100644 index 0367e29b6fb..00000000000 --- a/src/Core/Util/SortTrait.php +++ /dev/null @@ -1,23 +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\Util; - -class_exists(\ApiPlatform\Util\SortTrait::class); - -if (false) { - trait SortTrait - { - use \ApiPlatform\Util\SortTrait; - } -} diff --git a/src/Core/Validator/EventListener/ValidateListener.php b/src/Core/Validator/EventListener/ValidateListener.php deleted file mode 100644 index b5b9ae12341..00000000000 --- a/src/Core/Validator/EventListener/ValidateListener.php +++ /dev/null @@ -1,22 +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\Validator\EventListener; - -class_exists(\ApiPlatform\Symfony\EventListener\ValidateListener::class); - -if (false) { - final class ValidateListener extends \ApiPlatform\Symfony\EventListener\ValidateListener - { - } -} diff --git a/src/Core/Validator/Exception/ValidationException.php b/src/Core/Validator/Exception/ValidationException.php deleted file mode 100644 index a18f4def1f6..00000000000 --- a/src/Core/Validator/Exception/ValidationException.php +++ /dev/null @@ -1,22 +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\Validator\Exception; - -class_exists(\ApiPlatform\Validator\Exception\ValidationException::class); - -if (false) { - class ValidationException extends \ApiPlatform\Validator\Exception\ValidationException - { - } -} diff --git a/src/Doctrine/Common/Filter/BooleanFilterTrait.php b/src/Doctrine/Common/Filter/BooleanFilterTrait.php index 2b34b529958..199dc54763d 100644 --- a/src/Doctrine/Common/Filter/BooleanFilterTrait.php +++ b/src/Doctrine/Common/Filter/BooleanFilterTrait.php @@ -65,7 +65,7 @@ abstract protected function getProperties(): ?array; abstract protected function getLogger(): LoggerInterface; - abstract protected function normalizePropertyName($property): string; + abstract protected function normalizePropertyName($property); /** * Determines whether the given property refers to a boolean field. diff --git a/src/Doctrine/Common/Filter/DateFilterTrait.php b/src/Doctrine/Common/Filter/DateFilterTrait.php index 14d8b62fd05..958af548aca 100644 --- a/src/Doctrine/Common/Filter/DateFilterTrait.php +++ b/src/Doctrine/Common/Filter/DateFilterTrait.php @@ -55,7 +55,7 @@ public function getDescription(string $resourceClass): array abstract protected function getProperties(): ?array; - abstract protected function normalizePropertyName($property): string; + abstract protected function normalizePropertyName($property); /** * Determines whether the given property refers to a date field. diff --git a/src/Doctrine/Common/Filter/ExistsFilterTrait.php b/src/Doctrine/Common/Filter/ExistsFilterTrait.php index b8d73fe83cf..c7551e584bc 100644 --- a/src/Doctrine/Common/Filter/ExistsFilterTrait.php +++ b/src/Doctrine/Common/Filter/ExistsFilterTrait.php @@ -30,7 +30,7 @@ trait ExistsFilterTrait /** * @var string Keyword used to retrieve the value */ - private $existsParameterName; + private string $existsParameterName; /** * {@inheritdoc} @@ -68,7 +68,7 @@ abstract protected function getProperties(): ?array; abstract protected function getLogger(): LoggerInterface; - abstract protected function normalizePropertyName($property): string; + abstract protected function normalizePropertyName($property); private function normalizeValue($value, string $property): ?bool { diff --git a/src/Doctrine/Common/Filter/NumericFilterTrait.php b/src/Doctrine/Common/Filter/NumericFilterTrait.php index b342e1216c0..d2787ac8c1d 100644 --- a/src/Doctrine/Common/Filter/NumericFilterTrait.php +++ b/src/Doctrine/Common/Filter/NumericFilterTrait.php @@ -69,7 +69,7 @@ abstract protected function getProperties(): ?array; abstract protected function getLogger(): LoggerInterface; - abstract protected function normalizePropertyName($property): string; + abstract protected function normalizePropertyName($property); /** * Determines whether the given property refers to a numeric field. diff --git a/src/Doctrine/Common/Filter/OrderFilterTrait.php b/src/Doctrine/Common/Filter/OrderFilterTrait.php index 3bb6e0c11d5..ae59cbf5ec9 100644 --- a/src/Doctrine/Common/Filter/OrderFilterTrait.php +++ b/src/Doctrine/Common/Filter/OrderFilterTrait.php @@ -29,7 +29,7 @@ trait OrderFilterTrait /** * @var string Keyword used to retrieve the value */ - protected $orderParameterName; + protected string $orderParameterName; /** * {@inheritdoc} @@ -67,7 +67,7 @@ public function getDescription(string $resourceClass): array abstract protected function getProperties(): ?array; - abstract protected function normalizePropertyName($property): string; + abstract protected function normalizePropertyName($property); private function normalizeValue($value, string $property): ?string { diff --git a/src/Doctrine/Common/Filter/RangeFilterTrait.php b/src/Doctrine/Common/Filter/RangeFilterTrait.php index 484758216f7..6051ac48efd 100644 --- a/src/Doctrine/Common/Filter/RangeFilterTrait.php +++ b/src/Doctrine/Common/Filter/RangeFilterTrait.php @@ -58,7 +58,7 @@ abstract protected function getProperties(): ?array; abstract protected function getLogger(): LoggerInterface; - abstract protected function normalizePropertyName($property): string; + abstract protected function normalizePropertyName($property); /** * Gets filter description. diff --git a/src/Doctrine/Common/Filter/SearchFilterTrait.php b/src/Doctrine/Common/Filter/SearchFilterTrait.php index 8caa7e3a027..0b9d472e543 100644 --- a/src/Doctrine/Common/Filter/SearchFilterTrait.php +++ b/src/Doctrine/Common/Filter/SearchFilterTrait.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Common\Filter; +use ApiPlatform\Api\IdentifiersExtractorInterface; use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Doctrine\Common\PropertyHelperTrait; use ApiPlatform\Exception\InvalidArgumentException; @@ -29,8 +30,9 @@ trait SearchFilterTrait { use PropertyHelperTrait; - protected $iriConverter; - protected $propertyAccessor; + protected IriConverterInterface $iriConverter; + protected PropertyAccessorInterface $propertyAccessor; + protected ?IdentifiersExtractorInterface $identifiersExtractor; /** * {@inheritdoc} @@ -111,7 +113,7 @@ abstract protected function getIriConverter(): IriConverterInterface; abstract protected function getPropertyAccessor(): PropertyAccessorInterface; - abstract protected function normalizePropertyName($property): string; + abstract protected function normalizePropertyName($property); /** * Gets the ID from an IRI or a raw ID. diff --git a/src/Doctrine/Common/State/PersistProcessor.php b/src/Doctrine/Common/State/PersistProcessor.php index c0cddeeaa63..1f28f2c905f 100644 --- a/src/Doctrine/Common/State/PersistProcessor.php +++ b/src/Doctrine/Common/State/PersistProcessor.php @@ -25,8 +25,7 @@ final class PersistProcessor implements ProcessorInterface { use ClassInfoTrait; - /** @var ManagerRegistry */ - private $managerRegistry; + private ManagerRegistry $managerRegistry; public function __construct(ManagerRegistry $managerRegistry) { diff --git a/src/Doctrine/Common/State/RemoveProcessor.php b/src/Doctrine/Common/State/RemoveProcessor.php index 724e92da9cb..528305b9043 100644 --- a/src/Doctrine/Common/State/RemoveProcessor.php +++ b/src/Doctrine/Common/State/RemoveProcessor.php @@ -23,8 +23,7 @@ final class RemoveProcessor implements ProcessorInterface { use ClassInfoTrait; - /** @var ManagerRegistry */ - private $managerRegistry; + private ManagerRegistry $managerRegistry; public function __construct(ManagerRegistry $managerRegistry) { diff --git a/src/Doctrine/EventListener/PurgeHttpCacheListener.php b/src/Doctrine/EventListener/PurgeHttpCacheListener.php index 7fa2b10eb8a..7145fe3e7a1 100644 --- a/src/Doctrine/EventListener/PurgeHttpCacheListener.php +++ b/src/Doctrine/EventListener/PurgeHttpCacheListener.php @@ -22,6 +22,7 @@ use ApiPlatform\Exception\RuntimeException; use ApiPlatform\HttpCache\PurgerInterface; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Util\ClassInfoTrait; use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; @@ -39,20 +40,18 @@ */ final class PurgeHttpCacheListener { + use ClassInfoTrait; + private $purger; private $iriConverter; private $resourceClassResolver; private $propertyAccessor; private $tags = []; - public function __construct(PurgerInterface $purger, $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null) + public function __construct(PurgerInterface $purger, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null) { $this->purger = $purger; $this->iriConverter = $iriConverter; - 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->resourceClassResolver = $resourceClassResolver; $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); } @@ -120,7 +119,7 @@ private function gatherResourceAndItemTags($entity, bool $purgeItem): void { try { $resourceClass = $this->resourceClassResolver->getResourceClass($entity); - $iri = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromResourceClass($resourceClass) : $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, new GetCollection()); + $iri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, new GetCollection()); $this->tags[$iri] = $iri; if ($purgeItem) { @@ -163,13 +162,14 @@ private function addTagsFor($value): void private function addTagForItem($value): void { + if (!$this->resourceClassResolver->isResourceClass($this->getObjectClass($value))) { + return; + } + try { - // TODO: test if this is a resource class $iri = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($value) : $this->iriConverter->getIriFromResource($value); $this->tags[$iri] = $iri; } catch (RuntimeException|InvalidArgumentException $e) { } } } - -class_alias(PurgeHttpCacheListener::class, \ApiPlatform\Core\Bridge\Doctrine\EventListener\PurgeHttpCacheListener::class); diff --git a/src/Doctrine/EventListener/WriteListener.php b/src/Doctrine/EventListener/WriteListener.php deleted file mode 100644 index 5938b6223ba..00000000000 --- a/src/Doctrine/EventListener/WriteListener.php +++ /dev/null @@ -1,88 +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\Doctrine\EventListener; - -use ApiPlatform\Core\EventListener\WriteListener as BaseWriteListener; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectManager; -use Symfony\Component\HttpKernel\Event\ViewEvent; - -/** - * Bridges Doctrine and the API system. - * - * @deprecated - * - * @author Kévin Dunglas - */ -final class WriteListener -{ - private $managerRegistry; - - public function __construct(ManagerRegistry $managerRegistry) - { - @trigger_error(sprintf('The %s class is deprecated since version 2.2 and will be removed in 3.0. Use the %s class instead.', __CLASS__, BaseWriteListener::class), \E_USER_DEPRECATED); - - $this->managerRegistry = $managerRegistry; - } - - /** - * Persists, updates or delete data return by the controller if applicable. - */ - public function onKernelView(ViewEvent $event): void - { - $request = $event->getRequest(); - if ($request->isMethodSafe()) { - return; - } - - $resourceClass = $request->attributes->get('_api_resource_class'); - if (null === $resourceClass) { - return; - } - - $controllerResult = $event->getControllerResult(); - if (null === $objectManager = $this->getManager($resourceClass, $controllerResult)) { - return; - } - - switch ($request->getMethod()) { - case 'POST': - $objectManager->persist($controllerResult); - break; - case 'DELETE': - $objectManager->remove($controllerResult); - $event->setControllerResult(null); - break; - } - - $objectManager->flush(); - } - - /** - * Gets the manager if applicable. - * - * @param mixed $data - */ - private function getManager(string $resourceClass, $data): ?ObjectManager - { - $objectManager = $this->managerRegistry->getManagerForClass($resourceClass); - if (null === $objectManager || !\is_object($data)) { - return null; - } - - return $objectManager; - } -} - -class_alias(WriteListener::class, \ApiPlatform\Core\Bridge\Doctrine\EventListener\WriteListener::class); diff --git a/src/Doctrine/Odm/Extension/FilterExtension.php b/src/Doctrine/Odm/Extension/FilterExtension.php index ffad789b408..71777a6d8bc 100644 --- a/src/Doctrine/Odm/Extension/FilterExtension.php +++ b/src/Doctrine/Odm/Extension/FilterExtension.php @@ -28,8 +28,7 @@ */ final class FilterExtension implements AggregationCollectionExtensionInterface { - /** @var ContainerInterface */ - private $filterLocator; + private ContainerInterface $filterLocator; public function __construct(ContainerInterface $filterLocator) { @@ -41,7 +40,7 @@ public function __construct(ContainerInterface $filterLocator) */ public function applyToCollection(Builder $aggregationBuilder, string $resourceClass, Operation $operation = null, array &$context = []): void { - $resourceFilters = $operation ? $operation->getFilters() : null; + $resourceFilters = $operation?->getFilters(); if (empty($resourceFilters)) { return; diff --git a/src/Doctrine/Odm/Extension/OrderExtension.php b/src/Doctrine/Odm/Extension/OrderExtension.php index 9541ea15c6d..69c510fb4bf 100644 --- a/src/Doctrine/Odm/Extension/OrderExtension.php +++ b/src/Doctrine/Odm/Extension/OrderExtension.php @@ -36,8 +36,8 @@ final class OrderExtension implements AggregationCollectionExtensionInterface use MongoDbOdmPropertyHelperTrait; use PropertyHelperTrait; - private $order; - private $managerRegistry; + private ?string $order; + private ?ManagerRegistry $managerRegistry; public function __construct(string $order = null, ManagerRegistry $managerRegistry = null) { @@ -57,7 +57,11 @@ public function applyToCollection(Builder $aggregationBuilder, string $resourceC $classMetaData = $this->getClassMetadata($resourceClass); $identifiers = $classMetaData->getIdentifier(); - $defaultOrder = $operation ? $operation->getOrder() : null; + if (isset($context['operation'])) { + $defaultOrder = $context['operation']->getOrder() ?? []; + } else { + $defaultOrder = $operation?->getOrder(); + } if ($defaultOrder) { foreach ($defaultOrder as $field => $order) { diff --git a/src/Doctrine/Odm/Extension/PaginationExtension.php b/src/Doctrine/Odm/Extension/PaginationExtension.php index b47c2810ed8..186c25bd79c 100644 --- a/src/Doctrine/Odm/Extension/PaginationExtension.php +++ b/src/Doctrine/Odm/Extension/PaginationExtension.php @@ -33,8 +33,8 @@ */ final class PaginationExtension implements AggregationResultCollectionExtensionInterface { - private $managerRegistry; - private $pagination; + private ManagerRegistry $managerRegistry; + private Pagination $pagination; public function __construct(ManagerRegistry $managerRegistry, Pagination $pagination) { @@ -113,7 +113,7 @@ public function getResult(Builder $aggregationBuilder, string $resourceClass, Op throw new RuntimeException(sprintf('The manager for "%s" must be an instance of "%s".', $resourceClass, DocumentManager::class)); } - $attribute = $operation ? ($operation->getExtraProperties()['doctrine_mongodb'] ?? []) : []; + $attribute = $operation?->getExtraProperties()['doctrine_mongodb'] ?? []; $executeOptions = $attribute['execute_options'] ?? []; return new Paginator($aggregationBuilder->execute($executeOptions), $manager->getUnitOfWork(), $resourceClass, $aggregationBuilder->getPipeline()); diff --git a/src/Doctrine/Odm/Filter/AbstractFilter.php b/src/Doctrine/Odm/Filter/AbstractFilter.php index a9907515784..2b2e782e99a 100644 --- a/src/Doctrine/Odm/Filter/AbstractFilter.php +++ b/src/Doctrine/Odm/Filter/AbstractFilter.php @@ -36,14 +36,10 @@ abstract class AbstractFilter implements FilterInterface use MongoDbOdmPropertyHelperTrait; use PropertyHelperTrait; - /** @var ManagerRegistry */ - protected $managerRegistry; - /** @var LoggerInterface */ - protected $logger; - /** @var array|null */ - protected $properties; - /** @var NameConverterInterface|null */ - protected $nameConverter; + protected ManagerRegistry $managerRegistry; + protected LoggerInterface $logger; + protected ?array $properties; + protected ?NameConverterInterface $nameConverter; public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) { diff --git a/src/Doctrine/Odm/Filter/ExistsFilter.php b/src/Doctrine/Odm/Filter/ExistsFilter.php index 92a5142900a..833091541ed 100644 --- a/src/Doctrine/Odm/Filter/ExistsFilter.php +++ b/src/Doctrine/Odm/Filter/ExistsFilter.php @@ -53,7 +53,7 @@ public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $l */ public function apply(Builder $aggregationBuilder, string $resourceClass, Operation $operation = null, array &$context = []): void { - foreach ($context['filters'][$this->existsParameterName] ?? [] as $property => $value) { + foreach ($context['filters'][$this->existsParameterName] as $property => $value) { $this->filterProperty($this->denormalizePropertyName($property), $value, $aggregationBuilder, $resourceClass, $operation, $context); } } diff --git a/src/Doctrine/Odm/Filter/SearchFilter.php b/src/Doctrine/Odm/Filter/SearchFilter.php index 5460e366be8..447a6baf2a7 100644 --- a/src/Doctrine/Odm/Filter/SearchFilter.php +++ b/src/Doctrine/Odm/Filter/SearchFilter.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Odm\Filter; +use ApiPlatform\Api\IdentifiersExtractorInterface; use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface; use ApiPlatform\Doctrine\Common\Filter\SearchFilterTrait; @@ -43,12 +44,13 @@ final class SearchFilter extends AbstractFilter implements SearchFilterInterface public const DOCTRINE_INTEGER_TYPE = [MongoDbType::INTEGER, MongoDbType::INT]; - public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, ?IdentifiersExtractorInterface $identifiersExtractor, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); $this->iriConverter = $iriConverter; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); + $this->identifiersExtractor = $identifiersExtractor; } protected function getIriConverter(): IriConverterInterface diff --git a/src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php b/src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php index 9c90c4e8115..e1f05727d60 100644 --- a/src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php +++ b/src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php @@ -28,8 +28,8 @@ */ final class DoctrineMongoDbOdmPropertyMetadataFactory implements PropertyMetadataFactoryInterface { - private $decorated; - private $managerRegistry; + private PropertyMetadataFactoryInterface $decorated; + private ManagerRegistry $managerRegistry; public function __construct(ManagerRegistry $managerRegistry, PropertyMetadataFactoryInterface $decorated) { diff --git a/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php b/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php index ec2ea373e40..01e3e69644d 100644 --- a/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php +++ b/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php @@ -27,15 +27,9 @@ final class DoctrineMongoDbOdmResourceCollectionMetadataFactory implements ResourceMetadataCollectionFactoryInterface { - /** - * @var ManagerRegistry - */ - private $managerRegistry; + private ManagerRegistry $managerRegistry; - /** - * @var ResourceMetadataCollectionFactoryInterface - */ - private $decorated; + private ResourceMetadataCollectionFactoryInterface $decorated; public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataCollectionFactoryInterface $decorated) { diff --git a/src/Doctrine/Odm/Paginator.php b/src/Doctrine/Odm/Paginator.php index 11ff2ae9411..9cfb51472a8 100644 --- a/src/Doctrine/Odm/Paginator.php +++ b/src/Doctrine/Odm/Paginator.php @@ -31,38 +31,21 @@ final class Paginator implements \IteratorAggregate, PaginatorInterface public const LIMIT_ZERO_MARKER_FIELD = '___'; public const LIMIT_ZERO_MARKER = 'limit0'; - /** - * @var Iterator - */ - private $mongoDbOdmIterator; - /** - * @var array - */ - private $pipeline; - /** - * @var UnitOfWork - */ - private $unitOfWork; - /** - * @var string - */ - private $resourceClass; + private Iterator $mongoDbOdmIterator; - /** @var \ArrayIterator|null */ - private $iterator; + private array $pipeline; - /** - * @var int - */ - private $firstResult; - /** - * @var int - */ - private $maxResults; - /** - * @var int - */ - private $totalItems; + private UnitOfWork $unitOfWork; + + private string $resourceClass; + + private ?\ArrayIterator $iterator; + + private int $firstResult; + + private int $maxResults; + + private int $totalItems; public function __construct(Iterator $mongoDbOdmIterator, UnitOfWork $unitOfWork, string $resourceClass, array $pipeline) { @@ -184,5 +167,3 @@ private function hasLimitZeroStage(array $resultsFacetInfo): bool return false; } } - -class_alias(Paginator::class, \ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Paginator::class); diff --git a/src/Doctrine/Odm/PropertyInfo/DoctrineExtractor.php b/src/Doctrine/Odm/PropertyInfo/DoctrineExtractor.php index d6c22d3f185..29ed546d941 100644 --- a/src/Doctrine/Odm/PropertyInfo/DoctrineExtractor.php +++ b/src/Doctrine/Odm/PropertyInfo/DoctrineExtractor.php @@ -34,7 +34,7 @@ */ final class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface { - private $objectManager; + private ObjectManager $objectManager; public function __construct(ObjectManager $objectManager) { @@ -132,7 +132,7 @@ public function isWritable($class, $property, array $context = []): ?bool { if ( null === ($metadata = $this->getMetadata($class)) - || $metadata instanceof MongoDbClassMetadata && MongoDbClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType + || ($metadata instanceof MongoDbClassMetadata && MongoDbClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType) || !\in_array($property, $metadata->getIdentifierFieldNames(), true) ) { return null; @@ -155,33 +155,12 @@ private function getMetadata(string $class): ?ClassMetadata */ private function getPhpType(string $doctrineType): ?string { - switch ($doctrineType) { - case MongoDbType::INTEGER: - case MongoDbType::INT: - case MongoDbType::INTID: - case MongoDbType::KEY: - return Type::BUILTIN_TYPE_INT; - case MongoDbType::FLOAT: - return Type::BUILTIN_TYPE_FLOAT; - case MongoDbType::STRING: - case MongoDbType::ID: - case MongoDbType::OBJECTID: - case MongoDbType::TIMESTAMP: - case MongoDbType::BINDATA: - case MongoDbType::BINDATABYTEARRAY: - case MongoDbType::BINDATACUSTOM: - case MongoDbType::BINDATAFUNC: - case MongoDbType::BINDATAMD5: - case MongoDbType::BINDATAUUID: - case MongoDbType::BINDATAUUIDRFC4122: - return Type::BUILTIN_TYPE_STRING; - case MongoDbType::BOOLEAN: - case MongoDbType::BOOL: - return Type::BUILTIN_TYPE_BOOL; - } - - return null; + return match ($doctrineType) { + MongoDbType::INTEGER, MongoDbType::INT, MongoDbType::INTID, MongoDbType::KEY => Type::BUILTIN_TYPE_INT, + MongoDbType::FLOAT => Type::BUILTIN_TYPE_FLOAT, + MongoDbType::STRING, MongoDbType::ID, MongoDbType::OBJECTID, MongoDbType::TIMESTAMP, MongoDbType::BINDATA, MongoDbType::BINDATABYTEARRAY, MongoDbType::BINDATACUSTOM, MongoDbType::BINDATAFUNC, MongoDbType::BINDATAMD5, MongoDbType::BINDATAUUID, MongoDbType::BINDATAUUIDRFC4122 => Type::BUILTIN_TYPE_STRING, + MongoDbType::BOOLEAN, MongoDbType::BOOL => Type::BUILTIN_TYPE_BOOL, + default => null, + }; } } - -class_alias(DoctrineExtractor::class, \ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\PropertyInfo\DoctrineExtractor::class); diff --git a/src/Doctrine/Orm/AbstractPaginator.php b/src/Doctrine/Orm/AbstractPaginator.php index 258ab792de9..36599df1d47 100644 --- a/src/Doctrine/Orm/AbstractPaginator.php +++ b/src/Doctrine/Orm/AbstractPaginator.php @@ -20,10 +20,10 @@ abstract class AbstractPaginator implements \IteratorAggregate, PartialPaginatorInterface { - protected $paginator; - protected $iterator; - protected $firstResult; - protected $maxResults; + protected DoctrinePaginator $paginator; + protected array|\Traversable $iterator; + protected ?int $firstResult; + protected ?int $maxResults; /** * @throws InvalidArgumentException @@ -77,5 +77,3 @@ public function count(): int return iterator_count($this->getIterator()); } } - -class_alias(AbstractPaginator::class, \ApiPlatform\Core\Bridge\Doctrine\Orm\AbstractPaginator::class); diff --git a/src/Doctrine/Orm/Extension/EagerLoadingExtension.php b/src/Doctrine/Orm/Extension/EagerLoadingExtension.php index 3081d256610..5cd77106ea9 100644 --- a/src/Doctrine/Orm/Extension/EagerLoadingExtension.php +++ b/src/Doctrine/Orm/Extension/EagerLoadingExtension.php @@ -41,15 +41,12 @@ */ final class EagerLoadingExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface { - /** @var PropertyNameCollectionFactoryInterface */ - private $propertyNameCollectionFactory; - /** @var PropertyMetadataFactoryInterface */ - private $propertyMetadataFactory; - /** @var ClassMetadataFactoryInterface|null */ - private $classMetadataFactory; - private $maxJoins; - private $forceEager; - private $fetchPartial; + private PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory; + private PropertyMetadataFactoryInterface $propertyMetadataFactory; + private ?ClassMetadataFactoryInterface $classMetadataFactory; + private int $maxJoins; + private bool $forceEager; + private bool $fetchPartial; public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, int $maxJoins = 30, bool $forceEager = true, bool $fetchPartial = false, ClassMetadataFactoryInterface $classMetadataFactory = null) { @@ -87,13 +84,8 @@ private function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $ $options = []; - $forceEager = $this->forceEager; - $fetchPartial = $this->fetchPartial; - - if ($operation) { - $forceEager = $operation->getForceEager() ?? $this->forceEager; - $fetchPartial = $operation->getFetchPartial() ?? $this->fetchPartial; - } + $forceEager = $operation?->getForceEager() ?? $this->forceEager; + $fetchPartial = $operation?->getFetchPartial() ?? $this->fetchPartial; if (!isset($context['groups']) && !isset($context['attributes'])) { $contextType = isset($context['api_denormalize']) ? 'denormalization_context' : 'normalization_context'; @@ -139,7 +131,7 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt $currentDepth = $currentDepth > 0 ? $currentDepth - 1 : $currentDepth; $entityManager = $queryBuilder->getEntityManager(); $classMetadata = $entityManager->getClassMetadata($resourceClass); - $attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($resourceClass)->getAttributesMetadata() : []; + $attributesMetadata = $this->classMetadataFactory?->getMetadataFor($resourceClass)->getAttributesMetadata(); foreach ($classMetadata->associationMappings as $association => $mapping) { // Don't join if max depth is enabled and the current depth limit is reached diff --git a/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php b/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php index b67c41a0b7d..55c81125eb1 100644 --- a/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php +++ b/src/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php @@ -29,8 +29,8 @@ */ final class FilterEagerLoadingExtension implements QueryCollectionExtensionInterface { - private $resourceClassResolver; - private $forceEager; + private ?ResourceClassResolverInterface $resourceClassResolver; + private bool $forceEager; public function __construct(bool $forceEager = true, ResourceClassResolverInterface $resourceClassResolver = null) { @@ -50,10 +50,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $em = $queryBuilder->getEntityManager(); $classMetadata = $em->getClassMetadata($resourceClass); - $forceEager = $this->forceEager; - if ($operation) { - $forceEager = $operation->getForceEager() ?? $this->forceEager; - } + $forceEager = $operation?->getForceEager() ?? $this->forceEager; if (!$forceEager && !$this->hasFetchEagerAssociation($em, $classMetadata)) { return; diff --git a/src/Doctrine/Orm/Extension/FilterExtension.php b/src/Doctrine/Orm/Extension/FilterExtension.php index 2d75f61f6e5..f1ccbde02c2 100644 --- a/src/Doctrine/Orm/Extension/FilterExtension.php +++ b/src/Doctrine/Orm/Extension/FilterExtension.php @@ -29,8 +29,7 @@ */ final class FilterExtension implements QueryCollectionExtensionInterface { - /** @var ContainerInterface */ - private $filterLocator; + private ContainerInterface $filterLocator; public function __construct(ContainerInterface $filterLocator) { @@ -46,7 +45,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); } - $resourceFilters = $operation ? $operation->getFilters() : []; + $resourceFilters = $operation?->getFilters(); if (empty($resourceFilters)) { return; diff --git a/src/Doctrine/Orm/Extension/OrderExtension.php b/src/Doctrine/Orm/Extension/OrderExtension.php index 21b063847ae..34297b1f006 100644 --- a/src/Doctrine/Orm/Extension/OrderExtension.php +++ b/src/Doctrine/Orm/Extension/OrderExtension.php @@ -28,7 +28,7 @@ */ final class OrderExtension implements QueryCollectionExtensionInterface { - private $order; + private ?string $order; public function __construct(string $order = null) { @@ -54,7 +54,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $classMetaData = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass); $identifiers = $classMetaData->getIdentifier(); - $defaultOrder = $operation ? ($operation->getOrder() ?? []) : []; + $defaultOrder = $operation?->getOrder() ?? []; if ([] !== $defaultOrder) { foreach ($defaultOrder as $field => $order) { diff --git a/src/Doctrine/Orm/Extension/PaginationExtension.php b/src/Doctrine/Orm/Extension/PaginationExtension.php index f4e470195d5..8873772c2c4 100644 --- a/src/Doctrine/Orm/Extension/PaginationExtension.php +++ b/src/Doctrine/Orm/Extension/PaginationExtension.php @@ -36,8 +36,8 @@ class_exists(AbstractPaginator::class); */ final class PaginationExtension implements QueryResultCollectionExtensionInterface { - private $managerRegistry; - private $pagination; + private ManagerRegistry $managerRegistry; + private ?Pagination $pagination; public function __construct(ManagerRegistry $managerRegistry, Pagination $pagination) { @@ -132,7 +132,7 @@ private function addCountToContext(QueryBuilder $queryBuilder, array $context): */ private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryBuilder, Operation $operation = null, array $context = []): bool { - $fetchJoinCollection = $operation ? $operation->getPaginationFetchJoinCollection() : null; + $fetchJoinCollection = $operation?->getPaginationFetchJoinCollection(); if ((isset($context['collection_operation_name']) || isset($context['operation_name'])) && isset($fetchJoinCollection)) { return $fetchJoinCollection; @@ -165,7 +165,7 @@ private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryB */ private function shouldDoctrinePaginatorUseOutputWalkers(QueryBuilder $queryBuilder, Operation $operation = null, array $context = []): bool { - $useOutputWalkers = $operation ? $operation->getPaginationUseOutputWalkers() : null; + $useOutputWalkers = $operation?->getPaginationUseOutputWalkers(); if ((isset($context['collection_operation_name']) || isset($context['operation_name'])) && isset($useOutputWalkers)) { return $useOutputWalkers; diff --git a/src/Doctrine/Orm/Filter/AbstractFilter.php b/src/Doctrine/Orm/Filter/AbstractFilter.php index 500d88187bc..d34e0770951 100644 --- a/src/Doctrine/Orm/Filter/AbstractFilter.php +++ b/src/Doctrine/Orm/Filter/AbstractFilter.php @@ -28,14 +28,10 @@ abstract class AbstractFilter implements FilterInterface use OrmPropertyHelperTrait; use PropertyHelperTrait; - /** @var ManagerRegistry */ - protected $managerRegistry; - /** @var LoggerInterface */ - protected $logger; - /** @var array|null */ - protected $properties; - /** @var NameConverterInterface|null */ - protected $nameConverter; + protected ManagerRegistry $managerRegistry; + protected LoggerInterface $logger; + protected ?array $properties; + protected ?NameConverterInterface $nameConverter; public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) { diff --git a/src/Doctrine/Orm/Filter/DateFilter.php b/src/Doctrine/Orm/Filter/DateFilter.php index 34c4b39359f..07a67dfd885 100644 --- a/src/Doctrine/Orm/Filter/DateFilter.php +++ b/src/Doctrine/Orm/Filter/DateFilter.php @@ -128,11 +128,8 @@ protected function filterProperty(string $property, $values, QueryBuilder $query /** * Adds the where clause according to the chosen null management. - * - * @param mixed $value - * @param DBALType|string $type */ - protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, $value, string $nullManagement = null, $type = null) + protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, mixed $value, string $nullManagement = null, DBALType|string $type = null) { $type = (string) $type; $value = $this->normalizeValue($value, $operator); diff --git a/src/Doctrine/Orm/Filter/OrderFilter.php b/src/Doctrine/Orm/Filter/OrderFilter.php index c34a9db0782..580390d0f62 100644 --- a/src/Doctrine/Orm/Filter/OrderFilter.php +++ b/src/Doctrine/Orm/Filter/OrderFilter.php @@ -40,7 +40,7 @@ final class OrderFilter extends AbstractFilter implements OrderFilterInterface { use OrderFilterTrait; - private $orderNullsComparison; + private ?string $orderNullsComparison; public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null, ?string $orderNullsComparison = null) { diff --git a/src/Doctrine/Orm/Filter/SearchFilter.php b/src/Doctrine/Orm/Filter/SearchFilter.php index aa9da18ad33..a6ffe5b0d59 100644 --- a/src/Doctrine/Orm/Filter/SearchFilter.php +++ b/src/Doctrine/Orm/Filter/SearchFilter.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\Filter; +use ApiPlatform\Api\IdentifiersExtractorInterface; use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface; use ApiPlatform\Doctrine\Common\Filter\SearchFilterTrait; @@ -40,11 +41,12 @@ final class SearchFilter extends AbstractFilter implements SearchFilterInterface public const DOCTRINE_INTEGER_TYPE = Types::INTEGER; - public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, IdentifiersExtractorInterface $identifiersExtractor = null, NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); $this->iriConverter = $iriConverter; + $this->identifiersExtractor = $identifiersExtractor; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } @@ -145,11 +147,9 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB /** * Adds where clause according to the strategy. * - * @param mixed $values - * * @throws InvalidArgumentException If strategy does not exist */ - protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $values, bool $caseSensitive): void + protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, mixed $values, bool $caseSensitive): void { if (!\is_array($values)) { $values = [$values]; @@ -181,34 +181,31 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild $keyValueParameter = sprintf('%s_%s', $valueParameter, $key); $parameters[$caseSensitive ? $value : strtolower($value)] = $keyValueParameter; - switch ($strategy) { - case self::STRATEGY_PARTIAL: - $ors[] = $queryBuilder->expr()->like( - $wrapCase($aliasedField), - $wrapCase((string) $queryBuilder->expr()->concat("'%'", $keyValueParameter, "'%'")) - ); - break; - case self::STRATEGY_START: - $ors[] = $queryBuilder->expr()->like( + $ors[] = match ($strategy) { + self::STRATEGY_PARTIAL => $queryBuilder->expr()->like( + $wrapCase($aliasedField), + $wrapCase((string) $queryBuilder->expr()->concat("'%'", $keyValueParameter, "'%'")) + ), + self::STRATEGY_START => $queryBuilder->expr()->like( + $wrapCase($aliasedField), + $wrapCase((string) $queryBuilder->expr()->concat($keyValueParameter, "'%'")) + ), + self::STRATEGY_END => $queryBuilder->expr()->like( + $wrapCase($aliasedField), + $wrapCase((string) $queryBuilder->expr()->concat("'%'", $keyValueParameter)) + ), + self::STRATEGY_WORD_START => $queryBuilder->expr()->orX( + $queryBuilder->expr()->like( $wrapCase($aliasedField), $wrapCase((string) $queryBuilder->expr()->concat($keyValueParameter, "'%'")) - ); - break; - case self::STRATEGY_END: - $ors[] = $queryBuilder->expr()->like( + ), + $queryBuilder->expr()->like( $wrapCase($aliasedField), - $wrapCase((string) $queryBuilder->expr()->concat("'%'", $keyValueParameter)) - ); - break; - case self::STRATEGY_WORD_START: - $ors[] = $queryBuilder->expr()->orX( - $queryBuilder->expr()->like($wrapCase($aliasedField), $wrapCase((string) $queryBuilder->expr()->concat($keyValueParameter, "'%'"))), - $queryBuilder->expr()->like($wrapCase($aliasedField), $wrapCase((string) $queryBuilder->expr()->concat("'% '", $keyValueParameter, "'%'"))) - ); - break; - default: - throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy)); - } + $wrapCase((string) $queryBuilder->expr()->concat("'% '", $keyValueParameter, "'%'")) + ) + ), + default => throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy)), + }; } $queryBuilder->andWhere($queryBuilder->expr()->orX(...$ors)); @@ -238,28 +235,13 @@ protected function createWrapCase(bool $caseSensitive): \Closure */ protected function getType(string $doctrineType): string { - switch ($doctrineType) { - case Types::ARRAY: - return 'array'; - case Types::BIGINT: - case Types::INTEGER: - case Types::SMALLINT: - return 'int'; - case Types::BOOLEAN: - return 'bool'; - case Types::DATE_MUTABLE: - case Types::TIME_MUTABLE: - case Types::DATETIME_MUTABLE: - case Types::DATETIMETZ_MUTABLE: - case Types::DATE_IMMUTABLE: - case Types::TIME_IMMUTABLE: - case Types::DATETIME_IMMUTABLE: - case Types::DATETIMETZ_IMMUTABLE: - return \DateTimeInterface::class; - case Types::FLOAT: - return 'float'; - } - - return 'string'; + return match ($doctrineType) { + Types::ARRAY => 'array', + Types::BIGINT, Types::INTEGER, Types::SMALLINT => 'int', + Types::BOOLEAN => 'bool', + Types::DATE_MUTABLE, Types::TIME_MUTABLE, Types::DATETIME_MUTABLE, Types::DATETIMETZ_MUTABLE, Types::DATE_IMMUTABLE, Types::TIME_IMMUTABLE, Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_IMMUTABLE => \DateTimeInterface::class, + Types::FLOAT => 'float', + default => 'string', + }; } } diff --git a/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php b/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php index 611640e1424..5b08a8f202d 100644 --- a/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php +++ b/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php @@ -25,8 +25,8 @@ */ final class DoctrineOrmPropertyMetadataFactory implements PropertyMetadataFactoryInterface { - private $decorated; - private $managerRegistry; + private PropertyMetadataFactoryInterface $decorated; + private ManagerRegistry $managerRegistry; public function __construct(ManagerRegistry $managerRegistry, PropertyMetadataFactoryInterface $decorated) { diff --git a/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php b/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php index 7f50d12977b..c594009ac62 100644 --- a/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php +++ b/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php @@ -27,15 +27,9 @@ final class DoctrineOrmResourceCollectionMetadataFactory implements ResourceMetadataCollectionFactoryInterface { - /** - * @var ManagerRegistry - */ - private $managerRegistry; + private ManagerRegistry $managerRegistry; - /** - * @var ResourceMetadataCollectionFactoryInterface - */ - private $decorated; + private ResourceMetadataCollectionFactoryInterface $decorated; public function __construct(ManagerRegistry $managerRegistry, ResourceMetadataCollectionFactoryInterface $decorated) { diff --git a/src/Doctrine/Orm/Paginator.php b/src/Doctrine/Orm/Paginator.php index 21a3d18d22b..df61f23ce18 100644 --- a/src/Doctrine/Orm/Paginator.php +++ b/src/Doctrine/Orm/Paginator.php @@ -23,10 +23,7 @@ */ final class Paginator extends AbstractPaginator implements PaginatorInterface, QueryAwareInterface { - /** - * @var int|null - */ - private $totalItems; + private ?int $totalItems; /** * {@inheritdoc} @@ -56,5 +53,3 @@ public function getQuery(): Query return $this->paginator->getQuery(); } } - -class_alias(Paginator::class, \ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator::class); diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index 971336581ce..3b85f79b905 100644 --- a/src/Doctrine/Orm/State/CollectionProvider.php +++ b/src/Doctrine/Orm/State/CollectionProvider.php @@ -34,9 +34,9 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerTrait; - private $resourceMetadataCollectionFactory; - private $managerRegistry; - private $collectionExtensions; + private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory; + private ManagerRegistry $managerRegistry; + private iterable $collectionExtensions; /** * @param QueryCollectionExtensionInterface[] $collectionExtensions diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index 0021ebfe2ef..414da6214b5 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -34,9 +34,9 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerTrait; - private $resourceMetadataCollectionFactory; - private $managerRegistry; - private $itemExtensions; + private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory; + private ManagerRegistry $managerRegistry; + private iterable $itemExtensions; /** * @param QueryItemExtensionInterface[] $itemExtensions diff --git a/src/Doctrine/Orm/Util/QueryBuilderHelper.php b/src/Doctrine/Orm/Util/QueryBuilderHelper.php index 2d6790da33c..60f6c7371a4 100644 --- a/src/Doctrine/Orm/Util/QueryBuilderHelper.php +++ b/src/Doctrine/Orm/Util/QueryBuilderHelper.php @@ -208,7 +208,7 @@ private static function mapJoinAliases(iterable $joins): array $alias = $join->getAlias(); $relationship = $join->getJoin(); - if (false !== strpos($relationship, '.')) { + if (str_contains($relationship, '.')) { $aliasMap[$alias] = explode('.', $relationship); } else { $aliasMap[$alias] = $relationship; @@ -218,5 +218,3 @@ private static function mapJoinAliases(iterable $joins): array return $aliasMap; } } - -class_alias(QueryBuilderHelper::class, \ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper::class); diff --git a/src/Doctrine/Orm/Util/QueryChecker.php b/src/Doctrine/Orm/Util/QueryChecker.php index 92fa096b878..04941e1e7cd 100644 --- a/src/Doctrine/Orm/Util/QueryChecker.php +++ b/src/Doctrine/Orm/Util/QueryChecker.php @@ -120,7 +120,7 @@ public static function hasOrderByOnFetchJoinedToManyAssociation(QueryBuilder $qu foreach ($orderByParts as $orderBy) { foreach ($orderBy->getParts() as $part) { - if (false !== strpos($part, '.')) { + if (str_contains($part, '.')) { [$alias] = explode('.', $part); $orderByAliases[] = $alias; diff --git a/src/Doctrine/Orm/Util/QueryNameGenerator.php b/src/Doctrine/Orm/Util/QueryNameGenerator.php index bf049615c7d..810a1e050c4 100644 --- a/src/Doctrine/Orm/Util/QueryNameGenerator.php +++ b/src/Doctrine/Orm/Util/QueryNameGenerator.php @@ -22,8 +22,8 @@ */ final class QueryNameGenerator implements QueryNameGeneratorInterface { - private $incrementedAssociation = 1; - private $incrementedName = 1; + private int $incrementedAssociation = 1; + private int $incrementedName = 1; /** * {@inheritdoc} @@ -41,5 +41,3 @@ public function generateParameterName(string $name): string return sprintf('%s_p%d', str_replace('.', '_', $name), $this->incrementedName++); } } - -class_alias(QueryNameGenerator::class, \ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator::class); diff --git a/src/Documentation/Action/DocumentationAction.php b/src/Documentation/Action/DocumentationAction.php index b470f185cd8..2b78e9ff9a5 100644 --- a/src/Documentation/Action/DocumentationAction.php +++ b/src/Documentation/Action/DocumentationAction.php @@ -13,8 +13,6 @@ namespace ApiPlatform\Documentation\Action; -use ApiPlatform\Core\Api\FormatsProviderInterface; -use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface as LegacyOpenApiFactoryInterface; use ApiPlatform\Documentation\Documentation; use ApiPlatform\Documentation\DocumentationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; @@ -29,45 +27,19 @@ */ final class DocumentationAction { - private $resourceNameCollectionFactory; - private $title; - private $description; - private $version; - private $formats; - private $formatsProvider; - private $swaggerVersions; - private $openApiFactory; + private ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory; + private string $title; + private string $description; + private string $version; + private OpenApiFactoryInterface $openApiFactory; - /** - * @param int[] $swaggerVersions - * @param mixed|array|FormatsProviderInterface $formatsProvider - * @param LegacyOpenApiFactoryInterface|OpenApiFactoryInterface $openApiFactory - */ - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, string $title = '', string $description = '', string $version = '', $formatsProvider = null, array $swaggerVersions = [2, 3], $openApiFactory = null) + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, string $title = '', string $description = '', string $version = '', OpenApiFactoryInterface $openApiFactory = null) { $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->title = $title; $this->description = $description; $this->version = $version; - $this->swaggerVersions = $swaggerVersions; $this->openApiFactory = $openApiFactory; - - if (null === $openApiFactory) { - @trigger_error(sprintf('Not passing an instance of "%s" as 7th parameter of the constructor of "%s" is deprecated since API Platform 2.6', OpenApiFactoryInterface::class, __CLASS__), \E_USER_DEPRECATED); - } - - if (null === $formatsProvider) { - return; - } - - @trigger_error(sprintf('Passing an array or an instance of "%s" as 5th parameter of the constructor of "%s" is deprecated since API Platform 2.5', FormatsProviderInterface::class, __CLASS__), \E_USER_DEPRECATED); - if (\is_array($formatsProvider)) { - $this->formats = $formatsProvider; - - return; - } - - $this->formatsProvider = $formatsProvider; } public function __invoke(Request $request = null): DocumentationInterface @@ -82,17 +54,10 @@ public function __invoke(Request $request = null): DocumentationInterface $attributes = RequestAttributesExtractor::extractAttributes($request); } - // BC check to be removed in 3.0 - if (null !== $this->formatsProvider) { - $this->formats = $this->formatsProvider->getFormatsFromAttributes($attributes ?? []); - } - if ('json' === $request->getRequestFormat() && null !== $this->openApiFactory && 3 === ($context['spec_version'] ?? null)) { return $this->openApiFactory->__invoke($context ?? []); } - return new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version, $this->formats); + return new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version); } } - -class_alias(DocumentationAction::class, \ApiPlatform\Core\Documentation\Action\DocumentationAction::class); diff --git a/src/Documentation/Documentation.php b/src/Documentation/Documentation.php index fdc2cd3b8b0..82b8e7e802a 100644 --- a/src/Documentation/Documentation.php +++ b/src/Documentation/Documentation.php @@ -26,33 +26,13 @@ final class Documentation implements DocumentationInterface private $title; private $description; private $version; - private $mimeTypes = []; - public function __construct(ResourceNameCollection $resourceNameCollection, string $title = '', string $description = '', string $version = '', array $formats = null) + public function __construct(ResourceNameCollection $resourceNameCollection, string $title = '', string $description = '', string $version = '') { $this->resourceNameCollection = $resourceNameCollection; $this->title = $title; $this->description = $description; $this->version = $version; - - if (null === $formats) { - return; - } - - @trigger_error(sprintf('Passing a 5th parameter to the constructor of "%s" is deprecated since API Platform 2.5', __CLASS__), \E_USER_DEPRECATED); - - foreach ($formats as $mimeTypes) { - foreach ($mimeTypes as $mimeType) { - $this->mimeTypes[] = $mimeType; - } - } - } - - public function getMimeTypes(): array - { - @trigger_error(sprintf('The method "%s" is deprecated since API Platform 2.5, use the "formats" attribute instead', __METHOD__), \E_USER_DEPRECATED); - - return $this->mimeTypes; } public function getVersion(): string @@ -75,5 +55,3 @@ public function getResourceNameCollection(): ResourceNameCollection return $this->resourceNameCollection; } } - -class_alias(Documentation::class, \ApiPlatform\Core\Documentation\Documentation::class); diff --git a/src/Elasticsearch/Exception/IndexNotFoundException.php b/src/Elasticsearch/Exception/IndexNotFoundException.php index 13ccd4846b2..c12ca5d6b9e 100644 --- a/src/Elasticsearch/Exception/IndexNotFoundException.php +++ b/src/Elasticsearch/Exception/IndexNotFoundException.php @@ -25,5 +25,3 @@ final class IndexNotFoundException extends \Exception implements ExceptionInterface { } - -class_alias(IndexNotFoundException::class, \ApiPlatform\Core\Bridge\Elasticsearch\Exception\IndexNotFoundException::class); diff --git a/src/Elasticsearch/Exception/NonUniqueIdentifierException.php b/src/Elasticsearch/Exception/NonUniqueIdentifierException.php index 6851f208cee..e2d007b1f70 100644 --- a/src/Elasticsearch/Exception/NonUniqueIdentifierException.php +++ b/src/Elasticsearch/Exception/NonUniqueIdentifierException.php @@ -25,5 +25,3 @@ final class NonUniqueIdentifierException extends \Exception implements ExceptionInterface { } - -class_alias(NonUniqueIdentifierException::class, \ApiPlatform\Core\Bridge\Elasticsearch\Exception\NonUniqueIdentifierException::class); diff --git a/src/Elasticsearch/Extension/AbstractFilterExtension.php b/src/Elasticsearch/Extension/AbstractFilterExtension.php index 65438c3d1a1..05157429d34 100644 --- a/src/Elasticsearch/Extension/AbstractFilterExtension.php +++ b/src/Elasticsearch/Extension/AbstractFilterExtension.php @@ -25,8 +25,7 @@ */ abstract class AbstractFilterExtension implements RequestBodySearchCollectionExtensionInterface { - /** @var ContainerInterface */ - private $filterLocator; + private ContainerInterface $filterLocator; public function __construct(ContainerInterface $filterLocator) { @@ -38,7 +37,7 @@ public function __construct(ContainerInterface $filterLocator) */ public function applyToCollection(array $requestBody, string $resourceClass, ?Operation $operation = null, array $context = []): array { - $resourceFilters = $operation ? $operation->getFilters() : null; + $resourceFilters = $operation?->getFilters(); if (!$resourceFilters) { return $requestBody; diff --git a/src/Elasticsearch/Filter/AbstractFilter.php b/src/Elasticsearch/Filter/AbstractFilter.php index eb5b9b471c3..70a14f91440 100644 --- a/src/Elasticsearch/Filter/AbstractFilter.php +++ b/src/Elasticsearch/Filter/AbstractFilter.php @@ -33,9 +33,9 @@ abstract class AbstractFilter implements FilterInterface { use FieldDatatypeTrait { getNestedFieldPath as protected; } - protected $properties; - protected $propertyNameCollectionFactory; - protected $nameConverter; + protected ?array $properties; + protected PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory; + protected ?NameConverterInterface $nameConverter; public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, ?NameConverterInterface $nameConverter = null, ?array $properties = null) { diff --git a/src/Elasticsearch/Filter/OrderFilter.php b/src/Elasticsearch/Filter/OrderFilter.php index edb87c21be1..a7f1445c826 100644 --- a/src/Elasticsearch/Filter/OrderFilter.php +++ b/src/Elasticsearch/Filter/OrderFilter.php @@ -30,7 +30,7 @@ */ final class OrderFilter extends AbstractFilter implements SortFilterInterface { - private $orderParameterName; + private string $orderParameterName; /** * {@inheritdoc} diff --git a/src/Elasticsearch/Metadata/Document/DocumentMetadata.php b/src/Elasticsearch/Metadata/Document/DocumentMetadata.php index c035862c97a..8933d8cb260 100644 --- a/src/Elasticsearch/Metadata/Document/DocumentMetadata.php +++ b/src/Elasticsearch/Metadata/Document/DocumentMetadata.php @@ -26,8 +26,8 @@ final class DocumentMetadata { public const DEFAULT_TYPE = '_doc'; - private $index; - private $type; + private ?string $index; + private string $type; public function __construct(?string $index = null, string $type = self::DEFAULT_TYPE) { @@ -73,5 +73,3 @@ public function getType(): string return $this->type; } } - -class_alias(DocumentMetadata::class, \ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\DocumentMetadata::class); diff --git a/src/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php b/src/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php index 0bd6c20fa44..1bda4f5a540 100644 --- a/src/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php +++ b/src/Elasticsearch/Metadata/Document/Factory/AttributeDocumentMetadataFactory.php @@ -13,12 +13,9 @@ namespace ApiPlatform\Elasticsearch\Metadata\Document\Factory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; /** * Creates document's metadata using the attribute configuration. @@ -29,16 +26,10 @@ */ final class AttributeDocumentMetadataFactory implements DocumentMetadataFactoryInterface { - /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface - */ - private $resourceMetadataFactory; - private $decorated; + private ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory; + private ?DocumentMetadataFactoryInterface $decorated; - /** - * @param ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory - */ - public function __construct($resourceMetadataFactory, ?DocumentMetadataFactoryInterface $decorated = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, ?DocumentMetadataFactoryInterface $decorated = null) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->decorated = $decorated; @@ -61,10 +52,9 @@ public function create(string $resourceClass): DocumentMetadata $resourceMetadata = null; if (!$documentMetadata || null === $documentMetadata->getIndex()) { - /** @var ResourceMetadata|ResourceMetadataCollection */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $index = $resourceMetadata instanceof ResourceMetadata ? $resourceMetadata->getAttribute('elasticsearch_index') : ($resourceMetadata->getOperation()->getExtraProperties()['elasticsearch_index'] ?? null); + $index = $resourceMetadata->getOperation()->getExtraProperties()['elasticsearch_index'] ?? null; if (null !== $index) { $documentMetadata = $documentMetadata ? $documentMetadata->withIndex($index) : new DocumentMetadata($index); @@ -72,9 +62,8 @@ public function create(string $resourceClass): DocumentMetadata } if (!$documentMetadata || DocumentMetadata::DEFAULT_TYPE === $documentMetadata->getType()) { - /** @var ResourceMetadata|ResourceMetadataCollection */ $resourceMetadata = $resourceMetadata ?? $this->resourceMetadataFactory->create($resourceClass); - $type = $resourceMetadata instanceof ResourceMetadata ? $resourceMetadata->getAttribute('elasticsearch_type') : ($resourceMetadata->getOperation()->getExtraProperties()['elasticsearch_type'] ?? null); + $type = $resourceMetadata->getOperation()->getExtraProperties()['elasticsearch_type'] ?? null; if (null !== $type) { $documentMetadata = $documentMetadata ? $documentMetadata->withType($type) : new DocumentMetadata(null, $type); @@ -88,5 +77,3 @@ public function create(string $resourceClass): DocumentMetadata throw new IndexNotFoundException(sprintf('No index associated with the "%s" resource class.', $resourceClass)); } } - -class_alias(AttributeDocumentMetadataFactory::class, \ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\AttributeDocumentMetadataFactory::class); diff --git a/src/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php b/src/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php index 6f28aa420e3..4ec1d580cf1 100644 --- a/src/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php +++ b/src/Elasticsearch/Metadata/Document/Factory/CachedDocumentMetadataFactory.php @@ -29,9 +29,9 @@ final class CachedDocumentMetadataFactory implements DocumentMetadataFactoryInte { private const CACHE_KEY_PREFIX = 'index_metadata'; - private $cacheItemPool; - private $decorated; - private $localCache = []; + private CacheItemPoolInterface $cacheItemPool; + private DocumentMetadataFactoryInterface $decorated; + private array $localCache = []; public function __construct(CacheItemPoolInterface $cacheItemPool, DocumentMetadataFactoryInterface $decorated) { @@ -78,5 +78,3 @@ private function handleNotFound(DocumentMetadata $documentMetadata, string $reso return $documentMetadata; } } - -class_alias(CachedDocumentMetadataFactory::class, \ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\CachedDocumentMetadataFactory::class); diff --git a/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php b/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php index 6f89047ff93..51224775edb 100644 --- a/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php +++ b/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php @@ -13,12 +13,9 @@ namespace ApiPlatform\Elasticsearch\Metadata\Document\Factory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Util\Inflector; use Elasticsearch\Client; use Elasticsearch\Common\Exceptions\Missing404Exception; @@ -34,17 +31,11 @@ */ final class CatDocumentMetadataFactory implements DocumentMetadataFactoryInterface { - private $client; - /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface - */ - private $resourceMetadataFactory; - private $decorated; + private Client $client; + private ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory; + private ?DocumentMetadataFactoryInterface $decorated; - /** - * @param ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory - */ - public function __construct(Client $client, $resourceMetadataFactory, ?DocumentMetadataFactoryInterface $decorated = null) + public function __construct(Client $client, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, ?DocumentMetadataFactoryInterface $decorated = null) { $this->client = $client; $this->resourceMetadataFactory = $resourceMetadataFactory; @@ -69,13 +60,8 @@ public function create(string $resourceClass): DocumentMetadata return $documentMetadata; } - /** @var ResourceMetadata|ResourceMetadataCollection */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if ($resourceMetadata instanceof ResourceMetadata) { - $resourceShortName = $resourceMetadata->getShortName(); - } else { - $resourceShortName = $resourceMetadata->getOperation()->getShortName(); - } + $resourceShortName = $resourceMetadata->getOperation()->getShortName(); if (null === $resourceShortName) { return $this->handleNotFound($documentMetadata, $resourceClass); @@ -104,5 +90,3 @@ private function handleNotFound(?DocumentMetadata $documentMetadata, string $res throw new IndexNotFoundException(sprintf('No index associated with the "%s" resource class.', $resourceClass)); } } - -class_alias(CatDocumentMetadataFactory::class, \ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\CatDocumentMetadataFactory::class); diff --git a/src/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php b/src/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php index d194d46576e..eb56bd0709e 100644 --- a/src/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php +++ b/src/Elasticsearch/Metadata/Document/Factory/ConfiguredDocumentMetadataFactory.php @@ -25,8 +25,8 @@ */ final class ConfiguredDocumentMetadataFactory implements DocumentMetadataFactoryInterface { - private $mapping; - private $decorated; + private array $mapping; + private ?DocumentMetadataFactoryInterface $decorated; public function __construct(array $mapping, ?DocumentMetadataFactoryInterface $decorated = null) { @@ -69,5 +69,3 @@ public function create(string $resourceClass): DocumentMetadata return $documentMetadata; } } - -class_alias(ConfiguredDocumentMetadataFactory::class, \ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\ConfiguredDocumentMetadataFactory::class); diff --git a/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php b/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php index f61ab4179e7..2f69b874378 100644 --- a/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php +++ b/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php @@ -24,12 +24,9 @@ final class ElasticsearchProviderResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface { - /** - * @var ResourceMetadataCollectionFactoryInterface - */ - private $decorated; + private ResourceMetadataCollectionFactoryInterface $decorated; - private $client; + private Client $client; public function __construct(Client $client, ResourceMetadataCollectionFactoryInterface $decorated) { diff --git a/src/Elasticsearch/Paginator.php b/src/Elasticsearch/Paginator.php index 87837e5eb64..182a0c70c61 100644 --- a/src/Elasticsearch/Paginator.php +++ b/src/Elasticsearch/Paginator.php @@ -27,13 +27,13 @@ */ final class Paginator implements \IteratorAggregate, PaginatorInterface { - private $denormalizer; - private $documents; - private $resourceClass; - private $limit; - private $offset; - private $cachedDenormalizedDocuments = []; - private $denormalizationContext = []; + private DenormalizerInterface $denormalizer; + private array $documents; + private string $resourceClass; + private int $limit; + private int $offset; + private array $cachedDenormalizedDocuments = []; + private array $denormalizationContext = []; public function __construct(DenormalizerInterface $denormalizer, array $documents, string $resourceClass, int $limit, int $offset, array $denormalizationContext = []) { @@ -128,5 +128,3 @@ public function getIterator(): \Traversable } } } - -class_alias(Paginator::class, \ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Paginator::class); diff --git a/src/Elasticsearch/Serializer/DocumentNormalizer.php b/src/Elasticsearch/Serializer/DocumentNormalizer.php index dec82b9c5cf..a90a72595ec 100644 --- a/src/Elasticsearch/Serializer/DocumentNormalizer.php +++ b/src/Elasticsearch/Serializer/DocumentNormalizer.php @@ -119,5 +119,3 @@ private function populateIdentifier(array $data, string $class): array return $data; } } - -class_alias(DocumentNormalizer::class, \ApiPlatform\Core\Bridge\Elasticsearch\Serializer\DocumentNormalizer::class); diff --git a/src/Elasticsearch/Serializer/ItemNormalizer.php b/src/Elasticsearch/Serializer/ItemNormalizer.php index f88d1637d66..3b2d6eb13c1 100644 --- a/src/Elasticsearch/Serializer/ItemNormalizer.php +++ b/src/Elasticsearch/Serializer/ItemNormalizer.php @@ -30,7 +30,7 @@ final class ItemNormalizer implements NormalizerInterface, DenormalizerInterface { public const FORMAT = 'elasticsearch'; - private $decorated; + private NormalizerInterface $decorated; public function __construct(NormalizerInterface $decorated) { @@ -111,5 +111,3 @@ public function setSerializer(SerializerInterface $serializer) $this->decorated->setSerializer($serializer); } } - -class_alias(ItemNormalizer::class, \ApiPlatform\Core\Bridge\Elasticsearch\Serializer\ItemNormalizer::class); diff --git a/src/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php b/src/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php index 8c469cfd87e..d98b9dcabf8 100644 --- a/src/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php +++ b/src/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php @@ -26,7 +26,7 @@ */ final class InnerFieldsNameConverter implements AdvancedNameConverterInterface { - private $decorated; + private NameConverterInterface $decorated; public function __construct(?NameConverterInterface $decorated = null) { @@ -60,5 +60,3 @@ private function convertInnerFields(string $propertyName, bool $normalization, s return implode('.', $convertedProperties); } } - -class_alias(InnerFieldsNameConverter::class, \ApiPlatform\Core\Bridge\Elasticsearch\Serializer\NameConverter\InnerFieldsNameConverter::class); diff --git a/src/Elasticsearch/State/CollectionProvider.php b/src/Elasticsearch/State/CollectionProvider.php index 6c882e1246d..f85106c97ff 100644 --- a/src/Elasticsearch/State/CollectionProvider.php +++ b/src/Elasticsearch/State/CollectionProvider.php @@ -33,11 +33,11 @@ */ final class CollectionProvider implements ProviderInterface { - private $client; - private $documentMetadataFactory; - private $denormalizer; - private $pagination; - private $collectionExtensions; + private Client $client; + private DocumentMetadataFactoryInterface $documentMetadataFactory; + private DenormalizerInterface $denormalizer; + private Pagination $pagination; + private iterable $collectionExtensions; /** * @param RequestBodySearchCollectionExtensionInterface[] $collectionExtensions @@ -59,7 +59,6 @@ public function __construct(Client $client, DocumentMetadataFactoryInterface $do public function provide(Operation $operation, array $uriVariables = [], array $context = []) { $resourceClass = $operation->getClass(); - $operationName = $operation->getName(); $documentMetadata = $this->documentMetadataFactory->create($resourceClass); $body = []; diff --git a/src/Elasticsearch/State/ItemProvider.php b/src/Elasticsearch/State/ItemProvider.php index d0af8c93207..f36ac6fcc54 100644 --- a/src/Elasticsearch/State/ItemProvider.php +++ b/src/Elasticsearch/State/ItemProvider.php @@ -33,9 +33,9 @@ */ final class ItemProvider implements ProviderInterface { - private $client; - private $documentMetadataFactory; - private $denormalizer; + private Client $client; + private DocumentMetadataFactoryInterface $documentMetadataFactory; + private DenormalizerInterface $denormalizer; public function __construct(Client $client, DocumentMetadataFactoryInterface $documentMetadataFactory, DenormalizerInterface $denormalizer) { diff --git a/src/Elasticsearch/Util/FieldDatatypeTrait.php b/src/Elasticsearch/Util/FieldDatatypeTrait.php index a944623d706..df518f2b0d1 100644 --- a/src/Elasticsearch/Util/FieldDatatypeTrait.php +++ b/src/Elasticsearch/Util/FieldDatatypeTrait.php @@ -14,10 +14,7 @@ namespace ApiPlatform\Elasticsearch\Util; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; use ApiPlatform\Exception\PropertyNotFoundException; -use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use Symfony\Component\PropertyInfo\Type; @@ -32,15 +29,9 @@ */ trait FieldDatatypeTrait { - /** - * @var PropertyMetadataFactoryInterface|LegacyPropertyMetadataFactoryInterface - */ - private $propertyMetadataFactory; + private PropertyMetadataFactoryInterface $propertyMetadataFactory; - /** - * @var ResourceClassResolverInterface - */ - private $resourceClassResolver; + private ResourceClassResolverInterface $resourceClassResolver; /** * Is the decomposed given property of the given resource class potentially mapped as a nested field in Elasticsearch? @@ -63,20 +54,13 @@ private function getNestedFieldPath(string $resourceClass, string $property): ?s } try { - /** @var ApiProperty|PropertyMetadata $propertyMetadata */ $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $currentProperty); } catch (PropertyNotFoundException $e) { return null; } - // TODO: 3.0 this is the default + allow multiple types - if ($propertyMetadata instanceof ApiProperty) { // @phpstan-ignore-line - $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; - } - - if ($propertyMetadata instanceof PropertyMetadata) { - $type = $propertyMetadata->getType(); - } + // TODO: 3.0 allow multiple types + $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; if (null === $type) { return null; @@ -104,5 +88,3 @@ private function getNestedFieldPath(string $resourceClass, string $property): ?s return null; } } - -class_alias(FieldDatatypeTrait::class, \ApiPlatform\Core\Bridge\Elasticsearch\Util\FieldDatatypeTrait::class); diff --git a/src/Exception/DeserializationException.php b/src/Exception/DeserializationException.php index 21e719cf410..37dfcb55772 100644 --- a/src/Exception/DeserializationException.php +++ b/src/Exception/DeserializationException.php @@ -24,5 +24,3 @@ class DeserializationException extends \Exception implements ExceptionInterface, SerializerExceptionInterface { } - -class_alias(DeserializationException::class, \ApiPlatform\Core\Exception\DeserializationException::class); diff --git a/src/Exception/FilterValidationException.php b/src/Exception/FilterValidationException.php index b350f14c9d9..4fe0cbb1c7f 100644 --- a/src/Exception/FilterValidationException.php +++ b/src/Exception/FilterValidationException.php @@ -34,5 +34,3 @@ public function __toString(): string return implode("\n", $this->constraintViolationList); } } - -class_alias(FilterValidationException::class, \ApiPlatform\Core\Exception\FilterValidationException::class); diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index f691a1e94c8..bddf0a57112 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -21,5 +21,3 @@ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } - -class_alias(InvalidArgumentException::class, \ApiPlatform\Core\Exception\InvalidArgumentException::class); diff --git a/src/Exception/InvalidIdentifierException.php b/src/Exception/InvalidIdentifierException.php index f7ae0c6d851..961aad89db1 100644 --- a/src/Exception/InvalidIdentifierException.php +++ b/src/Exception/InvalidIdentifierException.php @@ -21,5 +21,3 @@ final class InvalidIdentifierException extends \Exception implements ExceptionInterface { } - -class_alias(InvalidIdentifierException::class, \ApiPlatform\Core\Exception\InvalidIdentifierException::class); diff --git a/src/Exception/InvalidResourceException.php b/src/Exception/InvalidResourceException.php index 580ad5ac473..a69543355d7 100644 --- a/src/Exception/InvalidResourceException.php +++ b/src/Exception/InvalidResourceException.php @@ -21,5 +21,3 @@ class InvalidResourceException extends \Exception implements ExceptionInterface { } - -class_alias(InvalidResourceException::class, \ApiPlatform\Core\Exception\InvalidResourceException::class); diff --git a/src/Exception/InvalidValueException.php b/src/Exception/InvalidValueException.php index 74e93c00417..96b023fdbae 100644 --- a/src/Exception/InvalidValueException.php +++ b/src/Exception/InvalidValueException.php @@ -16,5 +16,3 @@ class InvalidValueException extends InvalidArgumentException { } - -class_alias(InvalidValueException::class, \ApiPlatform\Core\Exception\InvalidValueException::class); diff --git a/src/Exception/ItemNotFoundException.php b/src/Exception/ItemNotFoundException.php index 43c017fd364..a66f39965d8 100644 --- a/src/Exception/ItemNotFoundException.php +++ b/src/Exception/ItemNotFoundException.php @@ -21,5 +21,3 @@ class ItemNotFoundException extends InvalidArgumentException { } - -class_alias(ItemNotFoundException::class, \ApiPlatform\Core\Exception\ItemNotFoundException::class); diff --git a/src/Exception/PropertyNotFoundException.php b/src/Exception/PropertyNotFoundException.php index 80146c5ffce..44df7ee03ec 100644 --- a/src/Exception/PropertyNotFoundException.php +++ b/src/Exception/PropertyNotFoundException.php @@ -21,5 +21,3 @@ class PropertyNotFoundException extends \Exception implements ExceptionInterface { } - -class_alias(PropertyNotFoundException::class, \ApiPlatform\Core\Exception\PropertyNotFoundException::class); diff --git a/src/Exception/ResourceClassNotFoundException.php b/src/Exception/ResourceClassNotFoundException.php index a1ee01887ce..0a0f0eaa9c4 100644 --- a/src/Exception/ResourceClassNotFoundException.php +++ b/src/Exception/ResourceClassNotFoundException.php @@ -21,5 +21,3 @@ class ResourceClassNotFoundException extends \Exception implements ExceptionInterface { } - -class_alias(ResourceClassNotFoundException::class, \ApiPlatform\Core\Exception\ResourceClassNotFoundException::class); diff --git a/src/Exception/ResourceClassNotSupportedException.php b/src/Exception/ResourceClassNotSupportedException.php index b45fea4183f..9d291d1ae09 100644 --- a/src/Exception/ResourceClassNotSupportedException.php +++ b/src/Exception/ResourceClassNotSupportedException.php @@ -21,5 +21,3 @@ class ResourceClassNotSupportedException extends \Exception implements ExceptionInterface { } - -class_alias(ResourceClassNotSupportedException::class, \ApiPlatform\Core\Exception\ResourceClassNotSupportedException::class); diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index ae09346244f..7eba6481b10 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -21,5 +21,3 @@ class RuntimeException extends \RuntimeException implements ExceptionInterface { } - -class_alias(RuntimeException::class, \ApiPlatform\Core\Exception\RuntimeException::class); diff --git a/src/GraphQl/Action/EntrypointAction.php b/src/GraphQl/Action/EntrypointAction.php index 6eb558a9883..ca03ed6a43f 100644 --- a/src/GraphQl/Action/EntrypointAction.php +++ b/src/GraphQl/Action/EntrypointAction.php @@ -221,5 +221,3 @@ private function decodeVariables(string $variables): array return $decoded; } } - -class_alias(EntrypointAction::class, \ApiPlatform\Core\GraphQl\Action\EntrypointAction::class); diff --git a/src/GraphQl/Action/GraphQlPlaygroundAction.php b/src/GraphQl/Action/GraphQlPlaygroundAction.php index 1c4650c9b13..7216ef7bb01 100644 --- a/src/GraphQl/Action/GraphQlPlaygroundAction.php +++ b/src/GraphQl/Action/GraphQlPlaygroundAction.php @@ -54,5 +54,3 @@ public function __invoke(Request $request): Response throw new BadRequestHttpException('GraphQL Playground is not enabled.'); } } - -class_alias(GraphQlPlaygroundAction::class, \ApiPlatform\Core\GraphQl\Action\GraphQlPlaygroundAction::class); diff --git a/src/GraphQl/Action/GraphiQlAction.php b/src/GraphQl/Action/GraphiQlAction.php index 0b548b99331..985edc615a8 100644 --- a/src/GraphQl/Action/GraphiQlAction.php +++ b/src/GraphQl/Action/GraphiQlAction.php @@ -54,5 +54,3 @@ public function __invoke(Request $request): Response throw new BadRequestHttpException('GraphiQL is not enabled.'); } } - -class_alias(GraphiQlAction::class, \ApiPlatform\Core\GraphQl\Action\GraphiQlAction::class); diff --git a/src/GraphQl/Error/ErrorHandler.php b/src/GraphQl/Error/ErrorHandler.php index dc04eb50954..9979ea118d5 100644 --- a/src/GraphQl/Error/ErrorHandler.php +++ b/src/GraphQl/Error/ErrorHandler.php @@ -28,5 +28,3 @@ public function __invoke(array $errors, callable $formatter): array return array_map($formatter, $errors); } } - -class_alias(ErrorHandler::class, \ApiPlatform\Core\GraphQl\Error\ErrorHandler::class); diff --git a/src/GraphQl/Executor.php b/src/GraphQl/Executor.php index f423d85edf5..d1b5f7736da 100644 --- a/src/GraphQl/Executor.php +++ b/src/GraphQl/Executor.php @@ -32,5 +32,3 @@ public function executeQuery(Schema $schema, $source, $rootValue = null, $contex return GraphQL::executeQuery($schema, $source, $rootValue, $context, $variableValues, $operationName, $fieldResolver, $validationRules); } } - -class_alias(Executor::class, \ApiPlatform\Core\GraphQl\Executor::class); diff --git a/src/GraphQl/Resolver/Factory/ItemResolverFactory.php b/src/GraphQl/Resolver/Factory/ItemResolverFactory.php index 5e43e6b6aa1..3ac8b912734 100644 --- a/src/GraphQl/Resolver/Factory/ItemResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/ItemResolverFactory.php @@ -71,6 +71,7 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul throw new \LogicException('Item from read stage should be a nullable object.'); } + $resourceClass = $operation->getOutput()['class'] ?? $resourceClass; // The item retrieved can be of another type when using an identifier (see Relay Nodes at query.feature:23) $resourceClass = $this->getResourceClass($item, $resourceClass); $queryResolverId = $operation->getResolver(); diff --git a/src/GraphQl/Resolver/Stage/DeserializeStage.php b/src/GraphQl/Resolver/Stage/DeserializeStage.php index d1262199c76..c7f2ceb37f2 100644 --- a/src/GraphQl/Resolver/Stage/DeserializeStage.php +++ b/src/GraphQl/Resolver/Stage/DeserializeStage.php @@ -44,7 +44,7 @@ public function __invoke($objectToPopulate, string $resourceClass, Operation $op return $objectToPopulate; } - $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation->getName(), $context, false); + $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation, $context, false); if (null !== $objectToPopulate) { $denormalizationContext[AbstractNormalizer::OBJECT_TO_POPULATE] = $objectToPopulate; } diff --git a/src/GraphQl/Resolver/Stage/ReadStage.php b/src/GraphQl/Resolver/Stage/ReadStage.php index 867eb4a485e..089f43c08ec 100644 --- a/src/GraphQl/Resolver/Stage/ReadStage.php +++ b/src/GraphQl/Resolver/Stage/ReadStage.php @@ -59,7 +59,7 @@ public function __invoke(?string $resourceClass, ?string $rootClass, Operation $ } $args = $context['args']; - $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation->getName(), $context, true); + $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation, $context, true); if (!$context['is_collection']) { $identifier = $this->getIdentifierFromContext($context); @@ -127,17 +127,6 @@ private function getNormalizedFilters(array $args): array // If the value contains arrays, we need to merge them for the filters to understand this syntax, proper to GraphQL to preserve the order of the arguments. if ($this->isSequentialArrayOfArrays($value)) { - if (\count($value[0]) > 1) { - $deprecationMessage = "The filter syntax \"$name: {"; - $filterArgsOld = []; - $filterArgsNew = []; - foreach ($value[0] as $filterArgName => $filterArgValue) { - $filterArgsOld[] = "$filterArgName: \"$filterArgValue\""; - $filterArgsNew[] = sprintf('{%s: "%s"}', $filterArgName, $filterArgValue); - } - $deprecationMessage .= sprintf('%s}" is deprecated since API Platform 2.6, use the following syntax instead: "%s: [%s]".', implode(', ', $filterArgsOld), $name, implode(', ', $filterArgsNew)); - @trigger_error($deprecationMessage, \E_USER_DEPRECATED); - } $value = array_merge(...$value); } $filters[$name] = $this->getNormalizedFilters($value); diff --git a/src/GraphQl/Resolver/Stage/SerializeStage.php b/src/GraphQl/Resolver/Stage/SerializeStage.php index cbe326b1205..01e4b6246cd 100644 --- a/src/GraphQl/Resolver/Stage/SerializeStage.php +++ b/src/GraphQl/Resolver/Stage/SerializeStage.php @@ -76,7 +76,7 @@ public function __invoke($itemOrCollection, string $resourceClass, Operation $op return null; } - $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, true); + $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation, $context, true); $data = null; if (!$isCollection) { diff --git a/src/GraphQl/Resolver/Stage/WriteStage.php b/src/GraphQl/Resolver/Stage/WriteStage.php index b0a466488bd..01d790abf65 100644 --- a/src/GraphQl/Resolver/Stage/WriteStage.php +++ b/src/GraphQl/Resolver/Stage/WriteStage.php @@ -42,7 +42,7 @@ public function __invoke($data, string $resourceClass, Operation $operation, arr return $data; } - $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation->getName(), $context, false); + $denormalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation, $context, false); return $this->processor->process($data, $operation, [], ['operation' => $operation] + $denormalizationContext); } diff --git a/src/GraphQl/Resolver/Util/IdentifierTrait.php b/src/GraphQl/Resolver/Util/IdentifierTrait.php index deb00fc0d67..8ec7c0c6d8d 100644 --- a/src/GraphQl/Resolver/Util/IdentifierTrait.php +++ b/src/GraphQl/Resolver/Util/IdentifierTrait.php @@ -33,5 +33,3 @@ private function getIdentifierFromContext(array $context): ?string return $args['id'] ?? null; } } - -class_alias(IdentifierTrait::class, \ApiPlatform\Core\GraphQl\Resolver\Util\IdentifierTrait::class); diff --git a/src/GraphQl/Serializer/ItemNormalizer.php b/src/GraphQl/Serializer/ItemNormalizer.php index 529d57b02eb..c8b83a8f489 100644 --- a/src/GraphQl/Serializer/ItemNormalizer.php +++ b/src/GraphQl/Serializer/ItemNormalizer.php @@ -14,10 +14,11 @@ namespace ApiPlatform\GraphQl\Serializer; use ApiPlatform\Api\IdentifiersExtractorInterface; +use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface as LegacyIdentifiersExtractorInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Serializer\ItemNormalizer as BaseItemNormalizer; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; @@ -43,13 +44,13 @@ final class ItemNormalizer extends BaseItemNormalizer public const ITEM_IDENTIFIERS_KEY = '#itemIdentifiers'; /** - * @var IdentifiersExtractorInterface|LegacyIdentifiersExtractorInterface + * @var IdentifiersExtractorInterface */ private $identifiersExtractor; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, ItemDataProviderInterface $itemDataProvider = null, bool $allowPlainIdentifiers = false, LoggerInterface $logger = null, iterable $dataTransformers = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null) { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $itemDataProvider, $allowPlainIdentifiers, $logger ?: new NullLogger(), $dataTransformers, $resourceMetadataCollectionFactory, $resourceAccessChecker); + parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $logger ?: new NullLogger(), $resourceMetadataCollectionFactory, $resourceAccessChecker); $this->identifiersExtractor = $identifiersExtractor; } @@ -57,9 +58,9 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName /** * {@inheritdoc} */ - public function supportsNormalization($data, $format = null): bool + public function supportsNormalization($data, $format = null, array $context = []): bool { - return self::FORMAT === $format && parent::supportsNormalization($data, $format); + return self::FORMAT === $format && parent::supportsNormalization($data, $format, $context); } /** @@ -71,22 +72,28 @@ public function supportsNormalization($data, $format = null): bool */ public function normalize($object, $format = null, array $context = []) { - if (isset($context['operation_name'])) { - unset($context['operation_name']); - } + $resourceClass = $this->getObjectClass($object); + + if ($outputClass = $this->getOutputClass($resourceClass, $context)) { + $context['graphql_identifiers'] = [ + self::ITEM_RESOURCE_CLASS_KEY => $context['operation']->getClass(), + self::ITEM_IDENTIFIERS_KEY => $this->identifiersExtractor->getIdentifiersFromItem($object, $context['operation'] ?? null), + ]; - if (null !== $this->getOutputClass($this->getObjectClass($object), $context)) { return parent::normalize($object, $format, $context); } + unset($context['operation_name'], $context['operation']); $data = parent::normalize($object, $format, $context); if (!\is_array($data)) { throw new UnexpectedValueException('Expected data to be an array.'); } - if (!($context['no_resolver_data'] ?? false)) { - $data[self::ITEM_RESOURCE_CLASS_KEY] = $this->getObjectClass($object); - $data[self::ITEM_IDENTIFIERS_KEY] = $this->identifiersExtractor->getIdentifiersFromItem($object); + if (isset($context['graphql_identifiers'])) { + $data = $data + $context['graphql_identifiers']; + } elseif (!($context['no_resolver_data'] ?? false)) { + $data[self::ITEM_RESOURCE_CLASS_KEY] = $resourceClass; + $data[self::ITEM_IDENTIFIERS_KEY] = $this->identifiersExtractor->getIdentifiersFromItem($object, $context['operation'] ?? null); } return $data; @@ -95,7 +102,7 @@ public function normalize($object, $format = null, array $context = []) /** * {@inheritdoc} */ - protected function normalizeCollectionOfRelations($propertyMetadata, $attributeValue, string $resourceClass, ?string $format, array $context): array + protected function normalizeCollectionOfRelations(ApiProperty $propertyMetadata, iterable $attributeValue, string $resourceClass, ?string $format, array $context): array { // to-many are handled directly by the GraphQL resolver return []; @@ -104,9 +111,9 @@ protected function normalizeCollectionOfRelations($propertyMetadata, $attributeV /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization($data, string $type, $format = null, array $context = []): bool { - return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format); + return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format, $context); } /** diff --git a/src/GraphQl/Serializer/ObjectNormalizer.php b/src/GraphQl/Serializer/ObjectNormalizer.php index 68d23ef8f8b..88bb0761df1 100644 --- a/src/GraphQl/Serializer/ObjectNormalizer.php +++ b/src/GraphQl/Serializer/ObjectNormalizer.php @@ -15,7 +15,6 @@ use ApiPlatform\Api\IdentifiersExtractorInterface; use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; use ApiPlatform\Util\ClassInfoTrait; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; @@ -37,13 +36,10 @@ final class ObjectNormalizer implements NormalizerInterface, CacheableSupportsMe private $iriConverter; private $identifiersExtractor; - public function __construct(NormalizerInterface $decorated, $iriConverter, IdentifiersExtractorInterface $identifiersExtractor) + public function __construct(NormalizerInterface $decorated, IriConverterInterface $iriConverter, IdentifiersExtractorInterface $identifiersExtractor) { $this->decorated = $decorated; $this->iriConverter = $iriConverter; - 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->identifiersExtractor = $identifiersExtractor; } diff --git a/src/GraphQl/Serializer/SerializerContextBuilder.php b/src/GraphQl/Serializer/SerializerContextBuilder.php index 5c4899cdbef..cfc8b473a1f 100644 --- a/src/GraphQl/Serializer/SerializerContextBuilder.php +++ b/src/GraphQl/Serializer/SerializerContextBuilder.php @@ -13,7 +13,6 @@ namespace ApiPlatform\GraphQl\Serializer; -use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Metadata\GraphQl\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use GraphQL\Type\Definition\ResolveInfo; @@ -36,33 +35,18 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource $this->nameConverter = $nameConverter; } - public function create(?string $resourceClass, string $operationName, array $resolverContext, bool $normalization): array + public function create(?string $resourceClass, Operation $operation, array $resolverContext, bool $normalization): array { - $context = ['resource_class' => $resourceClass, 'operation_name' => $operationName, 'graphql_operation_name' => $operationName]; - $operation = null; - - if ($resourceClass) { - $resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass); - try { - $operation = $resourceMetadata->getOperation($operationName); - } catch (OperationNotFoundException $e) { - // It's possible that the serialization context may not be tight to an existing operation - try { - $context['operation_name'] = $resourceMetadata->getOperation()->getName(); - } catch (OperationNotFoundException $e) { - } - } - } + $context = ['resource_class' => $resourceClass, 'operation_name' => $operation->getName(), 'graphql_operation_name' => $operation->getName()]; if (isset($resolverContext['fields'])) { $context['no_resolver_data'] = true; } - if ($operation) { - $context['input'] = $operation->getInput(); - $context['output'] = $operation->getOutput(); - $context = $normalization ? array_merge($operation->getNormalizationContext() ?? [], $context) : array_merge($operation->getDenormalizationContext() ?? [], $context); - } + $context['operation'] = $operation; + $context['input'] = $operation->getInput(); + $context['output'] = $operation->getOutput(); + $context = $normalization ? array_merge($operation->getNormalizationContext() ?? [], $context) : array_merge($operation->getDenormalizationContext() ?? [], $context); if ($normalization) { $context['attributes'] = $this->fieldsToAttributes($resourceClass, $operation instanceof Operation ? $operation : null, $resolverContext, $context); diff --git a/src/GraphQl/Serializer/SerializerContextBuilderInterface.php b/src/GraphQl/Serializer/SerializerContextBuilderInterface.php index 545c996cca7..1ec210b1aa9 100644 --- a/src/GraphQl/Serializer/SerializerContextBuilderInterface.php +++ b/src/GraphQl/Serializer/SerializerContextBuilderInterface.php @@ -13,6 +13,8 @@ namespace ApiPlatform\GraphQl\Serializer; +use ApiPlatform\Metadata\GraphQl\Operation; + /** * Builds the context used by the Symfony Serializer. * @@ -20,5 +22,5 @@ */ interface SerializerContextBuilderInterface { - public function create(string $resourceClass, string $operationName, array $resolverContext, bool $normalization): array; + public function create(string $resourceClass, Operation $operation, array $resolverContext, bool $normalization): array; } diff --git a/src/GraphQl/Type/Definition/IterableType.php b/src/GraphQl/Type/Definition/IterableType.php index 300b872978c..717af8b066f 100644 --- a/src/GraphQl/Type/Definition/IterableType.php +++ b/src/GraphQl/Type/Definition/IterableType.php @@ -148,5 +148,3 @@ private function parseIterableLiteral($valueNode) } } } - -class_alias(IterableType::class, \ApiPlatform\Core\GraphQl\Type\Definition\IterableType::class); diff --git a/src/GraphQl/Type/Definition/UploadType.php b/src/GraphQl/Type/Definition/UploadType.php index bfe3bac45cf..d0f90c351e2 100644 --- a/src/GraphQl/Type/Definition/UploadType.php +++ b/src/GraphQl/Type/Definition/UploadType.php @@ -89,5 +89,3 @@ public function parseValue($value): UploadedFile return $value; } } - -class_alias(UploadType::class, \ApiPlatform\Core\GraphQl\Type\Definition\UploadType::class); diff --git a/src/GraphQl/Type/SchemaBuilder.php b/src/GraphQl/Type/SchemaBuilder.php index 11041e9b241..7f237e16c24 100644 --- a/src/GraphQl/Type/SchemaBuilder.php +++ b/src/GraphQl/Type/SchemaBuilder.php @@ -63,18 +63,6 @@ public function getSchema(): Schema foreach ($resourceMetadata->getGraphQlOperations() ?? [] as $operationName => $operation) { $configuration = null !== $operation->getArgs() ? ['args' => $operation->getArgs()] : []; - // TODO: 3.0 remove these - if ('item_query' === $operationName) { - $queryFields += $this->fieldsBuilder->getItemQueryFields($resourceClass, $operation, $configuration); - continue; - } - - if ('collection_query' === $operationName) { - $queryFields += $this->fieldsBuilder->getCollectionQueryFields($resourceClass, $operation, $configuration); - - continue; - } - if ($operation instanceof Query && $operation instanceof CollectionOperationInterface) { $queryFields += $this->fieldsBuilder->getCollectionQueryFields($resourceClass, $operation, $configuration); diff --git a/src/GraphQl/Type/TypeBuilder.php b/src/GraphQl/Type/TypeBuilder.php index bad0d010b02..9378b922c86 100644 --- a/src/GraphQl/Type/TypeBuilder.php +++ b/src/GraphQl/Type/TypeBuilder.php @@ -59,12 +59,6 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo $shortName = $operation->getShortName(); $operationName = $operation->getName(); - $ioMetadata = $input ? $operation->getInput() : $operation->getOutput(); - if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) { - $resourceClass = $ioMetadata['class']; - $shortName = $ioMetadata['name'] ?? $shortName; - } - if ($operation instanceof Mutation) { $shortName = $operationName.ucfirst($shortName); } @@ -108,6 +102,11 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo return $resourceObjectType; } + $ioMetadata = $input ? $operation->getInput() : $operation->getOutput(); + if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) { + $resourceClass = $ioMetadata['class']; + } + $wrapData = !$wrapped && ($operation instanceof Mutation || $operation instanceof Subscription) && !$input && $depth < 1; $configuration = [ @@ -116,11 +115,7 @@ public function getResourceObjectType(?string $resourceClass, ResourceMetadataCo 'resolveField' => $this->defaultFieldResolver, 'fields' => function () use ($resourceClass, $operation, $operationName, $resourceMetadataCollection, $input, $wrapData, $depth, $ioMetadata) { if ($wrapData) { - try { - $queryNormalizationContext = $operation instanceof Query ? ($resourceMetadataCollection->getOperation($operationName)->getNormalizationContext() ?? []) : []; - } catch (OperationNotFoundException $e) { - $queryNormalizationContext = []; - } + $queryNormalizationContext = $this->getQueryOperation($resourceMetadataCollection)?->getNormalizationContext() ?? []; try { $mutationNormalizationContext = $operation instanceof Mutation || $operation instanceof Subscription ? ($resourceMetadataCollection->getOperation($operationName)->getNormalizationContext() ?? []) : []; @@ -311,4 +306,18 @@ private function getPageBasedPaginationFields(GraphQLType $resourceType): array 'paginationInfo' => GraphQLType::nonNull($paginationInfoObjectType), ]; } + + private function getQueryOperation(ResourceMetadataCollection $resourceMetadataCollection): ?Operation + { + foreach ($resourceMetadataCollection as $resourceMetadata) { + foreach ($resourceMetadata->getGraphQlOperations() as $operation) { + // Filter the custom queries. + if ($operation instanceof Query && !$operation->getResolver()) { + return $operation; + } + } + } + + return null; + } } diff --git a/src/GraphQl/Type/TypeNotFoundException.php b/src/GraphQl/Type/TypeNotFoundException.php index 404c6d54cff..96cb3c26eab 100644 --- a/src/GraphQl/Type/TypeNotFoundException.php +++ b/src/GraphQl/Type/TypeNotFoundException.php @@ -39,5 +39,3 @@ public function getTypeId(): string return $this->typeId; } } - -class_alias(TypeNotFoundException::class, \ApiPlatform\Core\GraphQl\Type\TypeNotFoundException::class); diff --git a/src/GraphQl/Type/TypesContainer.php b/src/GraphQl/Type/TypesContainer.php index 9e53f295f8b..d389bd7cb76 100644 --- a/src/GraphQl/Type/TypesContainer.php +++ b/src/GraphQl/Type/TypesContainer.php @@ -60,5 +60,3 @@ public function has($id): bool return \array_key_exists($id, $this->graphqlTypes); } } - -class_alias(TypesContainer::class, \ApiPlatform\Core\GraphQl\Type\TypesContainer::class); diff --git a/src/GraphQl/Type/TypesFactory.php b/src/GraphQl/Type/TypesFactory.php index 57a6e1b30b8..64fd466e470 100644 --- a/src/GraphQl/Type/TypesFactory.php +++ b/src/GraphQl/Type/TypesFactory.php @@ -48,5 +48,3 @@ public function getTypes(): array return $types; } } - -class_alias(TypesFactory::class, \ApiPlatform\Core\GraphQl\Type\TypesFactory::class); diff --git a/src/Hal/Serializer/CollectionNormalizer.php b/src/Hal/Serializer/CollectionNormalizer.php index 7b420645ab4..27f20e1a704 100644 --- a/src/Hal/Serializer/CollectionNormalizer.php +++ b/src/Hal/Serializer/CollectionNormalizer.php @@ -14,8 +14,6 @@ namespace ApiPlatform\Hal\Serializer; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Serializer\AbstractCollectionNormalizer; use ApiPlatform\Util\IriHelper; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -43,14 +41,9 @@ protected function getPaginationData($object, array $context = []): array [$paginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems] = $this->getPaginationConfig($object, $context); $parsed = IriHelper::parseIri($context['uri'] ?? '/', $this->pageParameterName); - /** @var ResourceMetadata|ResourceMetadataCollection */ $metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? ''); - if ($metadata instanceof ResourceMetadataCollection) { - $operation = $metadata->getOperation($context['operation_name'] ?? null); - $urlGenerationStrategy = $operation->getUrlGenerationStrategy(); - } else { - $urlGenerationStrategy = $metadata->getAttribute('url_generation_strategy'); - } + $operation = $metadata->getOperation($context['operation_name'] ?? null); + $urlGenerationStrategy = $operation->getUrlGenerationStrategy(); $data = [ '_links' => [ @@ -99,11 +92,9 @@ protected function getItemsData($object, string $format = null, array $context = throw new UnexpectedValueException('Expected item to be an array'); } $data['_embedded']['item'][] = $item; - $data['_links']['item'][] = $item['_links']['self']; + $data['_links']['item'][] = $item['_links']['self'] ?? null; } return $data; } } - -class_alias(CollectionNormalizer::class, \ApiPlatform\Core\Hal\Serializer\CollectionNormalizer::class); diff --git a/src/Hal/Serializer/EntrypointNormalizer.php b/src/Hal/Serializer/EntrypointNormalizer.php index ffa09cce83b..5c2f48ae24a 100644 --- a/src/Hal/Serializer/EntrypointNormalizer.php +++ b/src/Hal/Serializer/EntrypointNormalizer.php @@ -16,14 +16,10 @@ use ApiPlatform\Api\Entrypoint; use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -36,27 +32,15 @@ final class EntrypointNormalizer implements NormalizerInterface, CacheableSuppor { public const FORMAT = 'jsonhal'; - /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface - */ private $resourceMetadataFactory; - /** @var LegacyIriConverterInterface|IriConverterInterface */ private $iriConverter; private $urlGenerator; - public function __construct($resourceMetadataFactory, $iriConverter, UrlGeneratorInterface $urlGenerator) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, IriConverterInterface $iriConverter, UrlGeneratorInterface $urlGenerator) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->iriConverter = $iriConverter; $this->urlGenerator = $urlGenerator; - - if ($iriConverter instanceof LegacyIriConverterInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class)); - } - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } } /** @@ -67,32 +51,19 @@ public function normalize($object, $format = null, array $context = []): array $entrypoint = ['_links' => ['self' => ['href' => $this->urlGenerator->generate('api_entrypoint')]]]; foreach ($object->getResourceNameCollection() as $resourceClass) { - /** @var ResourceMetadata|ResourceMetadataCollection */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if ($resourceMetadata instanceof ResourceMetadata) { - if (!$resourceMetadata->getCollectionOperations()) { - continue; - } - - try { - $entrypoint['_links'][lcfirst($resourceMetadata->getShortName())]['href'] = $this->iriConverter->getIriFromResourceClass($resourceClass); - } catch (InvalidArgumentException $ex) { - // Ignore resources without GET operations - } - } - foreach ($resourceMetadata as $resource) { - foreach ($resource->getOperations() as $operationName => $operation) { + foreach ($resource->getOperations() as $operation) { /** @var Operation $operation */ if (!$operation instanceof CollectionOperationInterface) { continue; } try { - $href = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromResourceClass($resourceClass) : $this->iriConverter->getIriFromResource($operation->getClass(), UrlGeneratorInterface::ABS_PATH, $operation); + $href = $this->iriConverter->getIriFromResource($operation->getClass(), UrlGeneratorInterface::ABS_PATH, $operation); $entrypoint['_links'][lcfirst($operation->getShortName())]['href'] = $href; - } catch (InvalidArgumentException $ex) { + } catch (InvalidArgumentException) { // Ignore resources without GET operations } } @@ -118,5 +89,3 @@ public function hasCacheableSupportsMethod(): bool return true; } } - -class_alias(EntrypointNormalizer::class, \ApiPlatform\Core\Hal\Serializer\EntrypointNormalizer::class); diff --git a/src/Hal/Serializer/ItemNormalizer.php b/src/Hal/Serializer/ItemNormalizer.php index bce69d7092b..76bd14c4359 100644 --- a/src/Hal/Serializer/ItemNormalizer.php +++ b/src/Hal/Serializer/ItemNormalizer.php @@ -13,9 +13,6 @@ namespace ApiPlatform\Hal\Serializer; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Serializer\AbstractItemNormalizer; use ApiPlatform\Serializer\CacheKeyTrait; use ApiPlatform\Serializer\ContextTrait; @@ -44,9 +41,9 @@ final class ItemNormalizer extends AbstractItemNormalizer /** * {@inheritdoc} */ - public function supportsNormalization($data, $format = null): bool + public function supportsNormalization($data, $format = null, array $context = []): bool { - return self::FORMAT === $format && parent::supportsNormalization($data, $format); + return self::FORMAT === $format && parent::supportsNormalization($data, $format, $context); } /** @@ -56,7 +53,8 @@ public function supportsNormalization($data, $format = null): bool */ public function normalize($object, $format = null, array $context = []) { - if (null !== $this->getOutputClass($this->getObjectClass($object), $context)) { + $resourceClass = $this->getObjectClass($object); + if ($this->getOutputClass($resourceClass, $context)) { return parent::normalize($object, $format, $context); } @@ -64,9 +62,12 @@ public function normalize($object, $format = null, array $context = []) $context['cache_key'] = $this->getCacheKey($format, $context); } - $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); + if ($this->resourceClassResolver->isResourceClass($resourceClass)) { + $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); + } + $context = $this->initContext($resourceClass, $context); - $iri = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($object) : $this->iriConverter->getIriFromResource($object); + $iri = $this->iriConverter->getIriFromResource($object); $context['iri'] = $iri; $context['api_normalize'] = true; @@ -92,7 +93,7 @@ public function normalize($object, $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization($data, string $type, $format = null, array $context = []): bool { // prevent the use of lower priority normalizers (e.g. serializer.normalizer.object) for this format return self::FORMAT === $format; @@ -141,11 +142,10 @@ private function getComponents($object, ?string $format, array $context): array ]; foreach ($attributes as $attribute) { - /** @var ApiProperty|PropertyMetadata */ $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $options); // TODO: 3.0 support multiple types, default value of types will be [] instead of null - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : ($propertyMetadata->getBuiltinTypes()[0] ?? null); + $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; $isOne = $isMany = false; if (null !== $type) { @@ -278,5 +278,3 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str return false; } } - -class_alias(ItemNormalizer::class, \ApiPlatform\Core\Hal\Serializer\ItemNormalizer::class); diff --git a/src/Hal/Serializer/ObjectNormalizer.php b/src/Hal/Serializer/ObjectNormalizer.php index 4130716aba3..296aed73b62 100644 --- a/src/Hal/Serializer/ObjectNormalizer.php +++ b/src/Hal/Serializer/ObjectNormalizer.php @@ -14,7 +14,6 @@ namespace ApiPlatform\Hal\Serializer; use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -31,14 +30,10 @@ final class ObjectNormalizer implements NormalizerInterface, DenormalizerInterfa private $decorated; private $iriConverter; - public function __construct(NormalizerInterface $decorated, $iriConverter) + public function __construct(NormalizerInterface $decorated, IriConverterInterface $iriConverter) { $this->decorated = $decorated; $this->iriConverter = $iriConverter; - - if ($iriConverter instanceof LegacyIriConverterInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class)); - } } /** @@ -81,7 +76,7 @@ public function normalize($object, $format = null, array $context = []) $metadata = [ '_links' => [ 'self' => [ - 'href' => $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($originalResource) : $this->iriConverter->getIriFromResource($originalResource), + 'href' => $this->iriConverter->getIriFromResource($originalResource), ], ], ]; @@ -110,5 +105,3 @@ public function denormalize($data, $class, $format = null, array $context = []) throw new LogicException(sprintf('%s is a read-only format.', self::FORMAT)); } } - -class_alias(ObjectNormalizer::class, \ApiPlatform\Core\Hal\Serializer\ObjectNormalizer::class); diff --git a/src/HttpCache/EventListener/AddHeadersListener.php b/src/HttpCache/EventListener/AddHeadersListener.php index 1c185192784..1340e2cb6a8 100644 --- a/src/HttpCache/EventListener/AddHeadersListener.php +++ b/src/HttpCache/EventListener/AddHeadersListener.php @@ -13,7 +13,6 @@ namespace ApiPlatform\HttpCache\EventListener; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; @@ -35,21 +34,17 @@ final class AddHeadersListener private $sharedMaxAge; private $vary; private $public; - private $resourceMetadataFactory; private $staleWhileRevalidate; private $staleIfError; - /** - * @param ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory - */ - public function __construct(bool $etag = false, int $maxAge = null, int $sharedMaxAge = null, array $vary = null, bool $public = null, $resourceMetadataFactory = null, int $staleWhileRevalidate = null, int $staleIfError = null) + public function __construct(bool $etag = false, int $maxAge = null, int $sharedMaxAge = null, array $vary = null, bool $public = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, int $staleWhileRevalidate = null, int $staleIfError = null) { $this->etag = $etag; $this->maxAge = $maxAge; $this->sharedMaxAge = $sharedMaxAge; $this->vary = $vary; $this->public = $public; - $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->staleWhileRevalidate = $staleWhileRevalidate; $this->staleIfError = $staleIfError; } @@ -73,14 +68,7 @@ public function onKernelResponse(ResponseEvent $event): void } $operation = $this->initializeOperation($request); - $resourceCacheHeaders = $attributes['cache_headers'] ?? []; - - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - $resourceCacheHeaders = $resourceMetadata->getOperationAttribute($attributes, 'cache_headers', [], true); - } elseif ($operation) { - $resourceCacheHeaders = $operation->getCacheHeaders(); - } + $resourceCacheHeaders = $attributes['cache_headers'] ?? $operation?->getCacheHeaders() ?? []; if ($this->etag && !$response->getEtag()) { $response->setEtag(md5((string) $response->getContent())); @@ -115,5 +103,3 @@ public function onKernelResponse(ResponseEvent $event): void } } } - -class_alias(AddHeadersListener::class, \ApiPlatform\Core\HttpCache\EventListener\AddHeadersListener::class); diff --git a/src/HttpCache/EventListener/AddTagsListener.php b/src/HttpCache/EventListener/AddTagsListener.php index f52f691c1ac..0f83e0c357b 100644 --- a/src/HttpCache/EventListener/AddTagsListener.php +++ b/src/HttpCache/EventListener/AddTagsListener.php @@ -15,8 +15,6 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\HttpCache\PurgerInterface as LegacyPurgerInterface; use ApiPlatform\HttpCache\PurgerInterface; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -44,15 +42,10 @@ final class AddTagsListener use OperationRequestInitiatorTrait; use UriVariablesResolverTrait; - private $iriConverter; - private $purger; + private IriConverterInterface $iriConverter; + private ?PurgerInterface $purger; - /** - * @param LegacyPurgerInterface|PurgerInterface|null $purger - * @param LegacyIriConverterInterface|IriConverterInterface $iriConverter - * @param mixed|null $purger - */ - public function __construct($iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, $purger = null) + public function __construct(IriConverterInterface $iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, PurgerInterface $purger = null) { $this->iriConverter = $iriConverter; $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; @@ -108,5 +101,3 @@ public function onKernelResponse(ResponseEvent $event): void } } } - -class_alias(AddTagsListener::class, \ApiPlatform\Core\HttpCache\EventListener\AddTagsListener::class); diff --git a/src/HttpCache/VarnishPurger.php b/src/HttpCache/VarnishPurger.php index 23fb74d99e7..f35abec27e0 100644 --- a/src/HttpCache/VarnishPurger.php +++ b/src/HttpCache/VarnishPurger.php @@ -139,5 +139,3 @@ private function chunkRegexParts(array $parts): iterable yield from $this->chunkRegexParts($nextParts); } } - -class_alias(VarnishPurger::class, \ApiPlatform\Core\HttpCache\VarnishPurger::class); diff --git a/src/HttpCache/VarnishXKeyPurger.php b/src/HttpCache/VarnishXKeyPurger.php index 1b6e0ca1249..32f7523af0f 100644 --- a/src/HttpCache/VarnishXKeyPurger.php +++ b/src/HttpCache/VarnishXKeyPurger.php @@ -112,5 +112,3 @@ private function chunkKeys(array $keys): iterable } } } - -class_alias(VarnishXKeyPurger::class, \ApiPlatform\Core\HttpCache\VarnishXKeyPurger::class); diff --git a/src/Hydra/EventListener/AddLinkHeaderListener.php b/src/Hydra/EventListener/AddLinkHeaderListener.php index 18b100a3873..ecf15d120c2 100644 --- a/src/Hydra/EventListener/AddLinkHeaderListener.php +++ b/src/Hydra/EventListener/AddLinkHeaderListener.php @@ -58,5 +58,3 @@ public function onKernelResponse(ResponseEvent $event): void $request->attributes->set('_links', $linkProvider->withLink($link)); } } - -class_alias(AddLinkHeaderListener::class, \ApiPlatform\Core\Hydra\EventListener\AddLinkHeaderListener::class); diff --git a/src/Hydra/Serializer/CollectionFiltersNormalizer.php b/src/Hydra/Serializer/CollectionFiltersNormalizer.php index 4700ce16153..6f125ccc342 100644 --- a/src/Hydra/Serializer/CollectionFiltersNormalizer.php +++ b/src/Hydra/Serializer/CollectionFiltersNormalizer.php @@ -14,11 +14,8 @@ namespace ApiPlatform\Hydra\Serializer; use ApiPlatform\Api\FilterInterface; +use ApiPlatform\Api\FilterLocatorTrait; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\FilterLocatorTrait; -use ApiPlatform\Core\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Psr\Container\ContainerInterface; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -39,19 +36,15 @@ final class CollectionFiltersNormalizer implements NormalizerInterface, Normaliz private $resourceClassResolver; /** - * @param ContainerInterface|FilterCollection $filterLocator The new filter locator or the deprecated filter collection - * @param mixed $resourceMetadataFactory - * @param ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver + * @param ContainerInterface $filterLocator The new filter locator or the deprecated filter collection + * @param mixed $resourceMetadataFactory */ - public function __construct(NormalizerInterface $collectionNormalizer, $resourceMetadataFactory, $resourceClassResolver, $filterLocator) + public function __construct(NormalizerInterface $collectionNormalizer, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, ContainerInterface $filterLocator) { $this->setFilterLocator($filterLocator); $this->collectionNormalizer = $collectionNormalizer; $this->resourceMetadataFactory = $resourceMetadataFactory; $this->resourceClassResolver = $resourceClassResolver; - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } } /** @@ -85,19 +78,8 @@ public function normalize($object, $format = null, array $context = []) return $data; } $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']); - $resourceFilters = null; - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $operationName = $context['collection_operation_name'] ?? null; - if (null === $operationName) { - $resourceFilters = $resourceMetadata->getAttribute('filters', []); - } else { - $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); - } - } elseif ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($context['operation_name'] ?? null); - $resourceFilters = $operation->getFilters(); - } + $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($context['operation_name'] ?? null); + $resourceFilters = $operation->getFilters(); if (!$resourceFilters) { return $data; } @@ -147,5 +129,3 @@ private function getSearch(string $resourceClass, array $parts, array $filters): return ['@type' => 'hydra:IriTemplate', 'hydra:template' => sprintf('%s{?%s}', $parts['path'], implode(',', $variables)), 'hydra:variableRepresentation' => 'BasicRepresentation', 'hydra:mapping' => $mapping]; } } - -class_alias(CollectionFiltersNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\CollectionFiltersNormalizer::class); diff --git a/src/Hydra/Serializer/CollectionNormalizer.php b/src/Hydra/Serializer/CollectionNormalizer.php index f2f49304b56..7738aa886da 100644 --- a/src/Hydra/Serializer/CollectionNormalizer.php +++ b/src/Hydra/Serializer/CollectionNormalizer.php @@ -16,8 +16,6 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Api\OperationType; use ApiPlatform\JsonLd\ContextBuilderInterface; use ApiPlatform\JsonLd\Serializer\JsonLdContextTrait; use ApiPlatform\Serializer\ContextTrait; @@ -50,14 +48,10 @@ final class CollectionNormalizer implements NormalizerInterface, NormalizerAware self::IRI_ONLY => false, ]; - public function __construct(ContextBuilderInterface $contextBuilder, ResourceClassResolverInterface $resourceClassResolver, $iriConverter, array $defaultContext = []) + public function __construct(ContextBuilderInterface $contextBuilder, ResourceClassResolverInterface $resourceClassResolver, IriConverterInterface $iriConverter, array $defaultContext = []) { $this->contextBuilder = $contextBuilder; $this->resourceClassResolver = $resourceClassResolver; - - 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->defaultContext = array_merge($this->defaultContext, $defaultContext); } @@ -65,7 +59,7 @@ public function __construct(ContextBuilderInterface $contextBuilder, ResourceCla /** * {@inheritdoc} */ - public function supportsNormalization($data, $format = null): bool + public function supportsNormalization($data, $format = null, array $context = []): bool { return self::FORMAT === $format && is_iterable($data); } @@ -84,14 +78,7 @@ public function normalize($object, $format = null, array $context = []): array $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']); $context = $this->initContext($resourceClass, $context); $data = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context); - - if ($this->iriConverter instanceof LegacyIriConverterInterface) { - // TODO: remove in 3.0 - $data['@id'] = isset($context['operation_type']) && OperationType::SUBRESOURCE === $context['operation_type'] ? $this->iriConverter->getSubresourceIriFromResourceClass($resourceClass, $context) : $this->iriConverter->getIriFromResourceClass($resourceClass); - } else { - $data['@id'] = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context); - } - + $data['@id'] = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context); $data['@type'] = 'hydra:Collection'; $data['hydra:member'] = []; $iriOnly = $context[self::IRI_ONLY] ?? $this->defaultContext[self::IRI_ONLY]; @@ -99,7 +86,7 @@ public function normalize($object, $format = null, array $context = []): array foreach ($object as $obj) { if ($iriOnly) { - $data['hydra:member'][] = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($obj) : $this->iriConverter->getIriFromResource($obj); + $data['hydra:member'][] = $this->iriConverter->getIriFromResource($obj); } else { $data['hydra:member'][] = $this->normalizer->normalize($obj, $format, $context); } @@ -108,6 +95,7 @@ public function normalize($object, $format = null, array $context = []): array if ($object instanceof PaginatorInterface) { $data['hydra:totalItems'] = $object->getTotalItems(); } + if (\is_array($object) || ($object instanceof \Countable && !$object instanceof PartialPaginatorInterface)) { $data['hydra:totalItems'] = \count($object); } @@ -136,5 +124,3 @@ private function normalizeRawCollection(iterable $object, ?string $format, array return $data; } } - -class_alias(CollectionNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\CollectionNormalizer::class); diff --git a/src/Hydra/Serializer/ConstraintViolationListNormalizer.php b/src/Hydra/Serializer/ConstraintViolationListNormalizer.php index c988f0fd734..6fd05d74ba7 100644 --- a/src/Hydra/Serializer/ConstraintViolationListNormalizer.php +++ b/src/Hydra/Serializer/ConstraintViolationListNormalizer.php @@ -53,5 +53,3 @@ public function normalize($object, $format = null, array $context = []) ]; } } - -class_alias(ConstraintViolationListNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\ConstraintViolationListNormalizer::class); diff --git a/src/Hydra/Serializer/DocumentationNormalizer.php b/src/Hydra/Serializer/DocumentationNormalizer.php index ed072965c89..da91f1db980 100644 --- a/src/Hydra/Serializer/DocumentationNormalizer.php +++ b/src/Hydra/Serializer/DocumentationNormalizer.php @@ -15,15 +15,6 @@ use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\OperationMethodResolverInterface; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; use ApiPlatform\Documentation\Documentation; use ApiPlatform\JsonLd\ContextBuilderInterface; use ApiPlatform\Metadata\ApiProperty; @@ -31,8 +22,8 @@ use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Symfony\Component\PropertyInfo\Type; @@ -50,44 +41,20 @@ final class DocumentationNormalizer implements NormalizerInterface, CacheableSup { public const FORMAT = 'jsonld'; - /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface - */ - private $resourceMetadataFactory; - private $propertyNameCollectionFactory; + private ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory; + private PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory; + private PropertyMetadataFactoryInterface $propertyMetadataFactory; + private ResourceClassResolverInterface $resourceClassResolver; + private UrlGeneratorInterface $urlGenerator; + private ?NameConverterInterface $nameConverter; - /** - * @var PropertyMetadataFactoryInterface|LegacyPropertyMetadataFactoryInterface - */ - private $propertyMetadataFactory; - private $resourceClassResolver; - private $operationMethodResolver; - private $urlGenerator; - private $subresourceOperationFactory; - private $nameConverter; - - public function __construct($resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver = null, UrlGeneratorInterface $urlGenerator, SubresourceOperationFactoryInterface $subresourceOperationFactory = null, NameConverterInterface $nameConverter = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, UrlGeneratorInterface $urlGenerator, NameConverterInterface $nameConverter = null) { - if ($operationMethodResolver) { - @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', OperationMethodResolverInterface::class, __METHOD__), \E_USER_DEPRECATED); - } - $this->resourceMetadataFactory = $resourceMetadataFactory; - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - - if ($subresourceOperationFactory) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Using "%s" is deprecated and will be removed.', SubresourceOperationFactoryInterface::class)); - } - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; $this->resourceClassResolver = $resourceClassResolver; - $this->operationMethodResolver = $operationMethodResolver; $this->urlGenerator = $urlGenerator; - $this->subresourceOperationFactory = $subresourceOperationFactory; $this->nameConverter = $nameConverter; } @@ -104,15 +71,6 @@ public function normalize($object, $format = null, array $context = []) foreach ($object->getResourceNameCollection() as $resourceClass) { $resourceMetadataCollection = $this->resourceMetadataFactory->create($resourceClass); - if ($resourceMetadataCollection instanceof ResourceMetadata) { - $shortName = $resourceMetadataCollection->getShortName(); - $prefixedShortName = $resourceMetadataCollection->getIri() ?? "#$shortName"; - - $this->populateEntrypointProperties($resourceClass, $resourceMetadataCollection, $shortName, $prefixedShortName, $entrypointProperties); - $classes[] = $this->getClass($resourceClass, $resourceMetadataCollection, $shortName, $prefixedShortName, $context); - continue; - } - $resourceMetadata = $resourceMetadataCollection[0]; $shortName = $resourceMetadata->getShortName(); $prefixedShortName = $resourceMetadata->getTypes()[0] ?? "#$shortName"; @@ -125,10 +83,8 @@ public function normalize($object, $format = null, array $context = []) /** * Populates entrypoint properties. - * - * @param ResourceMetadata|ApiResource $resourceMetadata */ - private function populateEntrypointProperties(string $resourceClass, $resourceMetadata, string $shortName, string $prefixedShortName, array &$entrypointProperties, ?ResourceMetadataCollection $resourceMetadataCollection = null) + private function populateEntrypointProperties(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array &$entrypointProperties, ?ResourceMetadataCollection $resourceMetadataCollection = null) { $hydraCollectionOperations = $this->getHydraOperations($resourceClass, $resourceMetadata, $prefixedShortName, true, $resourceMetadataCollection); if (empty($hydraCollectionOperations)) { @@ -158,7 +114,7 @@ private function populateEntrypointProperties(string $resourceClass, $resourceMe 'hydra:writeable' => false, ]; - if ($resourceMetadata instanceof ResourceMetadata ? $resourceMetadata->getCollectionOperationAttribute('GET', 'deprecation_reason', null, true) : $resourceMetadata->getDeprecationReason()) { + if ($resourceMetadata->getDeprecationReason()) { $entrypointProperty['owl:deprecated'] = true; } @@ -167,18 +123,11 @@ private function populateEntrypointProperties(string $resourceClass, $resourceMe /** * Gets a Hydra class. - * - * @param ResourceMetadata|ApiResource $resourceMetadata */ - private function getClass(string $resourceClass, $resourceMetadata, string $shortName, string $prefixedShortName, array $context, ?ResourceMetadataCollection $resourceMetadataCollection = null): array + private function getClass(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context, ?ResourceMetadataCollection $resourceMetadataCollection = null): array { - if ($resourceMetadata instanceof ApiResource) { - $description = $resourceMetadata->getDescription(); - $isDeprecated = $resourceMetadata->getDeprecationReason(); - } else { - $description = $resourceMetadata->getDescription(); - $isDeprecated = $resourceMetadata->getAttribute('deprecation_reason'); - } + $description = $resourceMetadata->getDescription(); + $isDeprecated = $resourceMetadata->getDeprecationReason(); $class = [ '@id' => $prefixedShortName, @@ -200,35 +149,6 @@ private function getClass(string $resourceClass, $resourceMetadata, string $shor return $class; } - /** - * Gets the context for the property name factory. - */ - private function getPropertyNameCollectionFactoryContext(ResourceMetadata $resourceMetadata): array - { - $attributes = $resourceMetadata->getAttributes(); - $context = []; - - if (isset($attributes['normalization_context'][AbstractNormalizer::GROUPS])) { - $context['serializer_groups'] = (array) $attributes['normalization_context'][AbstractNormalizer::GROUPS]; - } - - if (!isset($attributes['denormalization_context'][AbstractNormalizer::GROUPS])) { - return $context; - } - - if (isset($context['serializer_groups'])) { - foreach ((array) $attributes['denormalization_context'][AbstractNormalizer::GROUPS] as $groupName) { - $context['serializer_groups'][] = $groupName; - } - - return $context; - } - - $context['serializer_groups'] = (array) $attributes['denormalization_context'][AbstractNormalizer::GROUPS]; - - return $context; - } - /** * Creates context for property metatata factories. */ @@ -265,54 +185,33 @@ private function getPropertyMetadataFactoryContext(ApiResource $resourceMetadata /** * Gets Hydra properties. - * - * @param ResourceMetadata|ApiResource $resourceMetadata */ - private function getHydraProperties(string $resourceClass, $resourceMetadata, string $shortName, string $prefixedShortName, array $context): array + private function getHydraProperties(string $resourceClass, ApiResource $resourceMetadata, string $shortName, string $prefixedShortName, array $context): array { $classes = []; - if ($resourceMetadata instanceof ResourceMetadata) { - foreach ($resourceMetadata->getCollectionOperations() as $operationName => $operation) { - $inputMetadata = $resourceMetadata->getTypedOperationAttribute(OperationType::COLLECTION, $operationName, 'input', ['class' => $resourceClass], true); - if (null !== $inputClass = $inputMetadata['class'] ?? null) { - $classes[$inputClass] = true; - } - - $outputMetadata = $resourceMetadata->getTypedOperationAttribute(OperationType::COLLECTION, $operationName, 'output', ['class' => $resourceClass], true); - if (null !== $outputClass = $outputMetadata['class'] ?? null) { - $classes[$outputClass] = true; - } + $classes[$resourceClass] = true; + foreach ($resourceMetadata->getOperations() as $operation) { + /** @var Operation $operation */ + if (!$operation instanceof CollectionOperationInterface) { + continue; } - } else { - $classes[$resourceClass] = true; - foreach ($resourceMetadata->getOperations() as $operation) { - /** @var Operation $operation */ - if (!$operation instanceof CollectionOperationInterface) { - continue; - } - $inputMetadata = $operation->getInput(); - if (null !== $inputClass = $inputMetadata['class'] ?? null) { - $classes[$inputClass] = true; - } + $inputMetadata = $operation->getInput(); + if (null !== $inputClass = $inputMetadata['class'] ?? null) { + $classes[$inputClass] = true; + } - $outputMetadata = $operation->getOutput(); - if (null !== $outputClass = $outputMetadata['class'] ?? null) { - $classes[$outputClass] = true; - } + $outputMetadata = $operation->getOutput(); + if (null !== $outputClass = $outputMetadata['class'] ?? null) { + $classes[$outputClass] = true; } } /** @var string[] $classes */ $classes = array_keys($classes); $properties = []; - if ($resourceMetadata instanceof ResourceMetadata) { - $propertyNameContext = $this->getPropertyNameCollectionFactoryContext($resourceMetadata); - $propertyContext = []; - } else { - [$propertyNameContext, $propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata); - } + [$propertyNameContext, $propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata); foreach ($classes as $class) { foreach ($this->propertyNameCollectionFactory->create($class, $propertyNameContext) as $propertyName) { @@ -335,38 +234,17 @@ private function getHydraProperties(string $resourceClass, $resourceMetadata, st /** * Gets Hydra operations. - * - * @param ResourceMetadata|ApiResource $resourceMetadata */ - private function getHydraOperations(string $resourceClass, $resourceMetadata, string $prefixedShortName, bool $collection, ?ResourceMetadataCollection $resourceMetadataCollection = null): array + private function getHydraOperations(string $resourceClass, ApiResource $resourceMetadata, string $prefixedShortName, bool $collection, ?ResourceMetadataCollection $resourceMetadataCollection = null): array { - if ($resourceMetadata instanceof ResourceMetadata) { - if (null === $operations = $collection ? $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) { - return []; - } - - $hydraOperations = []; - foreach ($operations as $operationName => $operation) { - $hydraOperations[] = $this->getHydraOperation($resourceClass, $resourceMetadata, $operationName, $operation, $prefixedShortName, $collection ? OperationType::COLLECTION : OperationType::ITEM); - } - } else { - $hydraOperations = []; - foreach ($resourceMetadataCollection as $resourceMetadata) { - foreach ($resourceMetadata->getOperations() as $operationName => $operation) { - if (($operation instanceof Post || $operation instanceof CollectionOperationInterface) !== $collection) { - continue; - } - - $hydraOperations[] = $this->getHydraOperation($resourceClass, $resourceMetadata, $operationName, $operation, $operation->getTypes()[0] ?? "#{$operation->getShortName()}", null); + $hydraOperations = []; + foreach ($resourceMetadataCollection as $resourceMetadata) { + foreach ($resourceMetadata->getOperations() as $operationName => $operation) { + if ((HttpOperation::METHOD_POST === $operation->getMethod() || $operation instanceof CollectionOperationInterface) !== $collection) { + continue; } - } - } - if (null !== $this->subresourceOperationFactory && !$this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $operation) { - $subresourceMetadata = $this->resourceMetadataFactory->create($operation['resource_class']); - $propertyMetadata = $this->propertyMetadataFactory->create(end($operation['identifiers'])[0], $operation['property']); - $hydraOperations[] = $this->getHydraOperation($resourceClass, $subresourceMetadata, $operation['route_name'], $operation, "#{$subresourceMetadata->getShortName()}", OperationType::SUBRESOURCE, $propertyMetadata->getSubresource()); + $hydraOperations[] = $this->getHydraOperation($resourceClass, $resourceMetadata, $operationName, $operation, $operation->getTypes()[0] ?? "#{$operation->getShortName()}", null); } } @@ -375,57 +253,28 @@ private function getHydraOperations(string $resourceClass, $resourceMetadata, st /** * Gets and populates if applicable a Hydra operation. - * - * @param ResourceMetadata|ApiResource $resourceMetadata - * @param SubresourceMetadata $subresourceMetadata - * @param array|HttpOperation $operation */ - private function getHydraOperation(string $resourceClass, $resourceMetadata, string $operationName, $operation, string $prefixedShortName, ?string $operationType = null, SubresourceMetadata $subresourceMetadata = null): array + private function getHydraOperation(string $resourceClass, ApiResource $resourceMetadata, string $operationName, HttpOperation $operation, string $prefixedShortName, ?string $operationType = null): array { - if ($operation instanceof HttpOperation) { - $method = $operation->getMethod() ?: HttpOperation::METHOD_GET; - } elseif ($this->operationMethodResolver) { - if (OperationType::COLLECTION === $operationType) { - $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); - } elseif (OperationType::ITEM === $operationType) { - $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); - } else { - $method = 'GET'; - } - } else { - $method = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'method', 'GET'); - } + $method = $operation->getMethod() ?: HttpOperation::METHOD_GET; - $hydraOperation = $operation instanceof HttpOperation ? ($operation->getHydraContext() ?? []) : ($operation['hydra_context'] ?? []); - if ($operation instanceof HttpOperation ? $operation->getDeprecationReason() : $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'deprecation_reason', null, true)) { + $hydraOperation = $operation->getHydraContext() ?? []; + if ($operation->getDeprecationReason()) { $hydraOperation['owl:deprecated'] = true; } - if ($operation instanceof HttpOperation) { - $shortName = $operation->getShortName(); - $inputMetadata = $operation->getInput() ?? []; - $outputMetadata = $operation->getOutput() ?? []; - $operationType = $operation instanceof CollectionOperationInterface ? OperationType::COLLECTION : OperationType::ITEM; - } else { - $shortName = $resourceMetadata->getShortName(); - $inputMetadata = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'input', ['class' => false]); - $outputMetadata = $resourceMetadata->getTypedOperationAttribute($operationType, $operationName, 'output', ['class' => false]); - } + $shortName = $operation->getShortName(); + $inputMetadata = $operation->getInput() ?? []; + $outputMetadata = $operation->getOutput() ?? []; $inputClass = \array_key_exists('class', $inputMetadata) ? $inputMetadata['class'] : false; $outputClass = \array_key_exists('class', $outputMetadata) ? $outputMetadata['class'] : false; - if ('GET' === $method && OperationType::COLLECTION === $operationType) { + if ('GET' === $method && $operation instanceof CollectionOperationInterface) { $hydraOperation += [ '@type' => ['hydra:Operation', 'schema:FindAction'], 'hydra:title' => "Retrieves the collection of $shortName resources.", - 'returns' => 'hydra:Collection', - ]; - } elseif ('GET' === $method && OperationType::SUBRESOURCE === $operationType) { - $hydraOperation += [ - '@type' => ['hydra:Operation', 'schema:FindAction'], - 'hydra:title' => $subresourceMetadata && $subresourceMetadata->isCollection() ? "Retrieves the collection of $shortName resources." : "Retrieves a $shortName resource.", - 'returns' => null === $outputClass ? 'owl:Nothing' : "#$shortName", + 'returns' => null === $outputClass ? 'owl:Nothing' : 'hydra:Collection', ]; } elseif ('GET' === $method) { $hydraOperation += [ @@ -475,19 +324,17 @@ private function getHydraOperation(string $resourceClass, $resourceMetadata, str /** * Gets the range of the property. - * - * @param ApiProperty|PropertyMetadata $propertyMetadata */ - private function getRange($propertyMetadata): ?string + private function getRange(ApiProperty $propertyMetadata): ?string { - $jsonldContext = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getAttributes()['jsonld_context'] ?? [] : $propertyMetadata->getJsonldContext(); + $jsonldContext = $propertyMetadata->getJsonldContext(); if (isset($jsonldContext['@type'])) { return $jsonldContext['@type']; } // TODO: 3.0 support multiple types, default value of types will be [] instead of null - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : $propertyMetadata->getBuiltinTypes()[0] ?? null; + $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; if (null === $type) { return null; } @@ -516,17 +363,13 @@ private function getRange($propertyMetadata): ?string if ($this->resourceClassResolver->isResourceClass($className)) { $resourceMetadata = $this->resourceMetadataFactory->create($className); - if ($resourceMetadata instanceof ResourceMetadataCollection) { - $operation = $resourceMetadata->getOperation(); + $operation = $resourceMetadata->getOperation(); - if (!$operation instanceof HttpOperation) { - return "#{$operation->getShortName()}"; - } - - return $operation->getTypes()[0] ?? "#{$operation->getShortName()}"; + if (!$operation instanceof HttpOperation) { + return "#{$operation->getShortName()}"; } - return $resourceMetadata->getIri() ?? "#{$resourceMetadata->getShortName()}"; + return $operation->getTypes()[0] ?? "#{$operation->getShortName()}"; } } @@ -617,17 +460,11 @@ private function getClasses(array $entrypointProperties, array $classes): array /** * Gets a property definition. - * - * @param ApiProperty|PropertyMetadata $propertyMetadata */ - private function getProperty($propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName): array + private function getProperty(ApiProperty $propertyMetadata, string $propertyName, string $prefixedShortName, string $shortName): array { - if ($propertyMetadata instanceof PropertyMetadata) { - $iri = $propertyMetadata->getIri(); - } else { - if ($iri = $propertyMetadata->getIris()) { - $iri = 1 === \count($iri) ? $iri[0] : $iri; - } + if ($iri = $propertyMetadata->getIris()) { + $iri = 1 === \count($iri) ? $iri[0] : $iri; } if (!isset($iri)) { @@ -642,7 +479,7 @@ private function getProperty($propertyMetadata, string $propertyName, string $pr ]; // TODO: 3.0 support multiple types, default value of types will be [] instead of null - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : $propertyMetadata->getBuiltinTypes()[0] ?? null; + $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; if (null !== $type && !$type->isCollection() && (null !== $className = $type->getClassName()) && $this->resourceClassResolver->isResourceClass($className)) { $propertyData['owl:maxCardinality'] = 1; @@ -665,7 +502,7 @@ private function getProperty($propertyMetadata, string $propertyName, string $pr $property['hydra:description'] = $description; } - if ($deprecationReason = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getAttribute('deprecation_reason') : $propertyMetadata->getDeprecationReason()) { + if ($deprecationReason = $propertyMetadata->getDeprecationReason()) { $property['owl:deprecated'] = true; } @@ -730,5 +567,3 @@ public function hasCacheableSupportsMethod(): bool return true; } } - -class_alias(DocumentationNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\DocumentationNormalizer::class); diff --git a/src/Hydra/Serializer/EntrypointNormalizer.php b/src/Hydra/Serializer/EntrypointNormalizer.php index cc8aafacbee..e4b22807462 100644 --- a/src/Hydra/Serializer/EntrypointNormalizer.php +++ b/src/Hydra/Serializer/EntrypointNormalizer.php @@ -16,14 +16,10 @@ use ApiPlatform\Api\Entrypoint; use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -40,17 +36,9 @@ final class EntrypointNormalizer implements NormalizerInterface, CacheableSuppor private $iriConverter; private $urlGenerator; - public function __construct($resourceMetadataFactory, $iriConverter, UrlGeneratorInterface $urlGenerator) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, IriConverterInterface $iriConverter, UrlGeneratorInterface $urlGenerator) { - 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; - 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->urlGenerator = $urlGenerator; } @@ -67,35 +55,22 @@ public function normalize($object, $format = null, array $context = []): array ]; foreach ($object->getResourceNameCollection() as $resourceClass) { - /** @var ResourceMetadata|ResourceMetadataCollection */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if ($resourceMetadata instanceof ResourceMetadata) { - if (empty($resourceMetadata->getCollectionOperations())) { - continue; - } - try { - $entrypoint[lcfirst($resourceMetadata->getShortName())] = $this->iriConverter->getIriFromResourceClass($resourceClass); - } catch (InvalidArgumentException $ex) { - // Ignore resources without GET operations - } - continue; - } - foreach ($resourceMetadata as $resource) { if ($resource->getExtraProperties()['is_alternate_resource_metadata'] ?? false) { continue; } - foreach ($resource->getOperations() as $operationName => $operation) { + foreach ($resource->getOperations() as $operation) { $key = lcfirst($resource->getShortName()); if (!$operation instanceof CollectionOperationInterface || isset($entrypoint[$key])) { continue; } try { - $entrypoint[$key] = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromResourceClass($resourceClass) : $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, $operation); - } catch (InvalidArgumentException|OperationNotFoundException $ex) { + $entrypoint[$key] = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, $operation); + } catch (InvalidArgumentException|OperationNotFoundException) { // Ignore resources without GET operations } } @@ -123,5 +98,3 @@ public function hasCacheableSupportsMethod(): bool return true; } } - -class_alias(EntrypointNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\EntrypointNormalizer::class); diff --git a/src/Hydra/Serializer/ErrorNormalizer.php b/src/Hydra/Serializer/ErrorNormalizer.php index b7beef065ae..d63b7f2b550 100644 --- a/src/Hydra/Serializer/ErrorNormalizer.php +++ b/src/Hydra/Serializer/ErrorNormalizer.php @@ -81,5 +81,3 @@ public function hasCacheableSupportsMethod(): bool return true; } } - -class_alias(ErrorNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\ErrorNormalizer::class); diff --git a/src/Hydra/Serializer/PartialCollectionViewNormalizer.php b/src/Hydra/Serializer/PartialCollectionViewNormalizer.php index 7cbe5d45ca5..c14c6b177f4 100644 --- a/src/Hydra/Serializer/PartialCollectionViewNormalizer.php +++ b/src/Hydra/Serializer/PartialCollectionViewNormalizer.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Hydra\Serializer; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\Pagination\PaginatorInterface; use ApiPlatform\State\Pagination\PartialPaginatorInterface; @@ -39,17 +38,12 @@ final class PartialCollectionViewNormalizer implements NormalizerInterface, Norm private $resourceMetadataFactory; private $propertyAccessor; - public function __construct(NormalizerInterface $collectionNormalizer, string $pageParameterName = 'page', string $enabledParameterName = 'pagination', $resourceMetadataFactory = null, PropertyAccessorInterface $propertyAccessor = null) + public function __construct(NormalizerInterface $collectionNormalizer, string $pageParameterName = 'page', string $enabledParameterName = 'pagination', ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, PropertyAccessorInterface $propertyAccessor = null) { $this->collectionNormalizer = $collectionNormalizer; $this->pageParameterName = $pageParameterName; $this->enabledParameterName = $enabledParameterName; $this->resourceMetadataFactory = $resourceMetadataFactory; - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); } @@ -91,10 +85,7 @@ public function normalize($object, $format = null, array $context = []) $isPaginatedWithCursor = false; $cursorPaginationAttribute = null; - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && isset($context['resource_class']) && $paginated) { - $metadata = $this->resourceMetadataFactory->create($context['resource_class']); - $isPaginatedWithCursor = null !== $cursorPaginationAttribute = $metadata->getCollectionOperationAttribute($context['collection_operation_name'] ?? $context['subresource_operation_name'], 'pagination_via_cursor', null, true); - } elseif ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && isset($context['resource_class']) && $paginated) { + if ($this->resourceMetadataFactory && isset($context['resource_class']) && $paginated) { $operation = $this->resourceMetadataFactory->create($context['resource_class'])->getOperation($context['operation_name'] ?? null); $isPaginatedWithCursor = [] !== $cursorPaginationAttribute = ($operation->getPaginationViaCursor() ?? []); } @@ -195,5 +186,3 @@ private function populateDataWithPagination(array $data, array $parsed, ?float $ return $data; } } - -class_alias(PartialCollectionViewNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\PartialCollectionViewNormalizer::class); diff --git a/src/JsonApi/Serializer/CollectionNormalizer.php b/src/JsonApi/Serializer/CollectionNormalizer.php index 38dd3044f68..f11535b0fe1 100644 --- a/src/JsonApi/Serializer/CollectionNormalizer.php +++ b/src/JsonApi/Serializer/CollectionNormalizer.php @@ -14,7 +14,7 @@ namespace ApiPlatform\JsonApi\Serializer; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Serializer\AbstractCollectionNormalizer; use ApiPlatform\Util\IriHelper; @@ -31,7 +31,7 @@ final class CollectionNormalizer extends AbstractCollectionNormalizer { public const FORMAT = 'jsonapi'; - public function __construct(ResourceClassResolverInterface $resourceClassResolver, string $pageParameterName, $resourceMetadataFactory) + public function __construct(ResourceClassResolverInterface $resourceClassResolver, string $pageParameterName, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory) { parent::__construct($resourceClassResolver, $pageParameterName, $resourceMetadataFactory); } @@ -44,14 +44,10 @@ protected function getPaginationData($object, array $context = []): array [$paginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems] = $this->getPaginationConfig($object, $context); $parsed = IriHelper::parseIri($context['uri'] ?? '/', $this->pageParameterName); - /** @var ResourceMetadata|ResourceMetadataCollection */ + /** @var ResourceMetadataCollection */ $metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? ''); - if ($metadata instanceof ResourceMetadataCollection) { - $operation = $metadata->getOperation($context['operation_name'] ?? null); - $urlGenerationStrategy = $operation->getUrlGenerationStrategy(); - } else { - $urlGenerationStrategy = $metadata->getAttribute('url_generation_strategy'); - } + $operation = $metadata->getOperation($context['operation_name'] ?? null); + $urlGenerationStrategy = $operation->getUrlGenerationStrategy(); $data = [ 'links' => [ @@ -117,5 +113,3 @@ protected function getItemsData($object, string $format = null, array $context = return $data; } } - -class_alias(CollectionNormalizer::class, \ApiPlatform\Core\JsonApi\Serializer\CollectionNormalizer::class); diff --git a/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php b/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php index 899bc471e3b..33b23986158 100644 --- a/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php +++ b/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php @@ -13,8 +13,6 @@ namespace ApiPlatform\JsonApi\Serializer; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -33,12 +31,9 @@ final class ConstraintViolationListNormalizer implements NormalizerInterface, Ca public const FORMAT = 'jsonapi'; private $nameConverter; - /** - * @var LegacyPropertyMetadataFactoryInterface|PropertyMetadataFactoryInterface - */ private $propertyMetadataFactory; - public function __construct($propertyMetadataFactory, NameConverterInterface $nameConverter = null) + public function __construct(PropertyMetadataFactoryInterface $propertyMetadataFactory, NameConverterInterface $nameConverter = null) { $this->propertyMetadataFactory = $propertyMetadataFactory; $this->nameConverter = $nameConverter; @@ -90,7 +85,7 @@ private function getSourcePointerFromViolation(ConstraintViolationInterface $vio } $class = \get_class($violation->getRoot()); - /** @var ApiProperty|PropertyMetadata */ + /** @var ApiProperty */ $propertyMetadata = $this->propertyMetadataFactory ->create( // Im quite sure this requires some thought in case of validations over relationships @@ -102,8 +97,7 @@ private function getSourcePointerFromViolation(ConstraintViolationInterface $vio $fieldName = $this->nameConverter->normalize($fieldName, $class, self::FORMAT); } - // TODO: 3.0 support multiple types, default value of types will be [] instead of null - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : ($propertyMetadata->getBuiltinTypes()[0] ?? null); + $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; if ($type && null !== $type->getClassName()) { return "data/relationships/$fieldName"; } @@ -111,5 +105,3 @@ private function getSourcePointerFromViolation(ConstraintViolationInterface $vio return "data/attributes/$fieldName"; } } - -class_alias(ConstraintViolationListNormalizer::class, \ApiPlatform\Core\JsonApi\Serializer\ConstraintViolationListNormalizer::class); diff --git a/src/JsonApi/Serializer/EntrypointNormalizer.php b/src/JsonApi/Serializer/EntrypointNormalizer.php index a385107ef40..69228850e0c 100644 --- a/src/JsonApi/Serializer/EntrypointNormalizer.php +++ b/src/JsonApi/Serializer/EntrypointNormalizer.php @@ -16,9 +16,6 @@ use ApiPlatform\Api\Entrypoint; use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\HttpOperation; @@ -41,19 +38,11 @@ final class EntrypointNormalizer implements NormalizerInterface, CacheableSuppor private $iriConverter; private $urlGenerator; - public function __construct($resourceMetadataFactory, $iriConverter, UrlGeneratorInterface $urlGenerator) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, IriConverterInterface $iriConverter, UrlGeneratorInterface $urlGenerator) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->iriConverter = $iriConverter; $this->urlGenerator = $urlGenerator; - - if ($iriConverter instanceof LegacyIriConverterInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class)); - } - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } } /** @@ -66,36 +55,20 @@ public function normalize($object, $format = null, array $context = []) $entrypoint = ['links' => ['self' => $this->urlGenerator->generate('api_entrypoint', [], UrlGeneratorInterface::ABS_URL)]]; foreach ($object->getResourceNameCollection() as $resourceClass) { - /** @var ResourceMetadata|ResourceMetadataCollection */ + /** @var ResourceMetadataCollection */ $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if ($resourceMetadata instanceof ResourceMetadata) { - if (!$resourceMetadata->getCollectionOperations()) { - continue; - } - - try { - $entrypoint['links'][lcfirst($resourceMetadata->getShortName())] = $this->iriConverter->getIriFromResourceClass($resourceClass, UrlGeneratorInterface::ABS_URL); - } catch (InvalidArgumentException $ex) { - // Ignore resources without GET operations - } - } - foreach ($resourceMetadata as $resource) { - foreach ($resource->getOperations() as $operationName => $operation) { + foreach ($resource->getOperations() as $operation) { if (!$operation instanceof CollectionOperationInterface || ($operation instanceof HttpOperation && $operation->getUriVariables())) { continue; } try { - if ($this->iriConverter instanceof LegacyIriConverterInterface) { - $iri = $this->iriConverter->getIriFromResourceClass($resourceClass, UrlGeneratorInterface::ABS_URL); - } else { - $iri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_URL, $operation); - } + $iri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_URL, $operation); $entrypoint['links'][lcfirst($resource->getShortName())] = $iri; - } catch (InvalidArgumentException $ex) { + } catch (InvalidArgumentException) { // Ignore resources without GET operations } } @@ -121,5 +94,3 @@ public function hasCacheableSupportsMethod(): bool return true; } } - -class_alias(EntrypointNormalizer::class, \ApiPlatform\Core\JsonApi\Serializer\EntrypointNormalizer::class); diff --git a/src/JsonApi/Serializer/ErrorNormalizer.php b/src/JsonApi/Serializer/ErrorNormalizer.php index e856f660081..316a6b62620 100644 --- a/src/JsonApi/Serializer/ErrorNormalizer.php +++ b/src/JsonApi/Serializer/ErrorNormalizer.php @@ -82,5 +82,3 @@ public function hasCacheableSupportsMethod(): bool return true; } } - -class_alias(ErrorNormalizer::class, \ApiPlatform\Core\JsonApi\Serializer\ErrorNormalizer::class); diff --git a/src/JsonApi/Serializer/ItemNormalizer.php b/src/JsonApi/Serializer/ItemNormalizer.php index 48cad34d2b9..c0c2dc177bb 100644 --- a/src/JsonApi/Serializer/ItemNormalizer.php +++ b/src/JsonApi/Serializer/ItemNormalizer.php @@ -13,14 +13,13 @@ namespace ApiPlatform\JsonApi\Serializer; +use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\ItemNotFoundException; use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Serializer\AbstractItemNormalizer; use ApiPlatform\Serializer\CacheKeyTrait; use ApiPlatform\Serializer\ContextTrait; @@ -32,6 +31,7 @@ use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -52,17 +52,17 @@ final class ItemNormalizer extends AbstractItemNormalizer private $componentsCache = []; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ?PropertyAccessorInterface $propertyAccessor, ?NameConverterInterface $nameConverter, $resourceMetadataFactory, array $defaultContext = [], iterable $dataTransformers = [], ResourceAccessCheckerInterface $resourceAccessChecker = null) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null) { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, null, null, false, $defaultContext, $dataTransformers, $resourceMetadataFactory, $resourceAccessChecker); + parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataCollectionFactory, $resourceAccessChecker); } /** * {@inheritdoc} */ - public function supportsNormalization($data, $format = null): bool + public function supportsNormalization($data, $format = null, array $context = []): bool { - return self::FORMAT === $format && parent::supportsNormalization($data, $format); + return self::FORMAT === $format && parent::supportsNormalization($data, $format, $context); } /** @@ -72,7 +72,8 @@ public function supportsNormalization($data, $format = null): bool */ public function normalize($object, $format = null, array $context = []) { - if (null !== $this->getOutputClass($this->getObjectClass($object), $context)) { + $resourceClass = $this->getObjectClass($object); + if ($this->getOutputClass($resourceClass, $context)) { return parent::normalize($object, $format, $context); } @@ -80,9 +81,12 @@ public function normalize($object, $format = null, array $context = []) $context['cache_key'] = $this->getCacheKey($format, $context); } - $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); + if ($isResourceClass = $this->resourceClassResolver->isResourceClass($resourceClass)) { + $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); + } + $context = $this->initContext($resourceClass, $context); - $iri = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($object) : $this->iriConverter->getIriFromResource($object); + $iri = $this->iriConverter->getIriFromResource($object); $context['iri'] = $iri; $context['api_normalize'] = true; @@ -126,9 +130,9 @@ public function normalize($object, $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization($data, string $type, $format = null, array $context = []): bool { - return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format); + return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format, $context); } /** @@ -146,10 +150,7 @@ public function denormalize($data, $class, $format = null, array $context = []) throw new NotNormalizableValueException('Update is not allowed for this operation.'); } - $context[self::OBJECT_TO_POPULATE] = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri( - $data['data']['id'], - $context + ['fetch_data' => false] - ) : $this->iriConverter->getResourceFromIri( + $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getResourceFromIri( $data['data']['id'], $context + ['fetch_data' => false] ); @@ -190,19 +191,17 @@ protected function setAttributeValue($object, $attribute, $value, $format = null * * @see http://jsonapi.org/format/#document-resource-object-linkage * - * @param ApiProperty|PropertyMetadata $propertyMetadata - * * @throws RuntimeException * @throws NotNormalizableValueException */ - protected function denormalizeRelation(string $attributeName, $propertyMetadata, string $className, $value, ?string $format, array $context) + protected function denormalizeRelation(string $attributeName, ApiProperty $propertyMetadata, string $className, $value, ?string $format, array $context) { if (!\is_array($value) || !isset($value['id'], $value['type'])) { throw new NotNormalizableValueException('Only resource linkage supported currently, see: http://jsonapi.org/format/#document-resource-object-linkage.'); } try { - return $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri($value['id'], $context + ['fetch_data' => true]) : $this->iriConverter->getResourceFromIri($value['id'], $context + ['fetch_data' => true]); + return $this->iriConverter->getResourceFromIri($value['id'], $context + ['fetch_data' => true]); } catch (ItemNotFoundException $e) { throw new RuntimeException($e->getMessage(), $e->getCode(), $e); } @@ -211,14 +210,12 @@ protected function denormalizeRelation(string $attributeName, $propertyMetadata, /** * {@inheritdoc} * - * @param ApiProperty|PropertyMetadata $propertyMetadata - * * @see http://jsonapi.org/format/#document-resource-object-linkage */ - protected function normalizeRelation($propertyMetadata, $relatedObject, string $resourceClass, ?string $format, array $context) + protected function normalizeRelation(ApiProperty $propertyMetadata, ?object $relatedObject, string $resourceClass, ?string $format, array $context) { if (null !== $relatedObject) { - $iri = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($relatedObject) : $this->iriConverter->getIriFromResource($relatedObject); + $iri = $this->iriConverter->getIriFromResource($relatedObject); $context['iri'] = $iri; if (isset($context['resources'])) { @@ -281,13 +278,12 @@ private function getComponents($object, ?string $format, array $context): array ]; foreach ($attributes as $attribute) { - /** @var ApiProperty|PropertyMetadata */ $propertyMetadata = $this ->propertyMetadataFactory ->create($context['resource_class'], $attribute, $options); // TODO: 3.0 support multiple types, default value of types will be [] instead of null - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : ($propertyMetadata->getBuiltinTypes()[0] ?? null); + $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; $isOne = $isMany = false; if (null !== $type) { @@ -463,18 +459,15 @@ private function getIncludedNestedResources(string $relationshipName, array $con }, $filtered); } - // TODO: 3.0 remove + // TODO: this code is similar to the one used in JsonLd private function getResourceShortName(string $resourceClass): string { - /** @var ResourceMetadata|ResourceMetadataCollection */ - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + if ($this->resourceClassResolver->isResourceClass($resourceClass)) { + $resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass); - if ($resourceMetadata instanceof ResourceMetadata) { - return $resourceMetadata->getShortName(); + return $resourceMetadata->getOperation()->getShortName(); } - return $resourceMetadata->getOperation()->getShortName(); + return (new \ReflectionClass($resourceClass))->getShortName(); } } - -class_alias(ItemNormalizer::class, \ApiPlatform\Core\JsonApi\Serializer\ItemNormalizer::class); diff --git a/src/JsonApi/Serializer/ObjectNormalizer.php b/src/JsonApi/Serializer/ObjectNormalizer.php index 5b017e5d7fc..64bced3587b 100644 --- a/src/JsonApi/Serializer/ObjectNormalizer.php +++ b/src/JsonApi/Serializer/ObjectNormalizer.php @@ -15,11 +15,7 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Util\ClassInfoTrait; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -35,30 +31,16 @@ final class ObjectNormalizer implements NormalizerInterface, CacheableSupportsMe public const FORMAT = 'jsonapi'; private $decorated; - /** - * @var IriConverterInterface|LegacyIriConverterInterface - */ private $iriConverter; private $resourceClassResolver; - /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface - */ private $resourceMetadataFactory; - public function __construct(NormalizerInterface $decorated, $iriConverter, ResourceClassResolverInterface $resourceClassResolver, $resourceMetadataFactory) + public function __construct(NormalizerInterface $decorated, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory) { $this->decorated = $decorated; $this->iriConverter = $iriConverter; $this->resourceClassResolver = $resourceClassResolver; $this->resourceMetadataFactory = $resourceMetadataFactory; - - if ($iriConverter instanceof LegacyIriConverterInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class)); - } - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } } /** @@ -97,12 +79,12 @@ public function normalize($object, $format = null, array $context = []) if (isset($originalResource)) { $resourceClass = $this->resourceClassResolver->getResourceClass($originalResource); $resourceData = [ - 'id' => $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($originalResource) : $this->iriConverter->getIriFromResource($originalResource), - 'type' => $this->getResourceShortName($resourceClass), + 'id' => $this->iriConverter->getIriFromResource($originalResource), + 'type' => $this->resourceMetadataFactory->create($resourceClass)->getOperation()->getShortName(), ]; } else { $resourceData = [ - 'id' => \function_exists('spl_object_id') ? spl_object_id($object) : spl_object_hash($object), + 'id' => '/.well-known/genid/'.(bin2hex(random_bytes(10))), 'type' => (new \ReflectionClass($this->getObjectClass($object)))->getShortName(), ]; } @@ -113,19 +95,4 @@ public function normalize($object, $format = null, array $context = []) return ['data' => $resourceData]; } - - // TODO: 3.0 remove - private function getResourceShortName(string $resourceClass): string - { - /** @var ResourceMetadata|ResourceMetadataCollection */ - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - if ($resourceMetadata instanceof ResourceMetadata) { - return $resourceMetadata->getShortName(); - } - - return $resourceMetadata->getOperation()->getShortName(); - } } - -class_alias(ObjectNormalizer::class, \ApiPlatform\Core\JsonApi\Serializer\ObjectNormalizer::class); diff --git a/src/JsonApi/Serializer/ReservedAttributeNameConverter.php b/src/JsonApi/Serializer/ReservedAttributeNameConverter.php index 402b4402dad..750d6a76635 100644 --- a/src/JsonApi/Serializer/ReservedAttributeNameConverter.php +++ b/src/JsonApi/Serializer/ReservedAttributeNameConverter.php @@ -70,5 +70,3 @@ public function denormalize($propertyName, string $class = null, string $format return $propertyName; } } - -class_alias(ReservedAttributeNameConverter::class, \ApiPlatform\Core\JsonApi\Serializer\ReservedAttributeNameConverter::class); diff --git a/src/JsonLd/Action/ContextAction.php b/src/JsonLd/Action/ContextAction.php index 593b87e8270..63fa573eaf4 100644 --- a/src/JsonLd/Action/ContextAction.php +++ b/src/JsonLd/Action/ContextAction.php @@ -13,8 +13,6 @@ namespace ApiPlatform\JsonLd\Action; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\OperationNotFoundException; use ApiPlatform\JsonLd\ContextBuilderInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -36,20 +34,13 @@ final class ContextAction private $contextBuilder; private $resourceNameCollectionFactory; - /** - * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface|null - */ - private $resourceMetadataFactory; + private $resourceMetadataCollectionFactory; - public function __construct(ContextBuilderInterface $contextBuilder, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, $resourceMetadataFactory) + public function __construct(ContextBuilderInterface $contextBuilder, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory) { $this->contextBuilder = $contextBuilder; $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->resourceMetadataFactory = $resourceMetadataFactory; - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } /** @@ -68,18 +59,16 @@ public function __invoke(string $shortName): array } foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { - /** @var ResourceMetadata|ResourceMetadataCollection */ - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + /** @var ResourceMetadataCollection */ + $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass); - if ($resourceMetadata instanceof ResourceMetadataCollection) { - try { - $resourceMetadata = $resourceMetadata->getOperation(); - } catch (OperationNotFoundException $e) { - continue; - } + try { + $resourceMetadataCollection = $resourceMetadataCollection->getOperation(); + } catch (OperationNotFoundException) { + continue; } - if ($shortName === $resourceMetadata->getShortName()) { + if ($shortName === $resourceMetadataCollection->getShortName()) { return ['@context' => $this->contextBuilder->getResourceContext($resourceClass)]; } } @@ -87,5 +76,3 @@ public function __invoke(string $shortName): array throw new NotFoundHttpException(); } } - -class_alias(ContextAction::class, \ApiPlatform\Core\JsonLd\Action\ContextAction::class); diff --git a/src/JsonLd/ContextBuilder.php b/src/JsonLd/ContextBuilder.php index 3b14141db8d..2569967c807 100644 --- a/src/JsonLd/ContextBuilder.php +++ b/src/JsonLd/ContextBuilder.php @@ -14,11 +14,7 @@ namespace ApiPlatform\JsonLd; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface as LegacyPropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; @@ -29,7 +25,6 @@ /** * {@inheritdoc} - * TODO: 3.0 simplify or remove the class. * * @author Kévin Dunglas */ @@ -40,24 +35,11 @@ final class ContextBuilder implements AnonymousContextBuilderInterface public const FORMAT = 'jsonld'; private $resourceNameCollectionFactory; - /** - * @param ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory - */ - private $resourceMetadataFactory; - /** - * @var LegacyPropertyNameCollectionFactoryInterface|PropertyNameCollectionFactoryInterface - */ - private $propertyNameCollectionFactory; - /** - * @var LegacyPropertyMetadataFactoryInterface|PropertyMetadataFactoryInterface - */ - private $propertyMetadataFactory; - private $urlGenerator; - - /** - * @var NameConverterInterface|null - */ - private $nameConverter; + private ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory; + private PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory; + private PropertyMetadataFactoryInterface $propertyMetadataFactory; + private UrlGeneratorInterface $urlGenerator; + private ?NameConverterInterface $nameConverter = null; public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, UrlGeneratorInterface $urlGenerator, NameConverterInterface $nameConverter = null) { @@ -67,10 +49,6 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName $this->propertyMetadataFactory = $propertyMetadataFactory; $this->urlGenerator = $urlGenerator; $this->nameConverter = $nameConverter; - - if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } } /** @@ -92,13 +70,7 @@ public function getEntrypointContext(int $referenceType = UrlGeneratorInterface: $context = $this->getBaseContext($referenceType); foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { - // TODO: remove in 3.0 - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $shortName = $this->resourceMetadataFactory->create($resourceClass)->getShortName(); - } else { - $shortName = $this->resourceMetadataFactory->create($resourceClass)[0]->getShortName(); - } - + $shortName = $this->resourceMetadataFactory->create($resourceClass)[0]->getShortName(); $resourceName = lcfirst($shortName); $context[$resourceName] = [ @@ -115,23 +87,6 @@ public function getEntrypointContext(int $referenceType = UrlGeneratorInterface: */ public function getResourceContext(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): array { - // TODO: Remove in 3.0 - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (null === $shortName = $resourceMetadata->getShortName()) { - return []; - } - - if ($resourceMetadata->getAttribute('normalization_context')['iri_only'] ?? false) { - $context = $this->getBaseContext($referenceType); - $context['hydra:member']['@type'] = '@id'; - - return $context; - } - - return $this->getResourceContextWithShortname($resourceClass, $referenceType, $shortName); - } - $operation = $this->resourceMetadataFactory->create($resourceClass)->getOperation(); if (null === $shortName = $operation->getShortName()) { return []; @@ -152,16 +107,6 @@ public function getResourceContext(string $resourceClass, int $referenceType = U */ public function getResourceContextUri(string $resourceClass, int $referenceType = null): string { - // TODO: remove in 3.0 - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - if (null === $referenceType) { - $referenceType = $resourceMetadata->getAttribute('url_generation_strategy'); - } - - return $this->urlGenerator->generate('api_jsonld_context', ['shortName' => $resourceMetadata->getShortName()], $referenceType); - } - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass)[0]; if (null === $referenceType) { $referenceType = $resourceMetadata->getUrlGenerationStrategy(); @@ -176,7 +121,8 @@ public function getResourceContextUri(string $resourceClass, int $referenceType public function getAnonymousResourceContext($object, array $context = [], int $referenceType = UrlGeneratorInterface::ABS_PATH): array { $outputClass = $this->getObjectClass($object); - $shortName = (new \ReflectionClass($outputClass))->getShortName(); + $operation = $context['operation'] ?? new Get(shortName: (new \ReflectionClass($outputClass))->getShortName()); + $shortName = $operation->getShortName(); $jsonLdContext = [ '@context' => $this->getResourceContextWithShortname( @@ -197,11 +143,7 @@ public function getAnonymousResourceContext($object, array $context = [], int $r // here the object can be different from the resource given by the $context['api_resource'] value if (isset($context['api_resource'])) { - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $jsonLdContext['@type'] = $this->resourceMetadataFactory->create($this->getObjectClass($context['api_resource']))->getShortName(); - } else { - $jsonLdContext['@type'] = $this->resourceMetadataFactory->create($this->getObjectClass($context['api_resource']))[0]->getShortName(); - } + $jsonLdContext['@type'] = $this->resourceMetadataFactory->create($this->getObjectClass($context['api_resource']))[0]->getShortName(); } return $jsonLdContext; @@ -210,14 +152,9 @@ public function getAnonymousResourceContext($object, array $context = [], int $r private function getResourceContextWithShortname(string $resourceClass, int $referenceType, string $shortName, ?HttpOperation $operation = null): array { $context = $this->getBaseContext($referenceType); - if ($this->propertyMetadataFactory instanceof LegacyPropertyMetadataFactoryInterface) { - $propertyContext = []; - } else { - $propertyContext = $operation ? ['normalization_groups' => $operation->getNormalizationContext()['groups'] ?? null, 'denormalization_groups' => $operation->getDenormalizationContext()['groups'] ?? null] : ['normalization_groups' => [], 'denormalization_groups' => []]; - } + $propertyContext = $operation ? ['normalization_groups' => $operation->getNormalizationContext()['groups'] ?? null, 'denormalization_groups' => $operation->getDenormalizationContext()['groups'] ?? null] : ['normalization_groups' => [], 'denormalization_groups' => []]; foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { - /** @var PropertyMetadata|ApiProperty */ $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName, $propertyContext); if ($propertyMetadata->isIdentifier() && true !== $propertyMetadata->isWritable()) { @@ -225,15 +162,10 @@ private function getResourceContextWithShortname(string $resourceClass, int $ref } $convertedName = $this->nameConverter ? $this->nameConverter->normalize($propertyName, $resourceClass, self::FORMAT) : $propertyName; - if ($propertyMetadata instanceof PropertyMetadata) { - $jsonldContext = ($propertyMetadata->getAttributes() ?? [])['jsonld_context'] ?? []; - $id = $propertyMetadata->getIri(); - } else { - $jsonldContext = $propertyMetadata->getJsonldContext() ?? []; + $jsonldContext = $propertyMetadata->getJsonldContext() ?? []; - if ($id = $propertyMetadata->getIris()) { - $id = 1 === \count($id) ? $id[0] : $id; - } + if ($id = $propertyMetadata->getIris()) { + $id = 1 === \count($id) ? $id[0] : $id; } if (!$id) { @@ -259,5 +191,3 @@ private function getResourceContextWithShortname(string $resourceClass, int $ref return $context; } } - -class_alias(ContextBuilder::class, \ApiPlatform\Core\JsonLd\ContextBuilder::class); diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php index cc487ac271c..0f43bbdfb9f 100644 --- a/src/JsonLd/Serializer/ItemNormalizer.php +++ b/src/JsonLd/Serializer/ItemNormalizer.php @@ -16,11 +16,12 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\JsonLd\AnonymousContextBuilderInterface; use ApiPlatform\JsonLd\ContextBuilderInterface; use ApiPlatform\Metadata\HttpOperation; +use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Serializer\AbstractItemNormalizer; use ApiPlatform\Serializer\ContextTrait; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; @@ -46,13 +47,9 @@ final class ItemNormalizer extends AbstractItemNormalizer private $contextBuilder; - public function __construct($resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ContextBuilderInterface $contextBuilder, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], iterable $dataTransformers = [], ResourceAccessCheckerInterface $resourceAccessChecker = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ContextBuilderInterface $contextBuilder, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceAccessCheckerInterface $resourceAccessChecker = null) { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, null, false, $defaultContext, $dataTransformers, $resourceMetadataFactory, $resourceAccessChecker); - - if ($iriConverter instanceof LegacyIriConverterInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class)); - } + parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataCollectionFactory, $resourceAccessChecker); $this->contextBuilder = $contextBuilder; } @@ -60,9 +57,9 @@ public function __construct($resourceMetadataFactory, PropertyNameCollectionFact /** * {@inheritdoc} */ - public function supportsNormalization($data, $format = null): bool + public function supportsNormalization($data, $format = null, array $context = []): bool { - return self::FORMAT === $format && parent::supportsNormalization($data, $format); + return self::FORMAT === $format && parent::supportsNormalization($data, $format, $context); } /** @@ -74,30 +71,34 @@ public function supportsNormalization($data, $format = null): bool */ public function normalize($object, $format = null, array $context = []) { - $objectClass = $this->getObjectClass($object); - $outputClass = $this->getOutputClass($objectClass, $context); - if (null !== $outputClass && !isset($context[self::IS_TRANSFORMED_TO_SAME_CLASS])) { + $resourceClass = $this->getObjectClass($object); + + if ($outputClass = $this->getOutputClass($resourceClass, $context)) { return parent::normalize($object, $format, $context); } // TODO: we should not remove the resource_class in the normalizeRawCollection as we would find out anyway that it's not the same as the requested one $previousResourceClass = $context['resource_class'] ?? null; - $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); - $context = $this->initContext($resourceClass, $context); - $metadata = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context); + if ($isResourceClass = $this->resourceClassResolver->isResourceClass($resourceClass)) { + $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); + $context = $this->initContext($resourceClass, $context); + $metadata = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context); + } elseif ($this->contextBuilder instanceof AnonymousContextBuilderInterface) { + // We should improve what's behind the context creation, its probably more complicated then it should + $metadata = $this->createJsonLdContext($this->contextBuilder, $object, $context); + } + + // maybe not needed anymore if (isset($context['operation']) && $previousResourceClass !== $resourceClass) { unset($context['operation'], $context['operation_name']); } - if ($this->iriConverter instanceof LegacyIriConverterInterface) { - $iri = $this->iriConverter->getIriFromItem($object); - } else { - $iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context); + if ($iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context)) { + $context['iri'] = $iri; + $metadata['@id'] = $iri; } - $context['iri'] = $iri; - $metadata['@id'] = $iri; $context['api_normalize'] = true; $data = parent::normalize($object, $format, $context); @@ -105,12 +106,8 @@ public function normalize($object, $format = null, array $context = []) return $data; } - // TODO: remove in 3.0 - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $metadata['@type'] = $resourceMetadata->getIri() ?: $resourceMetadata->getShortName(); - } elseif ($this->resourceMetadataFactory) { - $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation(); + if (!isset($metadata['@type']) && $isResourceClass) { + $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(); $types = $operation instanceof HttpOperation ? $operation->getTypes() : null; if (null === $types) { $types = [$operation->getShortName()]; @@ -124,9 +121,9 @@ public function normalize($object, $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization($data, string $type, $format = null, array $context = []): bool { - return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format); + return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format, $context); } /** @@ -144,11 +141,9 @@ public function denormalize($data, $class, $format = null, array $context = []) throw new NotNormalizableValueException('Update is not allowed for this operation.'); } - $context[self::OBJECT_TO_POPULATE] = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri($data['@id'], $context + ['fetch_data' => true]) : $this->iriConverter->getResourceFromIri($data['@id'], $context + ['fetch_data' => true]); + $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getResourceFromIri($data['@id'], $context + ['fetch_data' => true]); } return parent::denormalize($data, $class, $format, $context); } } - -class_alias(ItemNormalizer::class, \ApiPlatform\Core\JsonLd\Serializer\ItemNormalizer::class); diff --git a/src/JsonLd/Serializer/JsonLdContextTrait.php b/src/JsonLd/Serializer/JsonLdContextTrait.php index 296c6476143..d8603e03d6e 100644 --- a/src/JsonLd/Serializer/JsonLdContextTrait.php +++ b/src/JsonLd/Serializer/JsonLdContextTrait.php @@ -51,13 +51,11 @@ private function createJsonLdContext(AnonymousContextBuilderInterface $contextBu { // We're in a collection, don't add the @context part if (isset($context['jsonld_has_context'])) { - return $contextBuilder->getAnonymousResourceContext($object, ($context['output'] ?? []) + ['api_resource' => $context['api_resource'] ?? null, 'has_context' => true]); + return $contextBuilder->getAnonymousResourceContext($object, ['api_resource' => $context['api_resource'] ?? null, 'has_context' => true]); } $context['jsonld_has_context'] = true; - return $contextBuilder->getAnonymousResourceContext($object, ($context['output'] ?? []) + ['api_resource' => $context['api_resource'] ?? null]); + return $contextBuilder->getAnonymousResourceContext($object, ['api_resource' => $context['api_resource'] ?? null]); } } - -class_alias(JsonLdContextTrait::class, \ApiPlatform\Core\JsonLd\Serializer\JsonLdContextTrait::class); diff --git a/src/JsonLd/Serializer/ObjectNormalizer.php b/src/JsonLd/Serializer/ObjectNormalizer.php index 3bfe72d97f6..c3e0df18d28 100644 --- a/src/JsonLd/Serializer/ObjectNormalizer.php +++ b/src/JsonLd/Serializer/ObjectNormalizer.php @@ -14,7 +14,6 @@ namespace ApiPlatform\JsonLd\Serializer; use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\JsonLd\AnonymousContextBuilderInterface; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; @@ -34,15 +33,11 @@ final class ObjectNormalizer implements NormalizerInterface, CacheableSupportsMe private $iriConverter; private $anonymousContextBuilder; - public function __construct(NormalizerInterface $decorated, $iriConverter, AnonymousContextBuilderInterface $anonymousContextBuilder) + public function __construct(NormalizerInterface $decorated, IriConverterInterface $iriConverter, AnonymousContextBuilderInterface $anonymousContextBuilder) { $this->decorated = $decorated; $this->iriConverter = $iriConverter; $this->anonymousContextBuilder = $anonymousContextBuilder; - - if ($iriConverter instanceof LegacyIriConverterInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class)); - } } /** @@ -92,7 +87,7 @@ public function normalize($object, $format = null, array $context = []) if (isset($originalResource)) { try { - $context['output']['iri'] = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($originalResource) : $this->iriConverter->getIriFromResource($originalResource); + $context['output']['iri'] = $this->iriConverter->getIriFromResource($originalResource); } catch (InvalidArgumentException $e) { // The original resource has no identifiers } @@ -104,5 +99,3 @@ public function normalize($object, $format = null, array $context = []) return $metadata + $data; } } - -class_alias(ObjectNormalizer::class, \ApiPlatform\Core\JsonLd\Serializer\ObjectNormalizer::class); diff --git a/src/JsonSchema/Command/JsonSchemaGenerateCommand.php b/src/JsonSchema/Command/JsonSchemaGenerateCommand.php index d637a06723d..46a0f297d3e 100644 --- a/src/JsonSchema/Command/JsonSchemaGenerateCommand.php +++ b/src/JsonSchema/Command/JsonSchemaGenerateCommand.php @@ -13,8 +13,6 @@ namespace ApiPlatform\JsonSchema\Command; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface as LegacySchemaFactoryInterface; use ApiPlatform\JsonSchema\Schema; use ApiPlatform\JsonSchema\SchemaFactoryInterface; use ApiPlatform\Metadata\HttpOperation; @@ -57,8 +55,7 @@ protected function configure() $this ->setDescription('Generates the JSON Schema for a resource operation.') ->addArgument('resource', InputArgument::REQUIRED, 'The Fully Qualified Class Name (FQCN) of the resource') - ->addOption('itemOperation', null, InputOption::VALUE_REQUIRED, 'The item operation') - ->addOption('collectionOperation', null, InputOption::VALUE_REQUIRED, 'The collection operation') + ->addOption('operation', null, InputOption::VALUE_REQUIRED, 'The operation name') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The response format', (string) $this->formats[0]) ->addOption('type', null, InputOption::VALUE_REQUIRED, sprintf('The type of schema to generate (%s or %s)', Schema::TYPE_INPUT, Schema::TYPE_OUTPUT), Schema::TYPE_INPUT); } @@ -72,10 +69,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var string $resource */ $resource = $input->getArgument('resource'); - /** @var ?string $itemOperation */ - $itemOperation = $input->getOption('itemOperation'); - /** @var ?string $collectionOperation */ - $collectionOperation = $input->getOption('collectionOperation'); + $operation = $input->getOption('operation'); /** @var string $format */ $format = $input->getOption('format'); /** @var string $type */ @@ -91,30 +85,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidOptionException(sprintf('The response format "%s" is not supported. Supported formats are : %s.', $format, implode(', ', $this->formats))); } - /** @var ?string $operationType */ - $operationType = null; - /** @var ?string $operationName */ - $operationName = null; + $schema = $this->schemaFactory->buildSchema($resource, $format, $type, $operation ? (new class() extends HttpOperation {})->withName($operation) : null); - if ($itemOperation && $collectionOperation) { - $io->error('You can only use one of "--itemOperation" and "--collectionOperation" options at the same time.'); - - return 1; - } - - if (null !== $itemOperation || null !== $collectionOperation) { - $operationType = $itemOperation ? OperationType::ITEM : OperationType::COLLECTION; - $operationName = $itemOperation ?? $collectionOperation; - } - - if ($this->schemaFactory instanceof LegacySchemaFactoryInterface) { - $schema = $this->schemaFactory->buildSchema($resource, $format, $type, $operationType, $operationName); - } else { - $schema = $this->schemaFactory->buildSchema($resource, $format, $type, $operationName ? (new class() extends HttpOperation {})->withName($operationName) : null); - } - - if (null !== $operationType && null !== $operationName && !$schema->isDefined()) { - $io->error(sprintf('There is no %s defined for the operation "%s" of the resource "%s".', $type, $operationName, $resource)); + if (!$schema->isDefined()) { + $io->error(sprintf('There is no %s defined for the operation "%s" of the resource "%s".', $type, $operation, $resource)); return 1; } @@ -124,5 +98,3 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } } - -class_alias(JsonSchemaGenerateCommand::class, \ApiPlatform\Core\JsonSchema\Command\JsonSchemaGenerateCommand::class); diff --git a/src/JsonSchema/Schema.php b/src/JsonSchema/Schema.php index 1a8f48bf795..4a840bd0d38 100644 --- a/src/JsonSchema/Schema.php +++ b/src/JsonSchema/Schema.php @@ -133,5 +133,3 @@ private function removeDefinitionKeyPrefix(string $definitionKey): string return substr($definitionKey, $prefix); } } - -class_alias(Schema::class, \ApiPlatform\Core\JsonSchema\Schema::class); diff --git a/src/JsonSchema/SchemaFactory.php b/src/JsonSchema/SchemaFactory.php index 0c6c51986a4..5b86a687234 100644 --- a/src/JsonSchema/SchemaFactory.php +++ b/src/JsonSchema/SchemaFactory.php @@ -119,6 +119,8 @@ public function buildSchema(string $className, string $format = 'json', string $ // see https://github.com/json-schema-org/json-schema-spec/pull/737 if (Schema::VERSION_SWAGGER !== $version && $operation && $operation->getDeprecationReason()) { $definition['deprecated'] = true; + } else { + $definition['deprecated'] = false; } // externalDocs is an OpenAPI specific extension, but JSON Schema allows additional keys, so we always add it diff --git a/src/JsonSchema/TypeFactory.php b/src/JsonSchema/TypeFactory.php index 4e008ec134d..6c0c3aaba79 100644 --- a/src/JsonSchema/TypeFactory.php +++ b/src/JsonSchema/TypeFactory.php @@ -154,7 +154,7 @@ private function getClassType(?string $className, string $format, ?bool $readabl throw new \LogicException('The schema factory must be injected by calling the "setSchemaFactory" method.'); } - $subSchema = $this->schemaFactory instanceof LegacySchemaFactoryInterface ? $this->schemaFactory->buildSchema($className, $format, Schema::TYPE_OUTPUT, null, null, $subSchema, $serializerContext, false) : $this->schemaFactory->buildSchema($className, $format, Schema::TYPE_OUTPUT, null, $subSchema, $serializerContext, false); + $subSchema = $this->schemaFactory->buildSchema($className, $format, Schema::TYPE_OUTPUT, null, $subSchema, $serializerContext, false); return ['$ref' => $subSchema['$ref']]; } @@ -201,5 +201,3 @@ private function addNullabilityToTypeDefinition(array $jsonSchema, Type $type, ? return array_merge($jsonSchema, ['nullable' => true]); } } - -class_alias(TypeFactory::class, \ApiPlatform\Core\JsonSchema\TypeFactory::class); diff --git a/src/Metadata/ApiProperty.php b/src/Metadata/ApiProperty.php index bf97c45b7fd..64fe8c9f55a 100644 --- a/src/Metadata/ApiProperty.php +++ b/src/Metadata/ApiProperty.php @@ -169,7 +169,6 @@ public function __construct( $this->jsonSchemaContext = $jsonSchemaContext; $this->push = $push; $this->security = $security; - $this->openapiContext = $openapiContext; $this->securityPostDenormalize = $securityPostDenormalize; $this->types = $types; $this->builtinTypes = $builtinTypes; diff --git a/src/Metadata/Property/DeprecationMetadataTrait.php b/src/Metadata/Property/DeprecationMetadataTrait.php deleted file mode 100644 index a270f4ad86e..00000000000 --- a/src/Metadata/Property/DeprecationMetadataTrait.php +++ /dev/null @@ -1,48 +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\Metadata\Property; - -use ApiPlatform\Core\Annotation\ApiProperty; -use ApiPlatform\Metadata\ApiProperty as ApiPropertyMetadata; -use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; - -/** - * @internal - */ -trait DeprecationMetadataTrait -{ - private $camelCaseToSnakeCaseNameConverter; - - private function withDeprecatedAttributes(ApiPropertyMetadata $propertyMetadata, array $attributes): ApiPropertyMetadata - { - $extraProperties = []; - if (!$this->camelCaseToSnakeCaseNameConverter) { - $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter(); - } - - foreach ($attributes as $key => $value) { - $propertyName = $this->camelCaseToSnakeCaseNameConverter->denormalize($key); - - if (method_exists($propertyMetadata, $methodName = 'with'.ucfirst($propertyName))) { - trigger_deprecation('api-platform', '2.7', sprintf('Using "%s" inside attributes on the "%s" annotation is deprecated, use "%s" on the attribute "%s" instead', $key, ApiProperty::class, $propertyName, ApiPropertyMetadata::class)); - $propertyMetadata = $propertyMetadata->{$methodName}($value); - continue; - } - - $extraProperties[$key] = $value; - } - - return $propertyMetadata->withExtraProperties($propertyMetadata->getExtraProperties() + $extraProperties); - } -} diff --git a/src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php b/src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php index ed9c4f7d550..231e3938392 100644 --- a/src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/AttributePropertyMetadataFactory.php @@ -100,17 +100,7 @@ private function createMetadata(ApiProperty $attribute, ApiProperty $propertyMet } foreach (get_class_methods(ApiProperty::class) as $method) { - if ( - // TODO: remove these checks for deprecated methods in 3.0 - 'getAttribute' !== $method && - 'isChildInherited' !== $method && - 'getSubresource' !== $method && - 'getAttributes' !== $method && - // end of deprecated methods - - preg_match('/^(?:get|is)(.*)/', $method, $matches) && - null !== $val = $attribute->{$method}() - ) { + if (preg_match('/^(?:get|is)(.*)/', $method, $matches) && null !== $val = $attribute->{$method}()) { $propertyMetadata = $propertyMetadata->{"with{$matches[1]}"}($val); } } diff --git a/src/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php index 02aabedee40..58884ea4821 100644 --- a/src/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php +++ b/src/Metadata/Property/Factory/CachedPropertyNameCollectionFactory.php @@ -48,5 +48,3 @@ public function create(string $resourceClass, array $options = []): PropertyName }); } } - -class_alias(CachedPropertyNameCollectionFactory::class, \ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyNameCollectionFactory::class); diff --git a/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php b/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php index 6b019947b95..e9feb7cb77e 100644 --- a/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/ExtractorPropertyMetadataFactory.php @@ -16,7 +16,6 @@ use ApiPlatform\Exception\PropertyNotFoundException; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Extractor\PropertyExtractorInterface; -use ApiPlatform\Metadata\Property\DeprecationMetadataTrait; use Symfony\Component\PropertyInfo\Type; /** @@ -27,7 +26,6 @@ */ final class ExtractorPropertyMetadataFactory implements PropertyMetadataFactoryInterface { - use DeprecationMetadataTrait; private $extractor; private $decorated; @@ -65,11 +63,6 @@ public function create(string $resourceClass, string $property, array $options = $apiProperty = new ApiProperty(); foreach ($propertyMetadata as $key => $value) { - if ('subresource' === $key) { - trigger_deprecation('api-platform', '2.7', 'Using "subresource" is deprecated, declare another resource instead.'); - continue; - } - if ('builtinTypes' === $key && null !== $value) { $value = array_map(function (string $builtinType): Type { return new Type($builtinType); @@ -83,10 +76,6 @@ public function create(string $resourceClass, string $property, array $options = } } - if (isset($propertyMetadata['attributes'])) { - $apiProperty = $this->withDeprecatedAttributes($apiProperty, $propertyMetadata['attributes']); - } - return $apiProperty; } @@ -127,10 +116,6 @@ private function update(ApiProperty $propertyMetadata, array $metadata): ApiProp $propertyMetadata = $propertyMetadata->{'with'.ucfirst($metadataKey)}($metadata[$metadataKey]); } - if (isset($metadata['attributes'])) { - $propertyMetadata = $this->withDeprecatedAttributes($propertyMetadata, $metadata['attributes']); - } - return $propertyMetadata; } } diff --git a/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php index 6e9708421d4..27df8c93881 100644 --- a/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php +++ b/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php @@ -75,5 +75,3 @@ public function create(string $resourceClass, array $options = []): PropertyName return new PropertyNameCollection(array_values($propertyNames)); } } - -class_alias(ExtractorPropertyNameCollectionFactory::class, \ApiPlatform\Core\Metadata\Property\Factory\ExtractorPropertyNameCollectionFactory::class); diff --git a/src/Metadata/Property/Factory/PropertyInfoPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/PropertyInfoPropertyNameCollectionFactory.php index bad805cc564..02c16235a7e 100644 --- a/src/Metadata/Property/Factory/PropertyInfoPropertyNameCollectionFactory.php +++ b/src/Metadata/Property/Factory/PropertyInfoPropertyNameCollectionFactory.php @@ -42,5 +42,3 @@ public function create(string $resourceClass, array $options = []): PropertyName return new PropertyNameCollection($properties ?? []); } } - -class_alias(PropertyInfoPropertyNameCollectionFactory::class, \ApiPlatform\Core\Bridge\Symfony\PropertyInfo\Metadata\Property\PropertyInfoPropertyNameCollectionFactory::class); diff --git a/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php b/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php index 3e89393ebfb..57c7cbaa797 100644 --- a/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php @@ -65,6 +65,10 @@ public function create(string $resourceClass, string $property, array $options = $propertyMetadata = $this->transformReadWrite($propertyMetadata, $resourceClass, $property, $normalizationGroups, $denormalizationGroups); + if (!$this->isResourceClass($resourceClass) && ($builtinType = $propertyMetadata->getBuiltinTypes()[0] ?? null) && $builtinType->isCollection()) { + return $propertyMetadata->withReadableLink(true)->withWritableLink(true); + } + return $this->transformLinkStatus($propertyMetadata, $normalizationGroups, $denormalizationGroups); } @@ -158,7 +162,7 @@ private function transformLinkStatus(ApiProperty $propertyMetadata, array $norma * Groups are extracted in the following order: * * - From the "serializer_groups" key of the $options array. - * - From metadata of the given operation ("collection_operation_name" and "item_operation_name" keys). + * - From metadata of the given operation ("operation_name" key). * - From metadata of the current resource. * * @throws ResourceClassNotFoundException diff --git a/src/Metadata/Property/PropertyNameCollection.php b/src/Metadata/Property/PropertyNameCollection.php index de52f141b7a..c72dec5d879 100644 --- a/src/Metadata/Property/PropertyNameCollection.php +++ b/src/Metadata/Property/PropertyNameCollection.php @@ -51,5 +51,3 @@ public function count(): int return \count($this->properties); } } - -class_alias(PropertyNameCollection::class, \ApiPlatform\Core\Metadata\Property\PropertyNameCollection::class); diff --git a/src/Metadata/Resource/DeprecationMetadataTrait.php b/src/Metadata/Resource/DeprecationMetadataTrait.php deleted file mode 100644 index c229cc049a4..00000000000 --- a/src/Metadata/Resource/DeprecationMetadataTrait.php +++ /dev/null @@ -1,84 +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\Metadata\Resource; - -use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; - -/** - * @internal - */ -trait DeprecationMetadataTrait -{ - private $camelCaseToSnakeCaseNameConverter; - - public function getKeyValue(string $key, $value) - { - if (!$this->camelCaseToSnakeCaseNameConverter) { - $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter(); - } - - if ('attributes' === $key) { - trigger_deprecation('api-platform/core', '2.7', 'The "attributes" option is deprecated and will be renamed to "extra_properties".'); - $key = 'extra_properties'; - } elseif ('iri' === $key) { - trigger_deprecation('api-platform/core', '2.7', 'The "iri" is deprecated and will be renamed to "types".'); - $key = 'types'; - $value = [$value]; - } elseif ('validation_groups' === $key) { - trigger_deprecation('api-platform/core', '2.7', 'The "validation_groups" is deprecated and will be renamed to "validation_context" having an array with a "groups" key.'); - $key = 'validation_context'; - $value = ['groups' => $value]; - } elseif ('access_control' === $key) { - $key = 'security'; - trigger_deprecation('api-platform/core', '2.7', 'The "access_control" option is deprecated and will be renamed to "security".'); - } elseif ('access_control_message' === $key) { - $key = 'security_message'; - trigger_deprecation('api-platform/core', '2.7', 'The "access_control_message" option is deprecated and will be renamed to "security_message".'); - } elseif ('path' === $key) { - $key = 'uri_template'; - trigger_deprecation('api-platform/core', '2.7', 'The "path" option is deprecated and will be renamed to "uri_template".'); - // Transform default value to an empty array if null - } elseif (\in_array($key, ['denormalization_context', 'normalization_context', 'hydra_context', 'openapi_context', 'order', 'pagination_via_cursor', 'exception_to_status'], true)) { - if (null === $value) { - $value = []; - } elseif (!\is_array($value)) { - $value = [$value]; - } - } elseif ('route_prefix' === $key) { - $value = \is_string($value) ? $value : ''; - } elseif ('swagger_context' === $key) { - trigger_deprecation('api-platform/core', '2.7', 'The "swagger_context" option is deprecated and will be removed, use "openapi_context".'); - $key = 'openapi_context'; - $value = $value ?? []; - } elseif ('query_parameter_validation_enabled' === $key) { - $value = !$value ? false : $value; - // GraphQl related keys - } elseif (\in_array($key, ['collection_query', 'item_query', 'mutation'], true)) { - trigger_deprecation('api-platform/core', '2.7', 'To specify a GraphQl resolver use "resolver" instead of "mutation", "item_query" or "collection_query".'); - $key = 'resolver'; - } elseif ('filters' === $key) { - $value = null === $value ? [] : $value; - } elseif ('graphql' === $key) { - trigger_deprecation('api-platform/core', '2.7', 'The "graphql" option is deprecated and will be renamed to "graphQlOperations".'); - $key = 'graphQlOperations'; - } elseif ('identifiers' === $key) { - $key = 'uriVariables'; - } elseif ('doctrine_mongodb' === $key) { - $key = 'extra_properties'; - $value = ['doctrine_mongodb' => $value]; - } - - return [$this->camelCaseToSnakeCaseNameConverter->denormalize($key), $value]; - } -} diff --git a/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php index 1f2bd8433a5..783e7a37311 100644 --- a/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php @@ -32,10 +32,10 @@ use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; -use ApiPlatform\Metadata\Resource\DeprecationMetadataTrait; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; /** * Creates a resource metadata from {@see ApiResource} annotations. @@ -45,12 +45,11 @@ */ final class AttributesResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface { - use DeprecationMetadataTrait; - private $defaults; private $decorated; private $logger; private $graphQlEnabled; + private $camelCaseToSnakeCaseNameConverter; public function __construct(ResourceMetadataCollectionFactoryInterface $decorated = null, LoggerInterface $logger = null, array $defaults = [], bool $graphQlEnabled = false) { @@ -58,6 +57,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $decorate $this->decorated = $decorated; $this->logger = $logger ?? new NullLogger(); $this->graphQlEnabled = $graphQlEnabled; + $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter(); } /** @@ -226,7 +226,7 @@ private function getOperationWithDefaults(ApiResource $resource, Operation $oper } return [ - sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET), $operation instanceof GetCollection ? '_collection' : ''), + sprintf('_api_%s_%s%s', $operation->getUriTemplate() ?: $operation->getShortName(), strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET), $operation instanceof CollectionOperationInterface ? '_collection' : ''), $operation, ]; } @@ -236,22 +236,31 @@ private function getOperationWithDefaults(ApiResource $resource, Operation $oper */ private function addGlobalDefaults($operation) { - $extraProperties = $operation->getExtraProperties(); + $extraProperties = []; foreach ($this->defaults as $key => $value) { - [$newKey, $value] = $this->getKeyValue($key, $value); - $upperKey = ucfirst($newKey); + $upperKey = ucfirst($this->camelCaseToSnakeCaseNameConverter->denormalize($key)); $getter = 'get'.$upperKey; if (!method_exists($operation, $getter)) { if (!isset($extraProperties[$key])) { $extraProperties[$key] = $value; } - } elseif (null === $operation->{$getter}()) { + } else { + $currentValue = $operation->{$getter}(); + + if (\is_array($currentValue) && $currentValue) { + $operation = $operation->{'with'.$upperKey}(array_merge($value, $currentValue)); + } + + if (null !== $currentValue) { + continue; + } + $operation = $operation->{'with'.$upperKey}($value); } } - return $operation->withExtraProperties($extraProperties); + return $operation->withExtraProperties(array_merge($extraProperties, $operation->getExtraProperties())); } private function getResourceWithDefaults(string $resourceClass, string $shortName, ApiResource $resource): ApiResource diff --git a/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php index 6ba2c98ce24..a4fd02bccad 100644 --- a/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php @@ -24,8 +24,8 @@ use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; -use ApiPlatform\Metadata\Resource\DeprecationMetadataTrait; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; /** * Creates a resource metadata from {@see Resource} extractors (XML, YAML). @@ -35,16 +35,17 @@ */ final class ExtractorResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface { - use DeprecationMetadataTrait; private $extractor; private $decorated; private $defaults; + private $camelCaseToSnakeCaseNameConverter; public function __construct(ResourceExtractorInterface $extractor, ResourceMetadataCollectionFactoryInterface $decorated = null, array $defaults = []) { $this->extractor = $extractor; $this->decorated = $decorated; $this->defaults = $defaults; + $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter(); } /** @@ -130,7 +131,7 @@ private function buildOperations(?array $data, ApiResource $resource): array continue; } - [$camelCaseKey, $value] = $this->getKeyValue($key, $value); + $camelCaseKey = $this->camelCaseToSnakeCaseNameConverter->denormalize($key); $methodName = 'with'.ucfirst($camelCaseKey); if (method_exists($operation, $methodName)) { @@ -164,7 +165,7 @@ private function buildGraphQlOperations(?array $data, ApiResource $resource): ar continue; } - [$camelCaseKey, $value] = $this->getKeyValue($key, $value); + $camelCaseKey = $this->camelCaseToSnakeCaseNameConverter->denormalize($key); $methodName = 'with'.ucfirst($camelCaseKey); if (method_exists($operation, $methodName)) { @@ -184,7 +185,7 @@ private function buildGraphQlOperations(?array $data, ApiResource $resource): ar private function getOperationWithDefaults(ApiResource $resource, HttpOperation $operation): HttpOperation { foreach (($this->defaults['attributes'] ?? []) as $key => $value) { - [$key, $value] = $this->getKeyValue($key, $value); + $key = $this->camelCaseToSnakeCaseNameConverter->denormalize($key); if (null === $operation->{'get'.ucfirst($key)}()) { $operation = $operation->{'with'.ucfirst($key)}($value); } diff --git a/src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php index 55c17ffb23d..a644a8af63e 100644 --- a/src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php @@ -15,8 +15,7 @@ use ApiPlatform\Exception\ResourceClassNotFoundException; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; -use ApiPlatform\Util\AnnotationFilterExtractorTrait; -use Doctrine\Common\Annotations\Reader; +use ApiPlatform\Util\AttributeFilterExtractorTrait; /** * Creates a resource metadata from {@see Resource} annotations. @@ -26,17 +25,13 @@ */ final class FiltersResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface { - use AnnotationFilterExtractorTrait; + use AttributeFilterExtractorTrait; private $decorated; - private $reader; - public function __construct(ResourceMetadataCollectionFactoryInterface $decorated = null, ?Reader $reader = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $decorated = null) { $this->decorated = $decorated; - if ($reader) { - $this->reader = $reader; - } } /** @@ -56,11 +51,7 @@ public function create(string $resourceClass): ResourceMetadataCollection throw new ResourceClassNotFoundException(sprintf('Resource "%s" not found.', $resourceClass)); } - $filters = array_keys($this->readFilterAnnotations($reflectionClass, $this->reader)); - - if (\count($filters)) { - trigger_deprecation('api-platform/core', '2.7', 'Use php attributes instead of doctrine annotations to declare filters.'); - } + $filters = array_keys($this->readFilterAttributes($reflectionClass)); foreach ($resourceMetadataCollection as $i => $resource) { foreach ($operations = $resource->getOperations() as $operationName => $operation) { diff --git a/src/Metadata/Resource/Factory/LegacyResourceMetadataResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/LegacyResourceMetadataResourceMetadataCollectionFactory.php deleted file mode 100644 index f35d8fd179a..00000000000 --- a/src/Metadata/Resource/Factory/LegacyResourceMetadataResourceMetadataCollectionFactory.php +++ /dev/null @@ -1,265 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameGenerator; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\CollectionOperationInterface; -use ApiPlatform\Metadata\Delete; -use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\GraphQl\Mutation; -use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; -use ApiPlatform\Metadata\GraphQl\Query; -use ApiPlatform\Metadata\GraphQl\QueryCollection; -use ApiPlatform\Metadata\GraphQl\Subscription; -use ApiPlatform\Metadata\HttpOperation; -use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Operations; -use ApiPlatform\Metadata\Post; -use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Metadata\Resource\DeprecationMetadataTrait; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; - -final class LegacyResourceMetadataResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface -{ - use DeprecationMetadataTrait; - private $decorated; - private $resourceMetadataFactory; - private $propertyNameCollectionFactory; - private $propertyMetadataFactory; - private $defaults; - - public function __construct(ResourceMetadataCollectionFactoryInterface $decorated = null, ResourceMetadataFactoryInterface $resourceMetadataFactory = null, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory = null, PropertyMetadataFactoryInterface $propertyMetadataFactory = null, array $defaults = []) - { - $this->decorated = $decorated; - $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->defaults = $defaults + ['attributes' => []]; - } - - public function create(string $resourceClass): ResourceMetadataCollection - { - $resourceMetadataCollection = new ResourceMetadataCollection($resourceClass); - if ($this->decorated) { - $resourceMetadataCollection = $this->decorated->create($resourceClass); - } - - try { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - } catch (ResourceClassNotFoundException $resourceNotFoundException) { - return $resourceMetadataCollection; - } - - $attributes = $resourceMetadata->getAttributes() ?? []; - - foreach ($this->defaults['attributes'] as $key => $value) { - if (!$value) { - continue; - } - - if (!isset($attributes[$key])) { - $attributes[$key] = $value; - } - } - - $resource = (new ApiResource()) - ->withShortName($resourceMetadata->getShortName()) - ->withClass($resourceClass) - ->withExtraProperties(['is_legacy_resource_metadata' => true]); - - if ($description = $resourceMetadata->getDescription()) { - $resource = $resource->withDescription($description); - } - - if ($resourceMetadata->getIri()) { - $resource = $resource->withTypes([$resourceMetadata->getIri()]); - } - - foreach ($attributes as $key => $value) { - $resource = $this->setAttributeValue($resource, $key, $value); - } - - $resource = $this->identifiersToUriVariables($resourceMetadata, $resource); - - $operations = []; - foreach ($this->createOperations($resourceMetadata->getItemOperations(), OperationType::ITEM, $resource) as $operationName => $operation) { - $operationName = RouteNameGenerator::generate($operationName, $resourceMetadata->getShortName(), OperationType::ITEM); - $operations[$operationName] = $operation->withShortName($resourceMetadata->getShortName())->withName($operationName); - } - - foreach ($this->createOperations($resourceMetadata->getCollectionOperations(), OperationType::COLLECTION, $resource) as $operationName => $operation) { - $operationName = RouteNameGenerator::generate($operationName, $resourceMetadata->getShortName(), OperationType::COLLECTION); - $operations[$operationName] = $operation->withShortName($resourceMetadata->getShortName())->withName($operationName); - } - - if (!$resourceMetadata->getGraphql()) { - // GraphQl can be null or an empty array, an empty array doesn't disable graphql type creation - $resourceMetadataCollection[] = $resource->withGraphQlOperations($resourceMetadata->getGraphql())->withOperations(new Operations($operations)); - - return $resourceMetadataCollection; - } - - $graphQlOperations = []; - foreach ($resourceMetadata->getGraphql() as $operationName => $operation) { - if (false !== strpos($operationName, 'query') || isset($operation['item_query']) || isset($operation['collection_query'])) { - $graphQlOperation = isset($operation['collection_query']) || false !== strpos($operationName, 'collection') ? new QueryCollection() : new Query(); - /** @var GraphQlOperation $graphQlOperation */ - $graphQlOperation = $graphQlOperation->withName($operationName); - } else { - /** @var GraphQlOperation $graphQlOperation */ - $graphQlOperation = (new Mutation()) - ->withDescription(ucfirst("{$operationName}s a {$resourceMetadata->getShortName()}.")) - ->withName($operationName); - } - - $graphQlOperation = $graphQlOperation - ->withArgs($operation['args'] ?? null) - ->withResolver($operation['item_query'] ?? $operation['collection_query'] ?? $operation['mutation'] ?? null) - ->withClass($resourceClass); - - foreach ($operation as $key => $value) { - $graphQlOperation = $this->setAttributeValue($graphQlOperation, $key, $value); - } - - $graphQlOperation = $graphQlOperation->withResource($resource); - - if ('update' === $operationName && $graphQlOperation instanceof Mutation && $graphQlOperation->getMercure()) { - $graphQlOperations['update_subscription'] = (new Subscription()) - ->withDescription("Subscribes to the $operationName event of a {$graphQlOperation->getShortName()}.") - ->withName('update_subscription') - ->withOperation($graphQlOperation); - } - - $graphQlOperations[$operationName] = $graphQlOperation; - } - - $resourceMetadataCollection[] = $resource->withOperations(new Operations($operations))->withGraphQlOperations($graphQlOperations); - - return $resourceMetadataCollection; - } - - private function createOperations(array $operations, string $type, ApiResource $resource): iterable - { - $priority = 0; - foreach ($operations as $operationName => $operation) { - $newOperation = OperationType::COLLECTION === $type ? new GetCollection() : new HttpOperation(); - - $newOperation = $newOperation->withMethod($operation['method']) - ->withClass($resource->getClass()) - ->withPriority($priority++); - - if (HttpOperation::METHOD_DELETE === $operation['method']) { - $newOperation = (new Delete())->withOperation($newOperation); - } elseif (HttpOperation::METHOD_POST === $operation['method'] && !isset($operation['path'])) { - $newOperation = (new Post())->withOperation($newOperation)->withUriVariables([])->withRead(false); - } - - foreach ($operation as $key => $value) { - $newOperation = $this->setAttributeValue($newOperation, $key, $value); - } - - $newOperation = $newOperation->withResource($resource); - - if ($newOperation instanceof CollectionOperationInterface && $newOperation instanceof HttpOperation) { - $newOperation = $newOperation->withUriVariables([]); - } - - $newOperation = $newOperation->withExtraProperties($newOperation->getExtraProperties() + ['is_legacy_resource_metadata' => true]); - // Avoiding operation name collision by adding _collection, this is rewritten by the UriTemplateResourceMetadataCollectionFactory - yield sprintf('%s%s', $newOperation->getRouteName() ?? $operationName, OperationType::COLLECTION === $type ? '_collection' : '') => $newOperation; - } - } - - /** - * @param HttpOperation|GraphQlOperation|ApiResource $operation - * @param mixed $value - * - * @return HttpOperation|GraphQlOperation|ApiResource - */ - private function setAttributeValue($operation, string $key, $value) - { - if ('identifiers' === $key) { - if (!$operation instanceof ApiResource && $operation instanceof CollectionOperationInterface) { - return $operation; - } - - trigger_deprecation('api-platform/core', '2.7', 'The "identifiers" option is deprecated and will be renamed to "uriVariables".'); - if (\is_string($value)) { - $value = [$value => [$operation->getClass(), $value]]; - } - - $uriVariables = []; - foreach ($value ?? [] as $parameterName => $identifiedBy) { - $uriVariables[$parameterName] = (new Link())->withFromClass($identifiedBy[0])->withIdentifiers([$identifiedBy[1]])->withParameterName($parameterName); - } - - return $operation->withUriVariables($uriVariables); - } - - [$camelCaseKey, $value] = $this->getKeyValue($key, $value); - $methodName = 'with'.ucfirst($camelCaseKey); - - if (null === $value) { - return $operation; - } - - if (method_exists($operation, $methodName)) { - return $operation->{$methodName}($value); - } - - return $operation->withExtraProperties($operation->getExtraProperties() + [$key => $value]); - } - - /** - * @param ApiResource|HttpOperation $resource - * - * @return ApiResource|HttpOperation - */ - private function identifiersToUriVariables(ResourceMetadata $resourceMetadata, $resource) - { - $identifiers = []; - - foreach ($this->propertyNameCollectionFactory->create($resource->getClass()) as $property) { - $propertyMetadata = $this->propertyMetadataFactory->create($resource->getClass(), $property); - if ($propertyMetadata->isIdentifier()) { - $identifiers[] = $property; - } - } - - $compositeIdentifier = $resourceMetadata->getAttribute('composite_identifier', null); - $numIdentifiers = \count($identifiers); - if (null === $compositeIdentifier) { - $compositeIdentifier = $numIdentifiers > 1 ? true : false; - } - - if ($compositeIdentifier || 1 === $numIdentifiers) { - $parameterName = 1 === $numIdentifiers ? $identifiers[0] : 'id'; - - return $resource->withUriVariables([$parameterName => (new Link())->withFromClass($resource->getClass())->withIdentifiers($identifiers)->withParameterName($parameterName)->withCompositeIdentifier($compositeIdentifier)]); - } - - $uriVariables = []; - foreach ($identifiers as $identifier) { - $uriVariables[$identifier] = (new Link())->withFromClass($resource->getClass())->withIdentifiers([$identifier])->withParameterName($identifier)->withCompositeIdentifier($compositeIdentifier); - } - - return $resource->withUriVariables($uriVariables); - } -} diff --git a/src/Metadata/Resource/Factory/LegacySubresourceMetadataResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/LegacySubresourceMetadataResourceMetadataCollectionFactory.php deleted file mode 100644 index e90ddd94fda..00000000000 --- a/src/Metadata/Resource/Factory/LegacySubresourceMetadataResourceMetadataCollectionFactory.php +++ /dev/null @@ -1,190 +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\Metadata\Resource\Factory; - -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Get; -use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; -use ApiPlatform\Metadata\HttpOperation; -use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Operations; -use ApiPlatform\Metadata\Resource\DeprecationMetadataTrait; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; - -/** - * We have to compute a local cache having all the resource => subresource matching. - * - * @deprecated - */ -final class LegacySubresourceMetadataResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface -{ - use DeprecationMetadataTrait; - private $decorated; - private $resourceNameCollectionFactory; - private $subresourceOperationFactory; - private $localCache = []; - - public function __construct(SubresourceOperationFactoryInterface $subresourceOperationFactory, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataCollectionFactoryInterface $decorated = null) - { - $this->subresourceOperationFactory = $subresourceOperationFactory; - $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - $this->decorated = $decorated; - } - - public function create(string $resourceClass): ResourceMetadataCollection - { - $resourceMetadataCollection = new ResourceMetadataCollection($resourceClass); - if ($this->decorated) { - $resourceMetadataCollection = $this->decorated->create($resourceClass); - } - - if (0 === \count($this->localCache)) { - $this->computeSubresourceCache(); - } - - if (!isset($this->localCache[$resourceClass])) { - return $resourceMetadataCollection; - } - - $defaults = $resourceMetadataCollection[0] ?? new ApiResource(); - foreach ($this->localCache[$resourceClass] as $resource) { - $operations = iterator_to_array($resource->getOperations()); - $operation = current($operations); - $operationName = key($operations); - - foreach (get_class_methods($defaults) as $methodName) { - if (0 !== strpos($methodName, 'get')) { - continue; - } - - if (!method_exists($operation, $methodName)) { - continue; - } - - $operationValue = $operation->{$methodName}(); - if (null !== $operationValue) { - continue; - } - - if (($value = $defaults->{$methodName}()) !== null) { - $operation = $operation->{'with'.substr($methodName, 3)}($value); - } - } - - $resourceMetadataCollection[] = $resource->withOperations(new Operations([$operationName => $operation->withName($operationName)])); - } - - return $resourceMetadataCollection; - } - - private function computeSubresourceCache() - { - foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { - if (!isset($this->localCache[$resourceClass])) { - $this->localCache[$resourceClass] = []; - } - - foreach ($this->subresourceOperationFactory->create($resourceClass) as $subresourceMetadata) { - if (!isset($this->localCache[$subresourceMetadata['resource_class']])) { - $this->localCache[$subresourceMetadata['resource_class']] = []; - } - - $identifiers = []; - // Removing the third tuple element - $previousParameterName = null; - foreach ($subresourceMetadata['identifiers'] as $parameterName => [$class, $property, $isPresent]) { - if (!$isPresent) { - continue; - } - - $identifiers[$parameterName] = (new Link())->withFromClass($class)->withIdentifiers([$property])->withParameterName($parameterName)->withCompositeIdentifier(false); - if ($previousParameterName) { - $identifiers[$previousParameterName] = $identifiers[$previousParameterName]->withFromProperty($parameterName); - } - - $previousParameterName = $parameterName; - } - - $extraProperties = ['is_legacy_subresource' => true]; - if ($subresourceMetadata['property']) { - $extraProperties['legacy_subresource_property'] = $subresourceMetadata['property']; - } - - if ($subresourceMetadata['identifiers']) { - $extraProperties['legacy_subresource_identifiers'] = $subresourceMetadata['identifiers']; - unset($subresourceMetadata['identifiers']); - } - - $resource = (new ApiResource())->withExtraProperties($extraProperties)->withUriVariables($identifiers)->withStateless(false); - /* @var HttpOperation $operation */ - $operation = ($subresourceMetadata['collection'] ? new GetCollection() : new Get()); - $operation = $operation->withUriVariables($identifiers); - $operation = $operation->withExtraProperties($extraProperties + ['legacy_subresource_operation_name' => $subresourceMetadata['route_name']]); - - if ($subresourceMetadata['path'] && $operation instanceof HttpOperation) { - $resource = $resource->withUriTemplate($subresourceMetadata['path']); - $operation = $operation->withUriTemplate($subresourceMetadata['path']); - } - - if ($subresourceMetadata['shortNames'][0]) { - $resource = $resource->withShortName($subresourceMetadata['shortNames'][0]); - $operation = $operation->withShortName($subresourceMetadata['shortNames'][0]); - } - - if ($subresourceMetadata['resource_class']) { - $resource = $resource->withClass($subresourceMetadata['resource_class']); - $operation = $operation->withClass($subresourceMetadata['resource_class']); - } - - foreach ($subresourceMetadata as $key => $value) { - if ('route_name' === $key) { - continue; - } - $resource = $this->setAttributeValue($resource, $key, $value); - $operation = $this->setAttributeValue($operation, $key, $value); - } - - $resource = $resource->withOperations(new Operations([ - $subresourceMetadata['route_name'] => $operation, - ])); - - if ($subresourceMetadata['controller']) { // manage null values from subresources - $resource = $resource->withController($subresourceMetadata['controller']); - } - - $this->localCache[$resource->getClass()][] = $resource; - } - } - } - - /** - * @param HttpOperation|GraphQlOperation|ApiResource $operation - * @param mixed $value - * - * @return HttpOperation|GraphQlOperation|ApiResource - */ - private function setAttributeValue($operation, string $key, $value) - { - [$camelCaseKey, $value] = $this->getKeyValue($key, $value); - $methodName = 'with'.ucfirst($camelCaseKey); - - if (method_exists($operation, $methodName) && null !== $value) { - return $operation->{$methodName}($value); - } - - return $operation; - } -} diff --git a/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php index 43abbee0f52..4851eb72b10 100644 --- a/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php @@ -18,7 +18,6 @@ use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operations; -use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Operation\PathSegmentNameGeneratorInterface; use Symfony\Component\Routing\Route; @@ -137,7 +136,7 @@ private function configureUriVariables($operation) $operation = $this->normalizeUriVariables($operation); if (!($uriTemplate = $operation->getUriTemplate())) { - if ($operation instanceof Post) { + if ($operation instanceof HttpOperation && HttpOperation::METHOD_POST === $operation->getMethod()) { return $operation->withUriVariables([]); } diff --git a/src/Metadata/Resource/ResourceMetadataCollection.php b/src/Metadata/Resource/ResourceMetadataCollection.php index 383c12a47d2..aead554dc68 100644 --- a/src/Metadata/Resource/ResourceMetadataCollection.php +++ b/src/Metadata/Resource/ResourceMetadataCollection.php @@ -77,6 +77,11 @@ public function getOperation(?string $operationName = null, bool $forceCollectio $it->next(); } + // Idea: + // if ($metadata) { + // return (new class extends HttpOperation {})->withResource($metadata); + // } + $this->handleNotFound($operationName, $metadata); } diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index 6997c374cc2..11432a45c95 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -14,8 +14,6 @@ namespace ApiPlatform\OpenApi\Factory; use ApiPlatform\Api\FilterLocatorTrait; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface as LegacyPropertyNameCollectionFactoryInterface; use ApiPlatform\JsonSchema\Schema; use ApiPlatform\JsonSchema\SchemaFactoryInterface; use ApiPlatform\JsonSchema\TypeFactoryInterface; @@ -23,7 +21,6 @@ use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -33,7 +30,7 @@ use ApiPlatform\OpenApi\Model\ExternalDocumentation; use ApiPlatform\OpenApi\OpenApi; use ApiPlatform\OpenApi\Options; -use ApiPlatform\PathResolver\OperationPathResolverInterface; +use ApiPlatform\OpenApi\Serializer\NormalizeOperationNameTrait; use ApiPlatform\State\Pagination\PaginationOptions; use Psr\Container\ContainerInterface; use Symfony\Component\PropertyInfo\Type; @@ -45,21 +42,15 @@ final class OpenApiFactory implements OpenApiFactoryInterface { use FilterLocatorTrait; + use NormalizeOperationNameTrait; public const BASE_URL = 'base_url'; public const OPENAPI_DEFINITION_NAME = 'openapi_definition_name'; private $resourceNameCollectionFactory; private $resourceMetadataFactory; - /** - * @var LegacyPropertyNameCollectionFactoryInterface|PropertyNameCollectionFactoryInterface - */ private $propertyNameCollectionFactory; - /** - * @var LegacyPropertyMetadataFactoryInterface|PropertyMetadataFactoryInterface - */ private $propertyMetadataFactory; - private $operationPathResolver; private $formats; private $jsonSchemaFactory; private $jsonSchemaTypeFactory; @@ -68,7 +59,7 @@ final class OpenApiFactory implements OpenApiFactoryInterface private $router; private $routeCollection; - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, SchemaFactoryInterface $jsonSchemaFactory, TypeFactoryInterface $jsonSchemaTypeFactory, OperationPathResolverInterface $operationPathResolver, ContainerInterface $filterLocator, array $formats = [], Options $openApiOptions = null, PaginationOptions $paginationOptions = null, RouterInterface $router = null) + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, SchemaFactoryInterface $jsonSchemaFactory, TypeFactoryInterface $jsonSchemaTypeFactory, ContainerInterface $filterLocator, array $formats = [], Options $openApiOptions = null, PaginationOptions $paginationOptions = null, RouterInterface $router = null) { $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->jsonSchemaFactory = $jsonSchemaFactory; @@ -78,7 +69,6 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName $this->resourceMetadataFactory = $resourceMetadataFactory; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->operationPathResolver = $operationPathResolver; $this->openApiOptions = $openApiOptions ?: new Options('API Platform'); $this->paginationOptions = $paginationOptions ?: new PaginationOptions(); $this->router = $router; @@ -159,7 +149,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection [$requestMimeTypes, $responseMimeTypes] = $this->getMimeTypes($operation); - $operationId = $operation->getOpenapiContext()['operationId'] ?? $operationName; + $operationId = $operation->getOpenapiContext()['operationId'] ?? $this->normalizeOperationName($operationName); if ($path) { $pathItem = $paths->getPath($path) ?: new Model\PathItem(); @@ -239,7 +229,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection break; } - if (!$operation instanceof CollectionOperationInterface && !$operation instanceof Post) { + if (!$operation instanceof CollectionOperationInterface && HttpOperation::METHOD_POST !== $operation->getMethod()) { $responses['404'] = new Model\Response('Resource not found'); } diff --git a/src/OpenApi/Model/Components.php b/src/OpenApi/Model/Components.php index 698ad5f752e..681996bccb0 100644 --- a/src/OpenApi/Model/Components.php +++ b/src/OpenApi/Model/Components.php @@ -176,5 +176,3 @@ public function withPathItems(\ArrayObject $pathItems): self return $clone; } } - -class_alias(Components::class, \ApiPlatform\Core\OpenApi\Model\Components::class); diff --git a/src/OpenApi/Model/Contact.php b/src/OpenApi/Model/Contact.php index 3f008a75e14..81437ef0081 100644 --- a/src/OpenApi/Model/Contact.php +++ b/src/OpenApi/Model/Contact.php @@ -67,5 +67,3 @@ public function withEmail(?string $email): self return $clone; } } - -class_alias(Contact::class, \ApiPlatform\Core\OpenApi\Model\Contact::class); diff --git a/src/OpenApi/Model/Encoding.php b/src/OpenApi/Model/Encoding.php index 8ab44514058..8b83f4c71ba 100644 --- a/src/OpenApi/Model/Encoding.php +++ b/src/OpenApi/Model/Encoding.php @@ -107,5 +107,3 @@ public function withAllowReserved(bool $allowReserved): self return $clone; } } - -class_alias(Encoding::class, \ApiPlatform\Core\OpenApi\Model\Encoding::class); diff --git a/src/OpenApi/Model/ExtensionTrait.php b/src/OpenApi/Model/ExtensionTrait.php index 97724a0ae83..0033241e373 100644 --- a/src/OpenApi/Model/ExtensionTrait.php +++ b/src/OpenApi/Model/ExtensionTrait.php @@ -34,5 +34,3 @@ public function getExtensionProperties(): array return $this->extensionProperties; } } - -class_alias(ExtensionTrait::class, \ApiPlatform\Core\OpenApi\Model\ExtensionTrait::class); diff --git a/src/OpenApi/Model/ExternalDocumentation.php b/src/OpenApi/Model/ExternalDocumentation.php index 83153e42918..5c0c5b97d12 100644 --- a/src/OpenApi/Model/ExternalDocumentation.php +++ b/src/OpenApi/Model/ExternalDocumentation.php @@ -52,5 +52,3 @@ public function withUrl(string $url): self return $clone; } } - -class_alias(ExternalDocumentation::class, \ApiPlatform\Core\OpenApi\Model\ExternalDocumentation::class); diff --git a/src/OpenApi/Model/Info.php b/src/OpenApi/Model/Info.php index 947e3e39cef..aab2a0e4d65 100644 --- a/src/OpenApi/Model/Info.php +++ b/src/OpenApi/Model/Info.php @@ -127,5 +127,3 @@ public function withSummary(string $summary): self return $clone; } } - -class_alias(Info::class, \ApiPlatform\Core\OpenApi\Model\Info::class); diff --git a/src/OpenApi/Model/License.php b/src/OpenApi/Model/License.php index f856d603bb9..b103fc7fa11 100644 --- a/src/OpenApi/Model/License.php +++ b/src/OpenApi/Model/License.php @@ -67,5 +67,3 @@ public function withIdentifier(?string $identifier): self return $clone; } } - -class_alias(License::class, \ApiPlatform\Core\OpenApi\Model\License::class); diff --git a/src/OpenApi/Model/Link.php b/src/OpenApi/Model/Link.php index 9021e3e10c2..edd7180d221 100644 --- a/src/OpenApi/Model/Link.php +++ b/src/OpenApi/Model/Link.php @@ -97,5 +97,3 @@ public function withServer(Server $server): self return $clone; } } - -class_alias(Link::class, \ApiPlatform\Core\OpenApi\Model\Link::class); diff --git a/src/OpenApi/Model/MediaType.php b/src/OpenApi/Model/MediaType.php index b982360ef13..ae97fec3583 100644 --- a/src/OpenApi/Model/MediaType.php +++ b/src/OpenApi/Model/MediaType.php @@ -82,5 +82,3 @@ public function withEncoding(Encoding $encoding): self return $clone; } } - -class_alias(MediaType::class, \ApiPlatform\Core\OpenApi\Model\MediaType::class); diff --git a/src/OpenApi/Model/OAuthFlow.php b/src/OpenApi/Model/OAuthFlow.php index 884bbfe96f8..e8e6b35c1f6 100644 --- a/src/OpenApi/Model/OAuthFlow.php +++ b/src/OpenApi/Model/OAuthFlow.php @@ -82,5 +82,3 @@ public function withScopes(\ArrayObject $scopes): self return $clone; } } - -class_alias(OAuthFlow::class, \ApiPlatform\Core\OpenApi\Model\OAuthFlow::class); diff --git a/src/OpenApi/Model/OAuthFlows.php b/src/OpenApi/Model/OAuthFlows.php index f68e678ceea..0e0cc02db5c 100644 --- a/src/OpenApi/Model/OAuthFlows.php +++ b/src/OpenApi/Model/OAuthFlows.php @@ -82,5 +82,3 @@ public function withAuthorizationCode(OAuthFlow $authorizationCode): self return $clone; } } - -class_alias(OAuthFlows::class, \ApiPlatform\Core\OpenApi\Model\OAuthFlows::class); diff --git a/src/OpenApi/Model/Operation.php b/src/OpenApi/Model/Operation.php index e16bdf3d99b..d829f1d7a01 100644 --- a/src/OpenApi/Model/Operation.php +++ b/src/OpenApi/Model/Operation.php @@ -210,5 +210,3 @@ public function withServers(?array $servers = null): self return $clone; } } - -class_alias(Operation::class, \ApiPlatform\Core\OpenApi\Model\Operation::class); diff --git a/src/OpenApi/Model/Parameter.php b/src/OpenApi/Model/Parameter.php index eddf887f1f8..9965704db78 100644 --- a/src/OpenApi/Model/Parameter.php +++ b/src/OpenApi/Model/Parameter.php @@ -242,5 +242,3 @@ public function withContent(\ArrayObject $content): self return $clone; } } - -class_alias(Parameter::class, \ApiPlatform\Core\OpenApi\Model\Parameter::class); diff --git a/src/OpenApi/Model/PathItem.php b/src/OpenApi/Model/PathItem.php index de173a8518f..5f08abde98a 100644 --- a/src/OpenApi/Model/PathItem.php +++ b/src/OpenApi/Model/PathItem.php @@ -218,5 +218,3 @@ public function withParameters(array $parameters): self return $clone; } } - -class_alias(PathItem::class, \ApiPlatform\Core\OpenApi\Model\PathItem::class); diff --git a/src/OpenApi/Model/Paths.php b/src/OpenApi/Model/Paths.php index 7d0cae6c3ae..babfdb82cb0 100644 --- a/src/OpenApi/Model/Paths.php +++ b/src/OpenApi/Model/Paths.php @@ -34,5 +34,3 @@ public function getPaths(): array return $this->paths ?? []; } } - -class_alias(Paths::class, \ApiPlatform\Core\OpenApi\Model\Paths::class); diff --git a/src/OpenApi/Model/RequestBody.php b/src/OpenApi/Model/RequestBody.php index 3703ecd8b5b..350136faf70 100644 --- a/src/OpenApi/Model/RequestBody.php +++ b/src/OpenApi/Model/RequestBody.php @@ -67,5 +67,3 @@ public function withRequired(bool $required): self return $clone; } } - -class_alias(RequestBody::class, \ApiPlatform\Core\OpenApi\Model\RequestBody::class); diff --git a/src/OpenApi/Model/Response.php b/src/OpenApi/Model/Response.php index 29ed33ac79f..54c7326a2fc 100644 --- a/src/OpenApi/Model/Response.php +++ b/src/OpenApi/Model/Response.php @@ -82,5 +82,3 @@ public function withLinks(\ArrayObject $links): self return $clone; } } - -class_alias(Response::class, \ApiPlatform\Core\OpenApi\Model\Response::class); diff --git a/src/OpenApi/Model/Schema.php b/src/OpenApi/Model/Schema.php index 487049a4e4d..bf3fd3eb67c 100644 --- a/src/OpenApi/Model/Schema.php +++ b/src/OpenApi/Model/Schema.php @@ -19,7 +19,6 @@ final class Schema extends \ArrayObject { use ExtensionTrait; - private $nullable; private $discriminator; private $readOnly; private $writeOnly; @@ -29,12 +28,8 @@ final class Schema extends \ArrayObject private $deprecated; private $schema; - public function __construct(bool $nullable = null, $discriminator = null, bool $readOnly = false, bool $writeOnly = false, string $xml = null, $externalDocs = null, $example = null, bool $deprecated = false) + public function __construct($discriminator = null, bool $readOnly = false, bool $writeOnly = false, string $xml = null, $externalDocs = null, $example = null, bool $deprecated = false) { - if (null !== $nullable) { - @trigger_error('The nullable keyword has been removed from the Schema Object (null can be used as a type value). This behaviour will not be possible anymore in API Platform 3.0.', \E_USER_DEPRECATED); - $this->nullable = $nullable; - } $this->discriminator = $discriminator; $this->readOnly = $readOnly; $this->writeOnly = $writeOnly; @@ -172,5 +167,3 @@ public function withDeprecated(bool $deprecated): self return $clone; } } - -class_alias(Schema::class, \ApiPlatform\Core\OpenApi\Model\Schema::class); diff --git a/src/OpenApi/Model/SecurityScheme.php b/src/OpenApi/Model/SecurityScheme.php index bf255ccdb43..600770f257f 100644 --- a/src/OpenApi/Model/SecurityScheme.php +++ b/src/OpenApi/Model/SecurityScheme.php @@ -142,5 +142,3 @@ public function withOpenIdConnectUrl(string $openIdConnectUrl): self return $clone; } } - -class_alias(SecurityScheme::class, \ApiPlatform\Core\OpenApi\Model\SecurityScheme::class); diff --git a/src/OpenApi/Model/Server.php b/src/OpenApi/Model/Server.php index 1baa0c8aea2..35eaafe6b28 100644 --- a/src/OpenApi/Model/Server.php +++ b/src/OpenApi/Model/Server.php @@ -67,5 +67,3 @@ public function withVariables(\ArrayObject $variables): self return $clone; } } - -class_alias(Server::class, \ApiPlatform\Core\OpenApi\Model\Server::class); diff --git a/src/OpenApi/OpenApi.php b/src/OpenApi/OpenApi.php index 09e9d1a2b55..2efede9b088 100644 --- a/src/OpenApi/OpenApi.php +++ b/src/OpenApi/OpenApi.php @@ -174,5 +174,3 @@ public function withJsonSchemaDialect(?string $jsonSchemaDialect): self return $clone; } } - -class_alias(OpenApi::class, \ApiPlatform\Core\OpenApi\OpenApi::class); diff --git a/src/OpenApi/Options.php b/src/OpenApi/Options.php index a7ba9e871a3..65a66293e1d 100644 --- a/src/OpenApi/Options.php +++ b/src/OpenApi/Options.php @@ -139,5 +139,3 @@ public function getLicenseUrl(): ?string return $this->licenseUrl; } } - -class_alias(Options::class, \ApiPlatform\Core\OpenApi\Options::class); diff --git a/src/OpenApi/Serializer/ApiGatewayNormalizer.php b/src/OpenApi/Serializer/ApiGatewayNormalizer.php index 78b3d099f9a..7923bc68673 100644 --- a/src/OpenApi/Serializer/ApiGatewayNormalizer.php +++ b/src/OpenApi/Serializer/ApiGatewayNormalizer.php @@ -89,28 +89,28 @@ public function normalize($object, $format = null, array $context = []) } } - foreach ($data['definitions'] as $definition => $options) { + foreach ($data['components']['schemas'] as $definition => $options) { if (!isset($options['properties'])) { continue; } foreach ($options['properties'] as $property => $propertyOptions) { if (isset($propertyOptions['readOnly'])) { - unset($data['definitions'][$definition]['properties'][$property]['readOnly']); + unset($data['components']['schemas'][$definition]['properties'][$property]['readOnly']); } if (isset($propertyOptions['$ref']) && $this->isLocalRef($propertyOptions['$ref'])) { - $data['definitions'][$definition]['properties'][$property]['$ref'] = $this->normalizeRef($propertyOptions['$ref']); + $data['components']['schemas'][$definition]['properties'][$property]['$ref'] = $this->normalizeRef($propertyOptions['$ref']); } if (isset($propertyOptions['items']['$ref']) && $this->isLocalRef($propertyOptions['items']['$ref'])) { - $data['definitions'][$definition]['properties'][$property]['items']['$ref'] = $this->normalizeRef($propertyOptions['items']['$ref']); + $data['components']['schemas'][$definition]['properties'][$property]['items']['$ref'] = $this->normalizeRef($propertyOptions['items']['$ref']); } } } // $data['definitions'] is an instance of \ArrayObject - foreach (array_keys($data['definitions']->getArrayCopy()) as $definition) { + foreach (array_keys($data['components']['schemas']) as $definition) { if (!preg_match('/^[0-9A-Za-z]+$/', (string) $definition)) { - $data['definitions'][preg_replace('/[^0-9A-Za-z]/', '', (string) $definition)] = $data['definitions'][$definition]; - unset($data['definitions'][$definition]); + $data['components']['schemas'][preg_replace('/[^0-9A-Za-z]/', '', (string) $definition)] = $data['components']['schemas'][$definition]; + unset($data['components']['schemas'][$definition]); } } @@ -149,5 +149,3 @@ private function normalizeRef(string $ref): string return implode('/', $refParts); } } - -class_alias(ApiGatewayNormalizer::class, \ApiPlatform\Core\Swagger\Serializer\ApiGatewayNormalizer::class); diff --git a/src/OpenApi/Serializer/NormalizeOperationNameTrait.php b/src/OpenApi/Serializer/NormalizeOperationNameTrait.php new file mode 100644 index 00000000000..53bb0b22a10 --- /dev/null +++ b/src/OpenApi/Serializer/NormalizeOperationNameTrait.php @@ -0,0 +1,28 @@ + + * + * 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\OpenApi\Serializer; + +/** + * Transforms the operation name to a readable operation id. + * + * @author soyuka + */ +trait NormalizeOperationNameTrait +{ + private function normalizeOperationName(string $operationName): string + { + // .{_format} is related to the symfony router + return preg_replace('/^_/', '', str_replace(['/', '.{_format}', '{', '}'], ['', '', '_', ''], $operationName)); + } +} diff --git a/src/OpenApi/Serializer/OpenApiNormalizer.php b/src/OpenApi/Serializer/OpenApiNormalizer.php index 489d3c9123a..86991992af2 100644 --- a/src/OpenApi/Serializer/OpenApiNormalizer.php +++ b/src/OpenApi/Serializer/OpenApiNormalizer.php @@ -87,5 +87,3 @@ public function hasCacheableSupportsMethod(): bool return true; } } - -class_alias(OpenApiNormalizer::class, \ApiPlatform\Core\OpenApi\Serializer\OpenApiNormalizer::class); diff --git a/src/Operation/DashPathSegmentNameGenerator.php b/src/Operation/DashPathSegmentNameGenerator.php index 8d320e12528..3cac090173f 100644 --- a/src/Operation/DashPathSegmentNameGenerator.php +++ b/src/Operation/DashPathSegmentNameGenerator.php @@ -35,5 +35,3 @@ private function dashize(string $string): string return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '-$1', $string)); } } - -class_alias(DashPathSegmentNameGenerator::class, \ApiPlatform\Core\Operation\DashPathSegmentNameGenerator::class); diff --git a/src/Operation/UnderscorePathSegmentNameGenerator.php b/src/Operation/UnderscorePathSegmentNameGenerator.php index 09d75ec1fdf..c045cad6991 100644 --- a/src/Operation/UnderscorePathSegmentNameGenerator.php +++ b/src/Operation/UnderscorePathSegmentNameGenerator.php @@ -32,5 +32,3 @@ public function getSegmentName(string $name, bool $collection = true): string return $collection ? Inflector::pluralize($name) : $name; } } - -class_alias(UnderscorePathSegmentNameGenerator::class, \ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator::class); diff --git a/src/PathResolver/CustomOperationPathResolver.php b/src/PathResolver/CustomOperationPathResolver.php deleted file mode 100644 index 11b8f2f52c1..00000000000 --- a/src/PathResolver/CustomOperationPathResolver.php +++ /dev/null @@ -1,49 +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\PathResolver; - -use ApiPlatform\Core\Api\OperationTypeDeprecationHelper; - -/** - * Resolves the custom operations path. - * - * @author Guilhem N. - */ -final class CustomOperationPathResolver implements OperationPathResolverInterface -{ - private $deferred; - - public function __construct(OperationPathResolverInterface $deferred) - { - $this->deferred = $deferred; - } - - /** - * {@inheritdoc} - */ - public function resolveOperationPath(string $resourceShortName, array $operation, $operationType/* , string $operationName = null */): string - { - if (\func_num_args() >= 4) { - $operationName = func_get_arg(3); - } else { - @trigger_error(sprintf('Method %s() will have a 4th `string $operationName` argument in version 3.0. Not defining it is deprecated since 2.1.', __METHOD__), \E_USER_DEPRECATED); - - $operationName = null; - } - - return $operation['path'] ?? $this->deferred->resolveOperationPath($resourceShortName, $operation, OperationTypeDeprecationHelper::getOperationType($operationType), $operationName); - } -} - -class_alias(CustomOperationPathResolver::class, \ApiPlatform\Core\PathResolver\CustomOperationPathResolver::class); diff --git a/src/PathResolver/OperationPathResolver.php b/src/PathResolver/OperationPathResolver.php deleted file mode 100644 index a08957bb31d..00000000000 --- a/src/PathResolver/OperationPathResolver.php +++ /dev/null @@ -1,68 +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\PathResolver; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Api\OperationTypeDeprecationHelper; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Operation\PathSegmentNameGeneratorInterface; - -/** - * Generates an operation path. - * - * @author Antoine Bluchet - */ -final class OperationPathResolver implements OperationPathResolverInterface -{ - private $pathSegmentNameGenerator; - - public function __construct(PathSegmentNameGeneratorInterface $pathSegmentNameGenerator) - { - $this->pathSegmentNameGenerator = $pathSegmentNameGenerator; - } - - /** - * {@inheritdoc} - */ - public function resolveOperationPath(string $resourceShortName, array $operation, $operationType/* , string $operationName = null */): string - { - if (\func_num_args() < 4) { - @trigger_error(sprintf('Method %s() will have a 4th `string $operationName` argument in version 3.0. Not defining it is deprecated since 2.1.', __METHOD__), \E_USER_DEPRECATED); - } - - $operationType = OperationTypeDeprecationHelper::getOperationType($operationType); - - if (OperationType::SUBRESOURCE === $operationType) { - throw new InvalidArgumentException('Subresource operations are not supported by the OperationPathResolver.'); - } - - $path = '/'.$this->pathSegmentNameGenerator->getSegmentName($resourceShortName); - - if (OperationType::ITEM === $operationType) { - if (isset($operation['identifiers']) && (\count($operation['identifiers']) <= 1 || false === ($operation['has_composite_identifier'] ?? true))) { - foreach ($operation['identifiers'] as $parameterName => $identifier) { - $path .= sprintf('/{%s}', \is_string($parameterName) ? $parameterName : $identifier); - } - } else { - $path .= '/{id}'; - } - } - - $path .= '.{_format}'; - - return $path; - } -} - -class_alias(OperationPathResolver::class, \ApiPlatform\Core\PathResolver\OperationPathResolver::class); diff --git a/src/PathResolver/OperationPathResolverInterface.php b/src/PathResolver/OperationPathResolverInterface.php deleted file mode 100644 index a447dff270e..00000000000 --- a/src/PathResolver/OperationPathResolverInterface.php +++ /dev/null @@ -1,32 +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\PathResolver; - -/** - * Resolves the path of a resource operation. - * - * @author Paul Le Corre - */ -interface OperationPathResolverInterface -{ - /** - * Resolves the operation path. - * - * @param string $resourceShortName When the operation type is a subresource and the operation has more than one identifier, this value is the previous operation path - * @param array $operation The operation metadata - * @param string|bool $operationType One of the constants defined in ApiPlatform\Core\Api\OperationType - * If the property is a boolean, true represents OperationType::COLLECTION, false is for OperationType::ITEM - */ - public function resolveOperationPath(string $resourceShortName, array $operation, $operationType/* , string $operationName = null */): string; -} diff --git a/src/Problem/Serializer/ConstraintViolationListNormalizer.php b/src/Problem/Serializer/ConstraintViolationListNormalizer.php index e4935bb828d..70325c06227 100644 --- a/src/Problem/Serializer/ConstraintViolationListNormalizer.php +++ b/src/Problem/Serializer/ConstraintViolationListNormalizer.php @@ -56,5 +56,3 @@ public function normalize($object, $format = null, array $context = []): array ]; } } - -class_alias(ConstraintViolationListNormalizer::class, \ApiPlatform\Core\Problem\Serializer\ConstraintViolationListNormalizer::class); diff --git a/src/Problem/Serializer/ErrorNormalizer.php b/src/Problem/Serializer/ErrorNormalizer.php index b819a1282a9..2d424d58ea2 100644 --- a/src/Problem/Serializer/ErrorNormalizer.php +++ b/src/Problem/Serializer/ErrorNormalizer.php @@ -78,5 +78,3 @@ public function hasCacheableSupportsMethod(): bool return true; } } - -class_alias(ErrorNormalizer::class, \ApiPlatform\Core\Problem\Serializer\ErrorNormalizer::class); diff --git a/src/Problem/Serializer/ErrorNormalizerTrait.php b/src/Problem/Serializer/ErrorNormalizerTrait.php index 18c373db33e..773fade6bcf 100644 --- a/src/Problem/Serializer/ErrorNormalizerTrait.php +++ b/src/Problem/Serializer/ErrorNormalizerTrait.php @@ -53,5 +53,3 @@ private function getErrorCode($object): ?string return null; } } - -class_alias(ErrorNormalizerTrait::class, \ApiPlatform\Core\Problem\Serializer\ErrorNormalizerTrait::class); diff --git a/src/RamseyUuid/Serializer/UuidDenormalizer.php b/src/RamseyUuid/Serializer/UuidDenormalizer.php index 5d691261504..9df2f011c66 100644 --- a/src/RamseyUuid/Serializer/UuidDenormalizer.php +++ b/src/RamseyUuid/Serializer/UuidDenormalizer.php @@ -42,5 +42,3 @@ public function supportsDenormalization($data, $type, $format = null): bool return \is_string($data) && is_a($type, UuidInterface::class, true); } } - -class_alias(UuidDenormalizer::class, \ApiPlatform\Core\Bridge\RamseyUuid\Serializer\UuidDenormalizer::class); diff --git a/src/Serializer/AbstractCollectionNormalizer.php b/src/Serializer/AbstractCollectionNormalizer.php index 41c09dd820b..1fd727d7b0c 100644 --- a/src/Serializer/AbstractCollectionNormalizer.php +++ b/src/Serializer/AbstractCollectionNormalizer.php @@ -14,7 +14,6 @@ namespace ApiPlatform\Serializer; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\Pagination\PaginatorInterface; use ApiPlatform\State\Pagination\PartialPaginatorInterface; @@ -43,12 +42,9 @@ abstract class AbstractCollectionNormalizer implements NormalizerInterface, Norm protected $resourceClassResolver; protected $pageParameterName; - /** - * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface - */ protected $resourceMetadataFactory; - public function __construct(ResourceClassResolverInterface $resourceClassResolver, string $pageParameterName, $resourceMetadataFactory = null) + public function __construct(ResourceClassResolverInterface $resourceClassResolver, string $pageParameterName, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null) { $this->resourceClassResolver = $resourceClassResolver; $this->pageParameterName = $pageParameterName; @@ -155,5 +151,3 @@ abstract protected function getPaginationData($object, array $context = []): arr */ abstract protected function getItemsData($object, string $format = null, array $context = []): array; } - -class_alias(AbstractCollectionNormalizer::class, \ApiPlatform\Core\Serializer\AbstractCollectionNormalizer::class); diff --git a/src/Serializer/AbstractConstraintViolationListNormalizer.php b/src/Serializer/AbstractConstraintViolationListNormalizer.php index ba898d12fc9..fcc9f4f1430 100644 --- a/src/Serializer/AbstractConstraintViolationListNormalizer.php +++ b/src/Serializer/AbstractConstraintViolationListNormalizer.php @@ -85,5 +85,3 @@ protected function getMessagesAndViolations(ConstraintViolationListInterface $co return [$messages, $violations]; } } - -class_alias(AbstractConstraintViolationListNormalizer::class, \ApiPlatform\Core\Serializer\AbstractConstraintViolationListNormalizer::class); diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 7a366579c50..49d5474c978 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -14,15 +14,8 @@ namespace ApiPlatform\Serializer; use ApiPlatform\Api\IriConverterInterface; +use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\Bridge\Symfony\Messenger\DataTransformer as MessengerDataTransformer; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface; -use ApiPlatform\Core\DataTransformer\DataTransformerInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Exception\InvalidValueException; use ApiPlatform\Exception\ItemNotFoundException; @@ -44,9 +37,9 @@ use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -55,7 +48,7 @@ * * @author Kévin Dunglas */ -abstract class AbstractItemNormalizer extends AbstractObjectNormalizer +abstract class AbstractItemNormalizer extends AbstractObjectNormalizer implements ContextAwareDenormalizerInterface { use ClassInfoTrait; use ContextTrait; @@ -63,86 +56,46 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer public const IS_TRANSFORMED_TO_SAME_CLASS = 'is_transformed_to_same_class'; - /** - * @var PropertyNameCollectionFactoryInterface - */ - protected $propertyNameCollectionFactory; - /** - * @var LegacyPropertyMetadataFactoryInterface|PropertyMetadataFactoryInterface - */ - protected $propertyMetadataFactory; - /** - * @var LegacyIriConverterInterface|IriConverterInterface - */ - protected $iriConverter; - protected $resourceClassResolver; - protected $resourceAccessChecker; - protected $propertyAccessor; - protected $itemDataProvider; - protected $allowPlainIdentifiers; - protected $dataTransformers = []; + protected PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory; + protected PropertyMetadataFactoryInterface $propertyMetadataFactory; + protected IriConverterInterface $iriConverter; + protected ResourceClassResolverInterface $resourceClassResolver; + protected ResourceAccessCheckerInterface $resourceAccessChecker; + protected PropertyAccessorInterface $propertyAccessor; protected $localCache = []; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, ItemDataProviderInterface $itemDataProvider = null, bool $allowPlainIdentifiers = false, array $defaultContext = [], iterable $dataTransformers = [], $resourceMetadataFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null) { if (!isset($defaultContext['circular_reference_handler'])) { $defaultContext['circular_reference_handler'] = function ($object) { - return $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($object) : $this->iriConverter->getIriFromResource($object); + return $this->iriConverter->getIriFromResource($object); }; } - if (!interface_exists(AdvancedNameConverterInterface::class) && method_exists($this, 'setCircularReferenceHandler')) { - $this->setCircularReferenceHandler($defaultContext['circular_reference_handler']); - } parent::__construct($classMetadataFactory, $nameConverter, null, null, \Closure::fromCallable([$this, 'getObjectClass']), $defaultContext); $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; - - 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->resourceClassResolver = $resourceClassResolver; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); - $this->itemDataProvider = $itemDataProvider; - - if (true === $allowPlainIdentifiers) { - @trigger_error(sprintf('Allowing plain identifiers as argument of "%s" is deprecated since API Platform 2.7 and will not be possible anymore in API Platform 3.', self::class), \E_USER_DEPRECATED); - } - $this->allowPlainIdentifiers = $allowPlainIdentifiers; - - $this->dataTransformers = $dataTransformers; - - // Just skip our data transformer to trigger a proper deprecation - $customDataTransformers = array_filter(\is_array($dataTransformers) ? $dataTransformers : iterator_to_array($dataTransformers), function ($dataTransformer) { - return !$dataTransformer instanceof MessengerDataTransformer; - }); - - if (\count($customDataTransformers)) { - trigger_deprecation('api-platform/core', '2.7', 'The DataTransformer pattern is deprecated, use a Provider or a Processor and either use your input or return a new output there.'); - } - - 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->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->resourceAccessChecker = $resourceAccessChecker; } /** * {@inheritdoc} - * - * @return bool */ - public function supportsNormalization($data, $format = null) + public function supportsNormalization($data, $format = null, array $context = []): bool { if (!\is_object($data) || is_iterable($data)) { return false; } + if (($context['output']['class'] ?? null) === $this->getObjectClass($data)) { + return true; + } + return $this->resourceClassResolver->isResourceClass($this->getObjectClass($data)); } @@ -163,36 +116,28 @@ public function hasCacheableSupportsMethod(): bool */ public function normalize($object, $format = null, array $context = []) { - if (!($isTransformed = isset($context[self::IS_TRANSFORMED_TO_SAME_CLASS])) && $outputClass = $this->getOutputClass($this->getObjectClass($object), $context)) { - if (!$this->serializer instanceof NormalizerInterface) { + $resourceClass = $this->getObjectClass($object); + if ($outputClass = $this->getOutputClass($resourceClass, $context)) { + if (!$this->serializer instanceof DenormalizerInterface) { throw new LogicException('Cannot normalize the output because the injected serializer is not a normalizer'); } - if ($object !== $transformed = $this->transformOutput($object, $context, $outputClass)) { - $context['api_normalize'] = true; - $context['api_resource'] = $object; - unset($context['output'], $context['resource_class']); - } else { - $context[self::IS_TRANSFORMED_TO_SAME_CLASS] = true; - } + unset($context['output']); + unset($context['operation']); + unset($context['operation_name']); + $context['resource_class'] = $outputClass; + $context['api_sub_level'] = true; + $context[self::ALLOW_EXTRA_ATTRIBUTES] = false; - return $this->serializer->normalize($transformed, $format, $context); - } - if ($isTransformed) { - unset($context[self::IS_TRANSFORMED_TO_SAME_CLASS]); + return $this->serializer->normalize($object, $format, $context); } - $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); - $context = $this->initContext($resourceClass, $context); - - if (isset($context['iri'])) { - $iri = $context['iri']; - } else { - $iri = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($object) : $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_URL, $context['operation'] ?? null, $context); + if ($this->resourceClassResolver->isResourceClass($resourceClass)) { + $context = $this->initContext($resourceClass, $context); } - $context['iri'] = $iri; $context['api_normalize'] = true; + $iri = $context['iri'] = $context['iri'] ?? $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_URL, $context['operation'] ?? null, $context); /* * When true, converts the normalized data array of a resource into an @@ -212,6 +157,7 @@ public function normalize($object, $format = null, array $context = []) } $data = parent::normalize($object, $format, $context); + if ($emptyResourceAsIri && \is_array($data) && 0 === \count($data)) { return $iri; } @@ -221,11 +167,13 @@ public function normalize($object, $format = null, array $context = []) /** * {@inheritdoc} - * - * @return bool */ - public function supportsDenormalization($data, $type, $format = null) + public function supportsDenormalization($data, string $type, $format = null, array $context = []): bool { + if (($context['input']['class'] ?? null) === $type) { + return true; + } + return $this->localCache[$type] ?? $this->localCache[$type] = $this->resourceClassResolver->isResourceClass($type); } @@ -236,87 +184,65 @@ public function supportsDenormalization($data, $type, $format = null) */ public function denormalize($data, $class, $format = null, array $context = []) { - if (null === $objectToPopulate = $this->extractObjectToPopulate($class, $context, static::OBJECT_TO_POPULATE)) { - $normalizedData = \is_scalar($data) ? [$data] : $this->prepareForDenormalization($data); - $class = $this->getClassDiscriminatorResolvedClass($normalizedData, $class); - } - - $resourceClass = $this->resourceClassResolver->getResourceClass($objectToPopulate, $class); - $context['api_denormalize'] = true; - $context['resource_class'] = $resourceClass; - - if (null !== $inputClass = $this->getInputClass($resourceClass, $context)) { - if (null !== $dataTransformer = $this->getDataTransformer($data, $resourceClass, $context)) { - $dataTransformerContext = $context; - - unset($context['input']); - unset($context['resource_class']); + $resourceClass = $class; - if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException('Cannot denormalize the input because the injected serializer is not a denormalizer'); - } + if ($inputClass = $this->getInputClass($class, $context)) { + if (!$this->serializer instanceof DenormalizerInterface) { + throw new LogicException('Cannot normalize the output because the injected serializer is not a normalizer'); + } - if ($dataTransformer instanceof DataTransformerInitializerInterface) { - $context[AbstractObjectNormalizer::OBJECT_TO_POPULATE] = $dataTransformer->initialize($inputClass, $context); - $context[AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE] = true; - } + // $class = $inputClass; + unset($context['input']); + unset($context['operation']); + unset($context['operation_name']); + $context['resource_class'] = $inputClass; - try { - $denormalizedInput = $this->serializer->denormalize($data, $inputClass, $format, $context); - } catch (NotNormalizableValueException $e) { - throw new UnexpectedValueException('The input data is misformatted.', $e->getCode(), $e); - } + try { + return $this->serializer->denormalize($data, $inputClass, $format, $context); + } catch (NotNormalizableValueException $e) { + throw new UnexpectedValueException('The input data is misformatted.', $e->getCode(), $e); + } + } - if (!\is_object($denormalizedInput)) { - throw new UnexpectedValueException('Expected denormalized input to be an object.'); - } + if (null === $objectToPopulate = $this->extractObjectToPopulate($resourceClass, $context, static::OBJECT_TO_POPULATE)) { + $normalizedData = \is_scalar($data) ? [$data] : $this->prepareForDenormalization($data); + $class = $this->getClassDiscriminatorResolvedClass($normalizedData, $class); + } - return $dataTransformer->transform($denormalizedInput, $resourceClass, $dataTransformerContext); - } + $context['api_denormalize'] = true; - // Are we in a Request context? - if ($context['operation'] ?? $context['operation_type'] ?? false) { - $resourceClass = $inputClass; - $context['resource_class'] = $inputClass; - } + if ($this->resourceClassResolver->isResourceClass($class)) { + $resourceClass = $this->resourceClassResolver->getResourceClass($objectToPopulate, $class); + $context['resource_class'] = $resourceClass; } - $supportsPlainIdentifiers = $this->supportsPlainIdentifiers(); + // Are we in a Request context? + // if ($context['operation'] ?? $context['operation_type'] ?? false) { + // $resourceClass = $inputClass; + // $context['resource_class'] = $inputClass; + // } if (\is_string($data)) { try { - return $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri($data, $context + ['fetch_data' => true]) : $this->iriConverter->getResourceFromIri($data, $context + ['fetch_data' => true]); + return $this->iriConverter->getResourceFromIri($data, $context + ['fetch_data' => true]); } catch (ItemNotFoundException $e) { - if (!$supportsPlainIdentifiers) { - throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); - } + throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); } catch (InvalidArgumentException $e) { - if (!$supportsPlainIdentifiers) { - throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $data), $e->getCode(), $e); - } + throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $data), $e->getCode(), $e); } } if (!\is_array($data)) { - if (!$supportsPlainIdentifiers) { - throw new UnexpectedValueException(sprintf('Expected IRI or document for resource "%s", "%s" given.', $resourceClass, \gettype($data))); - } - - $item = $this->itemDataProvider->getItem($resourceClass, $data, null, $context + ['fetch_data' => true]); - if (null === $item) { - throw new ItemNotFoundException(sprintf('Item not found for resource "%s" with id "%s".', $resourceClass, $data)); - } - - return $item; + throw new UnexpectedValueException(sprintf('Expected IRI or document for resource "%s", "%s" given.', $resourceClass, \gettype($data))); } - $previousObject = null !== $objectToPopulate ? clone $objectToPopulate : null; - $object = parent::denormalize($data, $resourceClass, $format, $context); + $object = parent::denormalize($data, $class, $format, $context); - if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) { + if (!$this->resourceClassResolver->isResourceClass($class)) { return $object; } + $previousObject = isset($objectToPopulate) ? clone $objectToPopulate : null; // Revert attributes that aren't allowed to be changed after a post-denormalize check foreach (array_keys($data) as $attribute) { if (!$this->canAccessAttributePostDenormalize($object, $previousObject, $attribute, $context)) { @@ -587,8 +513,7 @@ protected function denormalizeCollection(string $attribute, $propertyMetadata, T /** * Denormalizes a relation. * - * @param ApiProperty|PropertyMetadata $propertyMetadata - * @param mixed $value + * @param mixed $value * * @throws LogicException * @throws UnexpectedValueException @@ -596,21 +521,15 @@ protected function denormalizeCollection(string $attribute, $propertyMetadata, T * * @return object|null */ - protected function denormalizeRelation(string $attributeName, $propertyMetadata, string $className, $value, ?string $format, array $context) + protected function denormalizeRelation(string $attributeName, ApiProperty $propertyMetadata, string $className, $value, ?string $format, array $context) { - $supportsPlainIdentifiers = $this->supportsPlainIdentifiers(); - if (\is_string($value)) { try { - return $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri($value, $context + ['fetch_data' => true]) : $this->iriConverter->getResourceFromIri($value, $context + ['fetch_data' => true]); + return $this->iriConverter->getResourceFromIri($value, $context + ['fetch_data' => true]); } catch (ItemNotFoundException $e) { - if (!$supportsPlainIdentifiers) { - throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); - } + throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); } catch (InvalidArgumentException $e) { - if (!$supportsPlainIdentifiers) { - throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $value), $e->getCode(), $e); - } + throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $value), $e->getCode(), $e); } } @@ -629,23 +548,12 @@ protected function denormalizeRelation(string $attributeName, $propertyMetadata, return $item; } catch (InvalidValueException $e) { - if (!$supportsPlainIdentifiers) { - throw $e; - } + throw $e; } } if (!\is_array($value)) { - if (!$supportsPlainIdentifiers) { - throw new UnexpectedValueException(sprintf('Expected IRI or nested document for attribute "%s", "%s" given.', $attributeName, \gettype($value))); - } - - $item = $this->itemDataProvider->getItem($className, $value, null, $context + ['fetch_data' => true]); - if (null === $item) { - throw new ItemNotFoundException(sprintf('Item not found for resource "%s" with id "%s".', $className, $value)); - } - - return $item; + throw new UnexpectedValueException(sprintf('Expected IRI or nested document for attribute "%s", "%s" given.', $attributeName, \gettype($value))); } throw new UnexpectedValueException(sprintf('Nested documents for attribute "%s" are not allowed. Use IRIs instead.', $attributeName)); @@ -663,39 +571,17 @@ protected function getFactoryOptions(array $context): array $options['serializer_groups'] = (array) $context[self::GROUPS]; } - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $operation = $context['operation'] ?? $this->resourceMetadataFactory->create($context['resource_class'])->getOperation($context['operation_name'] ?? null); + if (isset($context['resource_class']) && $this->resourceClassResolver->isResourceClass($context['resource_class'])) { + $resourceClass = $this->resourceClassResolver->getResourceClass($context['resource_class'], $context['resource_class']); + $operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation($context['operation_name'] ?? null); $options['normalization_groups'] = $operation->getNormalizationContext()['groups'] ?? null; $options['denormalization_groups'] = $operation->getDenormalizationContext()['groups'] ?? null; - } - - if (isset($context['operation_name'])) { - $options['operation_name'] = $context['operation_name']; - } - - if (isset($context['collection_operation_name'])) { - $options['collection_operation_name'] = $context['collection_operation_name']; - } - - if (isset($context['item_operation_name'])) { - $options['item_operation_name'] = $context['item_operation_name']; + $options['operation_name'] = $operation->getName(); } return $options; } - /** - * Creates the context to use when serializing a relation. - * - * @deprecated since version 2.1, to be removed in 3.0. - */ - protected function createRelationSerializationContext(string $resourceClass, array $context): array - { - @trigger_error(sprintf('The method %s() is deprecated since 2.1 and will be removed in 3.0.', __METHOD__), \E_USER_DEPRECATED); - - return $context; - } - /** * {@inheritdoc} * @@ -707,28 +593,19 @@ protected function createRelationSerializationContext(string $resourceClass, arr protected function getAttributeValue($object, $attribute, $format = null, array $context = []) { $context['api_attribute'] = $attribute; - /** @var ApiProperty|PropertyMetadata */ $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context)); try { $attributeValue = $this->propertyAccessor->getValue($object, $attribute); } catch (NoSuchPropertyException $e) { - // BC to be removed in 3.0 - if ($propertyMetadata instanceof PropertyMetadata && !$propertyMetadata->hasChildInherited()) { - throw $e; - } - if ($propertyMetadata instanceof ApiProperty) { - throw $e; - } - - $attributeValue = null; + throw $e; } if ($context['api_denormalize'] ?? false) { return $attributeValue; } - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : ($propertyMetadata->getBuiltinTypes()[0] ?? null); + $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; if ( $type && @@ -744,9 +621,7 @@ protected function getAttributeValue($object, $attribute, $format = null, array $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); $childContext = $this->createChildContext($context, $attribute, $format); $childContext['resource_class'] = $resourceClass; - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $childContext['operation'] = $this->resourceMetadataFactory->create($resourceClass)->getOperation(); - } + $childContext['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(); unset($childContext['iri'], $childContext['uri_variables']); return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext); @@ -764,9 +639,7 @@ protected function getAttributeValue($object, $attribute, $format = null, array $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); $childContext = $this->createChildContext($context, $attribute, $format); $childContext['resource_class'] = $resourceClass; - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $childContext['operation'] = $this->resourceMetadataFactory->create($resourceClass)->getOperation(); - } + $childContext['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(); unset($childContext['iri'], $childContext['uri_variables']); return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext); @@ -782,12 +655,8 @@ protected function getAttributeValue($object, $attribute, $format = null, array $childContext = $this->createChildContext($context, $attribute, $format); unset($childContext['iri'], $childContext['uri_variables']); - if ($propertyMetadata instanceof PropertyMetadata) { - $childContext['output']['iri'] = $propertyMetadata->getIri(); - } else { - if (null !== ($propertyIris = $propertyMetadata->getIris())) { - $childContext['output']['iri'] = 1 === \count($propertyIris) ? $propertyIris[0] : $propertyIris; - } + if (null !== ($propertyIris = $propertyMetadata->getIris())) { + $childContext['output']['iri'] = 1 === \count($propertyIris) ? $propertyIris[0] : $propertyIris; } return $this->serializer->normalize($attributeValue, $format, $childContext); @@ -799,12 +668,9 @@ protected function getAttributeValue($object, $attribute, $format = null, array /** * Normalizes a collection of relations (to-many). * - * @param ApiProperty|PropertyMetadata $propertyMetadata - * @param iterable $attributeValue - * * @throws UnexpectedValueException */ - protected function normalizeCollectionOfRelations($propertyMetadata, $attributeValue, string $resourceClass, ?string $format, array $context): array + protected function normalizeCollectionOfRelations(ApiProperty $propertyMetadata, iterable $attributeValue, string $resourceClass, ?string $format, array $context): array { $value = []; foreach ($attributeValue as $index => $obj) { @@ -821,15 +687,12 @@ protected function normalizeCollectionOfRelations($propertyMetadata, $attributeV /** * Normalizes a relation. * - * @param ApiProperty|PropertyMetadata $propertyMetadata - * @param object|null $relatedObject - * * @throws LogicException * @throws UnexpectedValueException * * @return string|array|\ArrayObject|null IRI or normalized object data */ - protected function normalizeRelation($propertyMetadata, $relatedObject, string $resourceClass, ?string $format, array $context) + protected function normalizeRelation(ApiProperty $propertyMetadata, ?object $relatedObject, string $resourceClass, ?string $format, array $context) { if (null === $relatedObject || !empty($context['attributes']) || $propertyMetadata->isReadableLink()) { if (!$this->serializer instanceof NormalizerInterface) { @@ -845,13 +708,13 @@ protected function normalizeRelation($propertyMetadata, $relatedObject, string $ return $normalizedRelatedObject; } - $iri = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($relatedObject) : $this->iriConverter->getIriFromResource($relatedObject); + $iri = $this->iriConverter->getIriFromResource($relatedObject); if (isset($context['resources'])) { $context['resources'][$iri] = $iri; } - $push = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getAttribute('push', false) : ($propertyMetadata->getPush() ?? false); + $push = $propertyMetadata->getPush() ?? false; if (isset($context['resources_to_push']) && $push) { $context['resources_to_push'][$iri] = $iri; } @@ -859,50 +722,10 @@ protected function normalizeRelation($propertyMetadata, $relatedObject, string $ return $iri; } - /** - * Finds the first supported data transformer if any. - * - * @param object|array $data object on normalize / array on denormalize - */ - protected function getDataTransformer($data, string $to, array $context = []): ?DataTransformerInterface - { - foreach ($this->dataTransformers as $dataTransformer) { - if ($dataTransformer->supportsTransformation($data, $to, $context)) { - return $dataTransformer; - } - } - - return null; - } - - /** - * For a given resource, it returns an output representation if any - * If not, the resource is returned. - * - * @param mixed $object - */ - protected function transformOutput($object, array $context = [], string $outputClass = null) - { - if (null === $outputClass) { - $outputClass = $this->getOutputClass($this->getObjectClass($object), $context); - } - - if (null !== $outputClass && null !== $dataTransformer = $this->getDataTransformer($object, $outputClass, $context)) { - return $dataTransformer->transform($object, $outputClass, $context); - } - - return $object; - } - private function createAttributeValue($attribute, $value, $format = null, array $context = []) { - if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) { - return $value; - } - - /** @var ApiProperty|PropertyMetadata */ $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context)); - $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : ($propertyMetadata->getBuiltinTypes()[0] ?? null); + $type = $propertyMetadata->getBuiltinTypes()[0] ?? null; if (null === $type) { // No type provided, blindly return the value @@ -941,9 +764,9 @@ private function createAttributeValue($attribute, $value, $format = null, array $resourceClass = $this->resourceClassResolver->getResourceClass(null, $className); $childContext = $this->createChildContext($context, $attribute, $format); $childContext['resource_class'] = $resourceClass; - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $childContext['operation'] = $this->resourceMetadataFactory->create($resourceClass)->getOperation(); - } + // if ($this->resourceMetadataCollectionFactory instanceof ResourceMetadataCollectionFactoryInterface) { + $childContext['operation'] = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation(); + // } return $this->denormalizeRelation($attribute, $propertyMetadata, $resourceClass, $value, $format, $childContext); } @@ -1040,16 +863,4 @@ private function setValue($object, string $attributeName, $value) // Properties not found are ignored } } - - /** - * TODO: to remove in 3.0. - * - * @deprecated since 2.7 - */ - private function supportsPlainIdentifiers(): bool - { - return $this->allowPlainIdentifiers && null !== $this->itemDataProvider; - } } - -class_alias(AbstractItemNormalizer::class, \ApiPlatform\Core\Serializer\AbstractItemNormalizer::class); diff --git a/src/Serializer/CacheKeyTrait.php b/src/Serializer/CacheKeyTrait.php index 56c9823c660..51cb44952d9 100644 --- a/src/Serializer/CacheKeyTrait.php +++ b/src/Serializer/CacheKeyTrait.php @@ -34,5 +34,3 @@ private function getCacheKey(?string $format, array $context) } } } - -class_alias(CacheKeyTrait::class, \ApiPlatform\Core\Serializer\CacheKeyTrait::class); diff --git a/src/Serializer/ContextTrait.php b/src/Serializer/ContextTrait.php index 90fe49392a4..daf67498f62 100644 --- a/src/Serializer/ContextTrait.php +++ b/src/Serializer/ContextTrait.php @@ -31,5 +31,3 @@ private function initContext(string $resourceClass, array $context): array ]); } } - -class_alias(ContextTrait::class, \ApiPlatform\Core\Serializer\ContextTrait::class); diff --git a/src/Serializer/Filter/GroupFilter.php b/src/Serializer/Filter/GroupFilter.php index e297ac4cfba..24999cab313 100644 --- a/src/Serializer/Filter/GroupFilter.php +++ b/src/Serializer/Filter/GroupFilter.php @@ -75,5 +75,3 @@ public function getDescription(string $resourceClass): array ]; } } - -class_alias(GroupFilter::class, \ApiPlatform\Core\Serializer\Filter\GroupFilter::class); diff --git a/src/Serializer/Filter/PropertyFilter.php b/src/Serializer/Filter/PropertyFilter.php index a5f34449af0..41e1e86ecff 100644 --- a/src/Serializer/Filter/PropertyFilter.php +++ b/src/Serializer/Filter/PropertyFilter.php @@ -168,5 +168,3 @@ private function denormalizePropertyName($property) return null !== $this->nameConverter ? $this->nameConverter->denormalize($property) : $property; } } - -class_alias(PropertyFilter::class, \ApiPlatform\Core\Serializer\Filter\PropertyFilter::class); diff --git a/src/Serializer/InputOutputMetadataTrait.php b/src/Serializer/InputOutputMetadataTrait.php index 66bbb61b71e..c17c0e32015 100644 --- a/src/Serializer/InputOutputMetadataTrait.php +++ b/src/Serializer/InputOutputMetadataTrait.php @@ -13,77 +13,38 @@ namespace ApiPlatform\Serializer; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\OperationNotFoundException; -use ApiPlatform\Exception\ResourceClassNotFoundException; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; trait InputOutputMetadataTrait { /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface|null + * @var ResourceMetadataCollectionFactoryInterface|null */ - protected $resourceMetadataFactory; + protected $resourceMetadataCollectionFactory; protected function getInputClass(string $class, array $context = []): ?string { - if (!$this->resourceMetadataFactory || !$this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - return $this->getInputOutputMetadata($class, 'input', $context); + if (!$this->resourceMetadataCollectionFactory) { + return $context['input']['class'] ?? null; } if (null !== ($context['input']['class'] ?? null)) { return $context['input']['class']; } - $operation = $context['operation'] ?? null; - if (!$operation) { - try { - $operation = $this->resourceMetadataFactory->create($class)->getOperation($context['operation_name'] ?? null); - } catch (OperationNotFoundException $e) { - return null; - } - } - - return $operation ? $operation->getInput()['class'] ?? null : null; + return null; } protected function getOutputClass(string $class, array $context = []): ?string { - if (!$this->resourceMetadataFactory || !$this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - return $this->getInputOutputMetadata($class, 'output', $context); + if (!$this->resourceMetadataCollectionFactory) { + return $context['output']['class'] ?? null; } if (null !== ($context['output']['class'] ?? null)) { return $context['output']['class']; } - $operation = $context['operation'] ?? null; - if (null === $operation) { - try { - $operation = $this->resourceMetadataFactory->create($class)->getOperation($context['operation_name'] ?? null); - } catch (OperationNotFoundException $e) { - return null; - } - } - - return $operation ? $operation->getOutput()['class'] ?? null : null; - } - - // TODO: remove in 3.0 - private function getInputOutputMetadata(string $class, string $inputOrOutput, array $context) - { - if (null === $this->resourceMetadataFactory || null !== ($context[$inputOrOutput]['class'] ?? null)) { - return $context[$inputOrOutput]['class'] ?? null; - } - - try { - $metadata = $this->resourceMetadataFactory->create($class); - } catch (ResourceClassNotFoundException $e) { - return null; - } - - return $metadata->getAttribute($inputOrOutput)['class'] ?? null; + return null; } } - -class_alias(InputOutputMetadataTrait::class, \ApiPlatform\Core\Serializer\InputOutputMetadataTrait::class); diff --git a/src/Serializer/ItemNormalizer.php b/src/Serializer/ItemNormalizer.php index e97c6daedbf..9b20c6ffada 100644 --- a/src/Serializer/ItemNormalizer.php +++ b/src/Serializer/ItemNormalizer.php @@ -14,11 +14,12 @@ namespace ApiPlatform\Serializer; use ApiPlatform\Api\IriConverterInterface; +use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -36,15 +37,9 @@ class ItemNormalizer extends AbstractItemNormalizer { private $logger; - /** - * @param mixed $propertyMetadataFactory - * @param LegacyIriConverterInterface|IriConverterInterface $iriConverter - * @param mixed $resourceClassResolver - * @param mixed|null $resourceMetadataFactory - */ - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, ItemDataProviderInterface $itemDataProvider = null, bool $allowPlainIdentifiers = false, LoggerInterface $logger = null, iterable $dataTransformers = [], $resourceMetadataFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null) { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $itemDataProvider, $allowPlainIdentifiers, [], $dataTransformers, $resourceMetadataFactory, $resourceAccessChecker); + parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, [], $resourceMetadataFactory, $resourceAccessChecker); $this->logger = $logger ?: new NullLogger(); } @@ -80,33 +75,13 @@ public function denormalize($data, $class, $format = null, array $context = []) private function updateObjectToPopulate(array $data, array &$context): void { try { - $context[self::OBJECT_TO_POPULATE] = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri((string) $data['id'], $context + ['fetch_data' => true]) : $this->iriConverter->getResourceFromIri((string) $data['id'], $context + ['fetch_data' => true]); + $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getResourceFromIri((string) $data['id'], $context + ['fetch_data' => true]); } catch (InvalidArgumentException $e) { - if ($this->iriConverter instanceof LegacyIriConverterInterface) { - // remove in 3.0 - $identifier = null; - $options = $this->getFactoryOptions($context); + $operation = $this->resourceMetadataCollectionFactory->create($context['resource_class'])->getOperation(); + // todo: we could guess uri variables with the operation and the data instead of hardcoding id + $iri = $this->iriConverter->getIriFromResource($context['resource_class'], UrlGeneratorInterface::ABS_PATH, $operation, ['uri_variables' => ['id' => $data['id']]]); - foreach ($this->propertyNameCollectionFactory->create($context['resource_class'], $options) as $propertyName) { - if (true === $this->propertyMetadataFactory->create($context['resource_class'], $propertyName)->isIdentifier()) { - $identifier = $propertyName; - break; - } - } - - if (null === $identifier) { - throw $e; - } - $iri = sprintf('%s/%s', $this->iriConverter->getIriFromResourceClass($context['resource_class']), $data[$identifier]); - } else { - $operation = $this->resourceMetadataFactory->create($context['resource_class'])->getOperation(); - // todo: we could guess uri variables with the operation and the data instead of hardcoding id - $iri = $this->iriConverter->getIriFromResource($context['resource_class'], UrlGeneratorInterface::ABS_PATH, $operation, ['uri_variables' => ['id' => $data['id']]]); - } - - $context[self::OBJECT_TO_POPULATE] = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri($iri, ['fetch_data' => true]) : $this->iriConverter->getResourceFromIri($iri, ['fetch_data' => true]); + $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getResourceFromIri($iri, ['fetch_data' => true]); } } } - -class_alias(ItemNormalizer::class, \ApiPlatform\Core\Serializer\ItemNormalizer::class); diff --git a/src/Serializer/JsonEncoder.php b/src/Serializer/JsonEncoder.php index 2a95e510bb0..a336ae787a9 100644 --- a/src/Serializer/JsonEncoder.php +++ b/src/Serializer/JsonEncoder.php @@ -86,5 +86,3 @@ public function decode($data, $format, array $context = []) return $this->jsonEncoder->decode($data, $format, $context); } } - -class_alias(JsonEncoder::class, \ApiPlatform\Core\Serializer\JsonEncoder::class); diff --git a/src/Serializer/Mapping/Factory/ClassMetadataFactory.php b/src/Serializer/Mapping/Factory/ClassMetadataFactory.php index fb2b29ee391..954c8bdb8dc 100644 --- a/src/Serializer/Mapping/Factory/ClassMetadataFactory.php +++ b/src/Serializer/Mapping/Factory/ClassMetadataFactory.php @@ -44,5 +44,3 @@ public function hasMetadataFor($value): bool return $this->decorated->hasMetadataFor(\is_object($value) ? $this->getObjectClass($value) : $this->getRealClassName($value)); } } - -class_alias(ClassMetadataFactory::class, \ApiPlatform\Core\Serializer\Mapping\Factory\ClassMetadataFactory::class); diff --git a/src/Serializer/ResourceList.php b/src/Serializer/ResourceList.php index caba8d0d6c0..3817529da26 100644 --- a/src/Serializer/ResourceList.php +++ b/src/Serializer/ResourceList.php @@ -19,5 +19,3 @@ class ResourceList extends \ArrayObject { } - -class_alias(ResourceList::class, \ApiPlatform\Core\Serializer\ResourceList::class); diff --git a/src/Serializer/SerializerContextBuilder.php b/src/Serializer/SerializerContextBuilder.php index e5d97ec8d7c..6f5ae3a4b96 100644 --- a/src/Serializer/SerializerContextBuilder.php +++ b/src/Serializer/SerializerContextBuilder.php @@ -13,12 +13,7 @@ namespace ApiPlatform\Serializer; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer; use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\RequestAttributesExtractor; use Symfony\Component\HttpFoundation\Request; @@ -33,13 +28,9 @@ final class SerializerContextBuilder implements SerializerContextBuilderInterfac { private $resourceMetadataFactory; - public function __construct($resourceMetadataFactory) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory) { $this->resourceMetadataFactory = $resourceMetadataFactory; - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } } /** @@ -51,70 +42,30 @@ public function createFromRequest(Request $request, bool $normalization, array $ throw new RuntimeException('Request attributes are not valid.'); } - // TODO: 3.0 change the condition to remove the ResourceMetadataFactorym only used to skip null values - if ( - $this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface - && (isset($attributes['operation_name']) || isset($attributes['operation'])) - ) { - $operation = $attributes['operation'] ?? $this->resourceMetadataFactory->create($attributes['resource_class'])->getOperation($attributes['operation_name']); - $context = $normalization ? ($operation->getNormalizationContext() ?? []) : ($operation->getDenormalizationContext() ?? []); - $context['operation_name'] = $operation->getName(); - $context['operation'] = $operation; - $context['resource_class'] = $attributes['resource_class']; - // TODO: 3.0 becomes true by default - $context['skip_null_values'] = $context['skip_null_values'] ?? $this->shouldSkipNullValues($attributes['resource_class'], $context['operation_name']); - // TODO: remove in 3.0, operation type will not exist anymore - $context['operation_type'] = $operation instanceof CollectionOperationInterface ? OperationType::COLLECTION : OperationType::ITEM; - $context['iri_only'] = $context['iri_only'] ?? false; - $context['request_uri'] = $request->getRequestUri(); - $context['uri'] = $request->getUri(); - $context['input'] = $operation->getInput(); - $context['output'] = $operation->getOutput(); + $operation = $attributes['operation'] ?? $this->resourceMetadataFactory->create($attributes['resource_class'])->getOperation($attributes['operation_name']); + $context = $normalization ? ($operation->getNormalizationContext() ?? []) : ($operation->getDenormalizationContext() ?? []); + $context['operation_name'] = $operation->getName(); + $context['operation'] = $operation; + $context['resource_class'] = $attributes['resource_class']; + $context['skip_null_values'] = $context['skip_null_values'] ?? true; + $context['iri_only'] = $context['iri_only'] ?? false; + $context['request_uri'] = $request->getRequestUri(); + $context['uri'] = $request->getUri(); + $context['input'] = $operation->getInput(); + $context['output'] = $operation->getOutput(); + + if ($operation->getTypes()) { $context['types'] = $operation->getTypes(); + } + + if ($operation->getUriVariables()) { $context['uri_variables'] = []; foreach (array_keys($operation->getUriVariables() ?? []) as $parameterName) { $context['uri_variables'][$parameterName] = $request->attributes->get($parameterName); } - - if (!$normalization) { - if (!isset($context['api_allow_update'])) { - $context['api_allow_update'] = \in_array($method = $request->getMethod(), ['PUT', 'PATCH'], true); - - if ($context['api_allow_update'] && 'PATCH' === $method) { - $context['deep_object_to_populate'] = $context['deep_object_to_populate'] ?? true; - } - } - - if ('csv' === $request->getContentType()) { - $context[CsvEncoder::AS_COLLECTION_KEY] = false; - } - } - - return $context; - } - - /** @var ResourceMetadata $resourceMetadata */ - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - $key = $normalization ? 'normalization_context' : 'denormalization_context'; - if (isset($attributes['collection_operation_name'])) { - $operationKey = 'collection_operation_name'; - $operationType = OperationType::COLLECTION; - } elseif (isset($attributes['item_operation_name'])) { - $operationKey = 'item_operation_name'; - $operationType = OperationType::ITEM; - } else { - $operationKey = 'subresource_operation_name'; - $operationType = OperationType::SUBRESOURCE; } - $context = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], $key, [], true); - $context['operation_type'] = $operationType; - $context[$operationKey] = $attributes[$operationKey]; - $context['iri_only'] = $resourceMetadata->getAttribute('normalization_context')['iri_only'] ?? false; - $context['input'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'input', null, true); - $context['output'] = $resourceMetadata->getTypedOperationAttribute($operationType, $attributes[$operationKey], 'output', null, true); - if (!$normalization) { if (!isset($context['api_allow_update'])) { $context['api_allow_update'] = \in_array($method = $request->getMethod(), ['PUT', 'PATCH'], true); @@ -129,69 +80,6 @@ public function createFromRequest(Request $request, bool $normalization, array $ } } - $context['resource_class'] = $attributes['resource_class']; - $context['request_uri'] = $request->getRequestUri(); - $context['uri'] = $request->getUri(); - - if (isset($attributes['subresource_context'])) { - $context['subresource_identifiers'] = []; - - foreach ($attributes['subresource_context']['identifiers'] as $parameterName => [$resourceClass]) { - if (!isset($context['subresource_resources'][$resourceClass])) { - $context['subresource_resources'][$resourceClass] = []; - } - - $context['subresource_identifiers'][$parameterName] = $context['subresource_resources'][$resourceClass][$parameterName] = $request->attributes->get($parameterName); - } - } - - if (isset($attributes['subresource_property'])) { - $context['subresource_property'] = $attributes['subresource_property']; - $context['subresource_resource_class'] = $attributes['subresource_resource_class'] ?? null; - } - - unset($context[DocumentationNormalizer::SWAGGER_DEFINITION_NAME]); - - if (isset($context['skip_null_values'])) { - return $context; - } - - // TODO: We should always use `skip_null_values` but changing this would be a BC break, for now use it only when `merge-patch+json` is activated on a Resource - if (!$this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - foreach ($resourceMetadata->getItemOperations() as $operation) { - if ('PATCH' === ($operation['method'] ?? '') && \in_array('application/merge-patch+json', $operation['input_formats']['json'] ?? [], true)) { - $context['skip_null_values'] = true; - - break; - } - } - } else { - $context['skip_null_values'] = $this->shouldSkipNullValues($attributes['resource_class'], $attributes['operation_name']); - } - return $context; } - - /** - * TODO: remove in 3.0, this will have no impact and skip_null_values will be default, no more resourceMetadataFactory call in this class. - */ - private function shouldSkipNullValues(string $class, string $operationName): bool - { - if (!$this->resourceMetadataFactory) { - return false; - } - - $collection = $this->resourceMetadataFactory->create($class); - foreach ($collection as $metadata) { - foreach ($metadata->getOperations() as $operation) { - if ('PATCH' === ($operation->getMethod() ?? '') && \in_array('application/merge-patch+json', $operation->getInputFormats()['json'] ?? [], true)) { - return true; - } - } - } - - return false; - } } - -class_alias(SerializerContextBuilder::class, \ApiPlatform\Core\Serializer\SerializerContextBuilder::class); diff --git a/src/Serializer/SerializerFilterContextBuilder.php b/src/Serializer/SerializerFilterContextBuilder.php index 234bdc841a8..3132861b8af 100644 --- a/src/Serializer/SerializerFilterContextBuilder.php +++ b/src/Serializer/SerializerFilterContextBuilder.php @@ -75,5 +75,3 @@ public function createFromRequest(Request $request, bool $normalization, array $ return $context; } } - -class_alias(SerializerFilterContextBuilder::class, \ApiPlatform\Core\Serializer\SerializerFilterContextBuilder::class); diff --git a/src/State/CallableProcessor.php b/src/State/CallableProcessor.php index 3a950f8fedf..5fcc820b7e7 100644 --- a/src/State/CallableProcessor.php +++ b/src/State/CallableProcessor.php @@ -13,7 +13,7 @@ namespace ApiPlatform\State; -use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Operation; use Psr\Container\ContainerInterface; diff --git a/src/State/CallableProvider.php b/src/State/CallableProvider.php index 96318ef865e..77bc7ca42c1 100644 --- a/src/State/CallableProvider.php +++ b/src/State/CallableProvider.php @@ -13,7 +13,7 @@ namespace ApiPlatform\State; -use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Operation; use Psr\Container\ContainerInterface; diff --git a/src/State/Pagination/ArrayPaginator.php b/src/State/Pagination/ArrayPaginator.php index d20f2ede837..94adbcea280 100644 --- a/src/State/Pagination/ArrayPaginator.php +++ b/src/State/Pagination/ArrayPaginator.php @@ -93,5 +93,3 @@ public function getIterator(): \Traversable return $this->iterator; } } - -class_alias(ArrayPaginator::class, \ApiPlatform\Core\DataProvider\ArrayPaginator::class); diff --git a/src/State/Pagination/Pagination.php b/src/State/Pagination/Pagination.php index 9b66510c275..5df332d0420 100644 --- a/src/State/Pagination/Pagination.php +++ b/src/State/Pagination/Pagination.php @@ -23,8 +23,8 @@ */ final class Pagination { - private $options; - private $graphQlOptions; + private array $options; + private array $graphQlOptions; public function __construct(array $options = [], array $graphQlOptions = []) { @@ -106,13 +106,8 @@ public function getLimit(Operation $operation = null, array $context = []): int { $graphql = (bool) ($context['graphql_operation_name'] ?? false); - $limit = $this->options['items_per_page']; - $clientLimit = $this->options['client_items_per_page']; - - if ($operation) { - $limit = $operation->getPaginationItemsPerPage() ?? $this->options['items_per_page']; - $clientLimit = $operation->getPaginationClientItemsPerPage() ?? $this->options['client_items_per_page']; - } + $limit = $operation?->getPaginationItemsPerPage() ?? $this->options['items_per_page']; + $clientLimit = $operation?->getPaginationClientItemsPerPage() ?? $this->options['client_items_per_page']; if ($graphql && null !== ($first = $this->getParameterFromContext($context, 'first'))) { $limit = $first; @@ -129,11 +124,7 @@ public function getLimit(Operation $operation = null, array $context = []): int if ($clientLimit) { $limit = (int) $this->getParameterFromContext($context, $this->options['items_per_page_parameter_name'], $limit); - $maxItemsPerPage = $this->options['maximum_items_per_page']; - - if ($operation) { - $maxItemsPerPage = $operation->getPaginationMaximumItemsPerPage() ?? $this->options['maximum_items_per_page']; - } + $maxItemsPerPage = $operation?->getPaginationMaximumItemsPerPage() ?? $this->options['maximum_items_per_page']; if (null !== $maxItemsPerPage && $limit > $maxItemsPerPage) { $limit = $maxItemsPerPage; @@ -211,10 +202,8 @@ private function getEnabled(array $context, Operation $operation = null, bool $p $enabled = $this->options[$partial ? 'partial' : 'enabled']; $clientEnabled = $this->options[$partial ? 'client_partial' : 'client_enabled']; - if ($operation) { - $enabled = ($partial ? $operation->getPaginationPartial() : $operation->getPaginationEnabled()) ?? $enabled; - $clientEnabled = ($partial ? $operation->getPaginationClientPartial() : $operation->getPaginationClientEnabled()) ?? $clientEnabled; - } + $enabled = ($partial ? $operation?->getPaginationPartial() : $operation?->getPaginationEnabled()) ?? $enabled; + $clientEnabled = ($partial ? $operation?->getPaginationClientPartial() : $operation?->getPaginationClientEnabled()) ?? $clientEnabled; if ($clientEnabled) { return filter_var($this->getParameterFromContext($context, $this->options[$partial ? 'partial_parameter_name' : 'enabled_parameter_name'], $enabled), \FILTER_VALIDATE_BOOLEAN); @@ -227,11 +216,7 @@ private function getGraphQlEnabled(?Operation $operation): bool { $enabled = $this->graphQlOptions['enabled']; - if (!$operation) { - return $enabled; - } - - return $operation->getPaginationEnabled() ?? $enabled; + return $operation?->getPaginationEnabled() ?? $enabled; } /** diff --git a/src/State/Pagination/PaginationOptions.php b/src/State/Pagination/PaginationOptions.php index 72fa7988a8a..2e62cc58926 100644 --- a/src/State/Pagination/PaginationOptions.php +++ b/src/State/Pagination/PaginationOptions.php @@ -102,5 +102,3 @@ public function getPartialPaginationParameterName(): string return $this->partialPaginationParameterName; } } - -class_alias(PaginationOptions::class, \ApiPlatform\Core\DataProvider\PaginationOptions::class); diff --git a/src/State/Pagination/TraversablePaginator.php b/src/State/Pagination/TraversablePaginator.php index bb5713de8f7..9b40f517804 100644 --- a/src/State/Pagination/TraversablePaginator.php +++ b/src/State/Pagination/TraversablePaginator.php @@ -88,5 +88,3 @@ public function getIterator(): \Traversable return $this->traversable; } } - -class_alias(TraversablePaginator::class, \ApiPlatform\Core\DataProvider\TraversablePaginator::class); diff --git a/src/State/UriVariablesResolverTrait.php b/src/State/UriVariablesResolverTrait.php index 4504173022f..773c1e86fc0 100644 --- a/src/State/UriVariablesResolverTrait.php +++ b/src/State/UriVariablesResolverTrait.php @@ -13,10 +13,8 @@ namespace ApiPlatform\State; +use ApiPlatform\Api\CompositeIdentifierParser; use ApiPlatform\Api\UriVariablesConverterInterface; -use ApiPlatform\Core\Identifier\CompositeIdentifierParser; -use ApiPlatform\Core\Identifier\ContextAwareIdentifierConverterInterface; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; use ApiPlatform\Exception\InvalidIdentifierException; use ApiPlatform\Metadata\HttpOperation; @@ -25,8 +23,7 @@ */ trait UriVariablesResolverTrait { - /** @var ContextAwareIdentifierConverterInterface|IdentifierConverterInterface|UriVariablesConverterInterface|null */ - private $uriVariablesConverter = null; + private ?UriVariablesConverterInterface $uriVariablesConverter = null; /** * Resolves an operation's UriVariables to their identifiers values. @@ -67,7 +64,7 @@ private function getOperationUriVariables(?HttpOperation $operation = null, arra if ($this->uriVariablesConverter) { $context = ['operation' => $operation]; - $identifiers = $this->uriVariablesConverter instanceof IdentifierConverterInterface ? $this->uriVariablesConverter->convert($identifiers, $operation->getClass() ?? $resourceClass) : $this->uriVariablesConverter->convert($identifiers, $operation->getClass() ?? $resourceClass, $context); + $identifiers = $this->uriVariablesConverter->convert($identifiers, $operation->getClass() ?? $resourceClass, $context); } return $identifiers; diff --git a/src/Symfony/Bundle/ApiPlatformBundle.php b/src/Symfony/Bundle/ApiPlatformBundle.php index 9fe0c17db3e..de96d544946 100644 --- a/src/Symfony/Bundle/ApiPlatformBundle.php +++ b/src/Symfony/Bundle/ApiPlatformBundle.php @@ -13,10 +13,9 @@ namespace ApiPlatform\Symfony\Bundle; -use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass; +use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AttributeFilterPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AuthenticatorManagerPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass; -use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DeprecateMercurePublisherPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\FilterPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlMutationResolverPass; @@ -44,17 +43,14 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new DataProviderPass()); // Run the compiler pass before the {@see ResolveInstanceofConditionalsPass} to allow autoconfiguration of generated filter definitions. - $container->addCompilerPass(new AnnotationFilterPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 101); + $container->addCompilerPass(new AttributeFilterPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 101); $container->addCompilerPass(new FilterPass()); $container->addCompilerPass(new ElasticsearchClientPass()); $container->addCompilerPass(new GraphQlTypePass()); $container->addCompilerPass(new GraphQlQueryResolverPass()); $container->addCompilerPass(new GraphQlMutationResolverPass()); - $container->addCompilerPass(new DeprecateMercurePublisherPass()); $container->addCompilerPass(new MetadataAwareNameConverterPass()); $container->addCompilerPass(new TestClientPass()); $container->addCompilerPass(new AuthenticatorManagerPass()); } } - -class_alias(ApiPlatformBundle::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class); diff --git a/src/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php b/src/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php index 83f0aa495c6..2681a94748a 100644 --- a/src/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php +++ b/src/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php @@ -76,5 +76,3 @@ private function getExpectedInputClass(Request $request): ?string return $context['input'] ?? $context['resource_class']; } } - -class_alias(PayloadArgumentResolver::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\ArgumentResolver\PayloadArgumentResolver::class); diff --git a/src/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/src/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php index f6974680a9d..d9ead7ef527 100644 --- a/src/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php +++ b/src/Symfony/Bundle/CacheWarmer/CachePoolClearerCacheWarmer.php @@ -59,5 +59,3 @@ public function isOptional(): bool return false; } } - -class_alias(CachePoolClearerCacheWarmer::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\CacheWarmer\CachePoolClearerCacheWarmer::class); diff --git a/src/Symfony/Bundle/Command/GraphQlExportCommand.php b/src/Symfony/Bundle/Command/GraphQlExportCommand.php index 84db8778593..a0ce0797c78 100644 --- a/src/Symfony/Bundle/Command/GraphQlExportCommand.php +++ b/src/Symfony/Bundle/Command/GraphQlExportCommand.php @@ -80,5 +80,3 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; } } - -class_alias(GraphQlExportCommand::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\Command\GraphQlExportCommand::class); diff --git a/src/Symfony/Bundle/Command/OpenApiCommand.php b/src/Symfony/Bundle/Command/OpenApiCommand.php index de02f179acc..e27f083d63c 100644 --- a/src/Symfony/Bundle/Command/OpenApiCommand.php +++ b/src/Symfony/Bundle/Command/OpenApiCommand.php @@ -96,5 +96,3 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } } - -class_alias(OpenApiCommand::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\Command\OpenApiCommand::class); diff --git a/src/Symfony/Bundle/DataCollector/RequestDataCollector.php b/src/Symfony/Bundle/DataCollector/RequestDataCollector.php index eaef3f46595..0caafda4f3a 100644 --- a/src/Symfony/Bundle/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Bundle/DataCollector/RequestDataCollector.php @@ -13,16 +13,6 @@ namespace ApiPlatform\Symfony\Bundle\DataCollector; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataPersister\TraceableChainDataPersister; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainCollectionDataProvider; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainItemDataProvider; -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainSubresourceDataProvider; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -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\Metadata\ApiResource; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\RequestAttributesExtractor; @@ -38,36 +28,15 @@ */ final class RequestDataCollector extends DataCollector { - use ApiResourceToLegacyResourceMetadataTrait; - - /** - * @var ResourceMetadataCollectionFactoryInterface - */ private $metadataFactory; private $filterLocator; - private $collectionDataProvider; - private $itemDataProvider; - private $subresourceDataProvider; - private $dataPersister; public function __construct( - $metadataFactory, + ResourceMetadataCollectionFactoryInterface $metadataFactory, ContainerInterface $filterLocator, - CollectionDataProviderInterface $collectionDataProvider = null, - ItemDataProviderInterface $itemDataProvider = null, - SubresourceDataProviderInterface $subresourceDataProvider = null, - DataPersisterInterface $dataPersister = null ) { $this->metadataFactory = $metadataFactory; $this->filterLocator = $filterLocator; - $this->collectionDataProvider = $collectionDataProvider; - $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)); - } } /** @@ -102,35 +71,8 @@ public function collect(Request $request, Response $response, \Throwable $except 'acceptable_content_types' => $request->getAcceptableContentTypes(), 'filters' => $filters, 'counters' => $counters, - 'dataProviders' => [], - 'dataPersisters' => ['responses' => []], 'request_attributes' => $requestAttributes, ]; - - if ($this->collectionDataProvider instanceof TraceableChainCollectionDataProvider) { - $this->data['dataProviders']['collection'] = [ - 'context' => $this->cloneVar($this->collectionDataProvider->getContext()), - 'responses' => $this->collectionDataProvider->getProvidersResponse(), - ]; - } - - if ($this->itemDataProvider instanceof TraceableChainItemDataProvider) { - $this->data['dataProviders']['item'] = [ - 'context' => $this->cloneVar($this->itemDataProvider->getContext()), - 'responses' => $this->itemDataProvider->getProvidersResponse(), - ]; - } - - if ($this->subresourceDataProvider instanceof TraceableChainSubresourceDataProvider) { - $this->data['dataProviders']['subresource'] = [ - 'context' => $this->cloneVar($this->subresourceDataProvider->getContext()), - 'responses' => $this->subresourceDataProvider->getProvidersResponse(), - ]; - } - - if ($this->dataPersister instanceof TraceableChainDataPersister) { - $this->data['dataPersisters']['responses'] = $this->dataPersister->getPersistersResponse(); - } } private function setFilters(ApiResource $resourceMetadata, int $index, array &$filters, array &$counters): void @@ -176,26 +118,6 @@ public function getCounters(): array return $this->data['counters'] ?? []; } - public function getCollectionDataProviders(): array - { - return $this->data['dataProviders']['collection'] ?? ['context' => [], 'responses' => []]; - } - - public function getItemDataProviders(): array - { - return $this->data['dataProviders']['item'] ?? ['context' => [], 'responses' => []]; - } - - public function getSubresourceDataProviders(): array - { - return $this->data['dataProviders']['subresource'] ?? ['context' => [], 'responses' => []]; - } - - public function getDataPersisters(): array - { - return $this->data['dataPersisters'] ?? ['responses' => []]; - } - public function getVersion(): ?string { if (!class_exists(Versions::class)) { diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index 61c2969f3af..31787b5ebce 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -15,14 +15,6 @@ use ApiPlatform\Api\FilterInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Annotation\ApiResource as ApiResourceAnnotation; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter as DoctrineOrmAbstractContextAwareFilter; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface; -use ApiPlatform\Core\DataTransformer\DataTransformerInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter as DoctrineMongoDbOdmAbstractFilter; @@ -42,7 +34,6 @@ use ApiPlatform\State\ProviderInterface; use ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface; use ApiPlatform\Symfony\Validator\ValidationGroupsGeneratorInterface; -use Doctrine\Common\Annotations\Annotation; use phpDocumentor\Reflection\DocBlockFactoryInterface; use Ramsey\Uuid\Uuid; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -119,7 +110,6 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerJsonHalConfiguration($formats, $loader, $config); $this->registerJsonProblemConfiguration($errorFormats, $loader); $this->registerGraphQlConfiguration($container, $config, $loader); - $this->registerLegacyBundlesConfiguration($container, $config, $loader); $this->registerCacheConfiguration($container); $this->registerDoctrineOrmConfiguration($container, $config, $loader); $this->registerDoctrineMongoDbOdmConfiguration($container, $config, $loader); @@ -133,18 +123,6 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerSecurityConfiguration($container, $loader, $config); $this->registerMakerConfiguration($container, $config, $loader); $this->registerArgumentResolverConfiguration($container, $loader, $config); - $this->registerLegacyServices($container, $config, $loader); - $this->registerUpgradeCommandConfiguration($container, $loader, $config); - - // TODO: remove in 3.x - $container->registerForAutoconfiguration(DataPersisterInterface::class) - ->addTag('api_platform.data_persister'); - $container->registerForAutoconfiguration(ItemDataProviderInterface::class) - ->addTag('api_platform.item_data_provider'); - $container->registerForAutoconfiguration(CollectionDataProviderInterface::class) - ->addTag('api_platform.collection_data_provider'); - $container->registerForAutoconfiguration(SubresourceDataProviderInterface::class) - ->addTag('api_platform.subresource_data_provider'); $container->registerForAutoconfiguration(FilterInterface::class) ->addTag('api_platform.filter'); @@ -156,57 +134,19 @@ public function load(array $configs, ContainerBuilder $container): void private function registerCommonConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, array $formats, array $patchFormats, array $errorFormats): void { + $loader->load('symfony/events.xml'); $loader->load('api.xml'); - $loader->load('v3/state.xml'); - - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/api.xml'); - $loader->load('legacy/data_provider.xml'); - $loader->load('legacy/backward_compatibility.xml'); - } else { - $loader->load('v3/api.xml'); - $loader->load('legacy/data_provider.xml'); - $loader->load('v3/backward_compatibility.xml'); - } - - $loader->load('data_persister.xml'); - $loader->load('data_provider.xml'); + $loader->load('state.xml'); $loader->load('filter.xml'); - if ($container->hasDefinition('api_platform.operation_method_resolver')) { - $container->getDefinition('api_platform.operation_method_resolver') - ->setDeprecated(...$this->buildDeprecationArgs('2.5', 'The "%service_id%" service is deprecated since API Platform 2.5.')); - } - - if ($container->hasDefinition('api_platform.formats_provider')) { - $container->getDefinition('api_platform.formats_provider') - ->setDeprecated(...$this->buildDeprecationArgs('2.5', 'The "%service_id%" service is deprecated since API Platform 2.5.')); - $container->getAlias('ApiPlatform\Core\Api\OperationAwareFormatsProviderInterface') - ->setDeprecated(...$this->buildDeprecationArgs('2.5', 'The "%alias_id%" alias is deprecated since API Platform 2.5.')); - } - - if ($container->hasDefinition('api_platform.operation_path_resolver.underscore')) { - $container->getDefinition('api_platform.operation_path_resolver.underscore') - ->setDeprecated(...$this->buildDeprecationArgs('2.1', 'The "%service_id%" service is deprecated since API Platform 2.1 and will be removed in 3.0. Use "api_platform.path_segment_name_generator.underscore" instead.')); - } - - if ($container->hasDefinition('api_platform.operation_path_resolver.underscore')) { - $container->getDefinition('api_platform.operation_path_resolver.dash') - ->setDeprecated(...$this->buildDeprecationArgs('2.1', 'The "%service_id%" service is deprecated since API Platform 2.1 and will be removed in 3.0. Use "api_platform.path_segment_name_generator.dash" instead.')); - } - - $container->getDefinition('api_platform.filters') - ->setDeprecated(...$this->buildDeprecationArgs('2.1', 'The "%service_id%" service is deprecated since 2.1 and will be removed in 3.0. Use the "api_platform.filter_locator" service instead.')); - if (class_exists(Uuid::class)) { $loader->load('ramsey_uuid.xml'); } if (class_exists(AbstractUid::class)) { - $loader->load('symfony_uid.xml'); + $loader->load('symfony/uid.xml'); } - $container->setParameter('api_platform.metadata_backward_compatibility_layer', $config['metadata_backward_compatibility_layer']); $container->setParameter('api_platform.enable_entrypoint', $config['enable_entrypoint']); $container->setParameter('api_platform.enable_docs', $config['enable_docs']); $container->setParameter('api_platform.title', $config['title']); @@ -278,19 +218,13 @@ private function getPaginationDefaults(array $defaults, array $collectionPaginat private function normalizeDefaults(array $defaults, bool $compatibility = false): array { - $key = $compatibility ? 'attributes' : 'extra_properties'; - $normalizedDefaults = [$key => $defaults[$key] ?? []]; - unset($defaults[$key]); + $normalizedDefaults = ['extra_properties' => $defaults['extra_properties'] ?? []]; + unset($defaults['extra_properties']); + $rc = new \ReflectionClass(ApiResource::class); $publicProperties = []; - - if ($compatibility) { - [$publicProperties,] = ApiResourceAnnotation::getConfigMetadata(); - } else { - $rc = new \ReflectionClass(ApiResource::class); - foreach ($rc->getConstructor()->getParameters() as $param) { - $publicProperties[$param->getName()] = true; - } + foreach ($rc->getConstructor()->getParameters() as $param) { + $publicProperties[$param->getName()] = true; } $nameConverter = new CamelCaseToSnakeCaseNameConverter(); @@ -301,7 +235,7 @@ private function normalizeDefaults(array $defaults, bool $compatibility = false) continue; } - $normalizedDefaults[$key][$option] = $value; + $normalizedDefaults['extra_properties'][$option] = $value; } return $normalizedDefaults; @@ -320,39 +254,11 @@ private function registerMetadataConfiguration(ContainerBuilder $container, arra )); } - $loader->load('legacy/metadata.xml'); - $container->getDefinition('api_platform.metadata.extractor.xml.legacy')->replaceArgument(0, $xmlResources); - - if (class_exists(Annotation::class)) { - $loader->load('legacy/metadata_annotation.xml'); - } - - if (interface_exists(DocBlockFactoryInterface::class)) { - $loader->load('legacy/metadata_php_doc.xml'); - } - - if (class_exists(Yaml::class)) { - $loader->load('legacy/metadata_yaml.xml'); - $container->getDefinition('api_platform.metadata.extractor.yaml.legacy')->replaceArgument(0, $yamlResources); - - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/metadata_yaml_backward_compatibility.xml'); - } - } - - // Load the legacy metadata as well - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/metadata_backward_compatibility.xml'); - - return; - } - // V3 metadata $loader->load('metadata/xml.xml'); $loader->load('metadata/links.xml'); $loader->load('metadata/property.xml'); $loader->load('metadata/resource.xml'); - $loader->load('v3/metadata.xml'); $container->getDefinition('api_platform.metadata.resource_extractor.xml')->replaceArgument(0, $xmlResources); $container->getDefinition('api_platform.metadata.property_extractor.xml')->replaceArgument(0, $xmlResources); @@ -454,9 +360,6 @@ private function registerOAuthConfiguration(ContainerBuilder $container, array $ $container->setParameter('api_platform.oauth.scopes', $config['oauth']['scopes']); $container->setParameter('api_platform.oauth.pkce', $config['oauth']['pkce']); - if ($container->hasDefinition('api_platform.swagger.action.ui')) { - $container->getDefinition('api_platform.swagger.action.ui')->setArgument(27, $config['oauth']['pkce']); - } if ($container->hasDefinition('api_platform.swagger_ui.action')) { $container->getDefinition('api_platform.swagger_ui.action')->setArgument(10, $config['oauth']['pkce']); } @@ -492,23 +395,6 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array throw new RuntimeException('You can not set "swagger_ui_extra_configuration" twice - in "openapi" and "swagger" section.'); } $container->setParameter('api_platform.swagger_ui.extra_configuration', $config['openapi']['swagger_ui_extra_configuration'] ?: $config['swagger']['swagger_ui_extra_configuration']); - - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/swagger.xml'); - $loader->load('legacy/openapi.xml'); - $loader->load('legacy/swagger_ui.xml'); - - if (true === $config['openapi']['backward_compatibility_layer']) { - $container->getDefinition('api_platform.swagger.normalizer.documentation')->addArgument($container->getDefinition('api_platform.openapi.normalizer')); - } - - return; - } - - // for swagger 2 support - $loader->load('legacy/swagger.xml'); - $loader->load('v3/openapi.xml'); - $loader->load('v3/swagger_ui.xml'); } private function registerJsonApiConfiguration(array $formats, XmlFileLoader $loader): void @@ -529,12 +415,6 @@ private function registerJsonLdHydraConfiguration(ContainerBuilder $container, a $loader->load('jsonld.xml'); $loader->load('hydra.xml'); - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/hydra.xml'); - } else { - $loader->load('v3/hydra.xml'); - } - if (!$container->has('api_platform.json_schema.schema_factory')) { $container->removeDefinition('api_platform.hydra.json_schema.schema_factory'); } @@ -551,12 +431,6 @@ private function registerJsonHalConfiguration(array $formats, XmlFileLoader $loa } $loader->load('hal.xml'); - - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/hal.xml'); - } else { - $loader->load('v3/hal.xml'); - } } private function registerJsonProblemConfiguration(array $errorFormats, XmlFileLoader $loader): void @@ -586,12 +460,6 @@ private function registerGraphQlConfiguration(ContainerBuilder $container, array $loader->load('graphql.xml'); - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/graphql.xml'); - } else { - $loader->load('v3/graphql.xml'); - } - $container->registerForAutoconfiguration(QueryItemResolverInterface::class) ->addTag('api_platform.graphql.query_resolver'); $container->registerForAutoconfiguration(QueryCollectionResolverInterface::class) @@ -604,51 +472,18 @@ private function registerGraphQlConfiguration(ContainerBuilder $container, array ->addTag('api_platform.graphql.error_handler'); } - private function registerLegacyBundlesConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void - { - /** @var string[] $bundles */ - $bundles = $container->getParameter('kernel.bundles'); - - if (isset($bundles['FOSUserBundle']) && $config['enable_fos_user']) { - $loader->load('fos_user.xml'); - } - - if (isset($bundles['NelmioApiDocBundle']) && $config['enable_nelmio_api_doc']) { - $loader->load('nelmio_api_doc.xml'); - - $container->getDefinition('api_platform.nelmio_api_doc.annotations_provider') - ->setDeprecated(...$this->buildDeprecationArgs('2.2', 'The "%service_id%" service is deprecated since API Platform 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.')); - $container->getDefinition('api_platform.nelmio_api_doc.parser') - ->setDeprecated(...$this->buildDeprecationArgs('2.2', 'The "%service_id%" service is deprecated since API Platform 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.')); - } - } - private function registerCacheConfiguration(ContainerBuilder $container): void { if (!$container->hasParameter('kernel.debug') || !$container->getParameter('kernel.debug')) { $container->removeDefinition('api_platform.cache_warmer.cache_pool_clearer'); } - if (!$container->hasParameter('api_platform.metadata_cache')) { - return; - } - - @trigger_error('The "api_platform.metadata_cache" parameter is deprecated since version 2.4 and will have no effect in 3.0.', \E_USER_DEPRECATED); - - // BC - if (!$container->getParameter('api_platform.metadata_cache')) { - $container->removeDefinition('api_platform.cache_warmer.cache_pool_clearer'); - - $container->register('api_platform.cache.metadata.property', ArrayAdapter::class); - $container->register('api_platform.cache.metadata.property.legacy', ArrayAdapter::class); - $container->register('api_platform.cache.metadata.resource', ArrayAdapter::class); - $container->register('api_platform.cache.metadata.resource.legacy', ArrayAdapter::class); - $container->register('api_platform.cache.metadata.resource_collection', ArrayAdapter::class); - $container->register('api_platform.cache.route_name_resolver', ArrayAdapter::class); - $container->register('api_platform.cache.identifiers_extractor', ArrayAdapter::class); - $container->register('api_platform.cache.subresource_operation_factory', ArrayAdapter::class); - $container->register('api_platform.elasticsearch.cache.metadata.document', ArrayAdapter::class); - } + $container->register('api_platform.cache.metadata.property', ArrayAdapter::class); + $container->register('api_platform.cache.metadata.resource', ArrayAdapter::class); + $container->register('api_platform.cache.metadata.resource_collection', ArrayAdapter::class); + $container->register('api_platform.cache.route_name_resolver', ArrayAdapter::class); + $container->register('api_platform.cache.identifiers_extractor', ArrayAdapter::class); + $container->register('api_platform.elasticsearch.cache.metadata.document', ArrayAdapter::class); } private function registerDoctrineOrmConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void @@ -661,17 +496,9 @@ private function registerDoctrineOrmConfiguration(ContainerBuilder $container, a ->addTag('api_platform.doctrine.orm.query_extension.item'); $container->registerForAutoconfiguration(DoctrineQueryCollectionExtensionInterface::class) ->addTag('api_platform.doctrine.orm.query_extension.collection'); - $container->registerForAutoconfiguration(DoctrineOrmAbstractContextAwareFilter::class) - ->setBindings(['$requestStack' => null]); - $container->registerForAutoconfiguration(DoctrineOrmAbstractFilter::class) - ->setBindings(['$requestStack' => null]); + $container->registerForAutoconfiguration(DoctrineOrmAbstractFilter::class); $loader->load('doctrine_orm.xml'); - $loader->load('legacy/doctrine_orm.xml'); - - if (!$config['metadata_backward_compatibility_layer']) { - $loader->load('v3/doctrine_orm.xml'); - } if ($this->isConfigEnabled($container, $config['eager_loading'])) { return; @@ -697,12 +524,6 @@ private function registerDoctrineMongoDbOdmConfiguration(ContainerBuilder $conta ->setBindings(['$managerRegistry' => new Reference('doctrine_mongodb')]); $loader->load('doctrine_mongodb_odm.xml'); - - if (!$config['metadata_backward_compatibility_layer']) { - $loader->load('v3/doctrine_odm.xml'); - } else { - $loader->load('legacy/doctrine_odm.xml'); - } } private function registerHttpCacheConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void @@ -756,12 +577,8 @@ private function getFormats(array $configFormats): array private function registerValidatorConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void { if (interface_exists(ValidatorInterface::class)) { - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/validator.xml'); - } else { - $loader->load('metadata/validator.xml'); - $loader->load('symfony/validator.xml'); - } + $loader->load('metadata/validator.xml'); + $loader->load('symfony/validator.xml'); $container->registerForAutoconfiguration(ValidationGroupsGeneratorInterface::class) ->addTag('api_platform.validation_groups_generator') @@ -789,20 +606,10 @@ private function registerDataCollectorConfiguration(ContainerBuilder $container, return; } - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/data_collector.xml'); - } else { - $loader->load('v3/data_collector.xml'); - } + $loader->load('data_collector.xml'); if ($container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug')) { $loader->load('debug.xml'); - - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/debug.xml'); - } else { - $loader->load('v3/debug.xml'); - } } } @@ -818,43 +625,27 @@ private function registerMercureConfiguration(ContainerBuilder $container, array } if ($this->isConfigEnabled($container, $config['doctrine'])) { - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/doctrine_orm_mercure_publisher.xml'); - } else { - $loader->load('v3/doctrine_orm_mercure_publisher.xml'); - } + $loader->load('doctrine_orm_mercure_publisher.xml'); if (class_exists(HubRegistry::class)) { $container->getDefinition('api_platform.doctrine.orm.listener.mercure.publish')->setArgument(6, new Reference(HubRegistry::class)); } } if ($this->isConfigEnabled($container, $config['doctrine_mongodb_odm'])) { - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/doctrine_odm_mercure_publisher.xml'); - } else { - $loader->load('v3/doctrine_odm_mercure_publisher.xml'); - } + $loader->load('doctrine_odm_mercure_publisher.xml'); if (class_exists(HubRegistry::class)) { $container->getDefinition('api_platform.doctrine_mongodb.odm.listener.mercure.publish')->setArgument(6, new Reference(HubRegistry::class)); } } if ($this->isConfigEnabled($container, $config['graphql'])) { - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/graphql_mercure.xml'); - } else { - $loader->load('v3/graphql_mercure.xml'); - } + $loader->load('graphql_mercure.xml'); if (class_exists(HubRegistry::class)) { $container->getDefinition('api_platform.graphql.subscription.mercure_iri_generator')->addArgument(new Reference(HubRegistry::class)); } else { $container->getDefinition('api_platform.graphql.subscription.mercure_iri_generator')->addArgument($config['mercure']['hub_url'] ?? '%mercure.default_hub%'); } } - - if ($config['metadata_backward_compatibility_layer']) { - $container->getDefinition('api_platform.mercure.listener.response.add_link_header')->setArgument(0, new Reference('api_platform.metadata.resource.metadata_factory')); - } } private function registerMessengerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void @@ -864,10 +655,6 @@ private function registerMessengerConfiguration(ContainerBuilder $container, arr } $loader->load('messenger.xml'); - - if (!$config['metadata_backward_compatibility_layer']) { - $loader->load('v3/messenger.xml'); - } } private function registerElasticsearchConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void @@ -881,11 +668,6 @@ private function registerElasticsearchConfiguration(ContainerBuilder $container, } $loader->load('elasticsearch.xml'); - $loader->load('legacy/elasticsearch.xml'); - - if (!$config['metadata_backward_compatibility_layer']) { - $loader->load('v3/elasticsearch.xml'); - } $container->registerForAutoconfiguration(RequestBodySearchCollectionExtensionInterface::class) ->addTag('api_platform.elasticsearch.request_body_search_extension.collection'); @@ -913,10 +695,6 @@ private function registerSecurityConfiguration(ContainerBuilder $container, XmlF } $loader->load('security.xml'); - - if ($config['metadata_backward_compatibility_layer']) { - $container->getDefinition('api_platform.security.listener.request.deny_access')->setArgument(0, new Reference('api_platform.metadata.resource.metadata_factory')); - } } private function registerOpenApiConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void @@ -929,12 +707,6 @@ private function registerOpenApiConfiguration(ContainerBuilder $container, array $container->setParameter('api_platform.openapi.license.url', $config['openapi']['license']['url']); $loader->load('json_schema.xml'); - - if ($config['metadata_backward_compatibility_layer']) { - $loader->load('legacy/json_schema.xml'); - } else { - $loader->load('v3/json_schema.xml'); - } } private function registerMakerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void @@ -948,39 +720,6 @@ private function registerMakerConfiguration(ContainerBuilder $container, array $ private function registerArgumentResolverConfiguration(ContainerBuilder $container, XmlFileLoader $loader, array $config): void { - if ($config['metadata_backward_compatibility_layer']) { - return; - } - $loader->load('argument_resolver.xml'); } - - private function registerLegacyServices(ContainerBuilder $container, array $config, XmlFileLoader $loader): void - { - $container->setParameter('api_platform.metadata_backward_compatibility_layer', $config['metadata_backward_compatibility_layer']); - - $loader->load('legacy/identifiers.xml'); - - if (!$config['metadata_backward_compatibility_layer']) { - $loader->load('symfony.xml'); - - return; - } - - $loader->load('legacy/api.xml'); - } - - private function registerUpgradeCommandConfiguration(ContainerBuilder $container, XmlFileLoader $loader, array $config): void - { - $loader->load('legacy/upgrade.xml'); - } - - private function buildDeprecationArgs(string $version, string $message): array - { - return method_exists(Definition::class, 'getDeprecation') - ? ['api-platform/core', $version, $message] - : [true, $message]; - } } - -class_alias(ApiPlatformExtension::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\ApiPlatformExtension::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeFilterPass.php similarity index 79% rename from src/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php rename to src/Symfony/Bundle/DependencyInjection/Compiler/AttributeFilterPass.php index 16e0669fa14..7668f7da83e 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeFilterPass.php @@ -13,10 +13,9 @@ namespace ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler; -use ApiPlatform\Core\Annotation\ApiFilter; -use ApiPlatform\Util\AnnotationFilterExtractorTrait; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Util\AttributeFilterExtractorTrait; use ApiPlatform\Util\ReflectionClassRecursiveIterator; -use Doctrine\Common\Annotations\Reader; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -24,23 +23,18 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** - * Registers filter services from {@see ApiFilter} annotations. + * Registers filter services from {@see ApiFilter} attribute. * * @internal * * @author Antoine Bluchet */ -final class AnnotationFilterPass implements CompilerPassInterface +final class AttributeFilterPass implements CompilerPassInterface { - use AnnotationFilterExtractorTrait; + use AttributeFilterExtractorTrait; private const TAG_FILTER_NAME = 'api_platform.filter'; - /** - * @var Reader|null - */ - private $reader; - /** * {@inheritdoc} */ @@ -58,11 +52,7 @@ public function process(ContainerBuilder $container): void */ private function createFilterDefinitions(\ReflectionClass $resourceReflectionClass, ContainerBuilder $container): void { - if (null === $this->reader) { - $this->reader = $container->has('annotation_reader') ? $container->get('annotation_reader') : null; - } - - foreach ($this->readFilterAnnotations($resourceReflectionClass, $this->reader) as $id => [$arguments, $filterClass]) { + foreach ($this->readFilterAttributes($resourceReflectionClass) as $id => [$arguments, $filterClass]) { if ($container->has($id)) { continue; } @@ -100,5 +90,3 @@ private function createFilterDefinitions(\ReflectionClass $resourceReflectionCla } } } - -class_alias(AnnotationFilterPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php index bf18c829ecf..772d370c794 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/AuthenticatorManagerPass.php @@ -35,5 +35,3 @@ public function process(ContainerBuilder $container): void } } } - -class_alias(AuthenticatorManagerPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\AuthenticatorManagerPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php index 05e93aa07a5..f1be702b296 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php @@ -13,8 +13,6 @@ namespace ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\DataProvider\SerializerAwareDataProviderInterface; use ApiPlatform\State\SerializerAwareProviderInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -35,10 +33,6 @@ final class DataProviderPass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { - foreach (OperationType::TYPES as $type) { - $this->addSerializerLocator($container, $type); - } - $services = $container->findTaggedServiceIds('api_platform.state_provider', true); foreach ($services as $id => $tags) { @@ -48,18 +42,4 @@ public function process(ContainerBuilder $container) } } } - - private function addSerializerLocator(ContainerBuilder $container, string $type): void - { - $services = $container->findTaggedServiceIds("api_platform.{$type}_data_provider", true); - - foreach ($services as $id => $tags) { - $definition = $container->getDefinition((string) $id); - if (is_a($definition->getClass(), SerializerAwareDataProviderInterface::class, true)) { - $definition->addMethodCall('setSerializerLocator', [new Reference('api_platform.serializer_locator')]); - } - } - } } - -class_alias(DataProviderPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php deleted file mode 100644 index b2e426b2f23..00000000000 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/DeprecateMercurePublisherPass.php +++ /dev/null @@ -1,43 +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\Symfony\Bundle\DependencyInjection\Compiler; - -use Symfony\Component\Config\Definition\BaseNode; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * Handles Mercure Publisher depreciation. - * - * @internal calls `setDeprecated` method with valid arguments - * depending which version of symfony/dependency-injection is used - */ -final class DeprecateMercurePublisherPass implements CompilerPassInterface -{ - public function process(ContainerBuilder $container) - { - $container - ->setAlias('api_platform.doctrine.listener.mercure.publish', 'api_platform.doctrine.orm.listener.mercure.publish') - ->setDeprecated(...$this->buildDeprecationArgs('2.6', 'Using "%alias_id%" service is deprecated since API Platform 2.6. Use "api_platform.doctrine.orm.listener.mercure.publish" instead.')); - } - - private function buildDeprecationArgs(string $version, string $message): array - { - return method_exists(BaseNode::class, 'getDeprecation') - ? ['api-platform/core', $version, $message] - : [$message]; - } -} - -class_alias(DeprecateMercurePublisherPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DeprecateMercurePublisherPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php index 22cb662bf4e..0d99dd7468c 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php @@ -55,5 +55,3 @@ public function process(ContainerBuilder $container) } } } - -class_alias(ElasticsearchClientPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php index b77616418e0..2897b0c4e30 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/FilterPass.php @@ -49,5 +49,3 @@ public function process(ContainerBuilder $container) $container->getDefinition('api_platform.filter_collection_factory')->addArgument(array_keys($filters)); } } - -class_alias(FilterPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\FilterPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php index 2cbcbba15bf..cb032239ece 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlMutationResolverPass.php @@ -45,5 +45,3 @@ public function process(ContainerBuilder $container) $container->getDefinition('api_platform.graphql.mutation_resolver_locator')->addArgument($mutations); } } - -class_alias(GraphQlMutationResolverPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\GraphQlMutationResolverPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php index bc00d4aaabf..882a3510317 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlQueryResolverPass.php @@ -45,5 +45,3 @@ public function process(ContainerBuilder $container) $container->getDefinition('api_platform.graphql.query_resolver_locator')->addArgument($resolvers); } } - -class_alias(GraphQlQueryResolverPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\GraphQlQueryResolverPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php index ada87331f64..3c412b33123 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/GraphQlTypePass.php @@ -46,5 +46,3 @@ public function process(ContainerBuilder $container) $container->getDefinition('api_platform.graphql.types_factory')->addArgument(array_keys($types)); } } - -class_alias(GraphQlTypePass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php index 42454ff26dd..aee2cbf109d 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php @@ -53,5 +53,3 @@ public function process(ContainerBuilder $container) $container->setAlias('api_platform.name_converter', 'serializer.name_converter.metadata_aware'); } } - -class_alias(MetadataAwareNameConverterPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php index da6d9593436..c2812085e03 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/TestClientPass.php @@ -41,5 +41,3 @@ public function process(ContainerBuilder $container) ); } } - -class_alias(TestClientPass::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass::class); diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index a8595ce6a91..53d5ccd50fa 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -14,11 +14,11 @@ namespace ApiPlatform\Symfony\Bundle\DependencyInjection; use ApiPlatform\Core\Annotation\ApiResource as LegacyApiResource; -use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Doctrine\Common\Filter\OrderFilterInterface; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Exception\FilterValidationException; use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\ApiResource; use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle; use Doctrine\ORM\EntityManagerInterface; @@ -678,4 +678,3 @@ private function buildDeprecationArgs(string $version, string $message): array } } -class_alias(Configuration::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Configuration::class); diff --git a/src/Symfony/Bundle/EventListener/SwaggerUiListener.php b/src/Symfony/Bundle/EventListener/SwaggerUiListener.php index 09020c02486..a7c9cf85b37 100644 --- a/src/Symfony/Bundle/EventListener/SwaggerUiListener.php +++ b/src/Symfony/Bundle/EventListener/SwaggerUiListener.php @@ -30,8 +30,6 @@ public function onKernelRequest(RequestEvent $event): void return; } - $request->attributes->set('_controller', 'api_platform.swagger.action.ui'); + $request->attributes->set('_controller', 'api_platform.swagger_ui.action'); } } - -class_alias(SwaggerUiListener::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\EventListener\SwaggerUiListener::class); diff --git a/src/Symfony/Bundle/Resources/config/api.xml b/src/Symfony/Bundle/Resources/config/api.xml index 30b0043ce66..1b256367d42 100644 --- a/src/Symfony/Bundle/Resources/config/api.xml +++ b/src/Symfony/Bundle/Resources/config/api.xml @@ -34,12 +34,12 @@ - + - + @@ -63,12 +63,8 @@ - - - %api_platform.allow_plain_identifiers% null - - + @@ -80,16 +76,6 @@ - - - - - - - - - - @@ -115,8 +101,6 @@ %api_platform.title% %api_platform.description% %api_platform.version% - null - %api_platform.swagger.versions% @@ -124,7 +108,7 @@ %api_platform.error_formats% %api_platform.exception_to_status% - + @@ -132,17 +116,22 @@ - - - - - - + + + - + + %api_platform.formats% + %api_platform.resource_class_directories% + %api_platform.graphql.enabled% + %api_platform.enable_entrypoint% + %api_platform.enable_docs% + %api_platform.graphql.graphiql.enabled% + %api_platform.graphql.graphql_playground.enabled% null + + - @@ -169,5 +158,17 @@ + + + + + + + + + + null + + diff --git a/src/Symfony/Bundle/Resources/config/v3/data_collector.xml b/src/Symfony/Bundle/Resources/config/data_collector.xml similarity index 75% rename from src/Symfony/Bundle/Resources/config/v3/data_collector.xml rename to src/Symfony/Bundle/Resources/config/data_collector.xml index b4456561b1e..689561d3f41 100644 --- a/src/Symfony/Bundle/Resources/config/v3/data_collector.xml +++ b/src/Symfony/Bundle/Resources/config/data_collector.xml @@ -8,10 +8,6 @@ - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/debug.xml b/src/Symfony/Bundle/Resources/config/debug.xml index d8becbdbb92..df1ad13979e 100644 --- a/src/Symfony/Bundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/Resources/config/debug.xml @@ -8,5 +8,12 @@ + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml index 5fa345f4c85..26cd5f3192c 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml +++ b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml @@ -12,15 +12,6 @@ - - - - - - - @@ -43,52 +34,125 @@ - + + + + + + + - + + + + + - - - - + + + + %api_platform.collection.exists_parameter_name% + + + + + + + + + + + + + + %api_platform.collection.order_parameter_name% + + + + + + + + + + + + + + + + + + + - + - - - - + + + + - + + %api_platform.collection.order% - - - - - + + + - - + + + + - - + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/v3/doctrine_odm_mercure_publisher.xml b/src/Symfony/Bundle/Resources/config/doctrine_odm_mercure_publisher.xml similarity index 100% rename from src/Symfony/Bundle/Resources/config/v3/doctrine_odm_mercure_publisher.xml rename to src/Symfony/Bundle/Resources/config/doctrine_odm_mercure_publisher.xml diff --git a/src/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Symfony/Bundle/Resources/config/doctrine_orm.xml index 2479d53ad20..ce2b5ec331a 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ b/src/Symfony/Bundle/Resources/config/doctrine_orm.xml @@ -20,6 +20,143 @@ + + + + + + %api_platform.collection.order_parameter_name% + + + %api_platform.collection.order_nulls_comparison% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %api_platform.collection.exists_parameter_name% + + + + + + + + + + %api_platform.eager_loading.max_joins% + %api_platform.eager_loading.force_eager% + %api_platform.eager_loading.fetch_partial% + + + + + + + + + + + + + + + + + + + %api_platform.eager_loading.force_eager% + + + + + + + + + + + + + + + + %api_platform.collection.order% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/v3/doctrine_orm_mercure_publisher.xml b/src/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml similarity index 100% rename from src/Symfony/Bundle/Resources/config/v3/doctrine_orm_mercure_publisher.xml rename to src/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml diff --git a/src/Symfony/Bundle/Resources/config/elasticsearch.xml b/src/Symfony/Bundle/Resources/config/elasticsearch.xml index 2bded5a314d..4c49b50809b 100644 --- a/src/Symfony/Bundle/Resources/config/elasticsearch.xml +++ b/src/Symfony/Bundle/Resources/config/elasticsearch.xml @@ -48,7 +48,7 @@ - + @@ -74,16 +74,82 @@ - + - - + + + + + + + + + + + + + + + + + + + %api_platform.collection.order% + + + + + + + + + + + + + + + + + + + + + + + + + + %api_platform.collection.order_parameter_name% + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/src/Symfony/Bundle/Resources/config/fos_user.xml b/src/Symfony/Bundle/Resources/config/fos_user.xml deleted file mode 100644 index 30c0141e64a..00000000000 --- a/src/Symfony/Bundle/Resources/config/fos_user.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/graphql.xml b/src/Symfony/Bundle/Resources/config/graphql.xml index e4781353ebf..34b50ac495a 100644 --- a/src/Symfony/Bundle/Resources/config/graphql.xml +++ b/src/Symfony/Bundle/Resources/config/graphql.xml @@ -91,6 +91,191 @@ + + + + + + + + + + %api_platform.graphql.nesting_separator% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %api_platform.graphql.nesting_separator% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + null + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %api_platform.exception_to_status% + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/hal.xml b/src/Symfony/Bundle/Resources/config/hal.xml index a4806bb5c22..d1afa9c33e3 100644 --- a/src/Symfony/Bundle/Resources/config/hal.xml +++ b/src/Symfony/Bundle/Resources/config/hal.xml @@ -5,6 +5,12 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + + + + + jsonhal @@ -36,10 +42,7 @@ - null - false - diff --git a/src/Symfony/Bundle/Resources/config/http_cache.xml b/src/Symfony/Bundle/Resources/config/http_cache.xml index b3f251ca124..77b0225af96 100644 --- a/src/Symfony/Bundle/Resources/config/http_cache.xml +++ b/src/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% - api_platform.metadata.resource.metadata_collection_factory.retro_compatible + diff --git a/src/Symfony/Bundle/Resources/config/hydra.xml b/src/Symfony/Bundle/Resources/config/hydra.xml index 6710e7b7eb4..0a5b331bf95 100644 --- a/src/Symfony/Bundle/Resources/config/hydra.xml +++ b/src/Symfony/Bundle/Resources/config/hydra.xml @@ -5,14 +5,15 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + + - null - null diff --git a/src/Symfony/Bundle/Resources/config/json_schema.xml b/src/Symfony/Bundle/Resources/config/json_schema.xml index da605582af7..864e12900f2 100644 --- a/src/Symfony/Bundle/Resources/config/json_schema.xml +++ b/src/Symfony/Bundle/Resources/config/json_schema.xml @@ -17,7 +17,7 @@ - + @@ -30,6 +30,7 @@ %api_platform.formats% + diff --git a/src/Symfony/Bundle/Resources/config/jsonapi.xml b/src/Symfony/Bundle/Resources/config/jsonapi.xml index 160cb912ad3..546c64b90f0 100644 --- a/src/Symfony/Bundle/Resources/config/jsonapi.xml +++ b/src/Symfony/Bundle/Resources/config/jsonapi.xml @@ -39,9 +39,9 @@ - + - + diff --git a/src/Symfony/Bundle/Resources/config/jsonld.xml b/src/Symfony/Bundle/Resources/config/jsonld.xml index a7437a579cb..abfae0e1213 100644 --- a/src/Symfony/Bundle/Resources/config/jsonld.xml +++ b/src/Symfony/Bundle/Resources/config/jsonld.xml @@ -7,7 +7,7 @@ - + @@ -16,7 +16,7 @@ - + @@ -26,7 +26,6 @@ - @@ -53,7 +52,7 @@ - + diff --git a/src/Symfony/Bundle/Resources/config/legacy/README b/src/Symfony/Bundle/Resources/config/legacy/README deleted file mode 100644 index b90488053fa..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/README +++ /dev/null @@ -1 +0,0 @@ -The `legacy` directory declares API Platform 2.x services. This directory will be removed in 3.0 but kept in 2.7. diff --git a/src/Symfony/Bundle/Resources/config/legacy/api.xml b/src/Symfony/Bundle/Resources/config/legacy/api.xml deleted file mode 100644 index cceb903baa5..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/api.xml +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - %api_platform.formats% - - - - - - - - - - - - %api_platform.formats% - %api_platform.resource_class_directories% - - %api_platform.graphql.enabled% - %api_platform.enable_entrypoint% - %api_platform.enable_docs% - %api_platform.graphql.graphiql.enabled% - %api_platform.graphql.graphql_playground.enabled% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.formats% - - - - - - - - - - - - - - - - - - - - - - - - null - - - - - - %api_platform.error_formats% - %api_platform.exception_to_status% - - - - - - api_platform.action.exception - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/backward_compatibility.xml b/src/Symfony/Bundle/Resources/config/legacy/backward_compatibility.xml deleted file mode 100644 index 00562814160..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/backward_compatibility.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/data_collector.xml b/src/Symfony/Bundle/Resources/config/legacy/data_collector.xml deleted file mode 100644 index 3e484801f45..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/data_collector.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/data_provider.xml b/src/Symfony/Bundle/Resources/config/legacy/data_provider.xml deleted file mode 100644 index 78458553729..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/data_provider.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - %api_platform.collection.pagination% - %api_platform.graphql.collection.pagination% - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/debug.xml b/src/Symfony/Bundle/Resources/config/legacy/debug.xml deleted file mode 100644 index 65045e79405..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/debug.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/doctrine_odm.xml b/src/Symfony/Bundle/Resources/config/legacy/doctrine_odm.xml deleted file mode 100644 index d8d4bbbe452..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/doctrine_odm.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.exists_parameter_name% - - - - - - - - - - - - - - %api_platform.collection.order_parameter_name% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.order% - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/doctrine_odm_mercure_publisher.xml b/src/Symfony/Bundle/Resources/config/legacy/doctrine_odm_mercure_publisher.xml deleted file mode 100644 index 8d7c9672b12..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/doctrine_odm_mercure_publisher.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - %api_platform.formats% - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/doctrine_orm.xml b/src/Symfony/Bundle/Resources/config/legacy/doctrine_orm.xml deleted file mode 100644 index 60ca6ed0622..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/doctrine_orm.xml +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - null - - - - - - - - - - - - - null - - - - - - - - - - - null - %api_platform.collection.order_parameter_name% - - - - - - - - null - - - - - - - - null - - - - - - - - null - - - - - - - - null - - - - - - - - null - - %api_platform.collection.exists_parameter_name% - - - - - - - - - - - %api_platform.eager_loading.max_joins% - %api_platform.eager_loading.force_eager% - null - null - %api_platform.eager_loading.fetch_partial% - - - - - - - - - - - - - - - - - - - - - %api_platform.eager_loading.force_eager% - - - - - - - - - - - - - - - - - %api_platform.collection.order% - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/doctrine_orm_mercure_publisher.xml b/src/Symfony/Bundle/Resources/config/legacy/doctrine_orm_mercure_publisher.xml deleted file mode 100644 index fbd3d138618..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/doctrine_orm_mercure_publisher.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - %api_platform.formats% - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/elasticsearch.xml b/src/Symfony/Bundle/Resources/config/legacy/elasticsearch.xml deleted file mode 100644 index a68c67a78bd..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/elasticsearch.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.order% - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.order_parameter_name% - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/graphql.xml b/src/Symfony/Bundle/Resources/config/legacy/graphql.xml deleted file mode 100644 index 4403fc2f7d8..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/graphql.xml +++ /dev/null @@ -1,283 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.graphql.nesting_separator% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.graphql.nesting_separator% - - - - - - - - - - - - - - - - - - - - - - - - - - - %kernel.debug% - %api_platform.graphql.graphiql.enabled% - %api_platform.graphql.graphql_playground.enabled% - %api_platform.graphql.default_ide% - - - - - - %api_platform.graphql.graphiql.enabled% - %api_platform.title% - %api_platform.asset_package% - - - - - - %api_platform.graphql.graphql_playground.enabled% - %api_platform.title% - %api_platform.asset_package% - - - - - - - - - - - - - - - %api_platform.allow_plain_identifiers% - null - - - - - - - - - - - - - - - - - - - - - - %api_platform.exception_to_status% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/graphql_mercure.xml b/src/Symfony/Bundle/Resources/config/legacy/graphql_mercure.xml deleted file mode 100644 index cc7249aed7a..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/graphql_mercure.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/hal.xml b/src/Symfony/Bundle/Resources/config/legacy/hal.xml deleted file mode 100644 index dab3af931ca..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/hal.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/hydra.xml b/src/Symfony/Bundle/Resources/config/legacy/hydra.xml deleted file mode 100644 index 14e86ebf8da..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/hydra.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/identifiers.xml b/src/Symfony/Bundle/Resources/config/legacy/identifiers.xml deleted file mode 100644 index 80eed2426af..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/identifiers.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - %api_platform.metadata_backward_compatibility_layer% - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/json_schema.xml b/src/Symfony/Bundle/Resources/config/legacy/json_schema.xml deleted file mode 100644 index 90943ab4059..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/json_schema.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/metadata.xml b/src/Symfony/Bundle/Resources/config/legacy/metadata.xml deleted file mode 100644 index 94f4449f4d0..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/metadata.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - %api_platform.patch_formats% - - - - - %api_platform.formats% - %api_platform.patch_formats% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/metadata_annotation.xml b/src/Symfony/Bundle/Resources/config/legacy/metadata_annotation.xml deleted file mode 100644 index 880407eaf77..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/metadata_annotation.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - %api_platform.resource_class_directories% - - - - - - - %api_platform.defaults% - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/metadata_backward_compatibility.xml b/src/Symfony/Bundle/Resources/config/legacy/metadata_backward_compatibility.xml deleted file mode 100644 index 36c90c2c795..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/metadata_backward_compatibility.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/metadata_php_doc.xml b/src/Symfony/Bundle/Resources/config/legacy/metadata_php_doc.xml deleted file mode 100644 index 5865e92dd93..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/metadata_php_doc.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/metadata_yaml.xml b/src/Symfony/Bundle/Resources/config/legacy/metadata_yaml.xml deleted file mode 100644 index cfb76ecbe08..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/metadata_yaml.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - %api_platform.defaults% - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/metadata_yaml_backward_compatibility.xml b/src/Symfony/Bundle/Resources/config/legacy/metadata_yaml_backward_compatibility.xml deleted file mode 100644 index 0dd363e5cfe..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/metadata_yaml_backward_compatibility.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/openapi.xml b/src/Symfony/Bundle/Resources/config/legacy/openapi.xml deleted file mode 100644 index 68b0df9f725..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/openapi.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - %api_platform.formats% - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/swagger.xml b/src/Symfony/Bundle/Resources/config/legacy/swagger.xml deleted file mode 100644 index 3bd9b39e36d..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/swagger.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - null - - null - %api_platform.oauth.enabled% - %api_platform.oauth.type% - %api_platform.oauth.flow% - %api_platform.oauth.tokenUrl% - %api_platform.oauth.authorizationUrl% - %api_platform.oauth.scopes% - %api_platform.swagger.api_keys% - - %api_platform.collection.pagination.enabled% - %api_platform.collection.pagination.page_parameter_name% - %api_platform.collection.pagination.client_items_per_page% - %api_platform.collection.pagination.items_per_page_parameter_name% - %api_platform.formats% - %api_platform.collection.pagination.client_enabled% - %api_platform.collection.pagination.enabled_parameter_name% - - %api_platform.swagger.versions% - - null - %api_platform.metadata_backward_compatibility_layer% - - - - - - - - - - - - %api_platform.title% - %api_platform.description% - %api_platform.version% - null - %api_platform.swagger.versions% - %api_platform.metadata_backward_compatibility_layer% - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/swagger_ui.xml b/src/Symfony/Bundle/Resources/config/legacy/swagger_ui.xml deleted file mode 100644 index c9739cc0f4c..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/swagger_ui.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - %api_platform.formats% - %api_platform.oauth.clientId% - %api_platform.oauth.clientSecret% - %api_platform.oauth.pkce% - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/upgrade.xml b/src/Symfony/Bundle/Resources/config/legacy/upgrade.xml deleted file mode 100644 index 33f0b6094fb..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/upgrade.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/legacy/validator.xml b/src/Symfony/Bundle/Resources/config/legacy/validator.xml deleted file mode 100644 index 9cf8d42f5e2..00000000000 --- a/src/Symfony/Bundle/Resources/config/legacy/validator.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.validator.query_parameter_validation% - - - - - diff --git a/src/Symfony/Bundle/Resources/config/maker.xml b/src/Symfony/Bundle/Resources/config/maker.xml index f829cf3af09..5b6be1706b0 100644 --- a/src/Symfony/Bundle/Resources/config/maker.xml +++ b/src/Symfony/Bundle/Resources/config/maker.xml @@ -5,15 +5,15 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - - - - - - - + + + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/mercure.xml b/src/Symfony/Bundle/Resources/config/mercure.xml index 4f1f652cec9..5dde0e384bb 100644 --- a/src/Symfony/Bundle/Resources/config/mercure.xml +++ b/src/Symfony/Bundle/Resources/config/mercure.xml @@ -8,8 +8,8 @@ - + diff --git a/src/Symfony/Bundle/Resources/config/messenger.xml b/src/Symfony/Bundle/Resources/config/messenger.xml index 69eacad40db..8e80e617450 100644 --- a/src/Symfony/Bundle/Resources/config/messenger.xml +++ b/src/Symfony/Bundle/Resources/config/messenger.xml @@ -7,11 +7,8 @@ - - - - - + + @@ -20,11 +17,6 @@ - - - - - diff --git a/src/Symfony/Bundle/Resources/config/metadata/property.xml b/src/Symfony/Bundle/Resources/config/metadata/property.xml index 82fe8b3c984..05498db2f5f 100644 --- a/src/Symfony/Bundle/Resources/config/metadata/property.xml +++ b/src/Symfony/Bundle/Resources/config/metadata/property.xml @@ -5,6 +5,8 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + @@ -52,11 +54,6 @@ - - - - - diff --git a/src/Symfony/Bundle/Resources/config/metadata/resource.xml b/src/Symfony/Bundle/Resources/config/metadata/resource.xml index dcefe35db1d..7b36b96de96 100644 --- a/src/Symfony/Bundle/Resources/config/metadata/resource.xml +++ b/src/Symfony/Bundle/Resources/config/metadata/resource.xml @@ -51,7 +51,6 @@ - diff --git a/src/Symfony/Bundle/Resources/config/metadata/resource_name.xml b/src/Symfony/Bundle/Resources/config/metadata/resource_name.xml index 2613336d50d..df4e7f3a425 100644 --- a/src/Symfony/Bundle/Resources/config/metadata/resource_name.xml +++ b/src/Symfony/Bundle/Resources/config/metadata/resource_name.xml @@ -23,5 +23,9 @@ + + %api_platform.resource_class_directories% + + diff --git a/src/Symfony/Bundle/Resources/config/nelmio_api_doc.xml b/src/Symfony/Bundle/Resources/config/nelmio_api_doc.xml deleted file mode 100644 index 9bfd8a8f6d6..00000000000 --- a/src/Symfony/Bundle/Resources/config/nelmio_api_doc.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/openapi.xml b/src/Symfony/Bundle/Resources/config/openapi.xml index b35d80da872..c54481d2095 100644 --- a/src/Symfony/Bundle/Resources/config/openapi.xml +++ b/src/Symfony/Bundle/Resources/config/openapi.xml @@ -61,6 +61,22 @@ + + + + + + + + + + %api_platform.formats% + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/data_provider.xml b/src/Symfony/Bundle/Resources/config/state.xml similarity index 66% rename from src/Symfony/Bundle/Resources/config/data_provider.xml rename to src/Symfony/Bundle/Resources/config/state.xml index edbe31d3d2e..123c296ef4e 100644 --- a/src/Symfony/Bundle/Resources/config/data_provider.xml +++ b/src/Symfony/Bundle/Resources/config/state.xml @@ -3,15 +3,29 @@ - + + + + + + + %api_platform.collection.pagination% + %api_platform.graphql.collection.pagination% + + + + + + + + - %api_platform.collection.pagination.enabled% %api_platform.collection.pagination.page_parameter_name% @@ -27,5 +41,4 @@ - diff --git a/src/Symfony/Bundle/Resources/config/swagger_ui.xml b/src/Symfony/Bundle/Resources/config/swagger_ui.xml index dd7c83de602..e60682f7fef 100644 --- a/src/Symfony/Bundle/Resources/config/swagger_ui.xml +++ b/src/Symfony/Bundle/Resources/config/swagger_ui.xml @@ -19,34 +19,17 @@ %api_platform.swagger_ui.extra_configuration% - - - - + + - %api_platform.title% - %api_platform.description% - %api_platform.version% + + + + %api_platform.formats% - %api_platform.oauth.enabled% %api_platform.oauth.clientId% %api_platform.oauth.clientSecret% - %api_platform.oauth.type% - %api_platform.oauth.flow% - %api_platform.oauth.tokenUrl% - %api_platform.oauth.authorizationUrl% - %api_platform.oauth.scopes% - %api_platform.show_webby% - %api_platform.enable_swagger_ui% - %api_platform.enable_re_doc% - %api_platform.graphql.enabled% - %api_platform.graphql.graphiql.enabled% - %api_platform.graphql.graphql_playground.enabled% - %api_platform.swagger.versions% - - %api_platform.asset_package% - %api_platform.swagger_ui.extra_configuration% %api_platform.oauth.pkce% diff --git a/src/Symfony/Bundle/Resources/config/symfony.xml b/src/Symfony/Bundle/Resources/config/symfony/events.xml similarity index 95% rename from src/Symfony/Bundle/Resources/config/symfony.xml rename to src/Symfony/Bundle/Resources/config/symfony/events.xml index 871e3810c43..71e30db5292 100644 --- a/src/Symfony/Bundle/Resources/config/symfony.xml +++ b/src/Symfony/Bundle/Resources/config/symfony/events.xml @@ -28,8 +28,8 @@ - + @@ -47,7 +47,6 @@ - @@ -80,9 +79,7 @@ api_platform.cache.metadata.property - api_platform.cache.metadata.property.legacy api_platform.cache.metadata.resource - api_platform.cache.metadata.resource.legacy api_platform.cache.metadata.resource_collection api_platform.cache.route_name_resolver api_platform.cache.identifiers_extractor diff --git a/src/Symfony/Bundle/Resources/config/symfony_uid.xml b/src/Symfony/Bundle/Resources/config/symfony/uid.xml similarity index 100% rename from src/Symfony/Bundle/Resources/config/symfony_uid.xml rename to src/Symfony/Bundle/Resources/config/symfony/uid.xml diff --git a/src/Symfony/Bundle/Resources/config/symfony/validator.xml b/src/Symfony/Bundle/Resources/config/symfony/validator.xml index 5dfeaa54e34..f5ec4ddbc8a 100644 --- a/src/Symfony/Bundle/Resources/config/symfony/validator.xml +++ b/src/Symfony/Bundle/Resources/config/symfony/validator.xml @@ -23,8 +23,8 @@ - + %api_platform.validator.query_parameter_validation% diff --git a/src/Symfony/Bundle/Resources/config/v3/api.xml b/src/Symfony/Bundle/Resources/config/v3/api.xml deleted file mode 100644 index d5708ddcf87..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/api.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - %api_platform.formats% - %api_platform.resource_class_directories% - null - %api_platform.graphql.enabled% - %api_platform.enable_entrypoint% - %api_platform.enable_docs% - %api_platform.graphql.graphiql.enabled% - %api_platform.graphql.graphql_playground.enabled% - null - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/backward_compatibility.xml b/src/Symfony/Bundle/Resources/config/v3/backward_compatibility.xml deleted file mode 100644 index c242414a4aa..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/backward_compatibility.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - %api_platform.defaults% - - - - - - - - - - - - - - - - - - - - %api_platform.metadata_backward_compatibility_layer% - - - - - - - - - - - - null - %api_platform.metadata_backward_compatibility_layer% - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/debug.xml b/src/Symfony/Bundle/Resources/config/v3/debug.xml deleted file mode 100644 index 412f179d7d8..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/debug.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/doctrine_odm.xml b/src/Symfony/Bundle/Resources/config/v3/doctrine_odm.xml deleted file mode 100644 index 5986aeeffea..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/doctrine_odm.xml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.exists_parameter_name% - - - - - - - - - - - - - - %api_platform.collection.order_parameter_name% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.order% - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/doctrine_orm.xml b/src/Symfony/Bundle/Resources/config/v3/doctrine_orm.xml deleted file mode 100644 index fd47b013977..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/doctrine_orm.xml +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.order_parameter_name% - - - %api_platform.collection.order_nulls_comparison% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.exists_parameter_name% - - - - - - - - - - - %api_platform.eager_loading.max_joins% - %api_platform.eager_loading.force_eager% - %api_platform.eager_loading.fetch_partial% - - - - - - - - - - - - - - - - - - - %api_platform.eager_loading.force_eager% - - - - - - - - - - - - - - - - %api_platform.collection.order% - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/elasticsearch.xml b/src/Symfony/Bundle/Resources/config/v3/elasticsearch.xml deleted file mode 100644 index 72b02844000..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/elasticsearch.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.order% - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.collection.order_parameter_name% - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/graphql.xml b/src/Symfony/Bundle/Resources/config/v3/graphql.xml deleted file mode 100644 index 180cba84b0e..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/graphql.xml +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - - - - - %api_platform.graphql.nesting_separator% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.graphql.nesting_separator% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.allow_plain_identifiers% - null - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.exception_to_status% - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/graphql_mercure.xml b/src/Symfony/Bundle/Resources/config/v3/graphql_mercure.xml deleted file mode 100644 index f682291900e..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/graphql_mercure.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/hal.xml b/src/Symfony/Bundle/Resources/config/v3/hal.xml deleted file mode 100644 index 6d5bc64890a..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/hal.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/hydra.xml b/src/Symfony/Bundle/Resources/config/v3/hydra.xml deleted file mode 100644 index 52c1128348d..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/hydra.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/json_schema.xml b/src/Symfony/Bundle/Resources/config/v3/json_schema.xml deleted file mode 100644 index 21f1c5334fd..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/json_schema.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/messenger.xml b/src/Symfony/Bundle/Resources/config/v3/messenger.xml deleted file mode 100644 index de581cca87e..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/messenger.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/metadata.xml b/src/Symfony/Bundle/Resources/config/v3/metadata.xml deleted file mode 100644 index 76885ef83ea..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/metadata.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - %api_platform.resource_class_directories% - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/openapi.xml b/src/Symfony/Bundle/Resources/config/v3/openapi.xml deleted file mode 100644 index 819742bf4d8..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/openapi.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - %api_platform.formats% - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/state.xml b/src/Symfony/Bundle/Resources/config/v3/state.xml deleted file mode 100644 index 6c47d09af37..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/state.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - %api_platform.collection.pagination% - %api_platform.graphql.collection.pagination% - - - - - - - - - - diff --git a/src/Symfony/Bundle/Resources/config/v3/swagger_ui.xml b/src/Symfony/Bundle/Resources/config/v3/swagger_ui.xml deleted file mode 100644 index 228cf725bdc..00000000000 --- a/src/Symfony/Bundle/Resources/config/v3/swagger_ui.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - %api_platform.formats% - %api_platform.oauth.clientId% - %api_platform.oauth.clientSecret% - %api_platform.oauth.pkce% - - - diff --git a/src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig b/src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig index c846f64526e..a27ec7dba40 100644 --- a/src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig +++ b/src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig @@ -73,8 +73,7 @@ {% set active_ui = app.request.get('ui', 'swagger_ui') %} {% if swaggerUiEnabled and active_ui != 'swagger_ui' %}Swagger UI{% endif %} {% if reDocEnabled and active_ui != 're_doc' %}ReDoc{% endif %} - {# FIXME: Typo in graphql => graphQl in SwaggerUiAction #} - {% if not graphqlEnabled %}GraphiQL{% endif %} + {% if not graphQlEnabled %}GraphiQL{% endif %} {% if graphiQlEnabled %}GraphiQL{% endif %} {% if graphQlPlaygroundEnabled %}GraphQL Playground{% endif %} diff --git a/src/Symfony/Bundle/SwaggerUi/SwaggerUiAction.php b/src/Symfony/Bundle/SwaggerUi/SwaggerUiAction.php index ca84d92ee1a..b3e4d30780b 100644 --- a/src/Symfony/Bundle/SwaggerUi/SwaggerUiAction.php +++ b/src/Symfony/Bundle/SwaggerUi/SwaggerUiAction.php @@ -13,10 +13,11 @@ namespace ApiPlatform\Symfony\Bundle\SwaggerUi; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Exception\RuntimeException; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface; use ApiPlatform\OpenApi\Options; +use ApiPlatform\OpenApi\Serializer\NormalizeOperationNameTrait; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -30,6 +31,8 @@ */ final class SwaggerUiAction { + use NormalizeOperationNameTrait; + private $twig; private $urlGenerator; private $normalizer; @@ -42,7 +45,7 @@ final class SwaggerUiAction private $oauthClientSecret; private $oauthPkce; - public function __construct($resourceMetadataFactory, ?TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, NormalizerInterface $normalizer, OpenApiFactoryInterface $openApiFactory, Options $openApiOptions, SwaggerUiContext $swaggerUiContext, array $formats = [], string $oauthClientId = null, string $oauthClientSecret = null, bool $oauthPkce = false) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, ?TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, NormalizerInterface $normalizer, OpenApiFactoryInterface $openApiFactory, Options $openApiOptions, SwaggerUiContext $swaggerUiContext, array $formats = [], string $oauthClientId = null, string $oauthClientSecret = null, bool $oauthPkce = false) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->twig = $twig; @@ -72,8 +75,7 @@ public function __invoke(Request $request) 'showWebby' => $this->swaggerUiContext->isWebbyShown(), 'swaggerUiEnabled' => $this->swaggerUiContext->isSwaggerUiEnabled(), 'reDocEnabled' => $this->swaggerUiContext->isRedocEnabled(), - // FIXME: typo graphql => graphQl - 'graphqlEnabled' => $this->swaggerUiContext->isGraphQlEnabled(), + 'graphQlEnabled' => $this->swaggerUiContext->isGraphQlEnabled(), 'graphiQlEnabled' => $this->swaggerUiContext->isGraphiQlEnabled(), 'graphQlPlaygroundEnabled' => $this->swaggerUiContext->isGraphQlPlaygroundEnabled(), 'assetPackage' => $this->swaggerUiContext->getAssetPackage(), @@ -100,21 +102,10 @@ public function __invoke(Request $request) $swaggerData['id'] = $request->attributes->get('id'); $swaggerData['queryParameters'] = $request->query->all(); - $metadata = $this->resourceMetadataFactory->create($resourceClass); + $metadata = $this->resourceMetadataFactory->create($resourceClass)->getOperation($request->attributes->get('_api_operation_name')); - if ($metadata instanceof ResourceMetadata) { - $swaggerData['shortName'] = $metadata->getShortName(); - if (null !== $collectionOperationName = $request->attributes->get('_api_collection_operation_name')) { - $swaggerData['operationId'] = sprintf('%s%sCollection', $collectionOperationName, ucfirst($swaggerData['shortName'])); - } elseif (null !== $itemOperationName = $request->attributes->get('_api_item_operation_name')) { - $swaggerData['operationId'] = sprintf('%s%sItem', $itemOperationName, ucfirst($swaggerData['shortName'])); - } elseif (null !== $subresourceOperationContext = $request->attributes->get('_api_subresource_context')) { - $swaggerData['operationId'] = $subresourceOperationContext['operationId']; - } - } else { - $swaggerData['shortName'] = $metadata[0]->getShortName(); - $swaggerData['operationId'] = $request->attributes->get('_api_operation_name'); - } + $swaggerData['shortName'] = $metadata->getShortName(); + $swaggerData['operationId'] = $this->normalizeOperationName($metadata->getName()); [$swaggerData['path'], $swaggerData['method']] = $this->getPathAndMethod($swaggerData); } @@ -135,5 +126,3 @@ private function getPathAndMethod(array $swaggerData): array throw new RuntimeException(sprintf('The operation "%s" cannot be found in the Swagger specification.', $swaggerData['operationId'])); } } - -class_alias(SwaggerUiAction::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\SwaggerUi\SwaggerUiAction::class); diff --git a/src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php b/src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php index 5b113ed8b22..f8e4cd347af 100644 --- a/src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php +++ b/src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php @@ -76,5 +76,3 @@ public function getExtraConfiguration(): array return $this->extraConfiguration; } } - -class_alias(SwaggerUiContext::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\SwaggerUi\SwaggerUiContext::class); diff --git a/src/Symfony/Bundle/Test/ApiTestAssertionsTrait.php b/src/Symfony/Bundle/Test/ApiTestAssertionsTrait.php index c141c828617..5fadbe45982 100644 --- a/src/Symfony/Bundle/Test/ApiTestAssertionsTrait.php +++ b/src/Symfony/Bundle/Test/ApiTestAssertionsTrait.php @@ -13,12 +13,11 @@ namespace ApiPlatform\Symfony\Bundle\Test; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface as LegacySchemaFactoryInterface; use ApiPlatform\JsonSchema\Schema; use ApiPlatform\JsonSchema\SchemaFactoryInterface; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Symfony\Bundle\Test\Constraint\ArraySubset; use ApiPlatform\Symfony\Bundle\Test\Constraint\MatchesJsonSchema; use PHPUnit\Framework\ExpectationFailedException; @@ -117,13 +116,14 @@ public static function assertMatchesResourceCollectionJsonSchema(string $resourc { $schemaFactory = self::getSchemaFactory(); - if ($schemaFactory instanceof LegacySchemaFactoryInterface) { - $schema = $schemaFactory->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, OperationType::COLLECTION, $operationName, null); + if ($resourceMetadataFactoryCollection = self::getResourceMetadataCollectionFactory()) { + $operation = $resourceMetadataFactoryCollection->create($resourceClass)->getOperation($operationName, true); } else { $operation = $operationName ? (new GetCollection())->withName($operationName) : new GetCollection(); - $schema = $schemaFactory->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, $operation, null); } + $schema = $schemaFactory->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, $operation, null); + static::assertMatchesJsonSchema($schema->getArrayCopy()); } @@ -131,13 +131,14 @@ public static function assertMatchesResourceItemJsonSchema(string $resourceClass { $schemaFactory = self::getSchemaFactory(); - if ($schemaFactory instanceof LegacySchemaFactoryInterface) { - $schema = $schemaFactory->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, OperationType::ITEM, $operationName, null); + if ($resourceMetadataFactoryCollection = self::getResourceMetadataCollectionFactory()) { + $operation = $resourceMetadataFactoryCollection->create($resourceClass)->getOperation($operationName); } else { $operation = $operationName ? (new Get())->withName($operationName) : new Get(); - $schema = $schemaFactory->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, $operation, null); } + $schema = $schemaFactory->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, $operation, null); + static::assertMatchesJsonSchema($schema->getArrayCopy()); } @@ -165,15 +166,12 @@ private static function getHttpResponse(): ResponseInterface return $response; } - /** - * @return SchemaFactoryInterface|LegacySchemaFactoryInterface - */ - private static function getSchemaFactory() + private static function getSchemaFactory(): SchemaFactoryInterface { $container = method_exists(static::class, 'getContainer') ? static::getContainer() : static::$container; // @phpstan-ignore-line try { - /** @var SchemaFactoryInterface|LegacySchemaFactoryInterface $schemaFactory */ + /** @var SchemaFactoryInterface $schemaFactory */ $schemaFactory = $container->get('api_platform.json_schema.schema_factory'); } catch (ServiceNotFoundException $e) { throw new \LogicException('You cannot use the resource JSON Schema assertions if the "api_platform.swagger.versions" config is null or empty.'); @@ -181,6 +179,17 @@ private static function getSchemaFactory() return $schemaFactory; } -} -class_alias(ApiTestAssertionsTrait::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestAssertionsTrait::class); + private static function getResourceMetadataCollectionFactory(): ?ResourceMetadataCollectionFactoryInterface + { + $container = method_exists(static::class, 'getContainer') ? static::getContainer() : static::$container; // @phpstan-ignore-line + + try { + $resourceMetadataFactoryCollection = $container->get('api_platform.metadata.resource.metadata_collection_factory'); + } catch (ServiceNotFoundException $e) { + return null; + } + + return $resourceMetadataFactoryCollection; + } +} diff --git a/src/Symfony/Bundle/Test/ApiTestCase.php b/src/Symfony/Bundle/Test/ApiTestCase.php index 94b121f285e..ae251ecdc13 100644 --- a/src/Symfony/Bundle/Test/ApiTestCase.php +++ b/src/Symfony/Bundle/Test/ApiTestCase.php @@ -105,5 +105,3 @@ protected function findIriBy(string $resourceClass, array $criteria): ?string return $iriConverter instanceof LegacyIriConverterInterface ? $iriConverter->getIriFromItem($item) : $iriConverter->getIriFromResource($item); } } - -class_alias(ApiTestCase::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase::class); diff --git a/src/Symfony/Bundle/Test/Client.php b/src/Symfony/Bundle/Test/Client.php index f7b45308104..aaae8e5c3f3 100644 --- a/src/Symfony/Bundle/Test/Client.php +++ b/src/Symfony/Bundle/Test/Client.php @@ -256,5 +256,3 @@ public function loginUser(UserInterface $user, string $firewallContext = 'main') return $this; } } - -class_alias(Client::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client::class); diff --git a/src/Symfony/Bundle/Test/Constraint/ArraySubset.php b/src/Symfony/Bundle/Test/Constraint/ArraySubset.php index 1eae632d5d6..1078a7a4135 100644 --- a/src/Symfony/Bundle/Test/Constraint/ArraySubset.php +++ b/src/Symfony/Bundle/Test/Constraint/ArraySubset.php @@ -13,17 +13,22 @@ namespace ApiPlatform\Symfony\Bundle\Test\Constraint; -use PHPUnit\Runner\Version; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; -use SebastianBergmann\Comparator\ComparisonFailure as LegacyComparisonFailure; +use PHPUnit\Framework\Constraint\Constraint; -if (!class_exists(ComparisonFailure::class) && class_exists(LegacyComparisonFailure::class)) { - class_alias(LegacyComparisonFailure::class, 'PHPUnit\SebastianBergmann\Comparator\ComparisonFailure'); -} +/** + * Is used for phpunit >= 9. + * + * @internal + */ +final class ArraySubset extends Constraint +{ + use ArraySubsetTrait; -// Aliases as string to avoid loading the class -if (\PHP_VERSION_ID >= 80000 || (float) Version::series() >= 9) { - class_alias('ApiPlatform\Symfony\Bundle\Test\Constraint\ArraySubsetV9', 'ApiPlatform\Symfony\Bundle\Test\Constraint\ArraySubset'); -} else { - class_alias('ApiPlatform\Symfony\Bundle\Test\Constraint\ArraySubsetLegacy', 'ApiPlatform\Symfony\Bundle\Test\Constraint\ArraySubset'); + /** + * {@inheritdoc} + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + return $this->_evaluate($other, $description, $returnResult); + } } diff --git a/src/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php b/src/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php deleted file mode 100644 index 139baae9a5f..00000000000 --- a/src/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php +++ /dev/null @@ -1,34 +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\Symfony\Bundle\Test\Constraint; - -use PHPUnit\Framework\Constraint\Constraint; - -/** - * Is used for phpunit < 8. - * - * @internal - */ -final class ArraySubsetLegacy extends Constraint -{ - use ArraySubsetTrait; - - /** - * {@inheritdoc} - */ - public function evaluate($other, $description = '', $returnResult = false): ?bool - { - return $this->_evaluate($other, $description, $returnResult); - } -} diff --git a/src/Symfony/Bundle/Test/Constraint/ArraySubsetTrait.php b/src/Symfony/Bundle/Test/Constraint/ArraySubsetTrait.php index 08c38b2a81f..4f371f3a1b4 100644 --- a/src/Symfony/Bundle/Test/Constraint/ArraySubsetTrait.php +++ b/src/Symfony/Bundle/Test/Constraint/ArraySubsetTrait.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Symfony\Bundle\Test\Constraint; -use PHPUnit\SebastianBergmann\Comparator\ComparisonFailure; +use SebastianBergmann\Comparator\ComparisonFailure; /** * Constraint that asserts that the array it is evaluated for has a specified subset. diff --git a/src/Symfony/Bundle/Test/Constraint/ArraySubsetV9.php b/src/Symfony/Bundle/Test/Constraint/ArraySubsetV9.php deleted file mode 100644 index 195657f4dd9..00000000000 --- a/src/Symfony/Bundle/Test/Constraint/ArraySubsetV9.php +++ /dev/null @@ -1,34 +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\Symfony\Bundle\Test\Constraint; - -use PHPUnit\Framework\Constraint\Constraint; - -/** - * Is used for phpunit >= 9. - * - * @internal - */ -final class ArraySubsetV9 extends Constraint -{ - use ArraySubsetTrait; - - /** - * {@inheritdoc} - */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool - { - return $this->_evaluate($other, $description, $returnResult); - } -} diff --git a/src/Symfony/Bundle/Test/Response.php b/src/Symfony/Bundle/Test/Response.php index e96ee96c100..4f06632ce64 100644 --- a/src/Symfony/Bundle/Test/Response.php +++ b/src/Symfony/Bundle/Test/Response.php @@ -180,5 +180,3 @@ public function cancel(): void $this->info['error'] = 'Response has been canceled.'; } } - -class_alias(Response::class, \ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Response::class); diff --git a/src/Symfony/EventListener/AddFormatListener.php b/src/Symfony/EventListener/AddFormatListener.php index b85c1f1cf29..26775e77a5d 100644 --- a/src/Symfony/EventListener/AddFormatListener.php +++ b/src/Symfony/EventListener/AddFormatListener.php @@ -14,8 +14,6 @@ namespace ApiPlatform\Symfony\EventListener; use ApiPlatform\Api\FormatMatcher; -use ApiPlatform\Core\Api\FormatsProviderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; @@ -35,35 +33,13 @@ final class AddFormatListener use OperationRequestInitiatorTrait; private $negotiator; - private $resourceMetadataFactory; private $formats = []; - private $formatsProvider; - private $formatMatcher; - /** - * @param ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface|FormatsProviderInterface|array $resourceMetadataFactory - */ - public function __construct(Negotiator $negotiator, $resourceMetadataFactory, array $formats = []) + public function __construct(Negotiator $negotiator, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, array $formats = []) { - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && !$resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - trigger_deprecation('api-plaform/core', '2.5', sprintf('Passing an array or an instance of "%s" as 2nd parameter of the constructor of "%s" is deprecated since API Platform 2.5, pass an instance of "%s" instead', FormatsProviderInterface::class, __CLASS__, ResourceMetadataFactoryInterface::class)); - } - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && $resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - $this->negotiator = $negotiator; - $this->resourceMetadataFactory = $resourceMetadataFactory instanceof ResourceMetadataFactoryInterface ? $resourceMetadataFactory : null; $this->formats = $formats; - - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface ? $resourceMetadataFactory : null; - - if (\is_array($resourceMetadataFactory)) { - $this->formats = $resourceMetadataFactory; - } elseif ($resourceMetadataFactory instanceof FormatsProviderInterface) { - $this->formatsProvider = $resourceMetadataFactory; - } + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } /** @@ -86,21 +62,7 @@ public function onKernelRequest(RequestEvent $event): void } $attributes = RequestAttributesExtractor::extractAttributes($request); - $formats = $this->formats; - - // BC check to be removed in 3.0 - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && $attributes) { - // TODO: Subresource operation metadata aren't available by default, for now we have to fallback on default formats. - // TODO: A better approach would be to always populate the subresource operation array. - $formats = $this - ->resourceMetadataFactory - ->create($attributes['resource_class']) - ->getOperationAttribute($attributes, 'output_formats', $this->formats, true); - } elseif ($this->formatsProvider instanceof FormatsProviderInterface) { - $formats = $this->formatsProvider->getFormatsFromAttributes($attributes); - } elseif ($operation && $operation->getOutputFormats()) { - $formats = $operation->getOutputFormats(); - } + $formats = $operation?->getOutputFormats() ?? $this->formats; $this->addRequestFormats($request, $formats); $this->formatMatcher = new FormatMatcher($formats); @@ -188,5 +150,3 @@ private function getNotAcceptableHttpException(string $accept, array $mimeTypes) )); } } - -class_alias(AddFormatListener::class, \ApiPlatform\Core\EventListener\AddFormatListener::class); diff --git a/src/Symfony/EventListener/AddLinkHeaderListener.php b/src/Symfony/EventListener/AddLinkHeaderListener.php index 88f16c383ed..6083522c956 100644 --- a/src/Symfony/EventListener/AddLinkHeaderListener.php +++ b/src/Symfony/EventListener/AddLinkHeaderListener.php @@ -13,12 +13,10 @@ namespace ApiPlatform\Symfony\EventListener; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\CorsTrait; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; -use Fig\Link\GenericLinkProvider; use Fig\Link\Link; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\Mercure\Discovery; @@ -33,28 +31,12 @@ final class AddLinkHeaderListener use CorsTrait; use OperationRequestInitiatorTrait; - /** - * @var ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface - */ - private $resourceMetadataFactory; - private $discovery; + private Discovery $discovery; - /** - * @param Discovery|string $discovery - * @param mixed $resourceMetadataFactory - */ - public function __construct($resourceMetadataFactory, $discovery) + public function __construct(Discovery $discovery, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null) { - $this->resourceMetadataFactory = $resourceMetadataFactory; - if ($resourceMetadataFactory && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - - if ($resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; - } - $this->discovery = $discovery; + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } /** @@ -63,10 +45,6 @@ public function __construct($resourceMetadataFactory, $discovery) public function onKernelResponse(ResponseEvent $event): void { $request = $event->getRequest(); - if ($this->isPreflightRequest($request)) { - return; - } - $operation = $this->initializeOperation($request); if ( @@ -76,33 +54,13 @@ public function onKernelResponse(ResponseEvent $event): void return; } - $mercure = $operation ? $operation->getMercure() : ($attributes['mercure'] ?? false); - // TODO: remove in 3.0 - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $mercure = $this->resourceMetadataFactory->create($resourceClass)->getAttribute('mercure', false); - } + $mercure = $operation?->getMercure() ?? ($attributes['mercure'] ?? false); if (!$mercure) { return; } - if (!$this->discovery instanceof Discovery) { - $link = new Link('mercure', $this->discovery); - if (null === $linkProvider = $request->attributes->get('_links')) { - $request->attributes->set('_links', new GenericLinkProvider([$link])); - - return; - } - - $request->attributes->set('_links', $linkProvider->withLink($link)); - - return; - } - $hub = \is_array($mercure) ? ($mercure['hub'] ?? null) : null; - $this->discovery->addLink($request, $hub); } } - -class_alias(AddLinkHeaderListener::class, \ApiPlatform\Core\Mercure\EventListener\AddLinkHeaderListener::class); diff --git a/src/Symfony/EventListener/DenyAccessListener.php b/src/Symfony/EventListener/DenyAccessListener.php index 9852c172947..7668b5e0a3e 100644 --- a/src/Symfony/EventListener/DenyAccessListener.php +++ b/src/Symfony/EventListener/DenyAccessListener.php @@ -13,21 +13,14 @@ namespace ApiPlatform\Symfony\EventListener; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Symfony\Security\ExpressionLanguage; -use ApiPlatform\Symfony\Security\ResourceAccessChecker; use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ViewEvent; -use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; /** * Denies access to the current resource if the logged user doesn't have sufficient permissions. @@ -38,39 +31,12 @@ final class DenyAccessListener { use OperationRequestInitiatorTrait; - /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface - */ - private $resourceMetadataFactory; - /** - * @var ResourceAccessCheckerInterface - */ - private $resourceAccessChecker; - - public function __construct($resourceMetadataFactory, /* ResourceAccessCheckerInterface */ $resourceAccessCheckerOrExpressionLanguage = null, AuthenticationTrustResolverInterface $authenticationTrustResolver = null, RoleHierarchyInterface $roleHierarchy = null, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authorizationChecker = null) - { - $this->resourceMetadataFactory = $resourceMetadataFactory; - - if ($resourceAccessCheckerOrExpressionLanguage instanceof ResourceAccessCheckerInterface) { - $this->resourceAccessChecker = $resourceAccessCheckerOrExpressionLanguage; - - return; - } - - $this->resourceAccessChecker = new ResourceAccessChecker($resourceAccessCheckerOrExpressionLanguage, $authenticationTrustResolver, $roleHierarchy, $tokenStorage, $authorizationChecker); - @trigger_error(sprintf('Passing an instance of "%s" or null as second argument of "%s" is deprecated since API Platform 2.2 and will not be possible anymore in API Platform 3. Pass an instance of "%s" and no extra argument instead.', ExpressionLanguage::class, self::class, ResourceAccessCheckerInterface::class), \E_USER_DEPRECATED); - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } else { - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; - } - } + private ?ResourceAccessCheckerInterface $resourceAccessChecker; - public function onKernelRequest(RequestEvent $event): void + public function __construct(?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ?ResourceAccessCheckerInterface $resourceAccessChecker = null) { - @trigger_error(sprintf('Method "%1$s::onKernelRequest" is deprecated since API Platform 2.4 and will not be available anymore in API Platform 3. Prefer calling "%1$s::onSecurity" instead.', self::class), \E_USER_DEPRECATED); - $this->onSecurityPostDenormalize($event); + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; + $this->resourceAccessChecker = $resourceAccessChecker; } public function onSecurity(RequestEvent $event): void @@ -99,7 +65,7 @@ public function onSecurityPostValidation(ViewEvent $event): void */ private function checkSecurity(Request $request, string $attribute, bool $backwardCompatibility, array $extraVariables = []): void { - if (!$attributes = RequestAttributesExtractor::extractAttributes($request)) { + if (!$this->resourceAccessChecker || !$attributes = RequestAttributesExtractor::extractAttributes($request)) { return; } @@ -107,38 +73,23 @@ private function checkSecurity(Request $request, string $attribute, bool $backwa $isGranted = null; $message = $attributes[$attribute.'_message'] ?? 'Access Denied.'; - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - - $isGranted = $resourceMetadata->getOperationAttribute($attributes, $attribute, null, true); - if ($backwardCompatibility && null === $isGranted) { - // Backward compatibility - $isGranted = $resourceMetadata->getOperationAttribute($attributes, 'access_control', null, true); - if (null !== $isGranted) { - @trigger_error('Using "access_control" attribute is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Use "security" attribute instead.', \E_USER_DEPRECATED); - } - } - - $message = $resourceMetadata->getOperationAttribute($attributes, $attribute.'_message', 'Access Denied.', true); - } elseif ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $operation = $this->initializeOperation($request); - if (!$operation) { - return; - } - - switch ($attribute) { - case 'security_post_denormalize': - $isGranted = $operation->getSecurityPostDenormalize(); - $message = $operation->getSecurityPostDenormalizeMessage(); - break; - case 'security_post_validation': - $isGranted = $operation->getSecurityPostValidation(); - $message = $operation->getSecurityPostValidationMessage(); - break; - default: - $isGranted = $operation->getSecurity(); - $message = $operation->getSecurityMessage(); - } + $operation = $this->initializeOperation($request); + if (!$operation) { + return; + } + + switch ($attribute) { + case 'security_post_denormalize': + $isGranted = $operation->getSecurityPostDenormalize(); + $message = $operation->getSecurityPostDenormalizeMessage(); + break; + case 'security_post_validation': + $isGranted = $operation->getSecurityPostValidation(); + $message = $operation->getSecurityPostValidationMessage(); + break; + default: + $isGranted = $operation->getSecurity(); + $message = $operation->getSecurityMessage(); } if (null === $isGranted) { @@ -155,5 +106,3 @@ private function checkSecurity(Request $request, string $attribute, bool $backwa } } } - -class_alias(DenyAccessListener::class, \ApiPlatform\Core\Security\EventListener\DenyAccessListener::class); diff --git a/src/Symfony/EventListener/DeserializeListener.php b/src/Symfony/EventListener/DeserializeListener.php index ab29409842e..dd46568282c 100644 --- a/src/Symfony/EventListener/DeserializeListener.php +++ b/src/Symfony/EventListener/DeserializeListener.php @@ -14,9 +14,6 @@ namespace ApiPlatform\Symfony\EventListener; use ApiPlatform\Api\FormatMatcher; -use ApiPlatform\Core\Api\FormatsProviderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Serializer\SerializerContextBuilderInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; @@ -35,44 +32,17 @@ final class DeserializeListener { use OperationRequestInitiatorTrait; - use ToggleableOperationAttributeTrait; public const OPERATION_ATTRIBUTE_KEY = 'deserialize'; private $serializer; private $serializerContextBuilder; - private $formats; - private $formatsProvider; - /** - * @param ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface|FormatsProviderInterface|array $resourceMetadataFactory - */ - public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, $resourceMetadataFactory) + public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null) { $this->serializer = $serializer; $this->serializerContextBuilder = $serializerContextBuilder; - - $this->resourceMetadataFactory = $resourceMetadataFactory; - - if ($resourceMetadataFactory) { - if (!$resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - @trigger_error(sprintf('Passing an array or an instance of "%s" as 3rd parameter of the constructor of "%s" is deprecated since API Platform 2.5, pass an instance of "%s" instead', FormatsProviderInterface::class, __CLASS__, ResourceMetadataFactoryInterface::class), \E_USER_DEPRECATED); - } - - if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - - if ($resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; - } - } - - if (\is_array($resourceMetadataFactory)) { - $this->formats = $resourceMetadataFactory; - } elseif ($resourceMetadataFactory instanceof FormatsProviderInterface) { - $this->formatsProvider = $resourceMetadataFactory; - } + $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; } /** @@ -85,48 +55,24 @@ public function onKernelRequest(RequestEvent $event): void $request = $event->getRequest(); $method = $request->getMethod(); - $operation = $this->initializeOperation($request); - if ( 'DELETE' === $method || $request->isMethodSafe() || !($attributes = RequestAttributesExtractor::extractAttributes($request)) + || !$attributes['receive'] ) { return; } - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && - (!$operation || !($operation->canDeserialize() ?? true) || !$attributes['receive']) - ) { - return; - } + $operation = $this->initializeOperation($request); - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && ( - !$attributes['receive'] - || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) - )) { + if (!($operation?->canDeserialize() ?? true)) { return; } $context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes); - $formats = $operation ? $operation->getInputFormats() ?? null : null; - - if (!$formats) { - // BC check to be removed in 3.0 - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - @trigger_error('When using a "route_name", be sure to define the "_api_operation" route defaults as we will not rely on metadata in API Platform 3.0.', \E_USER_DEPRECATED); - $formats = $this->resourceMetadataFactory - ->create($attributes['resource_class']) - ->getOperationAttribute($attributes, 'input_formats', [], true); - } elseif ($this->formatsProvider instanceof FormatsProviderInterface) { - $formats = $this->formatsProvider->getFormatsFromAttributes($attributes); - } else { - $formats = $this->formats; - } - } - - $format = $this->getFormat($request, $formats); + $format = $this->getFormat($request, $operation->getInputFormats() ?? []); $data = $request->attributes->get('data'); if (null !== $data) { $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $data; @@ -169,5 +115,3 @@ private function getFormat(Request $request, array $formats): string return $format; } } - -class_alias(DeserializeListener::class, \ApiPlatform\Core\EventListener\DeserializeListener::class); diff --git a/src/Symfony/EventListener/EventPriorities.php b/src/Symfony/EventListener/EventPriorities.php index 3ec8faa4c65..bfa958a26e7 100644 --- a/src/Symfony/EventListener/EventPriorities.php +++ b/src/Symfony/EventListener/EventPriorities.php @@ -36,5 +36,3 @@ final class EventPriorities // kernel.response public const POST_RESPOND = 0; } - -class_alias(EventPriorities::class, \ApiPlatform\Core\EventListener\EventPriorities::class); diff --git a/src/Symfony/EventListener/ExceptionListener.php b/src/Symfony/EventListener/ExceptionListener.php index 8afdb5b4cea..58e1efb367f 100644 --- a/src/Symfony/EventListener/ExceptionListener.php +++ b/src/Symfony/EventListener/ExceptionListener.php @@ -51,5 +51,3 @@ public function onKernelException(ExceptionEvent $event): void $this->exceptionListener->onKernelException($event); } } - -class_alias(ExceptionListener::class, \ApiPlatform\Core\EventListener\ExceptionListener::class); diff --git a/src/Symfony/EventListener/JsonApi/TransformFieldsetsParametersListener.php b/src/Symfony/EventListener/JsonApi/TransformFieldsetsParametersListener.php index 58b9e5eeb82..3ab911a4a79 100644 --- a/src/Symfony/EventListener/JsonApi/TransformFieldsetsParametersListener.php +++ b/src/Symfony/EventListener/JsonApi/TransformFieldsetsParametersListener.php @@ -13,10 +13,7 @@ namespace ApiPlatform\Symfony\EventListener\JsonApi; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use Symfony\Component\HttpKernel\Event\RequestEvent; /** @@ -27,18 +24,11 @@ */ final class TransformFieldsetsParametersListener { - /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface - */ - private $resourceMetadataFactory; + private $resourceMetadataCollectionFactory; - public function __construct($resourceMetadataFactory) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory) { - $this->resourceMetadataFactory = $resourceMetadataFactory; - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } public function onKernelRequest(RequestEvent $event): void @@ -66,9 +56,8 @@ public function onKernelRequest(RequestEvent $event): void return; } - /** @var ResourceMetadata|ResourceMetadataCollection */ - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $resourceShortName = $resourceMetadata instanceof ResourceMetadata ? $resourceMetadata->getShortName() : $resourceMetadata->getOperation()->getShortName(); + $resourceMetadata = $this->resourceMetadataCollectionFactory->create($resourceClass); + $resourceShortName = $resourceMetadata->getOperation()->getShortName(); $properties = []; foreach ($fieldsParameter as $resourceType => $fields) { @@ -88,5 +77,3 @@ public function onKernelRequest(RequestEvent $event): void $request->attributes->set('_api_filter_property', $properties); } } - -class_alias(TransformFieldsetsParametersListener::class, \ApiPlatform\Core\JsonApi\EventListener\TransformFieldsetsParametersListener::class); diff --git a/src/Symfony/EventListener/JsonApi/TransformFilteringParametersListener.php b/src/Symfony/EventListener/JsonApi/TransformFilteringParametersListener.php index a683dabde86..18efa74852d 100644 --- a/src/Symfony/EventListener/JsonApi/TransformFilteringParametersListener.php +++ b/src/Symfony/EventListener/JsonApi/TransformFilteringParametersListener.php @@ -41,5 +41,3 @@ public function onKernelRequest(RequestEvent $event): void $request->attributes->set('_api_filters', array_merge($filterParameter, $filters)); } } - -class_alias(TransformFilteringParametersListener::class, \ApiPlatform\Core\JsonApi\EventListener\TransformFilteringParametersListener::class); diff --git a/src/Symfony/EventListener/JsonApi/TransformPaginationParametersListener.php b/src/Symfony/EventListener/JsonApi/TransformPaginationParametersListener.php index 0c33f88edbf..9036fc8a8e0 100644 --- a/src/Symfony/EventListener/JsonApi/TransformPaginationParametersListener.php +++ b/src/Symfony/EventListener/JsonApi/TransformPaginationParametersListener.php @@ -38,10 +38,5 @@ public function onKernelRequest(RequestEvent $event): void $filters = $request->attributes->get('_api_filters', []); $request->attributes->set('_api_filters', array_merge($pageParameter, $filters)); - - /* @TODO remove the `_api_pagination` attribute in 3.0 (@meyerbaptiste) */ - $request->attributes->set('_api_pagination', $pageParameter); } } - -class_alias(TransformPaginationParametersListener::class, \ApiPlatform\Core\JsonApi\EventListener\TransformPaginationParametersListener::class); diff --git a/src/Symfony/EventListener/JsonApi/TransformSortingParametersListener.php b/src/Symfony/EventListener/JsonApi/TransformSortingParametersListener.php index 70e87d9b0ec..fc732b80107 100644 --- a/src/Symfony/EventListener/JsonApi/TransformSortingParametersListener.php +++ b/src/Symfony/EventListener/JsonApi/TransformSortingParametersListener.php @@ -63,5 +63,3 @@ public function onKernelRequest(RequestEvent $event): void $request->attributes->set('_api_filters', $filters); } } - -class_alias(TransformSortingParametersListener::class, \ApiPlatform\Core\JsonApi\EventListener\TransformSortingParametersListener::class); diff --git a/src/Symfony/EventListener/QueryParameterValidateListener.php b/src/Symfony/EventListener/QueryParameterValidateListener.php index 17c1ec5a4c7..950552558fb 100644 --- a/src/Symfony/EventListener/QueryParameterValidateListener.php +++ b/src/Symfony/EventListener/QueryParameterValidateListener.php @@ -14,9 +14,6 @@ namespace ApiPlatform\Symfony\EventListener; use ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator; -use ApiPlatform\Core\Filter\QueryParameterValidator as LegacyQueryParameterValidator; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; @@ -32,29 +29,15 @@ final class QueryParameterValidateListener { use OperationRequestInitiatorTrait; - use ToggleableOperationAttributeTrait; public const OPERATION_ATTRIBUTE_KEY = 'query_parameter_validate'; - private $resourceMetadataFactory; + private QueryParameterValidator $queryParameterValidator; + private bool $enabled; - private $queryParameterValidator; - - private $enabled; - - /** - * @param ResourceMetadataCollectionFactoryInterface|ResourceMetadataFactoryInterface $resourceMetadataFactory - * @param QueryParameterValidator|LegacyQueryParameterValidator $queryParameterValidator - */ - public function __construct($resourceMetadataFactory, $queryParameterValidator, bool $enabled = true) + public function __construct(QueryParameterValidator $queryParameterValidator, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, bool $enabled = true) { - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } else { - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; - } - - $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->queryParameterValidator = $queryParameterValidator; $this->enabled = $enabled; } @@ -62,7 +45,6 @@ public function __construct($resourceMetadataFactory, $queryParameterValidator, public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); - $operation = $this->initializeOperation($request); if ( !$request->isMethodSafe() @@ -72,33 +54,14 @@ public function onKernelRequest(RequestEvent $event) return; } - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && - (!$operation || !($operation->getQueryParameterValidationEnabled() ?? true) || !$operation instanceof CollectionOperationInterface) - ) { - return; - } + $operation = $this->initializeOperation($request); - // TODO: remove in 3.0 - $operationName = $attributes['collection_operation_name'] ?? null; - if (!$this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && - ( - null === $operationName - || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY, !$this->enabled) - ) - ) { + if (!($operation?->getQueryParameterValidationEnabled() ?? true) || !$operation instanceof CollectionOperationInterface) { return; } $queryString = RequestParser::getQueryString($request); $queryParameters = $queryString ? RequestParser::parseRequestParams($queryString) : []; - $resourceFilters = []; - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceFilters = $this->resourceMetadataFactory->create($attributes['resource_class'])->getCollectionOperationAttribute($operationName, 'filters', [], true); - } elseif ($operation) { - $resourceFilters = $operation->getFilters() ?? []; - } - $this->queryParameterValidator->validateFilters($attributes['resource_class'], $resourceFilters, $queryParameters); + $this->queryParameterValidator->validateFilters($attributes['resource_class'], $operation?->getFilters() ?? [], $queryParameters); } } - -class_alias(QueryParameterValidateListener::class, \ApiPlatform\Core\EventListener\QueryParameterValidateListener::class); diff --git a/src/Symfony/EventListener/ReadListener.php b/src/Symfony/EventListener/ReadListener.php index 17ffd741a33..fd13172e4ae 100644 --- a/src/Symfony/EventListener/ReadListener.php +++ b/src/Symfony/EventListener/ReadListener.php @@ -43,7 +43,7 @@ final class ReadListener private $serializerContextBuilder; private $provider; - public function __construct(ProviderInterface $provider, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, SerializerContextBuilderInterface $serializerContextBuilder = null, UriVariablesConverterInterface $uriVariablesConverter = null) + public function __construct(ProviderInterface $provider, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, SerializerContextBuilderInterface $serializerContextBuilder = null, UriVariablesConverterInterface $uriVariablesConverter = null) { $this->provider = $provider; $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; @@ -65,7 +65,7 @@ public function onKernelRequest(RequestEvent $event): void return; } - if (!$operation || !($operation->canRead() ?? true) || !$attributes['receive'] || (!$operation->getUriVariables() && !$request->isMethodSafe()) || ($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) || ($operation->getExtraProperties()['is_legacy_subresource'] ?? false)) { + if (!$operation || !($operation->canRead() ?? true) || !$attributes['receive'] || (!$operation->getUriVariables() && !$request->isMethodSafe())) { return; } diff --git a/src/Symfony/EventListener/RespondListener.php b/src/Symfony/EventListener/RespondListener.php index 1dbd7ed09f7..d4d4b8aa911 100644 --- a/src/Symfony/EventListener/RespondListener.php +++ b/src/Symfony/EventListener/RespondListener.php @@ -15,8 +15,6 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; @@ -37,20 +35,11 @@ final class RespondListener 'DELETE' => Response::HTTP_NO_CONTENT, ]; - private $resourceMetadataFactory; private $iriConverter; - public function __construct($resourceMetadataFactory = null, IriConverterInterface $iriConverter = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, IriConverterInterface $iriConverter = null) { - if ($resourceMetadataFactory && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - - if ($resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; - } - - $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; $this->iriConverter = $iriConverter; } @@ -82,36 +71,24 @@ public function onKernelView(ViewEvent $event): void 'X-Frame-Options' => 'deny', ]; - $status = $operation ? $operation->getStatus() : null; - - // TODO: remove this in 3.x - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && $attributes) { - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - - if ($sunset = $resourceMetadata->getOperationAttribute($attributes, 'sunset', null, true)) { - $headers['Sunset'] = (new \DateTimeImmutable($sunset))->format(\DateTime::RFC1123); - } + $status = $operation?->getStatus(); - $headers = $this->addAcceptPatchHeader($headers, $attributes, $resourceMetadata); - $status = $resourceMetadata->getOperationAttribute($attributes, 'status'); - } elseif ($operation) { - if ($sunset = $operation->getSunset()) { - $headers['Sunset'] = (new \DateTimeImmutable($sunset))->format(\DateTime::RFC1123); - } + if ($sunset = $operation?->getSunset()) { + $headers['Sunset'] = (new \DateTimeImmutable($sunset))->format(\DateTime::RFC1123); + } - if ($acceptPatch = $operation->getAcceptPatch()) { - $headers['Accept-Patch'] = $acceptPatch; - } + if ($acceptPatch = $operation?->getAcceptPatch()) { + $headers['Accept-Patch'] = $acceptPatch; + } - if ( - $this->iriConverter && - ($operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false) && - !($operation->getExtraProperties()['is_legacy_subresource'] ?? false) - && 301 === $operation->getStatus() - ) { - $status = 301; - $headers['Location'] = $this->iriConverter->getIriFromResource($request->attributes->get('data'), UrlGeneratorInterface::ABS_PATH, $operation); - } + if ( + $this->iriConverter && + $operation && + ($operation->getExtraProperties()['is_alternate_resource_metadata'] ?? false) + && 301 === $operation->getStatus() + ) { + $status = 301; + $headers['Location'] = $this->iriConverter->getIriFromResource($request->attributes->get('data'), UrlGeneratorInterface::ABS_PATH, $operation); } $status = $status ?? self::METHOD_TO_CODE[$request->getMethod()] ?? Response::HTTP_OK; @@ -130,31 +107,4 @@ public function onKernelView(ViewEvent $event): void $headers )); } - - private function addAcceptPatchHeader(array $headers, array $attributes, ResourceMetadata $resourceMetadata): array - { - if (!isset($attributes['item_operation_name'])) { - return $headers; - } - - $patchMimeTypes = []; - foreach ($resourceMetadata->getItemOperations() as $operation) { - if ('PATCH' !== ($operation['method'] ?? '') || !isset($operation['input_formats'])) { - continue; - } - - foreach ($operation['input_formats'] as $mimeTypes) { - foreach ($mimeTypes as $mimeType) { - $patchMimeTypes[] = $mimeType; - } - } - $headers['Accept-Patch'] = implode(', ', $patchMimeTypes); - - return $headers; - } - - return $headers; - } } - -class_alias(RespondListener::class, \ApiPlatform\Core\EventListener\RespondListener::class); diff --git a/src/Symfony/EventListener/SerializeListener.php b/src/Symfony/EventListener/SerializeListener.php index d9789078711..891fd2ef4ba 100644 --- a/src/Symfony/EventListener/SerializeListener.php +++ b/src/Symfony/EventListener/SerializeListener.php @@ -13,8 +13,6 @@ namespace ApiPlatform\Symfony\EventListener; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Exception\RuntimeException; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Serializer\ResourceList; @@ -38,26 +36,17 @@ final class SerializeListener { use OperationRequestInitiatorTrait; - use ToggleableOperationAttributeTrait; public const OPERATION_ATTRIBUTE_KEY = 'serialize'; private $serializer; private $serializerContextBuilder; - public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, $resourceMetadataFactory = null) + public function __construct(SerializerInterface $serializer, SerializerContextBuilderInterface $serializerContextBuilder, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null) { $this->serializer = $serializer; $this->serializerContextBuilder = $serializerContextBuilder; - $this->resourceMetadataFactory = $resourceMetadataFactory; - - if ($resourceMetadataFactory && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } - - if ($resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; - } + $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; } /** @@ -67,7 +56,6 @@ public function onKernelView(ViewEvent $event): void { $controllerResult = $event->getControllerResult(); $request = $event->getRequest(); - $operation = $this->initializeOperation($request); if ($controllerResult instanceof Response) { return; @@ -75,21 +63,12 @@ public function onKernelView(ViewEvent $event): void $attributes = RequestAttributesExtractor::extractAttributes($request); - // TODO: 3.0 remove condition - if ( - (!$this->resourceMetadataFactory || $this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) - && - ( - !($attributes['respond'] ?? $request->attributes->getBoolean('_api_respond', false)) - || ($attributes && $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY)) - ) - ) { + if (!($attributes['respond'] ?? $request->attributes->getBoolean('_api_respond', false))) { return; } - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && - ($operation && !($operation->canSerialize() ?? true)) - ) { + $operation = $this->initializeOperation($request); + if (!($operation?->canSerialize() ?? true)) { return; } @@ -154,5 +133,3 @@ private function serializeRawData(ViewEvent $event, Request $request, $controlle $event->setControllerResult($this->serializer->encode($controllerResult, $request->getRequestFormat())); } } - -class_alias(SerializeListener::class, \ApiPlatform\Core\EventListener\SerializeListener::class); diff --git a/src/Symfony/EventListener/ValidateListener.php b/src/Symfony/EventListener/ValidateListener.php index e610814a048..61bfe23f86f 100644 --- a/src/Symfony/EventListener/ValidateListener.php +++ b/src/Symfony/EventListener/ValidateListener.php @@ -13,8 +13,6 @@ namespace ApiPlatform\Symfony\EventListener; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ToggleableOperationAttributeTrait; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; @@ -31,23 +29,16 @@ final class ValidateListener { use OperationRequestInitiatorTrait; - use ToggleableOperationAttributeTrait; public const OPERATION_ATTRIBUTE_KEY = 'validate'; private $validator; private $resourceMetadataFactory; - public function __construct(ValidatorInterface $validator, $resourceMetadataFactory) + public function __construct(ValidatorInterface $validator, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory) { $this->validator = $validator; - $this->resourceMetadataFactory = $resourceMetadataFactory; - - if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } else { - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; - } + $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; } /** @@ -70,32 +61,10 @@ public function onKernelView(ViewEvent $event): void return; } - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && - (!$operation || !($operation->canValidate() ?? true)) - ) { - return; - } - - // TODO: 3.0 remove condition - if ( - $this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && ( - !$attributes['receive'] - || $this->isOperationAttributeDisabled($attributes, self::OPERATION_ATTRIBUTE_KEY) - ) - ) { + if (!$operation || !($operation->canValidate() ?? true)) { return; } - $validationContext = $operation ? ($operation->getValidationContext() ?? []) : []; - - if (!$validationContext && $this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']); - $validationGroups = $resourceMetadata->getOperationAttribute($attributes, 'validation_groups', null, true); - $validationContext = ['groups' => $validationGroups]; - } - - $this->validator->validate($controllerResult, $validationContext); + $this->validator->validate($controllerResult, $operation->getValidationContext() ?? []); } } - -class_alias(ValidateListener::class, \ApiPlatform\Core\Validator\EventListener\ValidateListener::class); diff --git a/src/Symfony/EventListener/WriteListener.php b/src/Symfony/EventListener/WriteListener.php index a9cb3f2a017..0ae832f91ce 100644 --- a/src/Symfony/EventListener/WriteListener.php +++ b/src/Symfony/EventListener/WriteListener.php @@ -20,9 +20,9 @@ use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\State\UriVariablesResolverTrait; +use ApiPlatform\Util\ClassInfoTrait; use ApiPlatform\Util\OperationRequestInitiatorTrait; use ApiPlatform\Util\RequestAttributesExtractor; -use ApiPlatform\Util\ResourceClassInfoTrait; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ViewEvent; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -35,23 +35,21 @@ */ final class WriteListener { + use ClassInfoTrait; use OperationRequestInitiatorTrait; - use ResourceClassInfoTrait; - use UriVariablesResolverTrait; public const OPERATION_ATTRIBUTE_KEY = 'write'; - private $processor; - private $iriConverter; + private ResourceClassResolverInterface $resourceClassResolver; + private ProcessorInterface $processor; + private IriConverterInterface $iriConverter; - public function __construct(ProcessorInterface $processor, IriConverterInterface $iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ResourceClassResolverInterface $resourceClassResolver, ?UriVariablesConverterInterface $uriVariablesConverter = null) + public function __construct(ProcessorInterface $processor, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ?UriVariablesConverterInterface $uriVariablesConverter = null) { $this->processor = $processor; $this->iriConverter = $iriConverter; $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; - // TODO 3.0: see ResourceClassInfoTrait - $this->resourceMetadataFactory = $resourceMetadataCollectionFactory; $this->resourceClassResolver = $resourceClassResolver; $this->uriVariablesConverter = $uriVariablesConverter; } @@ -73,11 +71,11 @@ public function onKernelView(ViewEvent $event): void return; } - if (!$operation || ($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) || !($operation->canWrite() ?? true) || !$attributes['persist']) { + if (!($operation?->canWrite() ?? true) || !$attributes['persist']) { return; } - if (!$operation->getProcessor()) { + if (!$operation?->getProcessor()) { return; } @@ -109,7 +107,7 @@ public function onKernelView(ViewEvent $event): void break; } - if ($this->isResourceClass($this->getObjectClass($controllerResult))) { + if ($this->resourceClassResolver->isResourceClass($this->getObjectClass($controllerResult))) { $request->attributes->set('_api_write_item_iri', $this->iriConverter->getIriFromResource($controllerResult)); } diff --git a/src/Symfony/Messenger/ContextStamp.php b/src/Symfony/Messenger/ContextStamp.php index c49268ea9a7..11be14f7ab8 100644 --- a/src/Symfony/Messenger/ContextStamp.php +++ b/src/Symfony/Messenger/ContextStamp.php @@ -39,5 +39,3 @@ public function getContext(): array return $this->context; } } - -class_alias(ContextStamp::class, \ApiPlatform\Core\Bridge\Symfony\Messenger\ContextStamp::class); diff --git a/src/Symfony/Messenger/DispatchTrait.php b/src/Symfony/Messenger/DispatchTrait.php index f60329b8b0e..0502be7a40f 100644 --- a/src/Symfony/Messenger/DispatchTrait.php +++ b/src/Symfony/Messenger/DispatchTrait.php @@ -53,5 +53,3 @@ private function dispatch($message) } } } - -class_alias(DispatchTrait::class, \ApiPlatform\Core\Bridge\Symfony\Messenger\DispatchTrait::class); diff --git a/src/Symfony/Messenger/RemoveStamp.php b/src/Symfony/Messenger/RemoveStamp.php index 4e7f690c428..dc12d1b2166 100644 --- a/src/Symfony/Messenger/RemoveStamp.php +++ b/src/Symfony/Messenger/RemoveStamp.php @@ -25,5 +25,3 @@ final class RemoveStamp implements StampInterface { } - -class_alias(RemoveStamp::class, \ApiPlatform\Core\Bridge\Symfony\Messenger\RemoveStamp::class); diff --git a/src/Symfony/Routing/ApiLoader.php b/src/Symfony/Routing/ApiLoader.php index 5a7459efdd6..9e9eb0e6fa3 100644 --- a/src/Symfony/Routing/ApiLoader.php +++ b/src/Symfony/Routing/ApiLoader.php @@ -13,19 +13,10 @@ namespace ApiPlatform\Symfony\Routing; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameGenerator; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; -use ApiPlatform\Exception\InvalidResourceException; use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\PathResolver\OperationPathResolverInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Config\Resource\DirectoryResource; @@ -42,49 +33,35 @@ */ final class ApiLoader extends Loader { - /** - * @deprecated since version 2.1, to be removed in 3.0. Use {@see RouteNameGenerator::ROUTE_NAME_PREFIX} instead. - */ - public const ROUTE_NAME_PREFIX = 'api_'; public const DEFAULT_ACTION_PATTERN = 'api_platform.action.'; private $fileLoader; private $resourceNameCollectionFactory; private $resourceMetadataFactory; - private $operationPathResolver; private $container; private $formats; private $resourceClassDirectories; - private $subresourceOperationFactory; private $graphqlEnabled; private $graphiQlEnabled; private $graphQlPlaygroundEnabled; private $entrypointEnabled; private $docsEnabled; - private $identifiersExtractor; - public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, $resourceMetadataFactory, OperationPathResolverInterface $operationPathResolver, ContainerInterface $container, array $formats, array $resourceClassDirectories = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, bool $graphqlEnabled = false, bool $entrypointEnabled = true, bool $docsEnabled = true, bool $graphiQlEnabled = false, bool $graphQlPlaygroundEnabled = false, IdentifiersExtractorInterface $identifiersExtractor = null) + public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, ContainerInterface $container, array $formats, array $resourceClassDirectories = [], bool $graphqlEnabled = false, bool $entrypointEnabled = true, bool $docsEnabled = true, bool $graphiQlEnabled = false, bool $graphQlPlaygroundEnabled = false) { /** @var string[]|string $paths */ $paths = $kernel->locateResource('@ApiPlatformBundle/Resources/config/routing'); $this->fileLoader = new XmlFileLoader(new FileLocator($paths)); $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; - - if ($resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class)); - } $this->resourceMetadataFactory = $resourceMetadataFactory; - $this->operationPathResolver = $operationPathResolver; $this->container = $container; $this->formats = $formats; $this->resourceClassDirectories = $resourceClassDirectories; - $this->subresourceOperationFactory = $subresourceOperationFactory; $this->graphqlEnabled = $graphqlEnabled; $this->graphiQlEnabled = $graphiQlEnabled; $this->graphQlPlaygroundEnabled = $graphQlPlaygroundEnabled; $this->entrypointEnabled = $entrypointEnabled; $this->docsEnabled = $docsEnabled; - $this->identifiersExtractor = $identifiersExtractor; } /** @@ -99,43 +76,12 @@ public function load($data, $type = null): RouteCollection $this->loadExternalFiles($routeCollection); foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - $this->loadLegacyMetadata($routeCollection, $resourceClass); - $this->loadLegacySubresources($routeCollection, $resourceClass); - continue; - } - foreach ($this->resourceMetadataFactory->create($resourceClass) as $resourceMetadata) { foreach ($resourceMetadata->getOperations() as $operationName => $operation) { if ($operation->getRouteName()) { continue; } - $legacyDefaults = []; - - if ($operation->getExtraProperties()['is_legacy_subresource'] ?? false) { - $legacyDefaults['_api_subresource_operation_name'] = $operationName; - $legacyDefaults['_api_subresource_context'] = [ - 'property' => $operation->getExtraProperties()['legacy_subresource_property'], - 'identifiers' => $operation->getExtraProperties()['legacy_subresource_identifiers'], - 'collection' => $operation instanceof CollectionOperationInterface, - 'operationId' => $operation->getExtraProperties()['legacy_subresource_operation_name'] ?? null, - ]; - $legacyDefaults['_api_identifiers'] = $operation->getExtraProperties()['legacy_subresource_identifiers']; - } elseif ($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) { - $legacyDefaults[sprintf('_api_%s_operation_name', $operation instanceof CollectionOperationInterface ? OperationType::COLLECTION : OperationType::ITEM)] = $operationName; - $legacyDefaults['_api_identifiers'] = []; - // Legacy identifiers - $hasCompositeIdentifier = false; - foreach ($operation->getUriVariables() ?? [] as $parameterName => $identifiedBy) { - $hasCompositeIdentifier = $identifiedBy->getCompositeIdentifier(); - foreach ($identifiedBy->getIdentifiers() ?? [] as $identifier) { - $legacyDefaults['_api_identifiers'][] = $identifier; - } - } - $legacyDefaults['_api_has_composite_identifier'] = $hasCompositeIdentifier; - } - $path = ($operation->getRoutePrefix() ?? '').$operation->getUriTemplate(); foreach ($operation->getUriVariables() ?? [] as $parameterName => $link) { if (!$expandedValue = $link->getExpandedValue()) { @@ -145,10 +91,16 @@ public function load($data, $type = null): RouteCollection $path = str_replace(sprintf('{%s}', $parameterName), $expandedValue, $path); } + if ($controller = $operation->getController()) { + if (!$this->container->has($controller)) { + throw new RuntimeException(sprintf('There is no builtin action for the "%s" operation. You need to define the controller yourself.', $operationName)); + } + } + $route = new Route( $path, - $legacyDefaults + [ - '_controller' => $operation->getController() ?? 'api_platform.action.placeholder', + [ + '_controller' => $controller ?? 'api_platform.action.placeholder', '_format' => null, '_stateless' => $operation->getStateless(), '_api_resource_class' => $resourceClass, @@ -213,136 +165,4 @@ private function loadExternalFiles(RouteCollection $routeCollection): void $routeCollection->addCollection($this->fileLoader->load('jsonld.xml')); } } - - /** - * TODO: remove in 3.0. - */ - private function loadLegacyMetadata(RouteCollection $routeCollection, string $resourceClass) - { - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $resourceShortName = $resourceMetadata->getShortName(); - - if (null === $resourceShortName) { - throw new InvalidResourceException(sprintf('Resource %s has no short name defined.', $resourceClass)); - } - - if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) { - foreach ($collectionOperations as $operationName => $operation) { - $this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceMetadata, OperationType::COLLECTION); - } - } - - if (null !== $itemOperations = $resourceMetadata->getItemOperations()) { - foreach ($itemOperations as $operationName => $operation) { - $this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceMetadata, OperationType::ITEM); - } - } - } - - /** - * TODO: remove in 3.0. - */ - private function loadLegacySubresources(RouteCollection $routeCollection, string $resourceClass) - { - if (null === $this->subresourceOperationFactory) { - return; - } - - foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $operation) { - if (null === $controller = $operation['controller'] ?? null) { - $controller = self::DEFAULT_ACTION_PATTERN.'get_subresource'; - - if (!$this->container->has($controller)) { - throw new RuntimeException(sprintf('There is no builtin action for the %s %s operation. You need to define the controller yourself.', OperationType::SUBRESOURCE, 'GET')); - } - } - - $routeCollection->add($operation['route_name'], new Route( - $operation['path'], - [ - '_controller' => $controller, - '_format' => $operation['defaults']['_format'] ?? null, - '_stateless' => $operation['stateless'] ?? null, - '_api_resource_class' => $operation['resource_class'], - '_api_identifiers' => $operation['identifiers'], - '_api_has_composite_identifier' => false, - '_api_subresource_operation_name' => $operation['route_name'], - '_api_subresource_context' => [ - 'property' => $operation['property'], - 'identifiers' => $operation['identifiers'], - 'collection' => $operation['collection'], - 'operationId' => $operationId, - ], - ] + ($operation['defaults'] ?? []), - $operation['requirements'] ?? [], - $operation['options'] ?? [], - $operation['host'] ?? '', - $operation['schemes'] ?? [], - ['GET'], - $operation['condition'] ?? '' - )); - } - } - - /** - * Creates and adds a route for the given operation to the route collection. - * - * @throws RuntimeException - */ - private function addRoute(RouteCollection $routeCollection, string $resourceClass, string $operationName, array $operation, ResourceMetadata $resourceMetadata, string $operationType): void - { - $resourceShortName = $resourceMetadata->getShortName(); - - if (isset($operation['route_name'])) { - if (!isset($operation['method'])) { - @trigger_error(sprintf('Not setting the "method" attribute is deprecated and will not be supported anymore in API Platform 3.0, set it for the %s operation "%s" of the class "%s".', OperationType::COLLECTION === $operationType ? 'collection' : 'item', $operationName, $resourceClass), \E_USER_DEPRECATED); - } - - return; - } - - if (!isset($operation['method'])) { - throw new RuntimeException(sprintf('Either a "route_name" or a "method" operation attribute must exist for the operation "%s" of the resource "%s".', $operationName, $resourceClass)); - } - - if (null === $controller = $operation['controller'] ?? null) { - $controller = sprintf('%s%s_%s', self::DEFAULT_ACTION_PATTERN, strtolower($operation['method']), $operationType); - - if (!$this->container->has($controller)) { - throw new RuntimeException(sprintf('There is no builtin action for the %s %s operation. You need to define the controller yourself.', $operationType, $operation['method'])); - } - } - - if ($resourceMetadata->getItemOperations()) { - $operation['identifiers'] = (array) ($operation['identifiers'] ?? $resourceMetadata->getAttribute('identifiers', $this->identifiersExtractor ? $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass) : ['id'])); - } - - $operation['has_composite_identifier'] = isset($operation['identifiers']) && \count($operation['identifiers']) > 1 ? $resourceMetadata->getAttribute('composite_identifier', true) : false; - $path = trim(trim($resourceMetadata->getAttribute('route_prefix', '')), '/'); - $path .= $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName); - - $route = new Route( - $path, - [ - '_controller' => $controller, - '_format' => $operation['defaults']['_format'] ?? null, - '_stateless' => $operation['stateless'] ?? null, - '_api_resource_class' => $resourceClass, - '_api_identifiers' => $operation['identifiers'] ?? [], - '_api_has_composite_identifier' => $operation['has_composite_identifier'] ?? true, - '_api_operation_name' => RouteNameGenerator::generate($operationName, $resourceShortName, $operationType), - sprintf('_api_%s_operation_name', $operationType) => $operationName, - ] + ($operation['defaults'] ?? []), - $operation['requirements'] ?? [], - $operation['options'] ?? [], - $operation['host'] ?? '', - $operation['schemes'] ?? [], - [$operation['method']], - $operation['condition'] ?? '' - ); - - $routeCollection->add(RouteNameGenerator::generate($operationName, $resourceShortName, $operationType), $route); - } } - -class_alias(ApiLoader::class, \ApiPlatform\Core\Bridge\Symfony\Routing\ApiLoader::class); diff --git a/src/Symfony/Routing/IriConverter.php b/src/Symfony/Routing/IriConverter.php index ac07b31aefa..cd849e2f780 100644 --- a/src/Symfony/Routing/IriConverter.php +++ b/src/Symfony/Routing/IriConverter.php @@ -28,11 +28,11 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; -use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProviderInterface; use ApiPlatform\State\UriVariablesResolverTrait; use ApiPlatform\Util\AttributesExtractor; +use ApiPlatform\Util\ClassInfoTrait; use ApiPlatform\Util\ResourceClassInfoTrait; use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface; use Symfony\Component\Routing\RouterInterface; @@ -46,6 +46,7 @@ */ final class IriConverter implements IriConverterInterface { + use ClassInfoTrait; use ResourceClassInfoTrait; use UriVariablesResolverTrait; @@ -77,8 +78,7 @@ public function getResourceFromIri(string $iri, array $context = [], ?Operation throw new InvalidArgumentException(sprintf('No route matches "%s".', $iri), $e->getCode(), $e); } - // TODO: 3.0 remove collection/item - $parameters['_api_operation_name'] = $parameters['_api_operation_name'] ?? $parameters['_api_collection_operation_name'] ?? $parameters['_api_item_operation_name'] ?? $parameters['_api_subresource_operation_name'] ?? null; + $parameters['_api_operation_name'] = $parameters['_api_operation_name'] ?? null; if (!isset($parameters['_api_resource_class'], $parameters['_api_operation_name'])) { throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri)); @@ -115,10 +115,15 @@ public function getResourceFromIri(string $iri, array $context = [], ?Operation */ public function getIriFromResource($item, int $referenceType = UrlGeneratorInterface::ABS_PATH, Operation $operation = null, array $context = []): ?string { - try { - $resourceClass = \is_string($item) ? $item : $this->getResourceClass($item, true); - } catch (InvalidArgumentException $e) { - return null; + $resourceClass = \is_string($item) ? $item : $this->getObjectClass($item); + + if (!$this->resourceClassResolver->isResourceClass($resourceClass)) { + return $this->generateSkolemIri($context); + } + + // This is only for when a class (that is not a resource) extends another one that is a resource, we should remove this behavior + if (!\is_string($item)) { + $resourceClass = $this->getResourceClass($item, true); } if (!$operation) { @@ -130,17 +135,10 @@ public function getIriFromResource($item, int $referenceType = UrlGeneratorInter unset($context['uri_variables']); } - // Legacy subresources had bad IRIs but we don't want to break these, remove this in 3.0 - $isLegacySubresource = ($operation->getExtraProperties()['is_legacy_subresource'] ?? false) && !$operation instanceof CollectionOperationInterface; - // Custom resources should have the same IRI as requested, it was not the case pre 2.7 - $isLegacyCustomResource = ($operation->getExtraProperties()['is_legacy_resource_metadata'] ?? false) && ($operation->getExtraProperties()['user_defined_uri_template'] ?? false); - // In symfony the operation name is the route name, try to find one if none provided if ( !$operation->getName() - || $operation instanceof Post - || $isLegacySubresource - || $isLegacyCustomResource + || ($operation instanceof HttpOperation && HttpOperation::METHOD_POST === $operation->getMethod()) ) { $forceCollection = $operation instanceof CollectionOperationInterface; try { @@ -151,14 +149,10 @@ public function getIriFromResource($item, int $referenceType = UrlGeneratorInter $identifiers = $context['uri_variables'] ?? []; - if ($isLegacySubresource || $isLegacyCustomResource) { - $identifiers = []; - } - if (\is_object($item)) { try { $identifiers = $this->identifiersExtractor->getIdentifiersFromItem($item, $operation); - } catch (RuntimeException $e) { + } catch (InvalidArgumentException|RuntimeException $e) { // We can try using context uri variables if any if (!$identifiers) { throw new InvalidArgumentException(sprintf('Unable to generate an IRI for the item of type "%s"', $resourceClass), $e->getCode(), $e); @@ -166,9 +160,8 @@ public function getIriFromResource($item, int $referenceType = UrlGeneratorInter } } - // TODO: call the Skolem IRI generator if (!$operation->getName()) { - return null; + return $this->generateSkolemIri($context); } try { @@ -177,4 +170,9 @@ public function getIriFromResource($item, int $referenceType = UrlGeneratorInter throw new InvalidArgumentException(sprintf('Unable to generate an IRI for the item of type "%s"', $resourceClass), $e->getCode(), $e); } } + + private function generateSkolemIri(array $context = []) + { + return $context['iri'] ?? '/.well-known/genid/'.(bin2hex(random_bytes(10))); + } } diff --git a/src/Symfony/Routing/Router.php b/src/Symfony/Routing/Router.php index 14f70c5d81a..c2f73563d28 100644 --- a/src/Symfony/Routing/Router.php +++ b/src/Symfony/Routing/Router.php @@ -107,5 +107,3 @@ public function generate($name, $parameters = [], $referenceType = null): string return $this->router->generate($name, $parameters, self::CONST_MAP[$referenceType ?? $this->urlGenerationStrategy]); } } - -class_alias(Router::class, \ApiPlatform\Core\Bridge\Symfony\Routing\Router::class); diff --git a/src/Symfony/Security/Core/Authorization/ExpressionLanguageProvider.php b/src/Symfony/Security/Core/Authorization/ExpressionLanguageProvider.php index 2d7e46a3c92..5ff56fb9d2a 100644 --- a/src/Symfony/Security/Core/Authorization/ExpressionLanguageProvider.php +++ b/src/Symfony/Security/Core/Authorization/ExpressionLanguageProvider.php @@ -34,5 +34,3 @@ public function getFunctions(): array ]; } } - -class_alias(ExpressionLanguageProvider::class, \ApiPlatform\Core\Security\Core\Authorization\ExpressionLanguageProvider::class); diff --git a/src/Symfony/Security/ExpressionLanguage.php b/src/Symfony/Security/ExpressionLanguage.php index 97db8549416..172f8d9c6fe 100644 --- a/src/Symfony/Security/ExpressionLanguage.php +++ b/src/Symfony/Security/ExpressionLanguage.php @@ -47,5 +47,3 @@ protected function registerFunctions() }); } } - -class_alias(ExpressionLanguage::class, \ApiPlatform\Core\Security\ExpressionLanguage::class); diff --git a/src/Symfony/Security/ResourceAccessChecker.php b/src/Symfony/Security/ResourceAccessChecker.php index 15ac6db8d3e..c6a3c5f350a 100644 --- a/src/Symfony/Security/ResourceAccessChecker.php +++ b/src/Symfony/Security/ResourceAccessChecker.php @@ -108,5 +108,3 @@ private function getEffectiveRoles(TokenInterface $token): array }, $this->roleHierarchy->getReachableRoles($token->getRoles())); // @phpstan-ignore-line } } - -class_alias(ResourceAccessChecker::class, \ApiPlatform\Core\Security\ResourceAccessChecker::class); diff --git a/src/Symfony/Validator/EventListener/ValidationExceptionListener.php b/src/Symfony/Validator/EventListener/ValidationExceptionListener.php index d66df684cb2..a34d0b4186e 100644 --- a/src/Symfony/Validator/EventListener/ValidationExceptionListener.php +++ b/src/Symfony/Validator/EventListener/ValidationExceptionListener.php @@ -71,5 +71,3 @@ public function onKernelException(ExceptionEvent $event): void )); } } - -class_alias(ValidationExceptionListener::class, \ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ValidationExceptionListener::class); diff --git a/src/Symfony/Validator/Exception/ValidationException.php b/src/Symfony/Validator/Exception/ValidationException.php index 3b2417271f8..269d6503396 100644 --- a/src/Symfony/Validator/Exception/ValidationException.php +++ b/src/Symfony/Validator/Exception/ValidationException.php @@ -54,5 +54,3 @@ public function __toString(): string return $message; } } - -class_alias(ValidationException::class, \ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException::class); diff --git a/src/Symfony/Validator/Validator.php b/src/Symfony/Validator/Validator.php index 7b96123a258..a26b8a4f0f5 100644 --- a/src/Symfony/Validator/Validator.php +++ b/src/Symfony/Validator/Validator.php @@ -50,10 +50,6 @@ public function validate($data, array $context = []) ($service = $this->container->get($validationGroups)) && \is_callable($service) ) { - if (!$service instanceof ValidationGroupsGeneratorInterface) { - @trigger_error(sprintf('Using a public validation groups generator service not implementing "%s" is deprecated since 2.6 and will be removed in 3.0.', ValidationGroupsGeneratorInterface::class), \E_USER_DEPRECATED); - } - $validationGroups = $service($data); } elseif (\is_callable($validationGroups)) { $validationGroups = $validationGroups($data); @@ -70,5 +66,3 @@ public function validate($data, array $context = []) } } } - -class_alias(Validator::class, \ApiPlatform\Core\Bridge\Symfony\Validator\Validator::class); diff --git a/src/Test/DoctrineMongoDbOdmFilterTestCase.php b/src/Test/DoctrineMongoDbOdmFilterTestCase.php index 54f0173a611..e89881bfd4c 100644 --- a/src/Test/DoctrineMongoDbOdmFilterTestCase.php +++ b/src/Test/DoctrineMongoDbOdmFilterTestCase.php @@ -26,30 +26,15 @@ */ abstract class DoctrineMongoDbOdmFilterTestCase extends KernelTestCase { - /** - * @var DocumentManager - */ - protected $manager; + protected DocumentManager $manager; - /** - * @var ManagerRegistry - */ - protected $managerRegistry; + protected ManagerRegistry $managerRegistry; - /** - * @var DocumentRepository - */ - protected $repository; + protected DocumentRepository $repository; - /** - * @var string - */ - protected $resourceClass; + protected string $resourceClass; - /** - * @var string - */ - protected $filterClass; + protected string $filterClass; protected function setUp(): void { @@ -63,12 +48,12 @@ protected function setUp(): void /** * @dataProvider provideApplyTestData */ - public function testApply(?array $properties, array $filterParameters, array $expectedPipeline, callable $factory = null, string $resourceClass = null) + public function testApply(?array $properties, array $filterParameters, array $expectedPipeline, callable $factory = null, string $resourceClass = null): void { $this->doTestApply($properties, $filterParameters, $expectedPipeline, $factory, $resourceClass); } - protected function doTestApply(?array $properties, array $filterParameters, array $expectedPipeline, callable $filterFactory = null, string $resourceClass = null) + protected function doTestApply(?array $properties, array $filterParameters, array $expectedPipeline, callable $filterFactory = null, string $resourceClass = null): void { if (null === $filterFactory) { $filterFactory = function (ManagerRegistry $managerRegistry, array $properties = null): FilterInterface { @@ -103,5 +88,3 @@ protected function buildFilter(?array $properties = null) abstract public function provideApplyTestData(): array; } - -class_alias(DoctrineMongoDbOdmFilterTestCase::class, \ApiPlatform\Core\Test\DoctrineMongoDbOdmFilterTestCase::class); diff --git a/src/Test/DoctrineMongoDbOdmSetup.php b/src/Test/DoctrineMongoDbOdmSetup.php index ed23666aef4..6806f33f955 100644 --- a/src/Test/DoctrineMongoDbOdmSetup.php +++ b/src/Test/DoctrineMongoDbOdmSetup.php @@ -119,5 +119,3 @@ private static function createCacheInstance(bool $isDevMode, ?Cache $cache) return class_exists(ArrayCache::class) ? new ArrayCache() : new ArrayAdapter(); } } - -class_alias(DoctrineMongoDbOdmSetup::class, \ApiPlatform\Core\Test\DoctrineMongoDbOdmSetup::class); diff --git a/src/Test/DoctrineMongoDbOdmTestCase.php b/src/Test/DoctrineMongoDbOdmTestCase.php index e426d4ff0c1..467a9d053db 100644 --- a/src/Test/DoctrineMongoDbOdmTestCase.php +++ b/src/Test/DoctrineMongoDbOdmTestCase.php @@ -13,11 +13,11 @@ namespace ApiPlatform\Test; -use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Cache\ArrayCache; use Doctrine\ODM\MongoDB\Configuration; use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver; +use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver; +use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeReader; use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Adapter\ArrayAdapter; use function sys_get_temp_dir; @@ -41,7 +41,7 @@ public static function createTestDocumentManager($paths = []) $config->setHydratorDir(sys_get_temp_dir()); $config->setProxyNamespace('SymfonyTests\Doctrine'); $config->setHydratorNamespace('SymfonyTests\Doctrine'); - $config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader(), $paths)); + $config->setMetadataDriverImpl(new AttributeDriver($paths, new AttributeReader())); if (method_exists($config, 'setMetadataCache')) { $config->setMetadataCache(new ArrayAdapter()); } else { @@ -51,5 +51,3 @@ public static function createTestDocumentManager($paths = []) return DocumentManager::create(null, $config); } } - -class_alias(DoctrineMongoDbOdmTestCase::class, \ApiPlatform\Core\Test\DoctrineMongoDbOdmTestCase::class); diff --git a/src/Test/DoctrineOrmFilterTestCase.php b/src/Test/DoctrineOrmFilterTestCase.php index 8bee39c346e..94f9aaf1d2e 100644 --- a/src/Test/DoctrineOrmFilterTestCase.php +++ b/src/Test/DoctrineOrmFilterTestCase.php @@ -27,11 +27,15 @@ */ abstract class DoctrineOrmFilterTestCase extends KernelTestCase { - protected $managerRegistry; - protected $repository; - protected $resourceClass = Dummy::class; - protected $alias = 'o'; - protected $filterClass; + protected ManagerRegistry $managerRegistry; + + protected EntityRepository $repository; + + protected string $resourceClass = Dummy::class; + + protected string $alias = 'o'; + + protected string $filterClass; protected function setUp(): void { diff --git a/src/Util/ArrayTrait.php b/src/Util/ArrayTrait.php index 2f0cebd6b5b..f17486cd9f8 100644 --- a/src/Util/ArrayTrait.php +++ b/src/Util/ArrayTrait.php @@ -40,5 +40,3 @@ public function arrayContainsOnly(array $array, string $type): bool }); } } - -class_alias(ArrayTrait::class, \ApiPlatform\Core\Util\ArrayTrait::class); diff --git a/src/Util/AnnotationFilterExtractorTrait.php b/src/Util/AttributeFilterExtractorTrait.php similarity index 55% rename from src/Util/AnnotationFilterExtractorTrait.php rename to src/Util/AttributeFilterExtractorTrait.php index f4f9d3bb31d..f63535b4e70 100644 --- a/src/Util/AnnotationFilterExtractorTrait.php +++ b/src/Util/AttributeFilterExtractorTrait.php @@ -13,9 +13,7 @@ namespace ApiPlatform\Util; -use ApiPlatform\Core\Annotation\ApiFilter; -use ApiPlatform\Metadata\ApiFilter as ApiFilterMetadata; -use Doctrine\Common\Annotations\Reader; +use ApiPlatform\Metadata\ApiFilter; /** * Generates a service id for a generic filter. @@ -24,7 +22,7 @@ * * @author Antoine Bluchet */ -trait AnnotationFilterExtractorTrait +trait AttributeFilterExtractorTrait { /** * Filters annotations to get back only ApiFilter annotations. @@ -33,39 +31,24 @@ trait AnnotationFilterExtractorTrait * * @return \Iterator only ApiFilter annotations */ - private function getFilterAnnotations(\Reflector $reflector, ?Reader $reader = null): \Iterator + private function getFilterAttributes(\Reflector $reflector): \Iterator { - if (\PHP_VERSION_ID >= 80000) { - $attributes = $reflector->getAttributes(ApiFilter::class) ?: $reflector->getAttributes(ApiFilterMetadata::class); + $attributes = $reflector->getAttributes(ApiFilter::class); - foreach ($attributes as $attribute) { - yield $attribute->newInstance(); - } - } - - if (null === $reader) { - return; - } - - $miscAnnotations = $reflector instanceof \ReflectionClass ? $reader->getClassAnnotations($reflector) : $reader->getPropertyAnnotations($reflector); - foreach ($miscAnnotations as $miscAnnotation) { - if (ApiFilter::class === \get_class($miscAnnotation)) { - yield $miscAnnotation; - } + foreach ($attributes as $attribute) { + yield $attribute->newInstance(); } } /** - * Given a filter annotation and reflection elements, find out the properties where the filter is applied. - * - * @param ApiFilter|ApiFilterMetadata $filterAnnotation + * Given a filter attribute and reflection elements, find out the properties where the filter is applied. */ - private function getFilterProperties($filterAnnotation, \ReflectionClass $reflectionClass, \ReflectionProperty $reflectionProperty = null): array + private function getFilterProperties(ApiFilter $filterAttribute, \ReflectionClass $reflectionClass, \ReflectionProperty $reflectionProperty = null): array { $properties = []; - if ($filterAnnotation->properties) { - foreach ($filterAnnotation->properties as $property => $strategy) { + if ($filterAttribute->properties) { + foreach ($filterAttribute->properties as $property => $strategy) { if (\is_int($property)) { $properties[$strategy] = null; } else { @@ -77,14 +60,14 @@ private function getFilterProperties($filterAnnotation, \ReflectionClass $reflec } if (null !== $reflectionProperty) { - $properties[$reflectionProperty->getName()] = $filterAnnotation->strategy ?: null; + $properties[$reflectionProperty->getName()] = $filterAttribute->strategy ?: null; return $properties; } - if ($filterAnnotation->strategy) { + if ($filterAttribute->strategy) { foreach ($reflectionClass->getProperties() as $reflectionProperty) { - $properties[$reflectionProperty->getName()] = $filterAnnotation->strategy; + $properties[$reflectionProperty->getName()] = $filterAttribute->strategy; } } @@ -92,37 +75,37 @@ private function getFilterProperties($filterAnnotation, \ReflectionClass $reflec } /** - * Reads filter annotations from a ReflectionClass. + * Reads filter attribute from a ReflectionClass. * * @return array Key is the filter id. It has two values, properties and the ApiFilter instance */ - private function readFilterAnnotations(\ReflectionClass $reflectionClass, Reader $reader = null): array + private function readFilterAttributes(\ReflectionClass $reflectionClass): array { $filters = []; - foreach ($this->getFilterAnnotations($reflectionClass, $reader) as $filterAnnotation) { - $filterClass = $filterAnnotation->filterClass; - $id = $this->generateFilterId($reflectionClass, $filterClass, $filterAnnotation->id); + foreach ($this->getFilterAttributes($reflectionClass) as $filterAttribute) { + $filterClass = $filterAttribute->filterClass; + $id = $this->generateFilterId($reflectionClass, $filterClass, $filterAttribute->id); if (!isset($filters[$id])) { - $filters[$id] = [$filterAnnotation->arguments, $filterClass]; + $filters[$id] = [$filterAttribute->arguments, $filterClass]; } - if ($properties = $this->getFilterProperties($filterAnnotation, $reflectionClass)) { + if ($properties = $this->getFilterProperties($filterAttribute, $reflectionClass)) { $filters[$id][0]['properties'] = $properties; } } foreach ($reflectionClass->getProperties() as $reflectionProperty) { - foreach ($this->getFilterAnnotations($reflectionProperty, $reader) as $filterAnnotation) { - $filterClass = $filterAnnotation->filterClass; - $id = $this->generateFilterId($reflectionClass, $filterClass, $filterAnnotation->id); + foreach ($this->getFilterAttributes($reflectionProperty) as $filterAttribute) { + $filterClass = $filterAttribute->filterClass; + $id = $this->generateFilterId($reflectionClass, $filterClass, $filterAttribute->id); if (!isset($filters[$id])) { - $filters[$id] = [$filterAnnotation->arguments, $filterClass]; + $filters[$id] = [$filterAttribute->arguments, $filterClass]; } - if ($properties = $this->getFilterProperties($filterAnnotation, $reflectionClass, $reflectionProperty)) { + if ($properties = $this->getFilterProperties($filterAttribute, $reflectionClass, $reflectionProperty)) { if (isset($filters[$id][0]['properties'])) { $filters[$id][0]['properties'] = array_merge($filters[$id][0]['properties'], $properties); } else { @@ -135,7 +118,7 @@ private function readFilterAnnotations(\ReflectionClass $reflectionClass, Reader $parent = $reflectionClass->getParentClass(); if (false !== $parent) { - return array_merge($filters, $this->readFilterAnnotations($parent, $reader)); + return array_merge($filters, $this->readFilterAttributes($parent)); } return $filters; @@ -155,5 +138,3 @@ private function generateFilterId(\ReflectionClass $reflectionClass, string $fil return 'annotated_'.Inflector::tableize(str_replace('\\', '', $reflectionClass->getName().(new \ReflectionClass($filterClass))->getName().$suffix)); } } - -class_alias(AnnotationFilterExtractorTrait::class, \ApiPlatform\Core\Util\AnnotationFilterExtractorTrait::class); diff --git a/src/Util/AttributesExtractor.php b/src/Util/AttributesExtractor.php index e4c63b6dff2..89470fad7b2 100644 --- a/src/Util/AttributesExtractor.php +++ b/src/Util/AttributesExtractor.php @@ -13,8 +13,6 @@ namespace ApiPlatform\Util; -use ApiPlatform\Core\Api\OperationType; - /** * Extracts data used by the library form given attributes. * @@ -35,22 +33,6 @@ private function __construct() public static function extractAttributes(array $attributes): array { $result = ['resource_class' => $attributes['_api_resource_class'] ?? null, 'has_composite_identifier' => $attributes['_api_has_composite_identifier'] ?? false]; - if ($subresourceContext = $attributes['_api_subresource_context'] ?? null) { - $result['subresource_context'] = $subresourceContext; - } - - // Normalizing identifiers tuples - // TODO: 3.0 remove - $identifiers = []; - foreach (($attributes['_api_identifiers'] ?? ['id']) as $parameterName => $identifiedBy) { - if (\is_string($identifiedBy)) { - $identifiers[$identifiedBy] = [$result['resource_class'], $identifiedBy]; - } else { - $identifiers[$parameterName] = $identifiedBy; - } - } - - $result['identifiers'] = $identifiers; if (null === $result['resource_class']) { return []; @@ -65,18 +47,6 @@ public static function extractAttributes(array $attributes): array $result['operation'] = $attributes['_api_operation']; } - // TODO: remove in 3.0 - if (!isset($result['operation']) || ($result['operation']->getExtraProperties()['is_legacy_resource_metadata'] ?? false) || ($result['operation']->getExtraProperties()['is_legacy_subresource'] ?? false)) { - foreach (OperationType::TYPES as $operationType) { - $attribute = "_api_{$operationType}_operation_name"; - if (isset($attributes[$attribute])) { - $result["{$operationType}_operation_name"] = $attributes[$attribute]; - $hasRequestAttributeKey = true; - break; - } - } - } - if ($previousObject = $attributes['previous_data'] ?? null) { $result['previous_data'] = $previousObject; } @@ -94,5 +64,3 @@ public static function extractAttributes(array $attributes): array return $result; } } - -class_alias(AttributesExtractor::class, \ApiPlatform\Core\Util\AttributesExtractor::class); diff --git a/src/Util/CachedTrait.php b/src/Util/CachedTrait.php index 831a4c6a041..abe06aca934 100644 --- a/src/Util/CachedTrait.php +++ b/src/Util/CachedTrait.php @@ -49,5 +49,3 @@ private function getCached(string $cacheKey, callable $getValue) return $this->localCache[$cacheKey] = $value; } } - -class_alias(CachedTrait::class, \ApiPlatform\Core\Cache\CachedTrait::class); diff --git a/src/Util/ClassInfoTrait.php b/src/Util/ClassInfoTrait.php index a481f901ab7..9bb039ec863 100644 --- a/src/Util/ClassInfoTrait.php +++ b/src/Util/ClassInfoTrait.php @@ -59,5 +59,3 @@ private function getRealClassName(string $className): string ); } } - -class_alias(ClassInfoTrait::class, \ApiPlatform\Core\Util\ClassInfoTrait::class); diff --git a/src/Util/ClientTrait.php b/src/Util/ClientTrait.php index ab88472f0cb..01fc07bc253 100644 --- a/src/Util/ClientTrait.php +++ b/src/Util/ClientTrait.php @@ -24,5 +24,3 @@ trait ClientTrait use ClientTrait72; } } - -class_alias(ClientTrait::class, \ApiPlatform\Core\Util\ClientTrait::class); diff --git a/src/Util/CloneTrait.php b/src/Util/CloneTrait.php index 24760cc763f..51623aecb41 100644 --- a/src/Util/CloneTrait.php +++ b/src/Util/CloneTrait.php @@ -35,5 +35,3 @@ public function clone($data) } } } - -class_alias(CloneTrait::class, \ApiPlatform\Core\Util\CloneTrait::class); diff --git a/src/Util/CorsTrait.php b/src/Util/CorsTrait.php index 1595a1fab77..c55ce2584c0 100644 --- a/src/Util/CorsTrait.php +++ b/src/Util/CorsTrait.php @@ -31,5 +31,3 @@ public function isPreflightRequest(Request $request): bool return $request->isMethod('OPTIONS') && $request->headers->has('Access-Control-Request-Method'); } } - -class_alias(CorsTrait::class, \ApiPlatform\Core\Util\CorsTrait::class); diff --git a/src/Util/ErrorFormatGuesser.php b/src/Util/ErrorFormatGuesser.php index a8163333308..a42e1fb5b34 100644 --- a/src/Util/ErrorFormatGuesser.php +++ b/src/Util/ErrorFormatGuesser.php @@ -53,5 +53,3 @@ public static function guessErrorFormat(Request $request, array $errorFormats): return $defaultFormat; } } - -class_alias(ErrorFormatGuesser::class, \ApiPlatform\Core\Util\ErrorFormatGuesser::class); diff --git a/src/Util/Inflector.php b/src/Util/Inflector.php index 412547054b2..85fdf60f0e2 100644 --- a/src/Util/Inflector.php +++ b/src/Util/Inflector.php @@ -53,5 +53,3 @@ public static function pluralize(string $word): string return class_exists(InflectorFactory::class) ? self::getInstance()->pluralize($word) : LegacyInflector::pluralize($word); // @phpstan-ignore-line } } - -class_alias(Inflector::class, \ApiPlatform\Core\Util\Inflector::class); diff --git a/src/Util/IriHelper.php b/src/Util/IriHelper.php index db6970b4388..e71bd762383 100644 --- a/src/Util/IriHelper.php +++ b/src/Util/IriHelper.php @@ -64,11 +64,6 @@ public static function createIri(array $parts, array $parameters, string $pagePa $parameters[$pageParameterName] = $page; } - if (\is_bool($urlGenerationStrategy)) { - @trigger_error(sprintf('Passing a bool as 5th parameter to "%s::createIri()" is deprecated since API Platform 2.6. Pass an "%s" constant (int) instead.', __CLASS__, UrlGeneratorInterface::class), \E_USER_DEPRECATED); - $urlGenerationStrategy = $urlGenerationStrategy ? UrlGeneratorInterface::ABS_URL : UrlGeneratorInterface::ABS_PATH; - } - $query = http_build_query($parameters, '', '&', \PHP_QUERY_RFC3986); $parts['query'] = preg_replace('/%5B\d+%5D/', '%5B%5D', $query); @@ -113,5 +108,3 @@ public static function createIri(array $parts, array $parameters, string $pagePa return $url; } } - -class_alias(IriHelper::class, \ApiPlatform\Core\Util\IriHelper::class); diff --git a/src/Util/OperationRequestInitiatorTrait.php b/src/Util/OperationRequestInitiatorTrait.php index e2037c5a0bb..5360ef38d41 100644 --- a/src/Util/OperationRequestInitiatorTrait.php +++ b/src/Util/OperationRequestInitiatorTrait.php @@ -36,17 +36,14 @@ private function initializeOperation(Request $request): ?HttpOperation return $request->attributes->get('_api_operation'); } - // TODO: 3.0 $resourceMetadataCollectionFactory is mandatory if (null === $request->attributes->get('_api_resource_class') || null === $this->resourceMetadataCollectionFactory) { return null; } - // TODO: 3.0 remove collection/item - $operationName = $request->attributes->get('_api_operation_name') ?? $request->attributes->get('_api_collection_operation_name') ?? $request->attributes->get('_api_item_operation_name') ?? $request->attributes->get('_api_subresource_operation_name'); + $operationName = $request->attributes->get('_api_operation_name') ?? null; /** @var HttpOperation $operation */ $operation = $this->resourceMetadataCollectionFactory->create($request->attributes->get('_api_resource_class'))->getOperation($operationName); $request->attributes->set('_api_operation', $operation); - $request->attributes->set('_api_operation_name', $operationName); return $operation; } diff --git a/src/Util/Reflection.php b/src/Util/Reflection.php index 5029011cb2a..97a6880b41a 100644 --- a/src/Util/Reflection.php +++ b/src/Util/Reflection.php @@ -39,5 +39,3 @@ public function getProperty(string $methodName): ?string return null; } } - -class_alias(Reflection::class, \ApiPlatform\Core\Util\Reflection::class); diff --git a/src/Util/ReflectionClassRecursiveIterator.php b/src/Util/ReflectionClassRecursiveIterator.php index 10957a432ad..4fef953fc12 100644 --- a/src/Util/ReflectionClassRecursiveIterator.php +++ b/src/Util/ReflectionClassRecursiveIterator.php @@ -56,7 +56,11 @@ public static function getReflectionClassesFromDirectories(array $directories): } } - $declared = array_merge(get_declared_classes(), get_declared_interfaces()); + $sortedClasses = get_declared_classes(); + sort($sortedClasses); + $sortedInterfaces = get_declared_interfaces(); + sort($sortedInterfaces); + $declared = array_merge($sortedClasses, $sortedInterfaces); foreach ($declared as $className) { $reflectionClass = new \ReflectionClass($className); $sourceFile = $reflectionClass->getFileName(); @@ -66,5 +70,3 @@ public static function getReflectionClassesFromDirectories(array $directories): } } } - -class_alias(ReflectionClassRecursiveIterator::class, \ApiPlatform\Core\Util\ReflectionClassRecursiveIterator::class); diff --git a/src/Util/RequestAttributesExtractor.php b/src/Util/RequestAttributesExtractor.php index 47b041c8e91..8565e922e92 100644 --- a/src/Util/RequestAttributesExtractor.php +++ b/src/Util/RequestAttributesExtractor.php @@ -35,5 +35,3 @@ public static function extractAttributes(Request $request): array return AttributesExtractor::extractAttributes($request->attributes->all()); } } - -class_alias(RequestAttributesExtractor::class, \ApiPlatform\Core\Util\RequestAttributesExtractor::class); diff --git a/src/Util/RequestParser.php b/src/Util/RequestParser.php index fff1adcb805..32c3366c09c 100644 --- a/src/Util/RequestParser.php +++ b/src/Util/RequestParser.php @@ -92,5 +92,3 @@ public static function getQueryString(Request $request): ?string return implode('&', $parts); } } - -class_alias(RequestParser::class, \ApiPlatform\Core\Util\RequestParser::class); diff --git a/src/Util/ResourceClassInfoTrait.php b/src/Util/ResourceClassInfoTrait.php index cf43b091641..abc08f7a147 100644 --- a/src/Util/ResourceClassInfoTrait.php +++ b/src/Util/ResourceClassInfoTrait.php @@ -14,8 +14,6 @@ namespace ApiPlatform\Util; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Exception\ResourceClassNotFoundException; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; /** @@ -33,7 +31,7 @@ trait ResourceClassInfoTrait private $resourceClassResolver; /** - * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface|null + * @var ResourceMetadataCollectionFactoryInterface|null */ private $resourceMetadataFactory; @@ -66,22 +64,11 @@ private function isResourceClass(string $class): bool return $this->resourceClassResolver->isResourceClass($class); } - if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) { + if ($this->resourceMetadataFactory) { return \count($this->resourceMetadataFactory->create($class)) > 0 ? true : false; } - // TODO: 3.0 remove - if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface) { - try { - $this->resourceMetadataFactory->create($class); - } catch (ResourceClassNotFoundException $e) { - return false; - } - } - // assume that it's a resource class return true; } } - -class_alias(ResourceClassInfoTrait::class, \ApiPlatform\Core\Util\ResourceClassInfoTrait::class); diff --git a/src/Util/ResponseTrait.php b/src/Util/ResponseTrait.php index fbd0957f18d..5932b944e23 100644 --- a/src/Util/ResponseTrait.php +++ b/src/Util/ResponseTrait.php @@ -24,5 +24,3 @@ trait ResponseTrait use ResponseTrait80; } } - -class_alias(ResponseTrait::class, \ApiPlatform\Core\Util\ResponseTrait::class); diff --git a/src/Util/SortTrait.php b/src/Util/SortTrait.php index 97e20eb7d94..04a8a31917c 100644 --- a/src/Util/SortTrait.php +++ b/src/Util/SortTrait.php @@ -33,5 +33,3 @@ private function arrayRecursiveSort(array &$array, callable $sortFunction): void $sortFunction($array); } } - -class_alias(SortTrait::class, \ApiPlatform\Core\Util\SortTrait::class); diff --git a/src/Validator/Exception/ValidationException.php b/src/Validator/Exception/ValidationException.php index d7838312a3c..361eb200485 100644 --- a/src/Validator/Exception/ValidationException.php +++ b/src/Validator/Exception/ValidationException.php @@ -23,5 +23,3 @@ class ValidationException extends RuntimeException { } - -class_alias(ValidationException::class, \ApiPlatform\Core\Validator\Exception\ValidationException::class); diff --git a/src/deprecated_interfaces.php b/src/deprecated_interfaces.php deleted file mode 100644 index 63faa05d738..00000000000 --- a/src/deprecated_interfaces.php +++ /dev/null @@ -1,104 +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); - -return [ - ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface::class => ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface::class, - ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface::class => ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface::class, - ApiPlatform\Core\Metadata\Extractor\ExtractorInterface::class => ApiPlatform\Metadata\Extractor\ResourceExtractorInterface::class, - - // Bridge\Doctrine => Doctrine - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\DateFilterInterface::class => ApiPlatform\Doctrine\Common\Filter\DateFilterInterface::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterInterface::class => ApiPlatform\Doctrine\Common\Filter\ExistsFilterInterface::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterInterface::class => ApiPlatform\Doctrine\Common\Filter\OrderFilterInterface::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\RangeFilterInterface::class => ApiPlatform\Doctrine\Common\Filter\RangeFilterInterface::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterInterface::class => ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface::class, - - // Bridge\Doctrine\MongoDbOdm => Doctrine\Odm - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface::class => ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface::class => ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface::class => ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface::class => ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface::class, - - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\FilterInterface::class => ApiPlatform\Doctrine\Odm\Filter\FilterInterface::class, - - // Bridge\Doctrine\Orm => Doctrine\Orm - ApiPlatform\Core\Bridge\Doctrine\Orm\QueryAwareInterface::class => ApiPlatform\Doctrine\Orm\QueryAwareInterface::class, - - ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface::class => ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface::class, - - // Bridge\Elasticsearch => Elasticsearch - ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface::class => ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface::class, - - // Bridge\Symfony\Validator - ApiPlatform\Core\Bridge\Symfony\Validator\ValidationGroupsGeneratorInterface::class => ApiPlatform\Symfony\Validator\ValidationGroupsGeneratorInterface::class, - ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface::class => ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface::class, - ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface::class => ApiPlatform\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface::class, - - // DataProvider => State/Pagination - ApiPlatform\Core\DataProvider\PaginatorInterface::class => ApiPlatform\State\Pagination\PaginatorInterface::class, - ApiPlatform\Core\DataProvider\PartialPaginatorInterface::class => ApiPlatform\State\Pagination\PartialPaginatorInterface::class, - - // Documentation - ApiPlatform\Core\Documentation\DocumentationInterface::class => ApiPlatform\Documentation\DocumentationInterface::class, - - // JsonLd - ApiPlatform\Core\JsonLd\AnonymousContextBuilderInterface::class => ApiPlatform\JsonLd\AnonymousContextBuilderInterface::class, - ApiPlatform\Core\JsonLd\ContextBuilderInterface::class => ApiPlatform\JsonLd\ContextBuilderInterface::class, - - // JsonSchema - ApiPlatform\Core\JsonSchema\SchemaFactoryInterface::class => ApiPlatform\JsonSchema\SchemaFactoryInterface::class, - ApiPlatform\Core\JsonSchema\TypeFactoryInterface::class => ApiPlatform\JsonSchema\TypeFactoryInterface::class, - - // OpenApi - ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface::class => ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface::class, - - // PathResolver - ApiPlatform\Core\PathResolver\OperationPathResolverInterface::class => ApiPlatform\PathResolver\OperationPathResolverInterface::class, - - // Security => Symfony/Security - ApiPlatform\Core\Security\ResourceAccessCheckerInterface::class => ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface::class, - - // Serializer - ApiPlatform\Core\Serializer\Filter\FilterInterface::class => ApiPlatform\Serializer\Filter\FilterInterface::class, - ApiPlatform\Core\Serializer\SerializerContextBuilderInterface::class => ApiPlatform\Serializer\SerializerContextBuilderInterface::class, - - ApiPlatform\Core\Validator\ValidatorInterface::class => ApiPlatform\Validator\ValidatorInterface::class, - - // API: - ApiPlatform\Core\Api\ResourceClassResolverInterface::class => ApiPlatform\Api\ResourceClassResolverInterface::class, - ApiPlatform\Core\Api\UrlGeneratorInterface::class => ApiPlatform\Api\UrlGeneratorInterface::class, - - // GraphQl - ApiPlatform\Core\GraphQl\ExecutorInterface::class => ApiPlatform\GraphQl\ExecutorInterface::class, - ApiPlatform\Core\GraphQl\Error\ErrorHandlerInterface::class => ApiPlatform\GraphQl\Error\ErrorHandlerInterface::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\ValidateStageInterface::class => ApiPlatform\GraphQl\Resolver\Stage\ValidateStageInterface::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStageInterface::class => ApiPlatform\GraphQl\Resolver\Stage\ReadStageInterface::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface::class => ApiPlatform\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStageInterface::class => ApiPlatform\GraphQl\Resolver\Stage\SecurityStageInterface::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\WriteStageInterface::class => ApiPlatform\GraphQl\Resolver\Stage\WriteStageInterface::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface::class => ApiPlatform\GraphQl\Resolver\Stage\SerializeStageInterface::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\DeserializeStageInterface::class => ApiPlatform\GraphQl\Resolver\Stage\DeserializeStageInterface::class, - ApiPlatform\Core\GraphQl\Resolver\QueryItemResolverInterface::class => ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface::class, - ApiPlatform\Core\GraphQl\Resolver\QueryCollectionResolverInterface::class => ApiPlatform\GraphQl\Resolver\QueryCollectionResolverInterface::class, - ApiPlatform\Core\GraphQl\Resolver\Factory\ResolverFactoryInterface::class => ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface::class, - ApiPlatform\Core\GraphQl\Resolver\MutationResolverInterface::class => ApiPlatform\GraphQl\Resolver\MutationResolverInterface::class, - ApiPlatform\Core\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface::class => ApiPlatform\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface::class, - ApiPlatform\Core\GraphQl\Subscription\SubscriptionIdentifierGeneratorInterface::class => ApiPlatform\GraphQl\Subscription\SubscriptionIdentifierGeneratorInterface::class, - ApiPlatform\Core\GraphQl\Subscription\SubscriptionManagerInterface::class => ApiPlatform\GraphQl\Subscription\SubscriptionManagerInterface::class, - ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface::class => ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface::class, - ApiPlatform\Core\GraphQl\Type\TypesFactoryInterface::class => ApiPlatform\GraphQl\Type\TypesFactoryInterface::class, - ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface::class => ApiPlatform\GraphQl\Type\Definition\TypeInterface::class, - ApiPlatform\Core\GraphQl\Type\TypesContainerInterface::class => ApiPlatform\GraphQl\Type\TypesContainerInterface::class, - - ApiPlatform\Core\Operation\PathSegmentNameGeneratorInterface::class => ApiPlatform\Operation\PathSegmentNameGeneratorInterface::class, -]; diff --git a/src/deprecation.php b/src/deprecation.php deleted file mode 100644 index d809589016b..00000000000 --- a/src/deprecation.php +++ /dev/null @@ -1,477 +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); - -// Must be declared first! -class_alias(ApiPlatform\Api\FilterInterface::class, ApiPlatform\Core\Api\FilterInterface::class); -class_alias(ApiPlatform\Api\ResourceClassResolverInterface::class, ApiPlatform\Core\Api\ResourceClassResolverInterface::class); - -$deprecatedInterfaces = include 'deprecated_interfaces.php'; -foreach ($deprecatedInterfaces as $oldInterfaceName => $interfaceName) { - // Do not replace existing interface - if (interface_exists($oldInterfaceName)) { - continue; - } - - if (!interface_exists($interfaceName)) { - dump("interface $interfaceName does not exist, replacement $oldInterfaceName neither"); - continue; - } - - class_alias($interfaceName, $oldInterfaceName); -} - -$deprecatedClassesWithAliases = [ - ApiPlatform\Core\Api\Entrypoint::class => ApiPlatform\Api\Entrypoint::class, - ApiPlatform\Core\Api\FormatMatcher::class => ApiPlatform\Api\FormatMatcher::class, - ApiPlatform\Core\Api\ResourceClassResolver::class => ApiPlatform\Api\ResourceClassResolver::class, - - ApiPlatform\Core\Metadata\Property\PropertyNameCollection::class => ApiPlatform\Metadata\Property\PropertyNameCollection::class, - ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyNameCollectionFactory::class => ApiPlatform\Metadata\Property\Factory\CachedPropertyNameCollectionFactory::class, - ApiPlatform\Core\Metadata\Property\Factory\ExtractorPropertyNameCollectionFactory::class => ApiPlatform\Metadata\Property\Factory\ExtractorPropertyNameCollectionFactory::class, - - // Test cases - ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase::class => ApiPlatform\Symfony\Bundle\Test\ApiTestCase::class, - ApiPlatform\Core\Test\DoctrineMongoDbOdmFilterTestCase::class => ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase::class, - ApiPlatform\Core\Test\DoctrineMongoDbOdmTestCase::class => ApiPlatform\Test\DoctrineMongoDbOdmTestCase::class, - ApiPlatform\Core\Test\DoctrineOrmFilterTestCase::class => ApiPlatform\Test\DoctrineOrmFilterTestCase::class, - - // Exceptions - ApiPlatform\Core\Exception\DeserializationException::class => ApiPlatform\Exception\DeserializationException::class, - ApiPlatform\Core\Exception\FilterValidationException::class => ApiPlatform\Exception\FilterValidationException::class, - ApiPlatform\Core\Exception\InvalidArgumentException::class => ApiPlatform\Exception\InvalidArgumentException::class, - ApiPlatform\Core\Exception\InvalidIdentifierException::class => ApiPlatform\Exception\InvalidIdentifierException::class, - ApiPlatform\Core\Exception\InvalidResourceException::class => ApiPlatform\Exception\InvalidResourceException::class, - ApiPlatform\Core\Exception\InvalidValueException::class => ApiPlatform\Exception\InvalidValueException::class, - ApiPlatform\Core\Exception\PropertyNotFoundException::class => ApiPlatform\Exception\PropertyNotFoundException::class, - ApiPlatform\Core\Exception\RuntimeException::class => ApiPlatform\Exception\RuntimeException::class, - ApiPlatform\Core\Exception\ResourceClassNotFoundException::class => ApiPlatform\Exception\ResourceClassNotFoundException::class, - ApiPlatform\Core\Exception\ResourceClassNotSupportedException::class => ApiPlatform\Exception\ResourceClassNotSupportedException::class, - ApiPlatform\Core\Exception\ItemNotFoundException::class => ApiPlatform\Exception\ItemNotFoundException::class, - - // Action - ApiPlatform\Core\Action\EntrypointAction::class => ApiPlatform\Action\EntrypointAction::class, - ApiPlatform\Core\Action\ExceptionAction::class => ApiPlatform\Action\ExceptionAction::class, - ApiPlatform\Core\Action\NotFoundAction::class => ApiPlatform\Action\NotFoundAction::class, - ApiPlatform\Core\Action\PlaceholderAction::class => ApiPlatform\Action\PlaceholderAction::class, - - // Bridge\Doctrine\EventListener - ApiPlatform\Core\Bridge\Doctrine\EventListener\PurgeHttpCacheListener::class => ApiPlatform\Doctrine\EventListener\PurgeHttpCacheListener::class, - ApiPlatform\Core\Bridge\Doctrine\EventListener\WriteListener::class => ApiPlatform\Doctrine\EventListener\WriteListener::class, - - // Bridge\Elasticsearch - ApiPlatform\Core\Bridge\Elasticsearch\Exception\IndexNotFoundException::class => ApiPlatform\Elasticsearch\Exception\IndexNotFoundException::class, - ApiPlatform\Core\Bridge\Elasticsearch\Exception\NonUniqueIdentifierException::class => ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException::class, - - ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\AttributeDocumentMetadataFactory::class => ApiPlatform\Elasticsearch\Metadata\Document\Factory\AttributeDocumentMetadataFactory::class, - ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\CachedDocumentMetadataFactory::class => ApiPlatform\Elasticsearch\Metadata\Document\Factory\CachedDocumentMetadataFactory::class, - ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\CatDocumentMetadataFactory::class => ApiPlatform\Elasticsearch\Metadata\Document\Factory\CatDocumentMetadataFactory::class, - ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\ConfiguredDocumentMetadataFactory::class => ApiPlatform\Elasticsearch\Metadata\Document\Factory\ConfiguredDocumentMetadataFactory::class, - ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\DocumentMetadata::class => ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata::class, - - ApiPlatform\Core\Bridge\Elasticsearch\Serializer\NameConverter\InnerFieldsNameConverter::class => ApiPlatform\Elasticsearch\Serializer\NameConverter\InnerFieldsNameConverter::class, - ApiPlatform\Core\Bridge\Elasticsearch\Serializer\DocumentNormalizer::class => ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer::class, - ApiPlatform\Core\Bridge\Elasticsearch\Serializer\ItemNormalizer::class => ApiPlatform\Elasticsearch\Serializer\ItemNormalizer::class, - ApiPlatform\Core\Bridge\Elasticsearch\Util\FieldDatatypeTrait::class => ApiPlatform\Elasticsearch\Util\FieldDatatypeTrait::class, - - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Paginator::class => ApiPlatform\Elasticsearch\Paginator::class, - - // Bridge\RamseyUuid - ApiPlatform\Core\Bridge\RamseyUuid\Serializer\UuidDenormalizer::class => ApiPlatform\RamseyUuid\Serializer\UuidDenormalizer::class, - - // Bridge\Symfony\Bundle - ApiPlatform\Core\Bridge\Symfony\Bundle\ArgumentResolver\PayloadArgumentResolver::class => ApiPlatform\Symfony\Bundle\ArgumentResolver\PayloadArgumentResolver::class, - - ApiPlatform\Core\Bridge\Symfony\Bundle\CacheWarmer\CachePoolClearerCacheWarmer::class => ApiPlatform\Symfony\Bundle\CacheWarmer\CachePoolClearerCacheWarmer::class, - - ApiPlatform\Core\Bridge\Symfony\Bundle\Command\GraphQlExportCommand::class => ApiPlatform\Symfony\Bundle\Command\GraphQlExportCommand::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\Command\OpenApiCommand::class => ApiPlatform\Symfony\Bundle\Command\OpenApiCommand::class, - - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\AuthenticatorManagerPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AuthenticatorManagerPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DeprecateMercurePublisherPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DeprecateMercurePublisherPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\FilterPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\FilterPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\GraphQlMutationResolverPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlMutationResolverPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\GraphQlQueryResolverPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlQueryResolverPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass::class, - - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\ApiPlatformExtension::class => ApiPlatform\Symfony\Bundle\DependencyInjection\ApiPlatformExtension::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Configuration::class => ApiPlatform\Symfony\Bundle\DependencyInjection\Configuration::class, - - ApiPlatform\Core\Bridge\Symfony\Bundle\EventListener\SwaggerUiListener::class => ApiPlatform\Symfony\Bundle\EventListener\SwaggerUiListener::class, - - ApiPlatform\Core\Bridge\Symfony\Bundle\SwaggerUi\SwaggerUiAction::class => ApiPlatform\Symfony\Bundle\SwaggerUi\SwaggerUiAction::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\SwaggerUi\SwaggerUiContext::class => ApiPlatform\Symfony\Bundle\SwaggerUi\SwaggerUiContext::class, - - ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestAssertionsTrait::class => ApiPlatform\Symfony\Bundle\Test\ApiTestAssertionsTrait::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client::class => ApiPlatform\Symfony\Bundle\Test\Client::class, - ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Response::class => ApiPlatform\Symfony\Bundle\Test\Response::class, - - ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class, - - // Bridge\Symfony\Messenger - ApiPlatform\Core\Bridge\Symfony\Messenger\ContextStamp::class => ApiPlatform\Symfony\Messenger\ContextStamp::class, - ApiPlatform\Core\Bridge\Symfony\Messenger\DispatchTrait::class => ApiPlatform\Symfony\Messenger\DispatchTrait::class, - ApiPlatform\Core\Bridge\Symfony\Messenger\RemoveStamp::class => ApiPlatform\Symfony\Messenger\RemoveStamp::class, - - // Bridge\Symfony\PropertyInfo\Metadata\Property => Metadata\Property - ApiPlatform\Core\Bridge\Symfony\PropertyInfo\Metadata\Property\PropertyInfoPropertyNameCollectionFactory::class => ApiPlatform\Metadata\Property\Factory\PropertyInfoPropertyNameCollectionFactory::class, - - // Bridge\Symfony\Routing - ApiPlatform\Core\Bridge\Symfony\Routing\ApiLoader::class => ApiPlatform\Symfony\Routing\ApiLoader::class, - ApiPlatform\Core\Bridge\Symfony\Routing\Router::class => ApiPlatform\Symfony\Routing\Router::class, - - // Bridge\Symfony\Validator - ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ValidationExceptionListener::class => ApiPlatform\Symfony\Validator\EventListener\ValidationExceptionListener::class, - ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException::class => ApiPlatform\Symfony\Validator\Exception\ValidationException::class, - ApiPlatform\Core\Bridge\Symfony\Validator\Validator::class => ApiPlatform\Symfony\Validator\Validator::class, - - // Cache - ApiPlatform\Core\Cache\CachedTrait::class => ApiPlatform\Util\CachedTrait::class, - - // DataProvider => State/Pagination - ApiPlatform\Core\DataProvider\ArrayPaginator::class => ApiPlatform\State\Pagination\ArrayPaginator::class, - ApiPlatform\Core\DataProvider\PaginationOptions::class => ApiPlatform\State\Pagination\PaginationOptions::class, - ApiPlatform\Core\DataProvider\TraversablePaginator::class => ApiPlatform\State\Pagination\TraversablePaginator::class, - - // Documentation - ApiPlatform\Core\Documentation\Action\DocumentationAction::class => ApiPlatform\Documentation\Action\DocumentationAction::class, - ApiPlatform\Core\Documentation\Documentation::class => ApiPlatform\Documentation\Documentation::class, - - // EventListener => Symfony/EventListener - ApiPlatform\Core\EventListener\AddFormatListener::class => ApiPlatform\Symfony\EventListener\AddFormatListener::class, - ApiPlatform\Core\EventListener\DeserializeListener::class => ApiPlatform\Symfony\EventListener\DeserializeListener::class, - ApiPlatform\Core\EventListener\EventPriorities::class => ApiPlatform\Symfony\EventListener\EventPriorities::class, - ApiPlatform\Core\EventListener\ExceptionListener::class => ApiPlatform\Symfony\EventListener\ExceptionListener::class, - ApiPlatform\Core\EventListener\QueryParameterValidateListener::class => ApiPlatform\Symfony\EventListener\QueryParameterValidateListener::class, - ApiPlatform\Core\EventListener\RespondListener::class => ApiPlatform\Symfony\EventListener\RespondListener::class, - ApiPlatform\Core\EventListener\SerializeListener::class => ApiPlatform\Symfony\EventListener\SerializeListener::class, - - // Hal - ApiPlatform\Core\Hal\Serializer\CollectionNormalizer::class => ApiPlatform\Hal\Serializer\CollectionNormalizer::class, - ApiPlatform\Core\Hal\Serializer\EntrypointNormalizer::class => ApiPlatform\Hal\Serializer\EntrypointNormalizer::class, - ApiPlatform\Core\Hal\Serializer\ItemNormalizer::class => ApiPlatform\Hal\Serializer\ItemNormalizer::class, - ApiPlatform\Core\Hal\Serializer\ObjectNormalizer::class => ApiPlatform\Hal\Serializer\ObjectNormalizer::class, - - // HttpCache - ApiPlatform\Core\HttpCache\EventListener\AddHeadersListener::class => ApiPlatform\HttpCache\EventListener\AddHeadersListener::class, - ApiPlatform\Core\HttpCache\EventListener\AddTagsListener::class => ApiPlatform\HttpCache\EventListener\AddTagsListener::class, - ApiPlatform\Core\HttpCache\VarnishPurger::class => ApiPlatform\HttpCache\VarnishPurger::class, - ApiPlatform\Core\HttpCache\VarnishXKeyPurger::class => ApiPlatform\HttpCache\VarnishXKeyPurger::class, - - // Hydra - ApiPlatform\Core\Hydra\EventListener\AddLinkHeaderListener::class => ApiPlatform\Hydra\EventListener\AddLinkHeaderListener::class, - ApiPlatform\Core\Hydra\JsonSchema\SchemaFactory::class => ApiPlatform\Hydra\JsonSchema\SchemaFactory::class, - ApiPlatform\Core\Hydra\Serializer\CollectionFiltersNormalizer::class => ApiPlatform\Hydra\Serializer\CollectionFiltersNormalizer::class, - ApiPlatform\Core\Hydra\Serializer\CollectionNormalizer::class => ApiPlatform\Hydra\Serializer\CollectionNormalizer::class, - ApiPlatform\Core\Hydra\Serializer\ConstraintViolationListNormalizer::class => ApiPlatform\Hydra\Serializer\ConstraintViolationListNormalizer::class, - ApiPlatform\Core\Hydra\Serializer\DocumentationNormalizer::class => ApiPlatform\Hydra\Serializer\DocumentationNormalizer::class, - ApiPlatform\Core\Hydra\Serializer\EntrypointNormalizer::class => ApiPlatform\Hydra\Serializer\EntrypointNormalizer::class, - ApiPlatform\Core\Hydra\Serializer\ErrorNormalizer::class => ApiPlatform\Hydra\Serializer\ErrorNormalizer::class, - ApiPlatform\Core\Hydra\Serializer\PartialCollectionViewNormalizer::class => ApiPlatform\Hydra\Serializer\PartialCollectionViewNormalizer::class, - - // JsonApi/EventListener => Symfony/EventListener - ApiPlatform\Core\JsonApi\EventListener\TransformFieldsetsParametersListener::class => ApiPlatform\Symfony\EventListener\JsonApi\TransformFieldsetsParametersListener::class, - ApiPlatform\Core\JsonApi\EventListener\TransformFilteringParametersListener::class => ApiPlatform\Symfony\EventListener\JsonApi\TransformFilteringParametersListener::class, - ApiPlatform\Core\JsonApi\EventListener\TransformPaginationParametersListener::class => ApiPlatform\Symfony\EventListener\JsonApi\TransformPaginationParametersListener::class, - ApiPlatform\Core\JsonApi\EventListener\TransformSortingParametersListener::class => ApiPlatform\Symfony\EventListener\JsonApi\TransformSortingParametersListener::class, - - // JsonApi/Serializer - ApiPlatform\Core\JsonApi\Serializer\CollectionNormalizer::class => ApiPlatform\JsonApi\Serializer\CollectionNormalizer::class, - ApiPlatform\Core\JsonApi\Serializer\ConstraintViolationListNormalizer::class => ApiPlatform\JsonApi\Serializer\ConstraintViolationListNormalizer::class, - ApiPlatform\Core\JsonApi\Serializer\EntrypointNormalizer::class => ApiPlatform\JsonApi\Serializer\EntrypointNormalizer::class, - ApiPlatform\Core\JsonApi\Serializer\ErrorNormalizer::class => ApiPlatform\JsonApi\Serializer\ErrorNormalizer::class, - ApiPlatform\Core\JsonApi\Serializer\ItemNormalizer::class => ApiPlatform\JsonApi\Serializer\ItemNormalizer::class, - ApiPlatform\Core\JsonApi\Serializer\ObjectNormalizer::class => ApiPlatform\JsonApi\Serializer\ObjectNormalizer::class, - ApiPlatform\Core\JsonApi\Serializer\ReservedAttributeNameConverter::class => ApiPlatform\JsonApi\Serializer\ReservedAttributeNameConverter::class, - - // JsonLd - ApiPlatform\Core\JsonLd\Action\ContextAction::class => ApiPlatform\JsonLd\Action\ContextAction::class, - ApiPlatform\Core\JsonLd\Serializer\ItemNormalizer::class => ApiPlatform\JsonLd\Serializer\ItemNormalizer::class, - ApiPlatform\Core\JsonLd\Serializer\JsonLdContextTrait::class => ApiPlatform\JsonLd\Serializer\JsonLdContextTrait::class, - ApiPlatform\Core\JsonLd\Serializer\ObjectNormalizer::class => ApiPlatform\JsonLd\Serializer\ObjectNormalizer::class, - ApiPlatform\Core\JsonLd\ContextBuilder::class => ApiPlatform\JsonLd\ContextBuilder::class, - - // JsonSchema - ApiPlatform\Core\JsonSchema\Command\JsonSchemaGenerateCommand::class => ApiPlatform\JsonSchema\Command\JsonSchemaGenerateCommand::class, - ApiPlatform\Core\JsonSchema\Schema::class => ApiPlatform\JsonSchema\Schema::class, - ApiPlatform\Core\JsonSchema\TypeFactory::class => ApiPlatform\JsonSchema\TypeFactory::class, - - // Mercure/EventListener => Symfony/EventListener - ApiPlatform\Core\Mercure\EventListener\AddLinkHeaderListener::class => ApiPlatform\Symfony\EventListener\AddLinkHeaderListener::class, - - // OpenApi - ApiPlatform\Core\OpenApi\Model\Components::class => ApiPlatform\OpenApi\Model\Components::class, - ApiPlatform\Core\OpenApi\Model\Contact::class => ApiPlatform\OpenApi\Model\Contact::class, - ApiPlatform\Core\OpenApi\Model\Encoding::class => ApiPlatform\OpenApi\Model\Encoding::class, - ApiPlatform\Core\OpenApi\Model\ExtensionTrait::class => ApiPlatform\OpenApi\Model\ExtensionTrait::class, - ApiPlatform\Core\OpenApi\Model\ExternalDocumentation::class => ApiPlatform\OpenApi\Model\ExternalDocumentation::class, - ApiPlatform\Core\OpenApi\Model\Info::class => ApiPlatform\OpenApi\Model\Info::class, - ApiPlatform\Core\OpenApi\Model\License::class => ApiPlatform\OpenApi\Model\License::class, - ApiPlatform\Core\OpenApi\Model\Link::class => ApiPlatform\OpenApi\Model\Link::class, - ApiPlatform\Core\OpenApi\Model\MediaType::class => ApiPlatform\OpenApi\Model\MediaType::class, - ApiPlatform\Core\OpenApi\Model\OAuthFlow::class => ApiPlatform\OpenApi\Model\OAuthFlow::class, - ApiPlatform\Core\OpenApi\Model\OAuthFlows::class => ApiPlatform\OpenApi\Model\OAuthFlows::class, - ApiPlatform\Core\OpenApi\Model\Parameter::class => ApiPlatform\OpenApi\Model\Parameter::class, - ApiPlatform\Core\OpenApi\Model\Paths::class => ApiPlatform\OpenApi\Model\Paths::class, - ApiPlatform\Core\OpenApi\Model\PathItem::class => ApiPlatform\OpenApi\Model\PathItem::class, - ApiPlatform\Core\OpenApi\Model\Operation::class => ApiPlatform\OpenApi\Model\Operation::class, - ApiPlatform\Core\OpenApi\Model\RequestBody::class => ApiPlatform\OpenApi\Model\RequestBody::class, - ApiPlatform\Core\OpenApi\Model\Response::class => ApiPlatform\OpenApi\Model\Response::class, - ApiPlatform\Core\OpenApi\Model\Schema::class => ApiPlatform\OpenApi\Model\Schema::class, - ApiPlatform\Core\OpenApi\Model\SecurityScheme::class => ApiPlatform\OpenApi\Model\SecurityScheme::class, - ApiPlatform\Core\OpenApi\Model\Server::class => ApiPlatform\OpenApi\Model\Server::class, - ApiPlatform\Core\OpenApi\Serializer\OpenApiNormalizer::class => ApiPlatform\OpenApi\Serializer\OpenApiNormalizer::class, - ApiPlatform\Core\OpenApi\OpenApi::class => ApiPlatform\OpenApi\OpenApi::class, - ApiPlatform\Core\OpenApi\Options::class => ApiPlatform\OpenApi\Options::class, - - // PathResolver - ApiPlatform\Core\PathResolver\CustomOperationPathResolver::class => ApiPlatform\PathResolver\CustomOperationPathResolver::class, - ApiPlatform\Core\PathResolver\OperationPathResolver::class => ApiPlatform\PathResolver\OperationPathResolver::class, - - // Problem - ApiPlatform\Core\Problem\Serializer\ConstraintViolationListNormalizer::class => ApiPlatform\Problem\Serializer\ConstraintViolationListNormalizer::class, - ApiPlatform\Core\Problem\Serializer\ErrorNormalizer::class => ApiPlatform\Problem\Serializer\ErrorNormalizer::class, - ApiPlatform\Core\Problem\Serializer\ErrorNormalizerTrait::class => ApiPlatform\Problem\Serializer\ErrorNormalizerTrait::class, - - // Security => Symfony/Security - ApiPlatform\Core\Security\Core\Authorization\ExpressionLanguageProvider::class => ApiPlatform\Symfony\Security\Core\Authorization\ExpressionLanguageProvider::class, - ApiPlatform\Core\Security\ExpressionLanguage::class => ApiPlatform\Symfony\Security\ExpressionLanguage::class, - ApiPlatform\Core\Security\ResourceAccessChecker::class => ApiPlatform\Symfony\Security\ResourceAccessChecker::class, - - // Security/EventListener => Symfony/EventListener - ApiPlatform\Core\Security\EventListener\DenyAccessListener::class => ApiPlatform\Symfony\EventListener\DenyAccessListener::class, - - // Serializer - ApiPlatform\Core\Serializer\Filter\GroupFilter::class => ApiPlatform\Serializer\Filter\GroupFilter::class, - ApiPlatform\Core\Serializer\Filter\PropertyFilter::class => ApiPlatform\Serializer\Filter\PropertyFilter::class, - ApiPlatform\Core\Serializer\Mapping\Factory\ClassMetadataFactory::class => ApiPlatform\Serializer\Mapping\Factory\ClassMetadataFactory::class, - ApiPlatform\Core\Serializer\AbstractCollectionNormalizer::class => ApiPlatform\Serializer\AbstractCollectionNormalizer::class, - ApiPlatform\Core\Serializer\AbstractConstraintViolationListNormalizer::class => ApiPlatform\Serializer\AbstractConstraintViolationListNormalizer::class, - ApiPlatform\Core\Serializer\AbstractItemNormalizer::class => ApiPlatform\Serializer\AbstractItemNormalizer::class, - ApiPlatform\Core\Serializer\CacheKeyTrait::class => ApiPlatform\Serializer\CacheKeyTrait::class, - ApiPlatform\Core\Serializer\ContextTrait::class => ApiPlatform\Serializer\ContextTrait::class, - ApiPlatform\Core\Serializer\InputOutputMetadataTrait::class => ApiPlatform\Serializer\InputOutputMetadataTrait::class, - ApiPlatform\Core\Serializer\ItemNormalizer::class => ApiPlatform\Serializer\ItemNormalizer::class, - ApiPlatform\Core\Serializer\JsonEncoder::class => ApiPlatform\Serializer\JsonEncoder::class, - ApiPlatform\Core\Serializer\ResourceList::class => ApiPlatform\Serializer\ResourceList::class, - ApiPlatform\Core\Serializer\SerializerContextBuilder::class => ApiPlatform\Serializer\SerializerContextBuilder::class, - ApiPlatform\Core\Serializer\SerializerFilterContextBuilder::class => ApiPlatform\Serializer\SerializerFilterContextBuilder::class, - - // Swagger/Serializer => OpenApi/Serializer - ApiPlatform\Core\Swagger\Serializer\ApiGatewayNormalizer::class => ApiPlatform\OpenApi\Serializer\ApiGatewayNormalizer::class, - - // Test - ApiPlatform\Core\Test\DoctrineMongoDbOdmSetup::class => ApiPlatform\Test\DoctrineMongoDbOdmSetup::class, - - // Util - ApiPlatform\Core\Util\AnnotationFilterExtractorTrait::class => ApiPlatform\Util\AnnotationFilterExtractorTrait::class, - ApiPlatform\Core\Util\ArrayTrait::class => ApiPlatform\Util\ArrayTrait::class, - ApiPlatform\Core\Util\AttributesExtractor::class => ApiPlatform\Util\AttributesExtractor::class, - ApiPlatform\Core\Util\ClassInfoTrait::class => ApiPlatform\Util\ClassInfoTrait::class, - ApiPlatform\Core\Util\ClientTrait::class => ApiPlatform\Util\ClientTrait::class, - ApiPlatform\Core\Util\CloneTrait::class => ApiPlatform\Util\CloneTrait::class, - ApiPlatform\Core\Util\CorsTrait::class => ApiPlatform\Util\CorsTrait::class, - ApiPlatform\Core\Util\ErrorFormatGuesser::class => ApiPlatform\Util\ErrorFormatGuesser::class, - ApiPlatform\Core\Util\Inflector::class => ApiPlatform\Util\Inflector::class, - ApiPlatform\Core\Util\IriHelper::class => ApiPlatform\Util\IriHelper::class, - ApiPlatform\Core\Util\Reflection::class => ApiPlatform\Util\Reflection::class, - ApiPlatform\Core\Util\ReflectionClassRecursiveIterator::class => ApiPlatform\Util\ReflectionClassRecursiveIterator::class, - ApiPlatform\Core\Util\RequestAttributesExtractor::class => ApiPlatform\Util\RequestAttributesExtractor::class, - ApiPlatform\Core\Util\RequestParser::class => ApiPlatform\Util\RequestParser::class, - ApiPlatform\Core\Util\ResourceClassInfoTrait::class => ApiPlatform\Util\ResourceClassInfoTrait::class, - ApiPlatform\Core\Util\ResponseTrait::class => ApiPlatform\Util\ResponseTrait::class, - ApiPlatform\Core\Util\SortTrait::class => ApiPlatform\Util\SortTrait::class, - - // Validator - ApiPlatform\Core\Validator\EventListener\ValidateListener::class => ApiPlatform\Symfony\EventListener\ValidateListener::class, - ApiPlatform\Core\Validator\Exception\ValidationException::class => ApiPlatform\Validator\Exception\ValidationException::class, - - ApiPlatform\Core\Filter\QueryParameterValidator::class => ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator::class, - ApiPlatform\Core\Filter\Validator\ArrayItems::class => ApiPlatform\Api\QueryParameterValidator\Validator\ArrayItems::class, - ApiPlatform\Core\Filter\Validator\Bounds::class => ApiPlatform\Api\QueryParameterValidator\Validator\Bounds::class, - ApiPlatform\Core\Filter\Validator\Enum::class => ApiPlatform\Api\QueryParameterValidator\Validator\Enum::class, - ApiPlatform\Core\Filter\Validator\Length::class => ApiPlatform\Api\QueryParameterValidator\Validator\Length::class, - ApiPlatform\Core\Filter\Validator\MultipleOf::class => ApiPlatform\Api\QueryParameterValidator\Validator\MultipleOf::class, - ApiPlatform\Core\Filter\Validator\Pattern::class => ApiPlatform\Api\QueryParameterValidator\Validator\Pattern::class, - ApiPlatform\Core\Filter\Validator\Required::class => ApiPlatform\Api\QueryParameterValidator\Validator\Required::class, - ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator::class => ApiPlatform\Operation\UnderscorePathSegmentNameGenerator::class, - ApiPlatform\Core\Operation\DashPathSegmentNameGenerator::class => ApiPlatform\Operation\DashPathSegmentNameGenerator::class, - - // GraphQl - ApiPlatform\Core\GraphQl\Executor::class => ApiPlatform\GraphQl\Executor::class, - ApiPlatform\Core\GraphQl\Error\ErrorHandler::class => ApiPlatform\GraphQl\Error\ErrorHandler::class, - ApiPlatform\Core\GraphQl\Resolver\Util\IdentifierTrait::class => ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait::class, - ApiPlatform\Core\GraphQl\Action\EntrypointAction::class => ApiPlatform\GraphQl\Action\EntrypointAction::class, - ApiPlatform\Core\GraphQl\Action\GraphiQlAction::class => ApiPlatform\GraphQl\Action\GraphiQlAction::class, - ApiPlatform\Core\GraphQl\Action\GraphQlPlaygroundAction::class => ApiPlatform\GraphQl\Action\GraphQlPlaygroundAction::class, - ApiPlatform\Core\GraphQl\Type\TypeNotFoundException::class => ApiPlatform\GraphQl\Type\TypeNotFoundException::class, - ApiPlatform\Core\GraphQl\Type\TypesFactory::class => ApiPlatform\GraphQl\Type\TypesFactory::class, - ApiPlatform\Core\GraphQl\Type\Definition\UploadType::class => ApiPlatform\GraphQl\Type\Definition\UploadType::class, - ApiPlatform\Core\GraphQl\Type\Definition\IterableType::class => ApiPlatform\GraphQl\Type\Definition\IterableType::class, - ApiPlatform\Core\GraphQl\Type\TypesContainer::class => ApiPlatform\GraphQl\Type\TypesContainer::class, - - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\PropertyInfo\DoctrineExtractor::class => ApiPlatform\Doctrine\Odm\PropertyInfo\DoctrineExtractor::class, -]; - -// These classes are deprecated but we don't want aliases as the interfaces changed -$deprecatedClassesWithoutAliases = [ - ApiPlatform\Core\JsonSchema\SchemaFactory::class => ApiPlatform\JsonSchema\SchemaFactory::class, - ApiPlatform\Core\Api\FilterLocatorTrait::class => ApiPlatform\Api\FilterLocatorTrait::class, - - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\AbstractFilter::class => ApiPlatform\Elasticsearch\Filter\AbstractFilter::class, - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\AbstractSearchFilter::class => ApiPlatform\Elasticsearch\Filter\AbstractSearchFilter::class, - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\MatchFilter::class => ApiPlatform\Elasticsearch\Filter\MatchFilter::class, - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\OrderFilter::class => ApiPlatform\Elasticsearch\Filter\OrderFilter::class, - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\TermFilter::class => ApiPlatform\Elasticsearch\Filter\TermFilter::class, - - ApiPlatform\Core\DataProvider\Pagination::class => ApiPlatform\State\Pagination\Pagination::class, - - ApiPlatform\Core\Metadata\Property\Factory\SerializerPropertyMetadataFactory::class => ApiPlatform\Metadata\Property\Factory\SerializerPropertyMetadataFactory::class, - ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyMetadataFactory::class => ApiPlatform\Metadata\Property\Factory\CachedPropertyMetadataFactory::class, - ApiPlatform\Core\Metadata\Property\Factory\ExtractorPropertyMetadataFactory::class => ApiPlatform\Metadata\Property\Factory\ExtractorPropertyMetadataFactory::class, - ApiPlatform\Core\Metadata\Property\Factory\DefaultPropertyMetadataFactory::class => ApiPlatform\Metadata\Property\Factory\DefaultPropertyMetadataFactory::class, - - ApiPlatform\Core\Metadata\Extractor\XmlExtractor::class => ApiPlatform\Metadata\Extractor\XmlResourceExtractor::class, - ApiPlatform\Core\Metadata\Extractor\YamlExtractor::class => ApiPlatform\Metadata\Extractor\YamlResourceExtractor::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Metadata\Property\DoctrineMongoDbOdmPropertyMetadataFactory::class => ApiPlatform\Doctrine\Odm\Metadata\Property\DoctrineMongoDbOdmPropertyMetadataFactory::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Metadata\Property\DoctrineOrmPropertyMetadataFactory::class => ApiPlatform\Doctrine\Orm\Metadata\Property\DoctrineOrmPropertyMetadataFactory::class, - ApiPlatform\Core\Bridge\Symfony\PropertyInfo\Metadata\Property\PropertyInfoPropertyMetadataFactory::class => ApiPlatform\Metadata\Property\Factory\PropertyInfoPropertyMetadataFactory::class, - ApiPlatform\Core\Metadata\Property\PropertyMetadata::class => ApiPlatform\Metadata\ApiProperty::class, - - ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStage::class => ApiPlatform\GraphQl\Resolver\Stage\ReadStage::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\WriteStage::class => ApiPlatform\GraphQl\Resolver\Stage\WriteStage::class, - ApiPlatform\Core\GraphQl\Type\TypeConverter::class => ApiPlatform\GraphQl\Type\TypeConverter::class, - ApiPlatform\Core\GraphQl\Type\TypeBuilder::class => ApiPlatform\GraphQl\Type\TypeBuilder::class, - ApiPlatform\Core\GraphQl\Type\FieldsBuilder::class => ApiPlatform\GraphQl\Type\FieldsBuilder::class, - ApiPlatform\Core\GraphQl\Type\SchemaBuilder::class => ApiPlatform\GraphQl\Type\SchemaBuilder::class, - ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilder::class => ApiPlatform\GraphQl\Serializer\SerializerContextBuilder::class, - ApiPlatform\Core\GraphQl\Resolver\Factory\ItemResolverFactory::class => ApiPlatform\GraphQl\Resolver\Factory\ItemResolverFactory::class, - ApiPlatform\Core\GraphQl\Resolver\Factory\CollectionResolverFactory::class => ApiPlatform\GraphQl\Resolver\Factory\CollectionResolverFactory::class, - ApiPlatform\Core\GraphQl\Resolver\Factory\ItemMutationResolverFactory::class => ApiPlatform\GraphQl\Resolver\Factory\ItemMutationResolverFactory::class, - ApiPlatform\Core\GraphQl\Resolver\Factory\ItemSubscriptionResolverFactory::class => ApiPlatform\GraphQl\Resolver\Factory\ItemSubscriptionResolverFactory::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStage::class => ApiPlatform\GraphQl\Resolver\Stage\SecurityStage::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityPostDenormalizeStage::class => ApiPlatform\GraphQl\Resolver\Stage\SecurityPostDenormalizeStage::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStage::class => ApiPlatform\GraphQl\Resolver\Stage\SerializeStage::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\DeserializeStage::class => ApiPlatform\GraphQl\Resolver\Stage\DeserializeStage::class, - ApiPlatform\Core\GraphQl\Resolver\Stage\ValidateStage::class => ApiPlatform\GraphQl\Resolver\Stage\ValidateStage::class, - ApiPlatform\Core\GraphQl\Resolver\ResourceFieldResolver::class => ApiPlatform\GraphQl\Resolver\ResourceFieldResolver::class, - ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer::class => ApiPlatform\GraphQl\Serializer\ItemNormalizer::class, - ApiPlatform\Core\GraphQl\Serializer\ObjectNormalizer::class => ApiPlatform\GraphQl\Serializer\ObjectNormalizer::class, - ApiPlatform\Core\GraphQl\Subscription\SubscriptionManager::class => ApiPlatform\GraphQl\Subscription\SubscriptionManager::class, - ApiPlatform\Core\GraphQl\Serializer\Exception\ErrorNormalizer::class => ApiPlatform\GraphQl\Serializer\Exception\ErrorNormalizer::class, - ApiPlatform\Core\GraphQl\Serializer\Exception\ValidationExceptionNormalizer::class => ApiPlatform\GraphQl\Serializer\Exception\ValidationExceptionNormalizer::class, - ApiPlatform\Core\GraphQl\Serializer\Exception\HttpExceptionNormalizer::class => ApiPlatform\GraphQl\Serializer\Exception\HttpExceptionNormalizer::class, - ApiPlatform\Core\GraphQl\Serializer\Exception\RuntimeExceptionNormalizer::class => ApiPlatform\GraphQl\Serializer\Exception\RuntimeExceptionNormalizer::class, - ApiPlatform\Core\GraphQl\Subscription\SubscriptionIdentifierGenerator::class => ApiPlatform\GraphQl\Subscription\SubscriptionIdentifierGenerator::class, - ApiPlatform\Core\GraphQl\Subscription\MercureSubscriptionIriGenerator::class => ApiPlatform\GraphQl\Subscription\MercureSubscriptionIriGenerator::class, - - ApiPlatform\Core\Bridge\Doctrine\EventListener\PublishMercureUpdatesListener::class => ApiPlatform\Doctrine\EventListener\PublishMercureUpdatesListener::class, - - ApiPlatform\Core\HttpCache\PurgerInterface::class => ApiPlatform\HttpCache\PurgerInterface::class, - - // Bridge\Doctrine - ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait::class => ApiPlatform\Doctrine\Common\PropertyHelperTrait::class, - - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\BooleanFilterTrait::class => ApiPlatform\Doctrine\Common\Filter\BooleanFilterTrait::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\DateFilterTrait::class => ApiPlatform\Doctrine\Common\Filter\DateFilterTrait::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterTrait::class => ApiPlatform\Doctrine\Common\Filter\ExistsFilterTrait::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\NumericFilterTrait::class => ApiPlatform\Doctrine\Common\Filter\NumericFilterTrait::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterTrait::class => ApiPlatform\Doctrine\Common\Filter\OrderFilterTrait::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\RangeFilterTrait::class => ApiPlatform\Doctrine\Common\Filter\RangeFilterTrait::class, - ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterTrait::class => ApiPlatform\Doctrine\Common\Filter\SearchFilterTrait::class, - - // Bridge\Doctrine\MongoDbOdm - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\FilterExtension::class => ApiPlatform\Doctrine\Odm\Extension\FilterExtension::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\OrderExtension::class => ApiPlatform\Doctrine\Orm\Extension\OrderExtension::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\PaginationExtension::class => ApiPlatform\Doctrine\Orm\Extension\PaginationExtension::class, - - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\AbstractFilter::class => ApiPlatform\Doctrine\Odm\Filter\AbstractFilter::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\BooleanFilter::class => ApiPlatform\Doctrine\Odm\Filter\BooleanFilter::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\DateFilter::class => ApiPlatform\Doctrine\Odm\Filter\DateFilter::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\ExistsFilter::class => ApiPlatform\Doctrine\Odm\Filter\ExistsFilter::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\NumericFilter::class => ApiPlatform\Doctrine\Odm\Filter\NumericFilter::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\OrderFilter::class => ApiPlatform\Doctrine\Odm\Filter\OrderFilter::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\RangeFilter::class => ApiPlatform\Doctrine\Odm\Filter\RangeFilter::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\SearchFilter::class => ApiPlatform\Doctrine\Odm\Filter\SearchFilter::class, - - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Paginator::class => ApiPlatform\Doctrine\Odm\Paginator::class, - ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\PropertyHelperTrait::class => ApiPlatform\Doctrine\Odm\PropertyHelperTrait::class, - - // Bridge\Doctrine\Orm - ApiPlatform\Core\Bridge\Doctrine\Orm\AbstractPaginator::class => ApiPlatform\Doctrine\Orm\AbstractPaginator::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator::class => ApiPlatform\Doctrine\Orm\Paginator::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\PropertyHelperTrait::class => ApiPlatform\Doctrine\Orm\PropertyHelperTrait::class, - - ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\EagerLoadingExtension::class => ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterEagerLoadingExtension::class => ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterExtension::class => ApiPlatform\Doctrine\Orm\Extension\FilterExtension::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\OrderExtension::class => ApiPlatform\Doctrine\Orm\Extension\OrderExtension::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\PaginationExtension::class => ApiPlatform\Doctrine\Orm\Extension\PaginationExtension::class, - - ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter::class => ApiPlatform\Doctrine\Orm\Filter\AbstractFilter::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter::class => ApiPlatform\Doctrine\Orm\Filter\BooleanFilter::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter::class => ApiPlatform\Doctrine\Orm\Filter\DateFilter::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\ExistsFilter::class => ApiPlatform\Doctrine\Orm\Filter\ExistsFilter::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\NumericFilter::class => ApiPlatform\Doctrine\Orm\Filter\NumericFilter::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter::class => ApiPlatform\Doctrine\Orm\Filter\OrderFilter::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter::class => ApiPlatform\Doctrine\Orm\Filter\RangeFilter::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter::class => ApiPlatform\Doctrine\Orm\Filter\SearchFilter::class, - - ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper::class => ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryChecker::class => ApiPlatform\Doctrine\Orm\Util\QueryChecker::class, - ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator::class => ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator::class, - - // Bridge\Elasticsearch - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension\AbstractFilterExtension::class => ApiPlatform\Elasticsearch\Extension\AbstractFilterExtension::class, - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension\ConstantScoreFilterExtension::class => ApiPlatform\Elasticsearch\Extension\ConstantScoreFilterExtension::class, - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension\SortExtension::class => ApiPlatform\Elasticsearch\Extension\SortExtension::class, - ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension\SortFilterExtension::class => ApiPlatform\Elasticsearch\Extension\SortFilterExtension::class, -]; - -spl_autoload_register(function ($className) use ($deprecatedInterfaces, $deprecatedClassesWithoutAliases, $deprecatedClassesWithAliases) { - // We can not class alias when working with doctrine annotations - static $deprecatedAnnotations = [ - 'ApiResource' => [ApiPlatform\Core\Annotation\ApiResource::class, ApiPlatform\Metadata\ApiResource::class], - 'ApiProperty' => [ApiPlatform\Core\Annotation\ApiProperty::class, ApiPlatform\Metadata\ApiProperty::class], - 'ApiFilter' => [ApiPlatform\Core\Annotation\ApiFilter::class, ApiPlatform\Metadata\ApiFilter::class], - ]; - - if (isset($deprecatedClassesWithoutAliases[$className])) { - trigger_deprecation('api-platform/core', '2.7', sprintf('The class %s is deprecated, use %s instead.', $className, $deprecatedClassesWithoutAliases[$className])); - - return; - } - - if (isset($deprecatedClassesWithAliases[$className])) { - trigger_deprecation('api-platform/core', '2.7', sprintf('The class %s is deprecated, use %s instead.', $className, $deprecatedClassesWithAliases[$className])); - - class_alias($deprecatedClassesWithAliases[$className], $className); - - return; - } - - if (isset($deprecatedAnnotations[$className])) { - [$annotationClassName, $attributeClassName] = $deprecatedAnnotations[$className]; - trigger_deprecation('api-platform/core', '2.7', sprintf('The Doctrine annotation %s is deprecated, use the PHP attribute %s instead.', $annotationClassName, $attributeClassName)); - - return; - } - - if (isset($deprecatedInterfaces[$className])) { - trigger_deprecation('api-platform/core', '2.7', sprintf('The interface %s is deprecated, use %s instead.', $className, $deprecatedInterfaces[$className])); - } -}); diff --git a/tests/Action/EntrypointActionTest.php b/tests/Action/EntrypointActionTest.php index 1ee49540df1..048aa110d5c 100644 --- a/tests/Action/EntrypointActionTest.php +++ b/tests/Action/EntrypointActionTest.php @@ -15,9 +15,9 @@ use ApiPlatform\Action\EntrypointAction; use ApiPlatform\Api\Entrypoint; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceNameCollection; +use ApiPlatform\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Action/ExceptionActionTest.php b/tests/Action/ExceptionActionTest.php index a33229d637d..f3d28abedaf 100644 --- a/tests/Action/ExceptionActionTest.php +++ b/tests/Action/ExceptionActionTest.php @@ -14,9 +14,6 @@ namespace ApiPlatform\Tests\Action; use ApiPlatform\Action\ExceptionAction; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; @@ -24,6 +21,7 @@ use ApiPlatform\Metadata\Operations; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\Tests\ProphecyTrait; use DomainException; use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; @@ -38,7 +36,6 @@ * @author Amrouche Hamza * @author Baptiste Meyer * - * @group legacy * @group time-sensitive */ class ExceptionActionTest extends TestCase @@ -69,62 +66,6 @@ public function testActionWithCatchableException() $this->assertTrue($response->headers->contains('X-Frame-Options', 'deny')); } - /** - * @dataProvider provideOperationExceptionToStatusCases - * @group legacy - */ - public function testLegacyActionWithOperationExceptionToStatus( - array $globalExceptionToStatus, - ?array $resourceExceptionToStatus, - ?array $operationExceptionToStatus, - int $expectedStatusCode - ) { - $this->expectDeprecation('Since api-platform/core 2.7: Use "ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface" instead of "ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface".'); - - $exception = new DomainException(); - $flattenException = FlattenException::create($exception); - - $serializer = $this->prophesize(SerializerInterface::class); - $serializer->serialize($flattenException, 'jsonproblem', ['statusCode' => $expectedStatusCode])->willReturn(''); - - $resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactory->create('Foo')->willReturn(new ResourceMetadata( - 'Foo', - null, - null, - [ - 'operation' => null !== $operationExceptionToStatus ? ['exception_to_status' => $operationExceptionToStatus] : [], - ], - null, - null !== $resourceExceptionToStatus ? ['exception_to_status' => $resourceExceptionToStatus] : [] - )); - - $exceptionAction = new ExceptionAction( - $serializer->reveal(), - [ - 'jsonproblem' => ['application/problem+json'], - 'jsonld' => ['application/ld+json'], - ], - $globalExceptionToStatus, - $resourceMetadataFactory->reveal() - ); - - $request = new Request(); - $request->setFormat('jsonproblem', 'application/problem+json'); - $request->attributes->replace([ - '_api_resource_class' => 'Foo', - '_api_item_operation_name' => 'operation', - ]); - - $response = $exceptionAction($flattenException, $request); - - $this->assertSame('', $response->getContent()); - $this->assertSame($expectedStatusCode, $response->getStatusCode()); - $this->assertTrue($response->headers->contains('Content-Type', 'application/problem+json; charset=utf-8')); - $this->assertTrue($response->headers->contains('X-Content-Type-Options', 'nosniff')); - $this->assertTrue($response->headers->contains('X-Frame-Options', 'deny')); - } - /** * @dataProvider provideOperationExceptionToStatusCases */ diff --git a/tests/Core/Identifier/CompositeIdentifierParserTest.php b/tests/Api/CompositeIdentifierParserTest.php similarity index 95% rename from tests/Core/Identifier/CompositeIdentifierParserTest.php rename to tests/Api/CompositeIdentifierParserTest.php index 79e87b99e0d..af119464b8b 100644 --- a/tests/Core/Identifier/CompositeIdentifierParserTest.php +++ b/tests/Api/CompositeIdentifierParserTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Identifier; +namespace ApiPlatform\Tests\Api; -use ApiPlatform\Core\Identifier\CompositeIdentifierParser; +use ApiPlatform\Api\CompositeIdentifierParser; use PHPUnit\Framework\TestCase; class CompositeIdentifierParserTest extends TestCase diff --git a/tests/Api/IdentifiersExtractorTest.php b/tests/Api/IdentifiersExtractorTest.php index 762d699d32b..0c4f194e0f9 100644 --- a/tests/Api/IdentifiersExtractorTest.php +++ b/tests/Api/IdentifiersExtractorTest.php @@ -15,7 +15,6 @@ use ApiPlatform\Api\IdentifiersExtractor; use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Link; @@ -23,6 +22,7 @@ use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Api/QueryParameterValidator/QueryParameterValidatorTest.php b/tests/Api/QueryParameterValidator/QueryParameterValidatorTest.php index c076153b81a..fa33da2bf0c 100644 --- a/tests/Api/QueryParameterValidator/QueryParameterValidatorTest.php +++ b/tests/Api/QueryParameterValidator/QueryParameterValidatorTest.php @@ -15,9 +15,9 @@ use ApiPlatform\Api\FilterInterface; use ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Exception\FilterValidationException; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Tests\ProphecyTrait; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; diff --git a/tests/Core/Behat/CommandContext.php b/tests/Behat/CommandContext.php similarity index 98% rename from tests/Core/Behat/CommandContext.php rename to tests/Behat/CommandContext.php index c1417b51443..5afe54b009e 100644 --- a/tests/Core/Behat/CommandContext.php +++ b/tests/Behat/CommandContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; use Behat\Gherkin\Node\PyStringNode; diff --git a/tests/Core/Behat/CoverageContext.php b/tests/Behat/CoverageContext.php similarity index 97% rename from tests/Core/Behat/CoverageContext.php rename to tests/Behat/CoverageContext.php index 2143d4e6bf1..ed16a536527 100644 --- a/tests/Core/Behat/CoverageContext.php +++ b/tests/Behat/CoverageContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\BeforeScenarioScope; diff --git a/tests/Core/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php similarity index 99% rename from tests/Core/Behat/DoctrineContext.php rename to tests/Behat/DoctrineContext.php index 7707d44e478..cea400d57ba 100644 --- a/tests/Core/Behat/DoctrineContext.php +++ b/tests/Behat/DoctrineContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use ApiPlatform\Tests\Fixtures\TestBundle\Doctrine\Orm\EntityManager; use ApiPlatform\Tests\Fixtures\TestBundle\Document\AbsoluteUrlDummy as AbsoluteUrlDummyDocument; diff --git a/tests/Core/Behat/ElasticsearchContext.php b/tests/Behat/ElasticsearchContext.php similarity index 98% rename from tests/Core/Behat/ElasticsearchContext.php rename to tests/Behat/ElasticsearchContext.php index 94a185f86e0..a11435d0889 100644 --- a/tests/Core/Behat/ElasticsearchContext.php +++ b/tests/Behat/ElasticsearchContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use Behat\Behat\Context\Context; diff --git a/tests/Core/Behat/GraphqlContext.php b/tests/Behat/GraphqlContext.php similarity index 99% rename from tests/Core/Behat/GraphqlContext.php rename to tests/Behat/GraphqlContext.php index a0621f3996c..73e90212104 100644 --- a/tests/Core/Behat/GraphqlContext.php +++ b/tests/Behat/GraphqlContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; use Behat\Behat\Context\Environment\InitializedContextEnvironment; diff --git a/tests/Core/Behat/HttpCacheContext.php b/tests/Behat/HttpCacheContext.php similarity index 96% rename from tests/Core/Behat/HttpCacheContext.php rename to tests/Behat/HttpCacheContext.php index 26e49e83e20..5eb40c5f4eb 100644 --- a/tests/Core/Behat/HttpCacheContext.php +++ b/tests/Behat/HttpCacheContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; use PHPUnit\Framework\ExpectationFailedException; diff --git a/tests/Core/Behat/HydraContext.php b/tests/Behat/HydraContext.php similarity index 99% rename from tests/Core/Behat/HydraContext.php rename to tests/Behat/HydraContext.php index 9ac151d6148..27fe569cb0c 100644 --- a/tests/Core/Behat/HydraContext.php +++ b/tests/Behat/HydraContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; use Behat\Behat\Context\Environment\InitializedContextEnvironment; diff --git a/tests/Core/Behat/JsonApiContext.php b/tests/Behat/JsonApiContext.php similarity index 99% rename from tests/Core/Behat/JsonApiContext.php rename to tests/Behat/JsonApiContext.php index 87855f328bb..e4c17635e87 100644 --- a/tests/Core/Behat/JsonApiContext.php +++ b/tests/Behat/JsonApiContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use ApiPlatform\Tests\Fixtures\TestBundle\Document\CircularReference as CircularReferenceDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\DummyFriend as DummyFriendDocument; diff --git a/tests/Core/Behat/JsonContext.php b/tests/Behat/JsonContext.php similarity index 98% rename from tests/Core/Behat/JsonContext.php rename to tests/Behat/JsonContext.php index 944dcec738d..059df954d78 100644 --- a/tests/Core/Behat/JsonContext.php +++ b/tests/Behat/JsonContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use Behat\Gherkin\Node\PyStringNode; diff --git a/tests/Core/Behat/JsonHalContext.php b/tests/Behat/JsonHalContext.php similarity index 98% rename from tests/Core/Behat/JsonHalContext.php rename to tests/Behat/JsonHalContext.php index fdab6e63737..f1f3c890fdf 100644 --- a/tests/Core/Behat/JsonHalContext.php +++ b/tests/Behat/JsonHalContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; use Behat\Behat\Context\Environment\InitializedContextEnvironment; diff --git a/tests/Core/Behat/MercureContext.php b/tests/Behat/MercureContext.php similarity index 97% rename from tests/Core/Behat/MercureContext.php rename to tests/Behat/MercureContext.php index 2ee18723932..35b2e213bbc 100644 --- a/tests/Core/Behat/MercureContext.php +++ b/tests/Behat/MercureContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; use Behat\Gherkin\Node\PyStringNode; diff --git a/tests/Core/Behat/OpenApiContext.php b/tests/Behat/OpenApiContext.php similarity index 99% rename from tests/Core/Behat/OpenApiContext.php rename to tests/Behat/OpenApiContext.php index 2ddc5ed1875..d53c529fc4b 100644 --- a/tests/Core/Behat/OpenApiContext.php +++ b/tests/Behat/OpenApiContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; use Behat\Behat\Context\Environment\InitializedContextEnvironment; diff --git a/tests/Core/Behat/XmlContext.php b/tests/Behat/XmlContext.php similarity index 96% rename from tests/Core/Behat/XmlContext.php rename to tests/Behat/XmlContext.php index 8e18ffd9408..af05fed1fce 100644 --- a/tests/Core/Behat/XmlContext.php +++ b/tests/Behat/XmlContext.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Behat; +namespace ApiPlatform\Tests\Behat; use Behat\Gherkin\Node\PyStringNode; use Behatch\Context\XmlContext as BaseXmlContext; diff --git a/tests/Core/Annotation/ApiFilterTest.php b/tests/Core/Annotation/ApiFilterTest.php deleted file mode 100644 index 45b175ecaa8..00000000000 --- a/tests/Core/Annotation/ApiFilterTest.php +++ /dev/null @@ -1,75 +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\Tests\Annotation; - -use ApiPlatform\Core\Annotation\ApiFilter; -use ApiPlatform\Tests\Fixtures\DummyFilter; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; - -/** - * @author Antoine Bluchet - */ -class ApiFilterTest extends TestCase -{ - public function testInvalidConstructor() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('This annotation needs a value representing the filter class.'); - - new ApiFilter(null); // @phpstan-ignore-line - } - - public function testInvalidFilter() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The filter class "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy" does not implement "ApiPlatform\\Api\\FilterInterface". Did you forget a use statement?'); - - new ApiFilter(['value' => Dummy::class]); // @phpstan-ignore-line - } - - public function testInvalidProperty() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Property "foo" does not exist on the ApiFilter annotation.'); - - new ApiFilter(['value' => DummyFilter::class, 'foo' => 'bar']); // @phpstan-ignore-line - } - - public function testAssignation() - { - $resource = new ApiFilter(['value' => DummyFilter::class, 'strategy' => 'test', 'properties' => ['one', 'two'], 'arguments' => ['args']]); // @phpstan-ignore-line - - $this->assertEquals($resource->filterClass, DummyFilter::class); - $this->assertEquals($resource->strategy, 'test'); - $this->assertEquals($resource->properties, ['one', 'two']); - $this->assertEquals($resource->arguments, ['args']); - } - - /** - * @requires PHP 8.0 - */ - public function testAssignationAttribute() - { - $filter = eval(<<<'PHP' -return new \ApiPlatform\Core\Annotation\ApiFilter(\ApiPlatform\Tests\Fixtures\DummyFilter::class, strategy: 'test', properties: ['one', 'two'], arguments: ['args']); -PHP -); - - $this->assertEquals($filter->filterClass, DummyFilter::class); - $this->assertEquals($filter->strategy, 'test'); - $this->assertEquals($filter->properties, ['one', 'two']); - $this->assertEquals($filter->arguments, ['args']); - } -} diff --git a/tests/Core/Annotation/ApiPropertyTest.php b/tests/Core/Annotation/ApiPropertyTest.php deleted file mode 100644 index e777127395b..00000000000 --- a/tests/Core/Annotation/ApiPropertyTest.php +++ /dev/null @@ -1,107 +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\Tests\Annotation; - -use ApiPlatform\Core\Annotation\ApiProperty; -use PHPUnit\Framework\TestCase; - -/** - * @author Kévin Dunglas - */ -class ApiPropertyTest extends TestCase -{ - public function testAssignation() - { - $property = new ApiProperty(); - $property->description = 'description'; - $property->readable = true; - $property->writable = true; - $property->readableLink = true; - $property->writableLink = true; - $property->required = true; - $property->iri = 'http://example.com/prop'; - $property->identifier = true; - $property->attributes = ['foo' => 'bar']; - - $this->assertEquals('description', $property->description); - $this->assertTrue($property->readable); - $this->assertTrue($property->writable); - $this->assertTrue($property->readableLink); - $this->assertTrue($property->writableLink); - $this->assertTrue($property->required); - $this->assertEquals('http://example.com/prop', $property->iri); - $this->assertTrue($property->identifier); - $this->assertEquals(['foo' => 'bar'], $property->attributes); - } - - public function testConstruct() - { - $property = new ApiProperty([ - 'deprecationReason' => 'this field is deprecated', - 'fetchable' => true, - 'fetchEager' => false, - 'jsonldContext' => ['foo' => 'bar'], - 'security' => 'is_granted(\'ROLE_ADMIN\')', - 'swaggerContext' => ['foo' => 'baz'], - 'openapiContext' => ['foo' => 'baz'], - 'push' => true, - 'attributes' => ['unknown' => 'unknown', 'fetchable' => false], - ]); - $this->assertEquals([ - 'deprecation_reason' => 'this field is deprecated', - 'fetchable' => false, - 'fetch_eager' => false, - 'jsonld_context' => ['foo' => 'bar'], - 'security' => 'is_granted(\'ROLE_ADMIN\')', - 'swagger_context' => ['foo' => 'baz'], - 'openapi_context' => ['foo' => 'baz'], - 'push' => true, - 'unknown' => 'unknown', - ], $property->attributes); - } - - /** - * @requires PHP 8.0 - */ - public function testConstructAttribute() - { - $property = eval(<<<'PHP' -return new \ApiPlatform\Core\Annotation\ApiProperty( - deprecationReason: 'this field is deprecated', - fetchable: true, - fetchEager: false, - jsonldContext: ['foo' => 'bar'], - security: 'is_granted(\'ROLE_ADMIN\')', - swaggerContext: ['foo' => 'baz'], - openapiContext: ['foo' => 'baz'], - push: true, - attributes: ['unknown' => 'unknown', 'fetchable' => false] -); -PHP - ); - - $this->assertEquals([ - 'deprecation_reason' => 'this field is deprecated', - 'fetchable' => false, - 'fetch_eager' => false, - 'jsonld_context' => ['foo' => 'bar'], - 'security' => 'is_granted(\'ROLE_ADMIN\')', - 'swagger_context' => ['foo' => 'baz'], - 'openapi_context' => ['foo' => 'baz'], - 'push' => true, - 'unknown' => 'unknown', - 'security_post_denormalize' => null, - ], $property->attributes); - } -} diff --git a/tests/Core/Annotation/ApiResourceTest.php b/tests/Core/Annotation/ApiResourceTest.php deleted file mode 100644 index 1e1a3b6e550..00000000000 --- a/tests/Core/Annotation/ApiResourceTest.php +++ /dev/null @@ -1,297 +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\Tests\Annotation; - -use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Annotation\ApiResource; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Tests\Fixtures\AnnotatedClass; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPhp8; -use Doctrine\Common\Annotations\AnnotationReader; -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; - -/** - * @author Kévin Dunglas - */ -class ApiResourceTest extends TestCase -{ - use ExpectDeprecationTrait; - - /** - * @group legacy - */ - public function testConstruct() - { - $resource = new ApiResource([ - 'security' => 'is_granted("ROLE_FOO")', - 'securityMessage' => 'You are not foo.', - 'securityPostDenormalize' => 'is_granted("ROLE_BAR")', - 'securityPostDenormalizeMessage' => 'You are not bar.', - 'securityPostValidation' => 'is_granted("ROLE_FOO")', - 'securityPostValidationMessage' => 'You are not foo.', - 'attributes' => ['foo' => 'bar', 'validation_groups' => ['baz', 'qux'], 'cache_headers' => ['max_age' => 0, 'shared_max_age' => 0, 'vary' => ['Custom-Vary-1', 'Custom-Vary-2']]], - 'collectionOperations' => ['bar' => ['foo']], - 'denormalizationContext' => ['groups' => ['foo']], - 'description' => 'description', - 'fetchPartial' => true, - 'forceEager' => false, - 'formats' => ['foo', 'bar' => ['application/bar']], - 'filters' => ['foo', 'bar'], - 'graphql' => ['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]], - 'input' => 'Foo', - 'iri' => 'http://example.com/res', - 'itemOperations' => ['foo' => ['bar']], - 'mercure' => ['private' => true], - 'messenger' => true, - 'normalizationContext' => ['groups' => ['bar']], - 'order' => ['foo', 'bar' => 'ASC'], - 'openapiContext' => ['description' => 'foo'], - 'output' => 'Bar', - 'paginationClientEnabled' => true, - 'paginationClientItemsPerPage' => true, - 'paginationClientPartial' => true, - 'paginationEnabled' => true, - 'paginationFetchJoinCollection' => true, - 'paginationItemsPerPage' => 42, - 'paginationMaximumItemsPerPage' => 50, - 'paginationPartial' => true, - 'routePrefix' => '/foo', - 'shortName' => 'shortName', - 'subresourceOperations' => [], - 'swaggerContext' => ['description' => 'bar'], - 'validationGroups' => ['foo', 'bar'], - 'sunset' => 'Thu, 11 Oct 2018 00:00:00 +0200', - 'urlGenerationStrategy' => UrlGeneratorInterface::ABS_PATH, - 'queryParameterValidationEnabled' => false, - ]); - - $this->assertSame('shortName', $resource->shortName); - $this->assertSame('description', $resource->description); - $this->assertSame('http://example.com/res', $resource->iri); - $this->assertSame(['foo' => ['bar']], $resource->itemOperations); - $this->assertSame(['bar' => ['foo']], $resource->collectionOperations); - $this->assertSame([], $resource->subresourceOperations); - $this->assertSame(['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]], $resource->graphql); - $this->assertEquals([ - 'security' => 'is_granted("ROLE_FOO")', - 'security_message' => 'You are not foo.', - 'security_post_denormalize' => 'is_granted("ROLE_BAR")', - 'security_post_denormalize_message' => 'You are not bar.', - 'security_post_validation' => 'is_granted("ROLE_FOO")', - 'security_post_validation_message' => 'You are not foo.', - 'denormalization_context' => ['groups' => ['foo']], - 'fetch_partial' => true, - 'foo' => 'bar', - 'force_eager' => false, - 'formats' => ['foo', 'bar' => ['application/bar']], - 'filters' => ['foo', 'bar'], - 'input' => 'Foo', - 'mercure' => ['private' => true], - 'messenger' => true, - 'normalization_context' => ['groups' => ['bar']], - 'order' => ['foo', 'bar' => 'ASC'], - 'openapi_context' => ['description' => 'foo'], - 'output' => 'Bar', - 'pagination_client_enabled' => true, - 'pagination_client_items_per_page' => true, - 'pagination_client_partial' => true, - 'pagination_enabled' => true, - 'pagination_fetch_join_collection' => true, - 'pagination_items_per_page' => 42, - 'pagination_maximum_items_per_page' => 50, - 'pagination_partial' => true, - 'route_prefix' => '/foo', - 'swagger_context' => ['description' => 'bar'], - 'validation_groups' => ['baz', 'qux'], - 'cache_headers' => ['max_age' => 0, 'shared_max_age' => 0, 'vary' => ['Custom-Vary-1', 'Custom-Vary-2']], - 'sunset' => 'Thu, 11 Oct 2018 00:00:00 +0200', - 'url_generation_strategy' => 1, - 'query_parameter_validation_enabled' => false, - ], $resource->attributes); - } - - /** - * @requires PHP 8.0 - */ - public function testConstructAttribute() - { - $resource = eval(<<<'PHP' -return new \ApiPlatform\Core\Annotation\ApiResource( - security: 'is_granted("ROLE_FOO")', - securityMessage: 'You are not foo.', - securityPostDenormalize: 'is_granted("ROLE_BAR")', - securityPostDenormalizeMessage: 'You are not bar.', - securityPostValidation: 'is_granted("ROLE_FOO")', - securityPostValidationMessage: 'You are not foo.', - attributes: ['foo' => 'bar', 'validation_groups' => ['baz', 'qux'], 'cache_headers' => ['max_age' => 0, 'shared_max_age' => 0, 'vary' => ['Custom-Vary-1', 'Custom-Vary-2']]], - collectionOperations: ['bar' => ['foo']], - denormalizationContext: ['groups' => ['foo']], - description: 'description', - fetchPartial: true, - forceEager: false, - formats: ['foo', 'bar' => ['application/bar']], - filters: ['foo', 'bar'], - graphql: ['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]], - input: 'Foo', - iri: 'http://example.com/res', - itemOperations: ['foo' => ['bar']], - mercure: ['private' => true], - messenger: true, - normalizationContext: ['groups' => ['bar']], - order: ['foo', 'bar' => 'ASC'], - openapiContext: ['description' => 'foo'], - output: 'Bar', - paginationClientEnabled: true, - paginationClientItemsPerPage: true, - paginationClientPartial: true, - paginationEnabled: true, - paginationFetchJoinCollection: true, - paginationItemsPerPage: 42, - paginationMaximumItemsPerPage: 50, - paginationPartial: true, - routePrefix: '/foo', - shortName: 'shortName', - subresourceOperations: [], - swaggerContext: ['description' => 'bar'], - validationGroups: ['foo', 'bar'], - sunset: 'Thu, 11 Oct 2018 00:00:00 +0200', - urlGenerationStrategy: \ApiPlatform\Api\UrlGeneratorInterface::ABS_PATH, - deprecationReason: 'reason', - elasticsearch: true, - hydraContext: ['hydra' => 'foo'], - paginationViaCursor: ['foo'], - stateless: true, - exceptionToStatus: [ - \DomainException::class => 400, - ], - queryParameterValidationEnabled: false, -); -PHP - ); - - $this->assertSame('shortName', $resource->shortName); - $this->assertSame('description', $resource->description); - $this->assertSame('http://example.com/res', $resource->iri); - $this->assertSame(['foo' => ['bar']], $resource->itemOperations); - $this->assertSame(['bar' => ['foo']], $resource->collectionOperations); - $this->assertSame([], $resource->subresourceOperations); - $this->assertSame(['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]], $resource->graphql); - $this->assertEquals([ - 'security' => 'is_granted("ROLE_FOO")', - 'security_message' => 'You are not foo.', - 'security_post_denormalize' => 'is_granted("ROLE_BAR")', - 'security_post_denormalize_message' => 'You are not bar.', - 'security_post_validation' => 'is_granted("ROLE_FOO")', - 'security_post_validation_message' => 'You are not foo.', - 'denormalization_context' => ['groups' => ['foo']], - 'fetch_partial' => true, - 'foo' => 'bar', - 'force_eager' => false, - 'formats' => ['foo', 'bar' => ['application/bar']], - 'filters' => ['foo', 'bar'], - 'input' => 'Foo', - 'mercure' => ['private' => true], - 'messenger' => true, - 'normalization_context' => ['groups' => ['bar']], - 'order' => ['foo', 'bar' => 'ASC'], - 'openapi_context' => ['description' => 'foo'], - 'output' => 'Bar', - 'pagination_client_enabled' => true, - 'pagination_client_items_per_page' => true, - 'pagination_client_partial' => true, - 'pagination_enabled' => true, - 'pagination_fetch_join_collection' => true, - 'pagination_items_per_page' => 42, - 'pagination_maximum_items_per_page' => 50, - 'pagination_partial' => true, - 'route_prefix' => '/foo', - 'swagger_context' => ['description' => 'bar'], - 'validation_groups' => ['baz', 'qux'], - 'cache_headers' => ['max_age' => 0, 'shared_max_age' => 0, 'vary' => ['Custom-Vary-1', 'Custom-Vary-2']], - 'sunset' => 'Thu, 11 Oct 2018 00:00:00 +0200', - 'url_generation_strategy' => 1, - 'deprecation_reason' => 'reason', - 'elasticsearch' => true, - 'hydra_context' => ['hydra' => 'foo'], - 'pagination_via_cursor' => ['foo'], - 'stateless' => true, - 'composite_identifier' => null, - 'exception_to_status' => [ - \DomainException::class => 400, - ], - 'query_parameter_validation_enabled' => false, - ], $resource->attributes); - } - - /** - * @requires PHP 8.0 - */ - public function testUseAttribute() - { - $this->assertSame('Hey PHP 8', (new \ReflectionClass(DummyPhp8::class))->getAttributes(ApiResource::class)[0]->getArguments()['description']); - } - - /** - * @group legacy - */ - public function testApiResourceAnnotation() - { - $reader = new AnnotationReader(); - /** - * @var ApiResource - */ - $resource = $reader->getClassAnnotation(new \ReflectionClass(AnnotatedClass::class), ApiResource::class); - - $this->assertSame('shortName', $resource->shortName); - $this->assertSame('description', $resource->description); - $this->assertSame('http://example.com/res', $resource->iri); - $this->assertSame(['bar' => ['foo']], $resource->collectionOperations); - $this->assertSame(['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]], $resource->graphql); - $this->assertEquals([ - 'foo' => 'bar', - 'route_prefix' => '/whatever', - 'security' => "is_granted('ROLE_FOO')", - 'security_message' => 'You are not foo.', - 'security_post_denormalize' => "is_granted('ROLE_BAR')", - 'security_post_denormalize_message' => 'You are not bar.', - 'cache_headers' => ['max_age' => 0, 'shared_max_age' => 0, 'vary' => ['Custom-Vary-1', 'Custom-Vary-2']], - ], $resource->attributes); - } - - public function testConstructWithInvalidAttribute() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unknown property "invalidAttribute" on annotation "ApiPlatform\\Core\\Annotation\\ApiResource".'); - - new ApiResource([ - 'shortName' => 'shortName', - 'routePrefix' => '/foo', - 'invalidAttribute' => 'exception', - ]); - } - - /** - * @group legacy - * @expectedDeprecation Attribute "accessControl" is deprecated in annotation since API Platform 2.5, prefer using "security" attribute instead - * @expectedDeprecation Attribute "accessControlMessage" is deprecated in annotation since API Platform 2.5, prefer using "securityMessage" attribute instead - */ - public function testWithDeprecatedAttributes() - { - new ApiResource([ - 'accessControl' => "is_granted('ROLE_USER')", - 'accessControlMessage' => 'Nope!', - ]); - } -} diff --git a/tests/Core/Annotation/ApiSubresourceTest.php b/tests/Core/Annotation/ApiSubresourceTest.php deleted file mode 100644 index fc1ed117299..00000000000 --- a/tests/Core/Annotation/ApiSubresourceTest.php +++ /dev/null @@ -1,53 +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\Tests\Annotation; - -use ApiPlatform\Core\Annotation\ApiSubresource; -use PHPUnit\Framework\TestCase; - -/** - * @author Cody Banman - */ -class ApiSubresourceTest extends TestCase -{ - public function testAssignation() - { - $property = new ApiSubresource(); - $property->maxDepth = 1; - - $this->assertEquals(1, $property->maxDepth); - } - - public function testConstruct() - { - $property = new ApiSubresource([ // @phpstan-ignore-line - 'maxDepth' => 1, - ]); - $this->assertEquals(1, $property->maxDepth); - } - - /** - * @requires PHP 8.0 - */ - public function testConstructAttribute() - { - $property = eval(<<<'PHP' -return new \ApiPlatform\Core\Annotation\ApiSubresource( - maxDepth: 1 -); -PHP - ); - $this->assertEquals(1, $property->maxDepth); - } -} diff --git a/tests/Core/Api/CachedIdentifiersExtractorTest.php b/tests/Core/Api/CachedIdentifiersExtractorTest.php deleted file mode 100644 index 96666aac644..00000000000 --- a/tests/Core/Api/CachedIdentifiersExtractorTest.php +++ /dev/null @@ -1,244 +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\Tests\Api; - -use ApiPlatform\Core\Api\CachedIdentifiersExtractor; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Doctrine\Generator\Uuid; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; - -/** - * @author Antoine Bluchet - * @group legacy - */ -class CachedIdentifiersExtractorTest extends TestCase -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - public function itemProvider() - { - $dummy = new Dummy(); - $dummy->setId($id = 1); - yield [$dummy, ['id' => $id]]; - - $dummy = new Dummy(); - $dummy->setId($id = new Uuid()); - yield [$dummy, ['id' => $id]]; - } - - /** - * @dataProvider itemProvider - * - * @param mixed $item - * @param mixed $expected - */ - public function testFirstPass($item, $expected) - { - $cacheItemKey = 'iri_identifiers'.md5(Dummy::class); - - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->willReturn(false); - $cacheItemProphecy->set(['id'])->shouldBeCalled()->willReturn($cacheItemProphecy->reveal()); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem($cacheItemKey)->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldBeCalled(); - - $decoratedProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $decoratedProphecy->getIdentifiersFromItem($item)->willReturn($expected); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($item)->willReturn(Dummy::class); - $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(Uuid::class)->willReturn(false); - - $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item)); - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item), 'Trigger the local cache'); - - $expected = ['id']; - - $decoratedProphecy->getIdentifiersFromResourceClass(Dummy::class)->willReturn($expected); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)); - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class), 'Trigger the local cache'); - } - - /** - * @dataProvider itemProvider - * - * @param mixed $item - * @param mixed $expected - */ - public function testSecondPass($item, $expected) - { - $cacheItemKey = 'iri_identifiers'.md5(Dummy::class); - - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->willReturn(true); - $cacheItemProphecy->get()->willReturn(['id']); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem($cacheItemKey)->willReturn($cacheItemProphecy); - - $decoratedProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $decoratedProphecy->getIdentifiersFromItem($item)->shouldNotBeCalled(); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($item)->willReturn(Dummy::class); - $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(Uuid::class)->willReturn(false); - - $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item)); - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item), 'Trigger the local cache'); - - $expected = ['id']; - - $decoratedProphecy->getIdentifiersFromResourceClass(Dummy::class)->willReturn($expected); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)); - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class), 'Trigger the local cache'); - } - - public function identifiersRelatedProvider() - { - $related = new RelatedDummy(); - $related->setId($relatedId = 2); - - $dummy = new Dummy(); - $dummy->setId($id = 1); - $dummy->setRelatedDummy($related); - - yield [$dummy, ['id' => $id, 'relatedDummy' => $relatedId]]; - - $related = new RelatedDummy(); - $related->setId($relatedId = 1); - - $dummy = new Dummy(); - $dummy->setId($id = new Uuid()); - $dummy->setRelatedDummy($related); - - yield [$dummy, ['id' => $id, 'relatedDummy' => $relatedId]]; - - $related = new RelatedDummy(); - $related->setId($relatedId = new Uuid()); - - $dummy = new Dummy(); - $dummy->setId($id = new Uuid()); - $dummy->setRelatedDummy($related); - - yield [$dummy, ['id' => $id, 'relatedDummy' => $relatedId]]; - } - - /** - * @dataProvider identifiersRelatedProvider - * - * @param mixed $item - * @param mixed $expected - */ - public function testFirstPassWithRelated($item, $expected) - { - $cacheItemKey = 'iri_identifiers'.md5(Dummy::class); - $relatedCacheItemKey = 'iri_identifiers'.md5(RelatedDummy::class); - - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->willReturn(true); - $cacheItemProphecy->get()->willReturn(['id', 'relatedDummy']); - - $relatedCacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $relatedCacheItemProphecy->isHit()->willReturn(false); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem($cacheItemKey)->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->getItem($relatedCacheItemKey)->willReturn($relatedCacheItemProphecy); - - $decoratedProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $decoratedProphecy->getIdentifiersFromItem($item)->willReturn($expected); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($item)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass(Argument::type(RelatedDummy::class))->willReturn(RelatedDummy::class); - $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(Uuid::class)->willReturn(false); - - $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item)); - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item), 'Trigger the local cache'); - } - - /** - * @dataProvider identifiersRelatedProvider - * - * @param mixed $item - * @param mixed $expected - */ - public function testSecondPassWithRelated($item, $expected) - { - $cacheItemKey = 'iri_identifiers'.md5(Dummy::class); - $relatedCacheItemKey = 'iri_identifiers'.md5(RelatedDummy::class); - - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->willReturn(true); - $cacheItemProphecy->get()->willReturn(['id', 'relatedDummy']); - - $relatedCacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $relatedCacheItemProphecy->isHit()->willReturn(true); - $relatedCacheItemProphecy->get()->willReturn(['id']); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem($cacheItemKey)->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->getItem($relatedCacheItemKey)->willReturn($relatedCacheItemProphecy); - - $decoratedProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $decoratedProphecy->getIdentifiersFromItem($item)->shouldNotBeCalled(); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($item)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass(Argument::type(RelatedDummy::class))->willReturn(RelatedDummy::class); - $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(Uuid::class)->willReturn(false); - - $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item)); - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item), 'Trigger the local cache'); - } - - /** - * @group legacy - */ - public function testDeprecationResourceClassResolver() - { - $this->expectDeprecation('Not injecting ApiPlatform\Core\Api\ResourceClassResolverInterface in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.'); - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $decoration = $this->prophesize(IdentifiersExtractorInterface::class); - - new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); - } -} diff --git a/tests/Core/Api/EntrypointTest.php b/tests/Core/Api/EntrypointTest.php deleted file mode 100644 index 85b0a3d7756..00000000000 --- a/tests/Core/Api/EntrypointTest.php +++ /dev/null @@ -1,32 +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\Tests\Api; - -use ApiPlatform\Api\Entrypoint; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; - -/** - * @author Amrouche Hamza - */ -class EntrypointTest extends TestCase -{ - public function testGetResourceNameCollection() - { - $resourceNameCollection = new ResourceNameCollection([Dummy::class]); - $entrypoint = new Entrypoint($resourceNameCollection); - $this->assertEquals($entrypoint->getResourceNameCollection(), $resourceNameCollection); - } -} diff --git a/tests/Core/Api/FilterCollectionFactoryTest.php b/tests/Core/Api/FilterCollectionFactoryTest.php deleted file mode 100644 index ef94c8edad4..00000000000 --- a/tests/Core/Api/FilterCollectionFactoryTest.php +++ /dev/null @@ -1,50 +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\Tests\Api; - -use ApiPlatform\Api\FilterInterface; -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\FilterCollectionFactory; -use ApiPlatform\Core\Tests\ProphecyTrait; -use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; - -/** - * @author Baptiste Meyer - */ -class FilterCollectionFactoryTest extends TestCase -{ - use ProphecyTrait; - - /** - * @group legacy - * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. - */ - public function testCreateFilterCollectionFromLocator() - { - $filter = $this->prophesize(FilterInterface::class)->reveal(); - - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $filterLocatorProphecy->has('foo')->willReturn(true)->shouldBeCalled(); - $filterLocatorProphecy->get('foo')->willReturn($filter)->shouldBeCalled(); - $filterLocatorProphecy->has('bar')->willReturn(false)->shouldBeCalled(); - - $filterCollection = (new FilterCollectionFactory(['foo', 'bar']))->createFilterCollectionFromLocator($filterLocatorProphecy->reveal()); - - $this->assertArrayNotHasKey('bar', $filterCollection); - $this->assertTrue(isset($filterCollection['foo'])); - $this->assertInstanceOf(FilterInterface::class, $filterCollection['foo']); - $this->assertEquals(new FilterCollection(['foo' => $filter]), $filterCollection); - } -} diff --git a/tests/Core/Api/FilterCollectionTest.php b/tests/Core/Api/FilterCollectionTest.php deleted file mode 100644 index 65461d060ad..00000000000 --- a/tests/Core/Api/FilterCollectionTest.php +++ /dev/null @@ -1,33 +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\Tests\Api; - -use ApiPlatform\Core\Api\FilterCollection; -use PHPUnit\Framework\TestCase; - -/** - * @author Kévin Dunglas - */ -class FilterCollectionTest extends TestCase -{ - /** - * @group legacy - * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. - */ - public function testIsArrayObject() - { - $filterCollection = new FilterCollection(); - $this->assertInstanceOf(\ArrayObject::class, $filterCollection); - } -} diff --git a/tests/Core/Api/FilterLocatorTraitTest.php b/tests/Core/Api/FilterLocatorTraitTest.php deleted file mode 100644 index 85cfd2413ab..00000000000 --- a/tests/Core/Api/FilterLocatorTraitTest.php +++ /dev/null @@ -1,161 +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\Tests\Api; - -use ApiPlatform\Api\FilterInterface; -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\FilterLocatorTrait; -use ApiPlatform\Core\Tests\ProphecyTrait; -use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; - -/** - * @author Baptiste Meyer - */ -class FilterLocatorTraitTest extends TestCase -{ - use ProphecyTrait; - - public function testSetFilterLocator() - { - $filterLocator = $this->prophesize(ContainerInterface::class)->reveal(); - - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator($filterLocator); - - $this->assertEquals($filterLocator, $filterLocatorTraitImpl->getFilterLocator()); - } - - /** - * @group legacy - * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. - */ - public function testSetFilterLocatorWithDeprecatedFilterCollection() - { - $filterCollection = new FilterCollection(); - - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator($filterCollection); - - $this->assertEquals($filterCollection, $filterLocatorTraitImpl->getFilterLocator()); - } - - public function testSetFilterLocatorWithNullAndNullAllowed() - { - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator(null, true); - - $this->assertNull($filterLocatorTraitImpl->getFilterLocator()); - } - - /** - * @group legacy - */ - public function testSetFilterLocatorWithNullAndNullNotAllowed() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The "$filterLocator" argument is expected to be an implementation of the "Psr\\Container\\ContainerInterface" interface.'); - - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator(null); - } - - /** - * @group legacy - */ - public function testSetFilterLocatorWithInvalidFilterLocator() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The "$filterLocator" argument is expected to be an implementation of the "Psr\\Container\\ContainerInterface" interface or null.'); - - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator(new \ArrayObject(), true); - } - - public function testGetFilter() - { - $filter = $this->prophesize(FilterInterface::class)->reveal(); - - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $filterLocatorProphecy->has('foo')->willReturn(true)->shouldBeCalled(); - $filterLocatorProphecy->get('foo')->willReturn($filter)->shouldBeCalled(); - - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator($filterLocatorProphecy->reveal()); - - $returnedFilter = $filterLocatorTraitImpl->getFilter('foo'); - - $this->assertInstanceOf(FilterInterface::class, $returnedFilter); - $this->assertEquals($filter, $returnedFilter); - } - - public function testGetFilterWithNonexistentFilterId() - { - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $filterLocatorProphecy->has('foo')->willReturn(false)->shouldBeCalled(); - - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator($filterLocatorProphecy->reveal()); - - $filter = $filterLocatorTraitImpl->getFilter('foo'); - - $this->assertNull($filter); - } - - /** - * @group legacy - * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. - */ - public function testGetFilterWithDeprecatedFilterCollection() - { - $filter = $this->prophesize(FilterInterface::class)->reveal(); - - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator(new FilterCollection(['foo' => $filter])); - - $returnedFilter = $filterLocatorTraitImpl->getFilter('foo'); - - $this->assertInstanceOf(FilterInterface::class, $returnedFilter); - $this->assertEquals($filter, $returnedFilter); - } - - /** - * @group legacy - * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. - */ - public function testGetFilterWithNonexistentFilterIdAndDeprecatedFilterCollection() - { - $filterLocatorTraitImpl = $this->getFilterLocatorTraitImpl(); - $filterLocatorTraitImpl->setFilterLocator(new FilterCollection()); - - $filter = $filterLocatorTraitImpl->getFilter('foo'); - - $this->assertNull($filter); - } - - private function getFilterLocatorTraitImpl() - { - return new class() { - use FilterLocatorTrait { - FilterLocatorTrait::setFilterLocator as public; - FilterLocatorTrait::getFilter as public; - } - - public function getFilterLocator() - { - return $this->filterLocator; - } - }; - } -} diff --git a/tests/Core/Api/FormatsProviderTest.php b/tests/Core/Api/FormatsProviderTest.php deleted file mode 100644 index e29e5430226..00000000000 --- a/tests/Core/Api/FormatsProviderTest.php +++ /dev/null @@ -1,189 +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\Tests\Api; - -use ApiPlatform\Core\Api\FormatsProvider; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use PHPUnit\Framework\TestCase; - -/** - * @author Anthony GRASSIOT - * - * @group legacy - */ -class FormatsProviderTest extends TestCase -{ - use ProphecyTrait; - - public function testNoResourceClass() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create()->shouldNotBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => 'application/ld+json']); - - $this->assertSame(['jsonld' => 'application/ld+json'], $formatProvider->getFormatsFromAttributes([])); - } - - public function testResourceClassWithoutFormatsAttributes() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata())->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json']]); - - $this->assertSame(['jsonld' => ['application/ld+json']], $formatProvider->getFormatsFromAttributes(['resource_class' => 'Foo', 'collection_operation_name' => 'get'])); - } - - public function testResourceClassWithFormatsAttributes() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => ['jsonld']]); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json'], 'json' => ['application/json']]); - - $this->assertSame(['jsonld' => ['application/ld+json']], $formatProvider->getFormatsFromAttributes(['resource_class' => 'Foo', 'collection_operation_name' => 'get'])); - } - - public function testResourceClassWithFormatsAttributesOverRiddingMimeTypes() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => ['jsonld' => ['application/foo'], 'bar' => ['application/bar', 'application/baz'], 'buz' => 'application/fuz']]); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json'], 'json' => ['application/json']]); - - $this->assertSame(['jsonld' => ['application/foo'], 'bar' => ['application/bar', 'application/baz'], 'buz' => ['application/fuz']], $formatProvider->getFormatsFromAttributes(['resource_class' => 'Foo', 'collection_operation_name' => 'get'])); - } - - public function testBadFormatsShortDeclaration() - { - $this->expectException(\ApiPlatform\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('You either need to add the format \'foo\' to your project configuration or declare a mime type for it in your annotation.'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => ['foo']]); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json']]); - - $formatProvider->getFormatsFromAttributes(['resource_class' => 'Foo', 'collection_operation_name' => 'get']); - } - - public function testInvalidFormatsShortDeclaration() - { - $this->expectException(\ApiPlatform\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('The \'formats\' attributes value must be a string when trying to include an already configured format, array given.'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => [['badFormat']]]); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json'], 'json' => ['application/json']]); - - $this->assertSame(['jsonld' => ['application/ld+json']], $formatProvider->getFormatsFromAttributes(['resource_class' => 'Foo', 'collection_operation_name' => 'get'])); - } - - public function testInvalidFormatsDeclaration() - { - $this->expectException(\ApiPlatform\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('The \'formats\' attributes must be an array, string given for resource class \'Foo\'.'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => 'badFormat']); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json'], 'json' => ['application/json']]); - - $this->assertSame(['jsonld' => ['application/ld+json']], $formatProvider->getFormatsFromAttributes(['resource_class' => 'Foo', 'collection_operation_name' => 'get'])); - } - - public function testResourceClassWithoutFormatsAttributesFromOperation() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata())->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json']]); - - $this->assertSame(['jsonld' => ['application/ld+json']], $formatProvider->getFormatsFromOperation('Foo', 'get', OperationType::COLLECTION)); - } - - public function testResourceClassWithFormatsAttributesFromOperation() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => ['jsonld']]); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json'], 'json' => ['application/json']]); - - $this->assertSame(['jsonld' => ['application/ld+json']], $formatProvider->getFormatsFromOperation('Foo', 'get', OperationType::COLLECTION)); - } - - public function testResourceClassWithFormatsAttributesOverRiddingMimeTypesFromOperation() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => ['jsonld' => ['application/foo'], 'bar' => ['application/bar', 'application/baz'], 'buz' => 'application/fuz']]); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json'], 'json' => ['application/json']]); - - $this->assertSame(['jsonld' => ['application/foo'], 'bar' => ['application/bar', 'application/baz'], 'buz' => ['application/fuz']], $formatProvider->getFormatsFromOperation('Foo', 'get', OperationType::COLLECTION)); - } - - public function testBadFormatsShortDeclarationFromOperation() - { - $this->expectException(\ApiPlatform\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('You either need to add the format \'foo\' to your project configuration or declare a mime type for it in your annotation.'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => ['foo']]); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json']]); - - $formatProvider->getFormatsFromOperation('Foo', 'get', OperationType::COLLECTION); - } - - public function testInvalidFormatsShortDeclarationFromOperation() - { - $this->expectException(\ApiPlatform\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('The \'formats\' attributes value must be a string when trying to include an already configured format, array given.'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => [['badFormat']]]); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json'], 'json' => ['application/json']]); - - $this->assertSame(['jsonld' => ['application/ld+json']], $formatProvider->getFormatsFromOperation('Foo', 'get', OperationType::COLLECTION)); - } - - public function testInvalidFormatsDeclarationFromOperation() - { - $this->expectException(\ApiPlatform\Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('The \'formats\' attributes must be an array, string given for resource class \'Foo\'.'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadata = new ResourceMetadata(null, null, null, null, null, ['formats' => 'badFormat']); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - - $formatProvider = new FormatsProvider($resourceMetadataFactoryProphecy->reveal(), ['jsonld' => ['application/ld+json'], 'json' => ['application/json']]); - - $this->assertSame(['jsonld' => ['application/ld+json']], $formatProvider->getFormatsFromOperation('Foo', 'get', OperationType::COLLECTION)); - } -} diff --git a/tests/Core/Api/IdentifiersExtractorTest.php b/tests/Core/Api/IdentifiersExtractorTest.php deleted file mode 100644 index 60e2bf924e7..00000000000 --- a/tests/Core/Api/IdentifiersExtractorTest.php +++ /dev/null @@ -1,276 +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\Tests\Api; - -use ApiPlatform\Core\Api\IdentifiersExtractor; -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\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Doctrine\Generator\Uuid; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Model\ResourceInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Model\ResourceInterfaceImplementation; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; - -/** - * @author Antoine Bluchet - */ -class IdentifiersExtractorTest extends TestCase -{ - use ProphecyTrait; - - public function testGetIdentifiersFromResourceClass() - { - [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy] = $this->getMetadataFactoryProphecies(Dummy::class, ['id']); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame(['id'], $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)); - } - - public function testGetCompositeIdentifiersFromResourceClass() - { - [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy] = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'name']); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame(['id', 'name'], $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)); - } - - public function itemProvider() - { - $dummy = new Dummy(); - $dummy->setId(1); - yield [$dummy, ['id' => 1]]; - - $uuid = new Uuid(); - $dummy = new Dummy(); - $dummy->setId($uuid); - yield [$dummy, ['id' => $uuid]]; - } - - /** - * @dataProvider itemProvider - * - * @param mixed $item - * @param mixed $expected - */ - public function testGetIdentifiersFromItem($item, $expected) - { - [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy] = $this->getMetadataFactoryProphecies(Dummy::class, ['id']); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($item)->willReturn(Dummy::class); - $resourceClassResolverProphecy->isResourceClass(Uuid::class)->willReturn(false); - - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item)); - } - - public function itemProviderComposite() - { - $dummy = new Dummy(); - $dummy->setId(1); - $dummy->setName('foo'); - yield [$dummy, ['id' => 1, 'name' => 'foo']]; - - $dummy = new Dummy(); - $dummy->setId($uuid = new Uuid()); - $dummy->setName('foo'); - yield [$dummy, ['id' => $uuid, 'name' => 'foo']]; - } - - /** - * @dataProvider itemProviderComposite - * - * @param mixed $item - * @param mixed $expected - */ - public function testGetCompositeIdentifiersFromItem($item, $expected) - { - [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy] = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'name']); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($item)->willReturn(Dummy::class); - $resourceClassResolverProphecy->isResourceClass(Uuid::class)->willReturn(false); - - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item)); - } - - public function itemProviderRelated() - { - $related = new RelatedDummy(); - $related->setId(2); - - $dummy = new Dummy(); - $dummy->setId(1); - $dummy->setRelatedDummy($related); - yield [$dummy, ['id' => 1, 'relatedDummy' => 2]]; - - $uuid2 = new Uuid(); - $related = new RelatedDummy(); - $related->setId($uuid2); - - $uuid = new Uuid(); - $dummy = new Dummy(); - $dummy->setId($uuid); - $dummy->setRelatedDummy($related); - yield [$dummy, ['id' => $uuid, 'relatedDummy' => $uuid2]]; - } - - /** - * @dataProvider itemProviderRelated - * - * @param mixed $item - * @param mixed $expected - */ - public function testGetRelatedIdentifiersFromItem($item, $expected) - { - $prophecies = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'relatedDummy']); - [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy] = $this->getMetadataFactoryProphecies(RelatedDummy::class, ['id'], $prophecies); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($item)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass(Argument::type(RelatedDummy::class))->willReturn(RelatedDummy::class); - $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(Uuid::class)->willReturn(false); - - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame($expected, $identifiersExtractor->getIdentifiersFromItem($item)); - } - - public function testThrowNoIdentifierFromItem() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No identifier found in "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\RelatedDummy" through relation "relatedDummy" of "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy" used as identifier'); - - $related = new RelatedDummy(); - $related->setId(2); - - $dummy = new Dummy(); - $dummy->setId(1); - $dummy->setRelatedDummy($related); - - $prophecies = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'relatedDummy']); - [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy] = $this->getMetadataFactoryProphecies(RelatedDummy::class, [], $prophecies); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($dummy)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass(Argument::type(RelatedDummy::class))->willReturn(RelatedDummy::class); - $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $identifiersExtractor->getIdentifiersFromItem($dummy); - } - - public function testGetsIdentifiersFromCorrectResourceClass(): void - { - $item = new ResourceInterfaceImplementation(); - $item->setFoo('woot'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(ResourceInterface::class)->willReturn(new PropertyNameCollection(['foo', 'fooz'])); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(ResourceInterface::class, 'foo')->willReturn((new PropertyMetadata())->withIdentifier(true)); - $propertyMetadataFactoryProphecy->create(ResourceInterface::class, 'fooz')->willReturn(new PropertyMetadata()); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($item)->willReturn(ResourceInterface::class); - - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $identifiersExtractor->getIdentifiersFromItem($item); - - $this->assertSame(['foo' => 'woot'], $identifiersExtractor->getIdentifiersFromItem($item)); - } - - public function testNoIdentifiers(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No identifier defined in "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy". You should add #[\ApiPlatform\Core\Annotation\ApiProperty(identifier: true)]" on the property identifying the resource.'); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn(new PropertyNameCollection(['foo'])); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo')->willReturn(new PropertyMetadata()); - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class); - } - - /** - * @group legacy - * @expectedDeprecation Not injecting ApiPlatform\Core\Api\ResourceClassResolverInterface in the IdentifiersExtractor might introduce cache issues with object identifiers. - */ - public function testLegacyGetIdentifiersFromItem() - { - [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy] = $this->getMetadataFactoryProphecies(Dummy::class, ['id']); - - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); - - $dummy = new Dummy(); - $dummy->setId(1); - - $this->assertSame(['id' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); - } - - private function getMetadataFactoryProphecies($class, $identifiers, array $prophecies = null) - { - // adds a random property that is not an identifier - $properties = array_merge(['foo'], $identifiers); - - if (!$prophecies) { - $prophecies = [$this->prophesize(PropertyNameCollectionFactoryInterface::class), $this->prophesize(PropertyMetadataFactoryInterface::class)]; - } - - [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy] = $prophecies; - - $propertyNameCollectionFactoryProphecy->create($class)->willReturn(new PropertyNameCollection($properties)); - - foreach ($properties as $prop) { - $metadata = new PropertyMetadata(); - $propertyMetadataFactoryProphecy->create($class, $prop)->willReturn($metadata->withIdentifier(\in_array($prop, $identifiers, true))); - } - - return [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy]; - } - - public function testDefaultIdentifierId(): void - { - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn(new PropertyNameCollection(['id'])); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id')->willReturn(new PropertyMetadata()); - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), null, $resourceClassResolverProphecy->reveal()); - - $this->assertSame(['id'], $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)); - } -} diff --git a/tests/Core/Api/ResourceClassResolverTest.php b/tests/Core/Api/ResourceClassResolverTest.php deleted file mode 100644 index c775778da41..00000000000 --- a/tests/Core/Api/ResourceClassResolverTest.php +++ /dev/null @@ -1,209 +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\Tests\Api; - -use ApiPlatform\Api\ResourceClassResolver; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\State\Pagination\PaginatorInterface; -use ApiPlatform\Tests\Fixtures\DummyResourceImplementation; -use ApiPlatform\Tests\Fixtures\DummyResourceInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritance; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritanceChild; -use PHPUnit\Framework\TestCase; - -/** - * @author Amrouche Hamza - */ -class ResourceClassResolverTest extends TestCase -{ - use ProphecyTrait; - - public function testGetResourceClassWithIntendedClassName() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class])); - - $dummy = new Dummy(); - $dummy->setName('Smail'); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertEquals(Dummy::class, $resourceClassResolver->getResourceClass($dummy, Dummy::class)); - } - - public function testGetResourceClassWithNonResourceClassName() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Specified class "ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar" is not a resource class.'); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class])); - - $dummy = new Dummy(); - $dummy->setName('Smail'); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $resourceClassResolver->getResourceClass($dummy, DummyCar::class, true); - } - - public function testGetResourceClassWithNoClassName() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class])); - - $dummy = new Dummy(); - $dummy->setName('Smail'); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertEquals(Dummy::class, $resourceClassResolver->getResourceClass($dummy)); - } - - public function testGetResourceClassWithTraversableAsValue() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class])); - - $dummy = new Dummy(); - $dummy->setName('JLM'); - - $dummies = new \ArrayIterator([$dummy]); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertEquals(Dummy::class, $resourceClassResolver->getResourceClass($dummies, Dummy::class)); - } - - public function testGetResourceClassWithTraversable() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([\ArrayObject::class])); - - $dummy = new \ArrayObject(); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertEquals(\ArrayObject::class, $resourceClassResolver->getResourceClass($dummy)); - } - - public function testGetResourceClassWithPaginatorInterfaceAsValue() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $paginatorProphecy = $this->prophesize(PaginatorInterface::class); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertEquals(Dummy::class, $resourceClassResolver->getResourceClass($paginatorProphecy->reveal(), Dummy::class)); - } - - public function testGetResourceClassWithWrongClassName() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No resource class found for object of type "stdClass".'); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $resourceClassResolver->getResourceClass(new \stdClass()); - } - - public function testGetResourceClassWithNoResourceClassName() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Resource type could not be determined. Resource class must be specified.'); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([])); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $resourceClassResolver->getResourceClass(new \ArrayIterator([])); - } - - public function testIsResourceClassWithIntendedClassName() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class])); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertTrue($resourceClassResolver->isResourceClass(Dummy::class)); - } - - public function testIsResourceClassWithWrongClassName() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([\ArrayIterator::class])); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertFalse($resourceClassResolver->isResourceClass('')); - } - - public function testGetResourceClassWithNoResourceClassNameAndNoObject() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Resource type could not be determined. Resource class must be specified.'); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $resourceClassResolver->getResourceClass(false); - } - - public function testGetResourceClassWithResourceClassNameAndNoObject() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class])); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertEquals(Dummy::class, $resourceClassResolver->getResourceClass(false, Dummy::class)); - } - - public function testGetResourceClassWithChildResource() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([DummyTableInheritance::class, DummyTableInheritanceChild::class])); - - $dummy = new DummyTableInheritanceChild(); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertEquals(DummyTableInheritanceChild::class, $resourceClassResolver->getResourceClass($dummy, DummyTableInheritance::class)); - } - - public function testGetResourceClassWithInterfaceResource() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([DummyResourceInterface::class])); - - $dummy = new DummyResourceImplementation(); - - $resourceClassResolver = new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()); - - $this->assertEquals(DummyResourceInterface::class, $resourceClassResolver->getResourceClass($dummy, DummyResourceInterface::class, true)); - } -} diff --git a/tests/Core/Bridge/Doctrine/Common/DataPersisterTest.php b/tests/Core/Bridge/Doctrine/Common/DataPersisterTest.php deleted file mode 100644 index 804339a9c9e..00000000000 --- a/tests/Core/Bridge/Doctrine/Common/DataPersisterTest.php +++ /dev/null @@ -1,162 +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\Tests\Bridge\Doctrine\Common; - -use ApiPlatform\Core\Bridge\Doctrine\Common\DataPersister; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectManager; -use PHPUnit\Framework\TestCase; -use Prophecy\Prediction\CallPrediction; -use Prophecy\Prediction\NoCallsPrediction; - -/** - * @author Baptiste Meyer - */ -class DataPersisterTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - $this->assertInstanceOf(DataPersisterInterface::class, new DataPersister($this->prophesize(ManagerRegistry::class)->reveal())); - } - - public function testSupports() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($this->prophesize(ObjectManager::class)->reveal())->shouldBeCalled(); - - $this->assertTrue((new DataPersister($managerRegistryProphecy->reveal()))->supports(new Dummy())); - } - - public function testDoesNotSupport() - { - $this->assertFalse((new DataPersister($this->prophesize(ManagerRegistry::class)->reveal()))->supports('dummy')); - } - - public function testPersist() - { - $dummy = new Dummy(); - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->contains($dummy)->willReturn(false); - $objectManagerProphecy->persist($dummy)->shouldBeCalled(); - $objectManagerProphecy->flush()->shouldBeCalled(); - $objectManagerProphecy->refresh($dummy)->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled(); - - $result = (new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy); - $this->assertSame($dummy, $result); - } - - public function testPersistIfEntityAlreadyManaged() - { - $dummy = new Dummy(); - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->contains($dummy)->willReturn(true); - $objectManagerProphecy->persist($dummy)->shouldNotBeCalled(); - $objectManagerProphecy->flush()->shouldBeCalled(); - $objectManagerProphecy->refresh($dummy)->shouldBeCalled(); - $objectManagerProphecy->getClassMetadata(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled(); - - $result = (new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy); - $this->assertSame($dummy, $result); - } - - public function testPersistWithNullManager() - { - $dummy = new Dummy(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $result = (new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy); - $this->assertSame($dummy, $result); - } - - public function testRemove() - { - $dummy = new Dummy(); - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->remove($dummy)->shouldBeCalled(); - $objectManagerProphecy->flush()->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy->reveal())->shouldBeCalled(); - - (new DataPersister($managerRegistryProphecy->reveal()))->remove($dummy); - } - - public function testRemoveWithNullManager() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - (new DataPersister($managerRegistryProphecy->reveal()))->remove(new Dummy()); - } - - public function getTrackingPolicyParameters() - { - return [ - 'deferred explicit ORM' => [ClassMetadataInfo::class, true, true], - 'deferred implicit ORM' => [ClassMetadataInfo::class, false, false], - 'deferred explicit ODM' => [ClassMetadata::class, true, true], - 'deferred implicit ODM' => [ClassMetadata::class, false, false], - ]; - } - - /** - * @dataProvider getTrackingPolicyParameters - * - * @param mixed $metadataClass - * @param mixed $deferredExplicit - * @param mixed $persisted - */ - public function testTrackingPolicy($metadataClass, $deferredExplicit, $persisted) - { - $dummy = new Dummy(); - - $classMetadataInfo = $this->prophesize($metadataClass); - if (method_exists($metadataClass, 'isChangeTrackingDeferredExplicit')) { - $classMetadataInfo->isChangeTrackingDeferredExplicit()->willReturn($deferredExplicit)->shouldBeCalled(); - } else { - $persisted = false; - } - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->getClassMetadata(Dummy::class)->willReturn($classMetadataInfo)->shouldBeCalled(); - $objectManagerProphecy->contains($dummy)->willReturn(true); - $objectManagerProphecy->persist($dummy)->should($persisted ? new CallPrediction() : new NoCallsPrediction()); - $objectManagerProphecy->flush()->shouldBeCalled(); - $objectManagerProphecy->refresh($dummy)->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($objectManagerProphecy)->shouldBeCalled(); - - $result = (new DataPersister($managerRegistryProphecy->reveal()))->persist($dummy); - $this->assertSame($dummy, $result); - } -} diff --git a/tests/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTraitTest.php b/tests/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTraitTest.php deleted file mode 100644 index 5c1f3a07a6b..00000000000 --- a/tests/Core/Bridge/Doctrine/Common/Util/IdentifierManagerTraitTest.php +++ /dev/null @@ -1,245 +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\Tests\Bridge\Doctrine\Common\Util; - -use ApiPlatform\Core\Bridge\Doctrine\Common\Util\IdentifierManagerTrait; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidIdentifierException; -use ApiPlatform\Exception\PropertyNotFoundException; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy as DummyDocument; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\Type as DBALType; -use Doctrine\DBAL\Types\Types; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as MongoDbOdmClassMetadata; -use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\Persistence\Mapping\ClassMetadata; -use Doctrine\Persistence\ObjectManager; -use PHPUnit\Framework\TestCase; -use Ramsey\Uuid\Doctrine\UuidType; - -/** - * @group legacy - */ -class IdentifierManagerTraitTest extends TestCase -{ - use ProphecyTrait; - - private function getIdentifierManagerTraitImpl(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory) - { - return new class($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory) { - use IdentifierManagerTrait { - IdentifierManagerTrait::normalizeIdentifiers as public; - } - - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory) - { - $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->resourceMetadataFactory = $resourceMetadataFactory; - } - }; - } - - /** - * @group legacy - */ - public function testSingleIdentifier() - { - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - $objectManager = $this->getEntityManager(Dummy::class, [ - 'id' => [ - 'type' => Types::INTEGER, - ], - ]); - - $identifierManager = $this->getIdentifierManagerTraitImpl($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory); - - $this->assertEquals($identifierManager->normalizeIdentifiers(1, $objectManager, Dummy::class), ['id' => 1]); - } - - /** - * @group legacy - * @group mongodb - */ - public function testSingleDocumentIdentifier() - { - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(DummyDocument::class, [ - 'id', - ]); - $objectManager = $this->getDocumentManager(DummyDocument::class, [ - 'id' => [ - 'type' => MongoDbType::INTEGER, - ], - ]); - - $identifierManager = $this->getIdentifierManagerTraitImpl($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory); - - $this->assertEquals($identifierManager->normalizeIdentifiers(1, $objectManager, DummyDocument::class), ['id' => 1]); - } - - /** - * @group legacy - */ - public function testCompositeIdentifier() - { - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'ida', - 'idb', - ]); - $objectManager = $this->getEntityManager(Dummy::class, [ - 'ida' => [ - 'type' => Types::INTEGER, - ], - 'idb' => [ - 'type' => Types::INTEGER, - ], - ]); - - $identifierManager = $this->getIdentifierManagerTraitImpl($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory); - - $this->assertEquals($identifierManager->normalizeIdentifiers('ida=1;idb=2', $objectManager, Dummy::class), ['ida' => 1, 'idb' => 2]); - } - - /** - * @group legacy - */ - public function testInvalidIdentifier() - { - $this->expectException(PropertyNotFoundException::class); - $this->expectExceptionMessage('Invalid identifier "idbad=1;idb=2", "ida" was not found for resource "dummy".'); - - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'ida', - 'idb', - ]); - $objectManager = $this->getEntityManager(Dummy::class, [ - 'ida' => [ - 'type' => Types::INTEGER, - ], - 'idb' => [ - 'type' => Types::INTEGER, - ], - ]); - - $identifierManager = $this->getIdentifierManagerTraitImpl($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory); - - $identifierManager->normalizeIdentifiers('idbad=1;idb=2', $objectManager, Dummy::class); - } - - /** - * Gets mocked metadata factories. - */ - private function getMetadataFactories(string $resourceClass, array $identifiers): array - { - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - - $nameCollection = ['foobar']; - - foreach ($identifiers as $identifier) { - $metadata = new PropertyMetadata(); - $metadata = $metadata->withIdentifier(true); - $propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata); - - $nameCollection[] = $identifier; - } - - // random property to prevent the use of non-identifiers metadata while looping - $propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata()); - - $propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection)); - $resourceMetadataFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadata('dummy')); - - return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()]; - } - - /** - * Gets a mocked entity manager. - */ - private function getEntityManager(string $resourceClass, array $identifierFields): ObjectManager - { - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->getIdentifier()->willReturn(array_keys($identifierFields)); - - foreach ($identifierFields as $name => $field) { - $classMetadataProphecy->getTypeOfField($name)->willReturn($field['type']); - } - - $platformProphecy = $this->prophesize(AbstractPlatform::class); - - $connectionProphecy = $this->prophesize(Connection::class); - $connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy); - - $managerProphecy = $this->prophesize(EntityManagerInterface::class); - $managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal()); - $managerProphecy->getConnection()->willReturn($connectionProphecy); - - return $managerProphecy->reveal(); - } - - /** - * Gets a mocked document manager. - */ - private function getDocumentManager(string $resourceClass, array $identifierFields): ObjectManager - { - $classMetadataProphecy = $this->prophesize(MongoDbOdmClassMetadata::class); - $classMetadataProphecy->getIdentifier()->willReturn(array_keys($identifierFields)); - - foreach ($identifierFields as $name => $field) { - $classMetadataProphecy->getTypeOfField($name)->willReturn($field['type']); - } - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal()); - - return $managerProphecy->reveal(); - } - - /** - * @group legacy - */ - public function testInvalidIdentifierConversion() - { - DBALType::addType('ramsey-uuid', UuidType::class); - - $this->expectException(InvalidIdentifierException::class); - $this->expectExceptionMessage('Invalid value "ida" provided for an identifier for resource "dummy".'); - - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'ida', - ]); - $objectManager = $this->getEntityManager(Dummy::class, [ - 'ida' => [ - 'type' => 'ramsey-uuid', - ], - ]); - - $identifierManager = $this->getIdentifierManagerTraitImpl($propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory); - - $identifierManager->normalizeIdentifiers('notanuuid', $objectManager, Dummy::class); - } -} diff --git a/tests/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProviderTest.php b/tests/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProviderTest.php deleted file mode 100644 index 82ecc83cacd..00000000000 --- a/tests/Core/Bridge/Doctrine/MongoDbOdm/CollectionDataProviderTest.php +++ /dev/null @@ -1,166 +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\Tests\Bridge\Doctrine\MongoDbOdm; - -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\CollectionDataProvider; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\Operations; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Iterator\Iterator; -use Doctrine\ODM\MongoDB\Repository\DocumentRepository; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectRepository; -use PHPUnit\Framework\TestCase; - -/** - * @author Alan Poulain - * - * @group mongodb - */ -class CollectionDataProviderTest extends TestCase -{ - use ProphecyTrait; - - private $managerRegistryProphecy; - private $resourceMetadataFactoryProphecy; - - /** - * {@inheritdoc} - */ - protected function setUp(): void - { - parent::setUp(); - - $this->managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $this->resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - } - - public function testGetCollection() - { - // No solution for this see https://github.com/doctrine/mongodb-odm/pull/2395 - if (method_exists(Builder::class, 'getAggregation')) { - $this->markTestSkipped('Can not mock Aggregation.'); - } - - $iterator = $this->prophesize(Iterator::class)->reveal(); - - $aggregationBuilderProphecy = $this->prophesize(Builder::class); - $aggregationBuilderProphecy->hydrate(Dummy::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled(); - $aggregationBuilderProphecy->execute([])->willReturn($iterator)->shouldBeCalled(); - $aggregationBuilder = $aggregationBuilderProphecy->reveal(); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder)->shouldBeCalled(); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $this->managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - $this->resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [(new ApiResource())->withOperations(new Operations(['foo' => new GetCollection()]))])); - - $extensionProphecy = $this->prophesize(AggregationCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($aggregationBuilder, Dummy::class, 'foo', [])->shouldBeCalled(); - - $dataProvider = new CollectionDataProvider($this->managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals($iterator, $dataProvider->getCollection(Dummy::class, 'foo')); - } - - public function testGetCollectionWithExecuteOptions() - { - if (method_exists(Builder::class, 'getAggregation')) { - $this->markTestSkipped('Can not mock Aggregation.'); - } - - $iterator = $this->prophesize(Iterator::class)->reveal(); - - $aggregationBuilderProphecy = $this->prophesize(Builder::class); - $aggregationBuilderProphecy->hydrate(Dummy::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled(); - $aggregationBuilderProphecy->execute(['allowDiskUse' => true])->willReturn($iterator)->shouldBeCalled(); - $aggregationBuilder = $aggregationBuilderProphecy->reveal(); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder)->shouldBeCalled(); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $this->managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - $this->resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [(new ApiResource())->withOperations(new Operations(['foo' => (new GetCollection())->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]])]))])); - - $extensionProphecy = $this->prophesize(AggregationCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($aggregationBuilder, Dummy::class, 'foo', [])->shouldBeCalled(); - - $dataProvider = new CollectionDataProvider($this->managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals($iterator, $dataProvider->getCollection(Dummy::class, 'foo')); - } - - public function testAggregationResultExtension() - { - $aggregationBuilderProphecy = $this->prophesize(Builder::class); - $aggregationBuilder = $aggregationBuilderProphecy->reveal(); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder)->shouldBeCalled(); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $this->managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationResultCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($aggregationBuilder, Dummy::class, 'foo', [])->shouldBeCalled(); - $extensionProphecy->supportsResult(Dummy::class, 'foo', [])->willReturn(true)->shouldBeCalled(); - $extensionProphecy->getResult($aggregationBuilder, Dummy::class, 'foo', [])->willReturn([])->shouldBeCalled(); - - $dataProvider = new CollectionDataProvider($this->managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->getCollection(Dummy::class, 'foo')); - } - - public function testCannotCreateAggregationBuilder() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The repository for "ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy" must be an instance of "Doctrine\ODM\MongoDB\Repository\DocumentRepository".'); - - $repositoryProphecy = $this->prophesize(ObjectRepository::class); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $this->managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - $dataProvider = new CollectionDataProvider($this->managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal()); - $this->assertEquals([], $dataProvider->getCollection(Dummy::class, 'foo')); - } - - public function testUnsupportedClass() - { - $this->managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationResultCollectionExtensionInterface::class); - - $dataProvider = new CollectionDataProvider($this->managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(Dummy::class, 'foo')); - } -} diff --git a/tests/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProviderTest.php b/tests/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProviderTest.php deleted file mode 100644 index 37c883f255f..00000000000 --- a/tests/Core/Bridge/Doctrine/MongoDbOdm/ItemDataProviderTest.php +++ /dev/null @@ -1,307 +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\Tests\Bridge\Doctrine\MongoDbOdm; - -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\ItemDataProvider; -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Aggregation\Stage\MatchStage as AggregationMatch; -use Doctrine\ODM\MongoDB\Iterator\Iterator; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ODM\MongoDB\Repository\DocumentRepository; -use Doctrine\ODM\MongoDB\Types\Type as MongoDbType; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectManager; -use Doctrine\Persistence\ObjectRepository; -use PHPUnit\Framework\TestCase; - -/** - * @group mongodb - * - * @author Alan Poulain - */ -class ItemDataProviderTest extends TestCase -{ - use ProphecyTrait; - - private $resourceMetadataFactoryProphecy; - - /** - * {@inheritdoc} - */ - protected function setUp(): void - { - parent::setUp(); - - $this->resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - } - - public function testGetItemSingleIdentifier() - { - $context = ['foo' => 'bar', 'fetch_data' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $matchProphecy = $this->prophesize(AggregationMatch::class); - $matchProphecy->field('id')->willReturn($matchProphecy)->shouldBeCalled(); - $matchProphecy->equals(1)->shouldBeCalled(); - - $iterator = $this->prophesize(Iterator::class); - $result = new \stdClass(); - $iterator->current()->willReturn($result)->shouldBeCalled(); - - $aggregationBuilderProphecy = $this->prophesize(Builder::class); - $aggregationBuilderProphecy->match()->willReturn($matchProphecy->reveal())->shouldBeCalled(); - $aggregationBuilderProphecy->hydrate(Dummy::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled(); - $aggregationBuilderProphecy->execute([])->willReturn($iterator->reveal())->shouldBeCalled(); - $aggregationBuilder = $aggregationBuilderProphecy->reveal(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, $aggregationBuilder); - - $this->resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); - - $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); - $extensionProphecy->applyToItem($aggregationBuilder, Dummy::class, ['id' => 1], 'foo', $context)->shouldBeCalled(); - - $dataProvider = new ItemDataProvider($managerRegistry, $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - - $this->assertEquals($result, $dataProvider->getItem(Dummy::class, ['id' => 1], 'foo', $context)); - } - - public function testGetItemWithExecuteOptions() - { - $context = ['foo' => 'bar', 'fetch_data' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $matchProphecy = $this->prophesize(AggregationMatch::class); - $matchProphecy->field('id')->willReturn($matchProphecy)->shouldBeCalled(); - $matchProphecy->equals(1)->shouldBeCalled(); - - $iterator = $this->prophesize(Iterator::class); - $result = new \stdClass(); - $iterator->current()->willReturn($result)->shouldBeCalled(); - - $aggregationBuilderProphecy = $this->prophesize(Builder::class); - $aggregationBuilderProphecy->match()->willReturn($matchProphecy->reveal())->shouldBeCalled(); - $aggregationBuilderProphecy->hydrate(Dummy::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled(); - $aggregationBuilderProphecy->execute(['allowDiskUse' => true])->willReturn($iterator->reveal())->shouldBeCalled(); - $aggregationBuilder = $aggregationBuilderProphecy->reveal(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, $aggregationBuilder); - - $this->resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata( - 'Dummy', - null, - null, - ['foo' => ['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]]] - )); - - $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); - $extensionProphecy->applyToItem($aggregationBuilder, Dummy::class, ['id' => 1], 'foo', $context)->shouldBeCalled(); - - $dataProvider = new ItemDataProvider($managerRegistry, $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - - $this->assertEquals($result, $dataProvider->getItem(Dummy::class, ['id' => 1], 'foo', $context)); - } - - public function testGetItemDoubleIdentifier() - { - $matchProphecy = $this->prophesize(AggregationMatch::class); - $matchProphecy->field('ida')->willReturn($matchProphecy)->shouldBeCalled(); - $matchProphecy->field('idb')->willReturn($matchProphecy)->shouldBeCalled(); - $matchProphecy->equals(1)->shouldBeCalled(); - $matchProphecy->equals(2)->shouldBeCalled(); - - $iterator = $this->prophesize(Iterator::class); - $result = new \stdClass(); - $iterator->current()->willReturn($result)->shouldBeCalled(); - - $aggregationBuilderProphecy = $this->prophesize(Builder::class); - $aggregationBuilderProphecy->match()->willReturn($matchProphecy->reveal())->shouldBeCalled(); - $aggregationBuilderProphecy->hydrate(Dummy::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled(); - $aggregationBuilderProphecy->execute([])->willReturn($iterator->reveal())->shouldBeCalled(); - $aggregationBuilder = $aggregationBuilderProphecy->reveal(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'ida', - 'idb', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, $aggregationBuilder); - - $this->resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); - - $context = [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); - $extensionProphecy->applyToItem($aggregationBuilder, Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', $context)->shouldBeCalled(); - - $dataProvider = new ItemDataProvider($managerRegistry, $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - - $this->assertEquals($result, $dataProvider->getItem(Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', $context)); - } - - /** - * @group legacy - */ - public function testGetItemWrongCompositeIdentifier() - { - $this->expectException(PropertyNotFoundException::class); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'ida', - 'idb', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, $this->prophesize(Builder::class)->reveal(), [ - 'ida' => [ - 'type' => MongoDbType::INTEGER, - ], - 'idb' => [ - 'type' => MongoDbType::INTEGER, - ], - ]); - - $dataProvider = new ItemDataProvider($managerRegistry, $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - $dataProvider->getItem(Dummy::class, 'ida=1;', 'foo'); - } - - public function testAggregationResultExtension() - { - $matchProphecy = $this->prophesize(AggregationMatch::class); - $matchProphecy->field('id')->willReturn($matchProphecy)->shouldBeCalled(); - $matchProphecy->equals(1)->shouldBeCalled(); - - $aggregationBuilderProphecy = $this->prophesize(Builder::class); - $aggregationBuilderProphecy->match()->willReturn($matchProphecy->reveal())->shouldBeCalled(); - $aggregationBuilder = $aggregationBuilderProphecy->reveal(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, $aggregationBuilder); - - $context = [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - $extensionProphecy = $this->prophesize(AggregationResultItemExtensionInterface::class); - $extensionProphecy->applyToItem($aggregationBuilder, Dummy::class, ['id' => 1], 'foo', $context)->shouldBeCalled(); - $extensionProphecy->supportsResult(Dummy::class, 'foo', $context)->willReturn(true)->shouldBeCalled(); - $extensionProphecy->getResult($aggregationBuilder, Dummy::class, 'foo', $context)->willReturn([])->shouldBeCalled(); - - $dataProvider = new ItemDataProvider($managerRegistry, $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - - $this->assertEquals([], $dataProvider->getItem(Dummy::class, ['id' => 1], 'foo', $context)); - } - - public function testUnsupportedClass() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - - $dataProvider = new ItemDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(Dummy::class, 'foo')); - } - - public function testCannotCreateAggregationBuilder() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The repository for "ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy" must be an instance of "Doctrine\ODM\MongoDB\Repository\DocumentRepository".'); - - $repositoryProphecy = $this->prophesize(ObjectRepository::class); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal()); - - $extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - - (new ItemDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]))->getItem(Dummy::class, 'foo', null, [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]); - } - - /** - * Gets mocked metadata factories. - */ - private function getMetadataFactories(string $resourceClass, array $identifiers): array - { - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - - $nameCollection = ['foobar']; - - foreach ($identifiers as $identifier) { - $metadata = new PropertyMetadata(); - $metadata = $metadata->withIdentifier(true); - $propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata); - - $nameCollection[] = $identifier; - } - - // random property to prevent the use of non-identifiers metadata while looping - $propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata()); - - $propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection)); - - return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()]; - } - - /** - * Gets a mocked manager registry. - */ - private function getManagerRegistry(string $resourceClass, Builder $aggregationBuilder, array $identifierFields = []): ManagerRegistry - { - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->getIdentifier()->willReturn(array_keys($identifierFields)); - - foreach ($identifierFields as $name => $field) { - $classMetadataProphecy->getTypeOfField($name)->willReturn($field['type']); - } - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal()); - $managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal()); - - return $managerRegistryProphecy->reveal(); - } -} diff --git a/tests/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProviderTest.php b/tests/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProviderTest.php deleted file mode 100644 index 54def94b3d0..00000000000 --- a/tests/Core/Bridge/Doctrine/MongoDbOdm/SubresourceDataProviderTest.php +++ /dev/null @@ -1,541 +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\Tests\Bridge\Doctrine\MongoDbOdm; - -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\SubresourceDataProvider; -use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; -use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedOwningDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\ThirdLevel; -use Doctrine\ODM\MongoDB\Aggregation\Builder; -use Doctrine\ODM\MongoDB\Aggregation\Stage\Lookup; -use Doctrine\ODM\MongoDB\Aggregation\Stage\MatchStage as AggregationMatch; -use Doctrine\ODM\MongoDB\DocumentManager; -use Doctrine\ODM\MongoDB\Iterator\Iterator; -use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ODM\MongoDB\Repository\DocumentRepository; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectRepository; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; - -/** - * @group mongodb - * - * @author Alan Poulain - */ -class SubresourceDataProviderTest extends TestCase -{ - use ProphecyTrait; - - private $resourceMetadataFactoryProphecy; - - /** - * {@inheritdoc} - */ - protected function setUp(): void - { - parent::setUp(); - - $this->resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - } - - private function getMetadataProphecies(array $resourceClassesIdentifiers) - { - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - - foreach ($resourceClassesIdentifiers as $resourceClass => $identifiers) { - $nameCollection = ['foobar']; - - foreach ($identifiers as $identifier) { - $metadata = new PropertyMetadata(); - $metadata = $metadata->withIdentifier(true); - $propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata); - - $nameCollection[] = $identifier; - } - - // random property to prevent the use of non-identifiers metadata while looping - $propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata()); - - $propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection)); - } - - return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()]; - } - - private function getManagerRegistryProphecy(Builder $aggregationBuilder, array $identifiers, string $resourceClass) - { - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass($resourceClass)->willReturn($managerProphecy->reveal()); - - return $managerRegistryProphecy->reveal(); - } - - public function testNotASubresource() - { - $this->expectException(ResourceClassNotSupportedException::class); - $this->expectExceptionMessage('The given resource class is not a subresource.'); - - $identifiers = ['id']; - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - $aggregationBuilder = $this->prophesize(Builder::class)->reveal(); - $managerRegistry = $this->getManagerRegistryProphecy($aggregationBuilder, $identifiers, Dummy::class); - - $dataProvider = new SubresourceDataProvider($managerRegistry, $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, []); - - $dataProvider->getSubresource(Dummy::class, ['id' => 1], []); - } - - public function testGetSubresource() - { - $aggregationBuilder = $this->prophesize(Builder::class); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->shouldBeCalled()->willReturn($aggregationBuilder->reveal()); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(RelatedDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - - $managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - - $dummyAggregationBuilder = $this->prophesize(Builder::class); - $dummyLookup = $this->prophesize(Lookup::class); - $dummyLookup->alias('relatedDummies')->shouldBeCalled(); - $dummyAggregationBuilder->lookup('relatedDummies')->shouldBeCalled()->willReturn($dummyLookup->reveal()); - - $dummyMatch = $this->prophesize(AggregationMatch::class); - $dummyMatch->equals(1)->shouldBeCalled(); - $dummyMatch->field('id')->shouldBeCalled()->willReturn($dummyMatch); - $dummyAggregationBuilder->match()->shouldBeCalled()->willReturn($dummyMatch->reveal()); - - $dummyIterator = $this->prophesize(Iterator::class); - $dummyIterator->toArray()->shouldBeCalled()->willReturn([['_id' => 1, 'relatedDummies' => [['_id' => 2]]]]); - $dummyAggregationBuilder->execute([])->shouldBeCalled()->willReturn($dummyIterator->reveal()); - - $managerProphecy->createAggregationBuilder(Dummy::class)->shouldBeCalled()->willReturn($dummyAggregationBuilder->reveal()); - - $match = $this->prophesize(AggregationMatch::class); - $match->in([2])->shouldBeCalled(); - $match->field('_id')->shouldBeCalled()->willReturn($match); - $aggregationBuilder->match()->shouldBeCalled()->willReturn($match); - - $iterator = $this->prophesize(Iterator::class); - $iterator->toArray()->shouldBeCalled()->willReturn([]); - $aggregationBuilder->execute([])->shouldBeCalled()->willReturn($iterator->reveal()); - $aggregationBuilder->hydrate(RelatedDummy::class)->shouldBeCalled()->willReturn($aggregationBuilder); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - $this->resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => ['id']]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'relatedDummies', 'identifiers' => ['id' => [Dummy::class, 'id']], 'collection' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals([], $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1]], $context)); - } - - public function testGetSubSubresourceItem() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $identifiers = ['id']; - - // First manager (Dummy) - $dummyAggregationBuilder = $this->prophesize(Builder::class); - $dummyLookup = $this->prophesize(Lookup::class); - $dummyLookup->alias('relatedDummies')->shouldBeCalled(); - $dummyAggregationBuilder->lookup('relatedDummies')->shouldBeCalled()->willReturn($dummyLookup->reveal()); - - $dummyMatch = $this->prophesize(AggregationMatch::class); - $dummyMatch->equals(1)->shouldBeCalled(); - $dummyMatch->field('id')->shouldBeCalled()->willReturn($dummyMatch); - $dummyAggregationBuilder->match()->shouldBeCalled()->willReturn($dummyMatch->reveal()); - - $dummyIterator = $this->prophesize(Iterator::class); - $dummyIterator->toArray()->shouldBeCalled()->willReturn([['_id' => 1, 'relatedDummies' => [['_id' => 2]]]]); - $dummyAggregationBuilder->execute([])->shouldBeCalled()->willReturn($dummyIterator->reveal()); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - - $dummyManagerProphecy = $this->prophesize(DocumentManager::class); - $dummyManagerProphecy->createAggregationBuilder(Dummy::class)->shouldBeCalled()->willReturn($dummyAggregationBuilder->reveal()); - $dummyManagerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($dummyManagerProphecy->reveal()); - - // Second manager (RelatedDummy) - $rAggregationBuilder = $this->prophesize(Builder::class); - $rLookup = $this->prophesize(Lookup::class); - $rLookup->alias('thirdLevel')->shouldBeCalled(); - $rAggregationBuilder->lookup('thirdLevel')->shouldBeCalled()->willReturn($rLookup->reveal()); - - $rMatch = $this->prophesize(AggregationMatch::class); - $rMatch->equals(1)->shouldBeCalled(); - $rMatch->field('id')->shouldBeCalled()->willReturn($rMatch); - $previousRMatch = $this->prophesize(AggregationMatch::class); - $previousRMatch->in([2])->shouldBeCalled(); - $previousRMatch->field('_id')->shouldBeCalled()->willReturn($previousRMatch); - $rAggregationBuilder->match()->shouldBeCalled()->willReturn($rMatch->reveal(), $previousRMatch->reveal()); - - $rIterator = $this->prophesize(Iterator::class); - $rIterator->toArray()->shouldBeCalled()->willReturn([['_id' => 1, 'thirdLevel' => [['_id' => 3]]]]); - $rAggregationBuilder->execute([])->shouldBeCalled()->willReturn($rIterator->reveal()); - - $rClassMetadataProphecy = $this->prophesize(ClassMetadata::class); - $rClassMetadataProphecy->hasAssociation('thirdLevel')->shouldBeCalled()->willReturn(true); - - $rDummyManagerProphecy = $this->prophesize(DocumentManager::class); - $rDummyManagerProphecy->createAggregationBuilder(RelatedDummy::class)->shouldBeCalled()->willReturn($rAggregationBuilder->reveal()); - $rDummyManagerProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($rClassMetadataProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($rDummyManagerProphecy->reveal()); - - $result = new \stdClass(); - // Origin manager (ThirdLevel) - $aggregationBuilder = $this->prophesize(Builder::class); - - $match = $this->prophesize(AggregationMatch::class); - $match->in([3])->shouldBeCalled(); - $match->field('_id')->shouldBeCalled()->willReturn($match); - $aggregationBuilder->match()->shouldBeCalled()->willReturn($match); - - $iterator = $this->prophesize(Iterator::class); - $iterator->current()->shouldBeCalled()->willReturn($result); - $aggregationBuilder->execute([])->shouldBeCalled()->willReturn($iterator->reveal()); - $aggregationBuilder->hydrate(ThirdLevel::class)->shouldBeCalled()->willReturn($aggregationBuilder); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->shouldBeCalled()->willReturn($aggregationBuilder->reveal()); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(ThirdLevel::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(ThirdLevel::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - $this->resourceMetadataFactoryProphecy->create(ThirdLevel::class)->willReturn(new ResourceMetadata()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers, RelatedDummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'thirdLevel', 'identifiers' => ['id' => [Dummy::class, 'id'], 'relatedDummies' => [RelatedDummy::class, 'id']], 'collection' => false, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals($result, $dataProvider->getSubresource(ThirdLevel::class, ['id' => ['id' => 1], 'relatedDummies' => ['id' => 1]], $context)); - } - - public function testGetSubSubresourceItemWithExecuteOptions() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $identifiers = ['id']; - - // First manager (Dummy) - $dummyAggregationBuilder = $this->prophesize(Builder::class); - $dummyLookup = $this->prophesize(Lookup::class); - $dummyLookup->alias('relatedDummies')->shouldBeCalled(); - $dummyAggregationBuilder->lookup('relatedDummies')->shouldBeCalled()->willReturn($dummyLookup->reveal()); - - $dummyMatch = $this->prophesize(AggregationMatch::class); - $dummyMatch->equals(1)->shouldBeCalled(); - $dummyMatch->field('id')->shouldBeCalled()->willReturn($dummyMatch); - $dummyAggregationBuilder->match()->shouldBeCalled()->willReturn($dummyMatch->reveal()); - - $dummyIterator = $this->prophesize(Iterator::class); - $dummyIterator->toArray()->shouldBeCalled()->willReturn([['_id' => 1, 'relatedDummies' => [['_id' => 2]]]]); - $dummyAggregationBuilder->execute(['allowDiskUse' => true])->shouldBeCalled()->willReturn($dummyIterator->reveal()); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - - $dummyManagerProphecy = $this->prophesize(DocumentManager::class); - $dummyManagerProphecy->createAggregationBuilder(Dummy::class)->shouldBeCalled()->willReturn($dummyAggregationBuilder->reveal()); - $dummyManagerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($dummyManagerProphecy->reveal()); - - // Second manager (RelatedDummy) - $rAggregationBuilder = $this->prophesize(Builder::class); - $rLookup = $this->prophesize(Lookup::class); - $rLookup->alias('thirdLevel')->shouldBeCalled(); - $rAggregationBuilder->lookup('thirdLevel')->shouldBeCalled()->willReturn($rLookup->reveal()); - - $rMatch = $this->prophesize(AggregationMatch::class); - $rMatch->equals(1)->shouldBeCalled(); - $rMatch->field('id')->shouldBeCalled()->willReturn($rMatch); - $previousRMatch = $this->prophesize(AggregationMatch::class); - $previousRMatch->in([2])->shouldBeCalled(); - $previousRMatch->field('_id')->shouldBeCalled()->willReturn($previousRMatch); - $rAggregationBuilder->match()->shouldBeCalled()->willReturn($rMatch->reveal(), $previousRMatch->reveal()); - - $rIterator = $this->prophesize(Iterator::class); - $rIterator->toArray()->shouldBeCalled()->willReturn([['_id' => 1, 'thirdLevel' => [['_id' => 3]]]]); - $rAggregationBuilder->execute(['allowDiskUse' => true])->shouldBeCalled()->willReturn($rIterator->reveal()); - - $rClassMetadataProphecy = $this->prophesize(ClassMetadata::class); - $rClassMetadataProphecy->hasAssociation('thirdLevel')->shouldBeCalled()->willReturn(true); - - $rDummyManagerProphecy = $this->prophesize(DocumentManager::class); - $rDummyManagerProphecy->createAggregationBuilder(RelatedDummy::class)->shouldBeCalled()->willReturn($rAggregationBuilder->reveal()); - $rDummyManagerProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($rClassMetadataProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($rDummyManagerProphecy->reveal()); - - $result = new \stdClass(); - // Origin manager (ThirdLevel) - $aggregationBuilder = $this->prophesize(Builder::class); - - $match = $this->prophesize(AggregationMatch::class); - $match->in([3])->shouldBeCalled(); - $match->field('_id')->shouldBeCalled()->willReturn($match); - $aggregationBuilder->match()->shouldBeCalled()->willReturn($match); - - $iterator = $this->prophesize(Iterator::class); - $iterator->current()->shouldBeCalled()->willReturn($result); - $aggregationBuilder->execute(['allowDiskUse' => true])->shouldBeCalled()->willReturn($iterator->reveal()); - $aggregationBuilder->hydrate(ThirdLevel::class)->shouldBeCalled()->willReturn($aggregationBuilder); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->shouldBeCalled()->willReturn($aggregationBuilder->reveal()); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(ThirdLevel::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(ThirdLevel::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - $this->resourceMetadataFactoryProphecy->create(ThirdLevel::class)->willReturn(new ResourceMetadata( - 'ThirdLevel', - null, - null, - null, - null, - null, - ['third_level_operation_name' => ['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]]] - )); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers, RelatedDummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'thirdLevel', 'identifiers' => ['id' => [Dummy::class, 'id'], 'relatedDummies' => [RelatedDummy::class, 'id']], 'collection' => false, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals($result, $dataProvider->getSubresource(ThirdLevel::class, ['id' => ['id' => 1], 'relatedDummies' => ['id' => 1]], $context, 'third_level_operation_name')); - } - - public function testGetSubresourceOneToOneOwningRelation() - { - // RelatedOwningDummy OneToOne Dummy - $identifiers = ['id']; - $aggregationBuilder = $this->prophesize(Builder::class); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->shouldBeCalled()->willReturn($aggregationBuilder->reveal()); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(RelatedOwningDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('ownedDummy')->willReturn(true)->shouldBeCalled(); - - $managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - - $lookup = $this->prophesize(Lookup::class); - $lookup->alias('ownedDummy')->shouldBeCalled(); - $aggregationBuilder->lookup('ownedDummy')->shouldBeCalled()->willReturn($lookup->reveal()); - $managerProphecy->createAggregationBuilder(Dummy::class)->shouldBeCalled()->willReturn($aggregationBuilder->reveal()); - - $match = $this->prophesize(AggregationMatch::class); - $match->equals(1)->shouldBeCalled(); - $match->field('id')->shouldBeCalled()->willReturn($match); - $previousMatch = $this->prophesize(AggregationMatch::class); - $previousMatch->in([3])->shouldBeCalled(); - $previousMatch->field('_id')->shouldBeCalled()->willReturn($previousMatch); - $aggregationBuilder->match()->shouldBeCalled()->willReturn($match->reveal(), $previousMatch->reveal()); - - $iterator = $this->prophesize(Iterator::class); - $iterator->toArray()->shouldBeCalled()->willReturn([['_id' => 1, 'ownedDummy' => [['_id' => 3]]]]); - $result = new \stdClass(); - $iterator->current()->shouldBeCalled()->willReturn($result); - $aggregationBuilder->execute([])->shouldBeCalled()->willReturn($iterator->reveal()); - $aggregationBuilder->hydrate(RelatedOwningDummy::class)->shouldBeCalled()->willReturn($aggregationBuilder); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(RelatedOwningDummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - $this->resourceMetadataFactoryProphecy->create(RelatedOwningDummy::class)->willReturn(new ResourceMetadata()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'ownedDummy', 'identifiers' => ['id' => [Dummy::class, 'id']], 'collection' => false, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals($result, $dataProvider->getSubresource(RelatedOwningDummy::class, ['id' => ['id' => 1]], $context)); - } - - public function testAggregationResultExtension() - { - $identifiers = ['id']; - $aggregationBuilder = $this->prophesize(Builder::class); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->shouldBeCalled()->willReturn($aggregationBuilder->reveal()); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(RelatedDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - - $managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - - $lookup = $this->prophesize(Lookup::class); - $lookup->alias('relatedDummies')->shouldBeCalled(); - $aggregationBuilder->lookup('relatedDummies')->shouldBeCalled()->willReturn($lookup->reveal()); - $managerProphecy->createAggregationBuilder(Dummy::class)->shouldBeCalled()->willReturn($aggregationBuilder->reveal()); - - $match = $this->prophesize(AggregationMatch::class); - $match->equals(1)->shouldBeCalled(); - $match->field('id')->shouldBeCalled()->willReturn($match); - $previousMatch = $this->prophesize(AggregationMatch::class); - $previousMatch->in([3])->shouldBeCalled(); - $previousMatch->field('_id')->shouldBeCalled()->willReturn($previousMatch); - $aggregationBuilder->match()->shouldBeCalled()->willReturn($match->reveal(), $previousMatch->reveal()); - - $iterator = $this->prophesize(Iterator::class); - $iterator->toArray()->shouldBeCalled()->willReturn([['_id' => 1, 'relatedDummies' => [['_id' => 3]]]]); - $aggregationBuilder->execute([])->shouldBeCalled()->willReturn($iterator->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - $this->resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $extensionProphecy = $this->prophesize(AggregationResultCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($aggregationBuilder, RelatedDummy::class, null, Argument::type('array'))->shouldBeCalled(); - $extensionProphecy->supportsResult(RelatedDummy::class, null, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $extensionProphecy->getResult($aggregationBuilder, RelatedDummy::class, null, Argument::type('array'))->willReturn([])->shouldBeCalled(); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - - $context = ['property' => 'relatedDummies', 'identifiers' => ['id' => [Dummy::class, 'id']], 'collection' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals([], $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1]], $context)); - } - - public function testCannotCreateQueryBuilder() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The repository for "ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy" must be an instance of "Doctrine\ODM\MongoDB\Repository\DocumentRepository".'); - - $identifiers = ['id']; - $repositoryProphecy = $this->prophesize(ObjectRepository::class); - - $managerProphecy = $this->prophesize(DocumentManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - $dataProvider->getSubresource(Dummy::class, ['id' => 1], []); - } - - public function testThrowResourceClassNotSupportedException() - { - $this->expectException(ResourceClassNotSupportedException::class); - - $identifiers = ['id']; - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - $dataProvider->getSubresource(Dummy::class, ['id' => 1], []); - } - - public function testGetSubresourceCollectionItem() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $identifiers = ['id']; - - $rAggregationBuilder = $this->prophesize(Builder::class); - - $rClassMetadataProphecy = $this->prophesize(ClassMetadata::class); - $rClassMetadataProphecy->hasAssociation('id')->shouldBeCalled()->willReturn(false); - $rClassMetadataProphecy->isIdentifier('id')->shouldBeCalled()->willReturn(true); - - $rDummyManagerProphecy = $this->prophesize(DocumentManager::class); - $rDummyManagerProphecy->createAggregationBuilder(RelatedDummy::class)->shouldBeCalled()->willReturn($rAggregationBuilder->reveal()); - $rDummyManagerProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($rClassMetadataProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($rDummyManagerProphecy->reveal()); - - $result = new \stdClass(); - - $rIterator = $this->prophesize(Iterator::class); - $rIterator->current()->shouldBeCalled()->willReturn($result); - $rAggregationBuilder->execute([])->shouldBeCalled()->willReturn($rIterator->reveal()); - $rAggregationBuilder->hydrate(RelatedDummy::class)->shouldBeCalled()->willReturn($rAggregationBuilder); - - $aggregationBuilder = $this->prophesize(Builder::class); - - $repositoryProphecy = $this->prophesize(DocumentRepository::class); - $repositoryProphecy->createAggregationBuilder()->shouldBeCalled()->willReturn($aggregationBuilder->reveal()); - - $rDummyManagerProphecy->getRepository(RelatedDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - $this->resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers, RelatedDummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $this->resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'id', 'identifiers' => ['id' => [Dummy::class, 'id', true], [RelatedDummy::class, 'relatedDummies', true]], 'collection' => false, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals($result, $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1], 'relatedDummies' => ['id' => 2]], $context)); - } -} diff --git a/tests/Core/Bridge/Doctrine/Orm/CollectionDataProviderTest.php b/tests/Core/Bridge/Doctrine/Orm/CollectionDataProviderTest.php deleted file mode 100644 index 008ddcfb360..00000000000 --- a/tests/Core/Bridge/Doctrine/Orm/CollectionDataProviderTest.php +++ /dev/null @@ -1,114 +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\Tests\Bridge\Doctrine\Orm; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\CollectionDataProvider; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use Doctrine\ORM\AbstractQuery; -use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectManager; -use Doctrine\Persistence\ObjectRepository; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; - -/** - * @author Kévin Dunglas - */ -class CollectionDataProviderTest extends TestCase -{ - use ProphecyTrait; - - public function testGetCollection() - { - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getResult()->willReturn([])->shouldBeCalled(); - - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->getQuery()->willReturn($queryProphecy->reveal())->shouldBeCalled(); - $queryBuilder = $queryBuilderProphecy->reveal(); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder)->shouldBeCalled(); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, 'foo', [])->shouldBeCalled(); - - $dataProvider = new CollectionDataProvider($managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->getCollection(Dummy::class, 'foo')); - } - - public function testQueryResultExtension() - { - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilder = $queryBuilderProphecy->reveal(); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder)->shouldBeCalled(); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryResultCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, 'foo', [])->shouldBeCalled(); - $extensionProphecy->supportsResult(Dummy::class, 'foo', [])->willReturn(true)->shouldBeCalled(); - $extensionProphecy->getResult($queryBuilder, Dummy::class, 'foo', [])->willReturn([])->shouldBeCalled(); - - $dataProvider = new CollectionDataProvider($managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->getCollection(Dummy::class, 'foo')); - } - - public function testCannotCreateQueryBuilder() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The repository class must have a "createQueryBuilder" method.'); - - $repositoryProphecy = $this->prophesize(ObjectRepository::class); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - $dataProvider = new CollectionDataProvider($managerRegistryProphecy->reveal()); - $this->assertEquals([], $dataProvider->getCollection(Dummy::class, 'foo')); - } - - public function testUnsupportedClass() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryResultCollectionExtensionInterface::class); - - $dataProvider = new CollectionDataProvider($managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertFalse($dataProvider->supports(Dummy::class, 'foo')); - } -} diff --git a/tests/Core/Bridge/Doctrine/Orm/ItemDataProviderTest.php b/tests/Core/Bridge/Doctrine/Orm/ItemDataProviderTest.php deleted file mode 100644 index f50b63efe19..00000000000 --- a/tests/Core/Bridge/Doctrine/Orm/ItemDataProviderTest.php +++ /dev/null @@ -1,305 +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\Tests\Bridge\Doctrine\Orm; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; -use ApiPlatform\Core\Bridge\Doctrine\Orm\ItemDataProvider; -use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\PropertyNotFoundException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\AbstractQuery; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Query\Expr; -use Doctrine\ORM\Query\Expr\Comparison; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectRepository; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; - -/** - * @author Antoine Bluchet - * @group legacy - */ -class ItemDataProviderTest extends TestCase -{ - use ProphecyTrait; - - public function testGetItemSingleIdentifier() - { - $context = ['foo' => 'bar', 'fetch_data' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->willReturn([])->shouldBeCalled(); - - $comparisonProphecy = $this->prophesize(Comparison::class); - $comparison = $comparisonProphecy->reveal(); - - $exprProphecy = $this->prophesize(Expr::class); - $exprProphecy->eq('o.id', ':id_p1')->willReturn($comparisonProphecy)->shouldBeCalled(); - - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->getQuery()->willReturn($queryProphecy->reveal())->shouldBeCalled(); - $queryBuilderProphecy->expr()->willReturn($exprProphecy->reveal())->shouldBeCalled(); - $queryBuilderProphecy->andWhere($comparison)->shouldBeCalled(); - $queryBuilderProphecy->getRootAliases()->shouldBeCalled()->willReturn(['o']); - $queryBuilderProphecy->setParameter('id_p1', 1, Types::INTEGER)->shouldBeCalled(); - - $queryBuilder = $queryBuilderProphecy->reveal(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, [ - 'id' => [ - 'type' => Types::INTEGER, - ], - ], $queryBuilder); - - $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo', $context)->shouldBeCalled(); - - $dataProvider = new ItemDataProvider($managerRegistry, $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()], $resourceMetadataFactory); - - $this->assertEquals([], $dataProvider->getItem(Dummy::class, ['id' => 1], 'foo', $context)); - } - - public function testGetItemDoubleIdentifier() - { - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->willReturn([])->shouldBeCalled(); - - $comparisonProphecy = $this->prophesize(Comparison::class); - $comparison = $comparisonProphecy->reveal(); - - $exprProphecy = $this->prophesize(Expr::class); - $exprProphecy->eq('o.ida', ':ida_p1')->willReturn($comparisonProphecy)->shouldBeCalled(); - $exprProphecy->eq('o.idb', ':idb_p2')->willReturn($comparisonProphecy)->shouldBeCalled(); - - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->getQuery()->willReturn($queryProphecy->reveal())->shouldBeCalled(); - $queryBuilderProphecy->expr()->willReturn($exprProphecy->reveal())->shouldBeCalled(); - $queryBuilderProphecy->andWhere($comparison)->shouldBeCalled(); - $queryBuilderProphecy->getRootAliases()->shouldBeCalled()->willReturn(['o']); - - $queryBuilderProphecy->setParameter('ida_p1', 1, Types::INTEGER)->shouldBeCalled(); - $queryBuilderProphecy->setParameter('idb_p2', 2, Types::INTEGER)->shouldBeCalled(); - - $queryBuilder = $queryBuilderProphecy->reveal(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'ida', - 'idb', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, [ - 'ida' => [ - 'type' => Types::INTEGER, - ], - 'idb' => [ - 'type' => Types::INTEGER, - ], - ], $queryBuilder); - - $context = [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', $context)->shouldBeCalled(); - - $dataProvider = new ItemDataProvider($managerRegistry, $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()], $resourceMetadataFactory); - - $this->assertEquals([], $dataProvider->getItem(Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', $context)); - } - - /** - * @group legacy - */ - public function testGetItemWrongCompositeIdentifier() - { - $this->expectException(PropertyNotFoundException::class); - - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'ida', - 'idb', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, [ - 'ida' => [ - 'type' => Types::INTEGER, - ], - 'idb' => [ - 'type' => Types::INTEGER, - ], - ], $this->prophesize(QueryBuilder::class)->reveal()); - - $dataProvider = new ItemDataProvider($managerRegistry, $propertyNameCollectionFactory, $propertyMetadataFactory, [], $resourceMetadataFactory); - $dataProvider->getItem(Dummy::class, 'ida=1;', 'foo'); - } - - public function testQueryResultExtension() - { - $comparisonProphecy = $this->prophesize(Comparison::class); - $comparison = $comparisonProphecy->reveal(); - - $exprProphecy = $this->prophesize(Expr::class); - $exprProphecy->eq('o.id', ':id_p1')->willReturn($comparisonProphecy)->shouldBeCalled(); - - $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); - $queryBuilderProphecy->expr()->willReturn($exprProphecy->reveal())->shouldBeCalled(); - $queryBuilderProphecy->andWhere($comparison)->shouldBeCalled(); - $queryBuilderProphecy->getRootAliases()->shouldBeCalled()->willReturn(['o']); - $queryBuilderProphecy->setParameter('id_p1', 1, Types::INTEGER)->shouldBeCalled(); - - $queryBuilder = $queryBuilderProphecy->reveal(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - $managerRegistry = $this->getManagerRegistry(Dummy::class, [ - 'id' => [ - 'type' => Types::INTEGER, - ], - ], $queryBuilder); - - $context = [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - $extensionProphecy = $this->prophesize(QueryResultItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo', $context)->shouldBeCalled(); - $extensionProphecy->supportsResult(Dummy::class, 'foo', $context)->willReturn(true)->shouldBeCalled(); - $extensionProphecy->getResult($queryBuilder, Dummy::class, 'foo', $context)->willReturn([])->shouldBeCalled(); - - $dataProvider = new ItemDataProvider($managerRegistry, $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()], $resourceMetadataFactory); - - $this->assertEquals([], $dataProvider->getItem(Dummy::class, ['id' => 1], 'foo', $context)); - } - - public function testUnsupportedClass() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - - $dataProvider = new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()], $resourceMetadataFactory); - $this->assertFalse($dataProvider->supports(Dummy::class, 'foo')); - } - - public function testCannotCreateQueryBuilder() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The repository class must have a "createQueryBuilder" method.'); - - $repositoryProphecy = $this->prophesize(ObjectRepository::class); - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->getIdentifier()->willReturn([ - 'id', - ]); - $classMetadataProphecy->getTypeOfField('id')->willReturn(Types::INTEGER); - - $platformProphecy = $this->prophesize(AbstractPlatform::class); - - $connectionProphecy = $this->prophesize(Connection::class); - $connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy); - - $managerProphecy = $this->prophesize(EntityManagerInterface::class); - $managerProphecy->getClassMetadata(Dummy::class)->willReturn($classMetadataProphecy->reveal()); - $managerProphecy->getConnection()->willReturn($connectionProphecy); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal()); - - $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - - [$propertyNameCollectionFactory, $propertyMetadataFactory, $resourceMetadataFactory] = $this->getMetadataFactories(Dummy::class, [ - 'id', - ]); - - $itemDataProvider = new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()], $resourceMetadataFactory); - $itemDataProvider->getItem(Dummy::class, ['id' => 1234], null, [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]); - } - - /** - * Gets mocked metadata factories. - */ - private function getMetadataFactories(string $resourceClass, array $identifiers): array - { - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - - $nameCollection = ['foobar']; - - foreach ($identifiers as $identifier) { - $metadata = new PropertyMetadata(); - $metadata = $metadata->withIdentifier(true); - $propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata); - - $nameCollection[] = $identifier; - } - - // random property to prevent the use of non-identifiers metadata while looping - $propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata()); - - $propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection)); - $resourceMetadataFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadata('dummy')); - - return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()]; - } - - /** - * Gets a mocked manager registry. - */ - private function getManagerRegistry(string $resourceClass, array $identifierFields, QueryBuilder $queryBuilder): ManagerRegistry - { - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->getIdentifier()->willReturn(array_keys($identifierFields)); - - foreach ($identifierFields as $name => $field) { - $classMetadataProphecy->getTypeOfField($name)->willReturn($field['type']); - } - - $platformProphecy = $this->prophesize(AbstractPlatform::class); - - $connectionProphecy = $this->prophesize(Connection::class); - $connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder); - - $managerProphecy = $this->prophesize(EntityManagerInterface::class); - $managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal()); - $managerProphecy->getConnection()->willReturn($connectionProphecy); - $managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal()); - - return $managerRegistryProphecy->reveal(); - } -} diff --git a/tests/Core/Bridge/Doctrine/Orm/SubresourceDataProviderTest.php b/tests/Core/Bridge/Doctrine/Orm/SubresourceDataProviderTest.php deleted file mode 100644 index 32b412ee011..00000000000 --- a/tests/Core/Bridge/Doctrine/Orm/SubresourceDataProviderTest.php +++ /dev/null @@ -1,615 +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\Tests\Bridge\Doctrine\Orm; - -use ApiPlatform\Core\Bridge\Doctrine\Orm\SubresourceDataProvider; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; -use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedOwningDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThirdLevel; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\AbstractQuery; -use Doctrine\ORM\EntityManager; -use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Query\Expr; -use Doctrine\ORM\Query\Expr\Func; -use Doctrine\ORM\QueryBuilder; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectManager; -use Doctrine\Persistence\ObjectRepository; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; - -/** - * @author Kévin Dunglas - */ -class SubresourceDataProviderTest extends TestCase -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - private function assertIdentifierManagerMethodCalls($managerProphecy) - { - $platformProphecy = $this->prophesize(AbstractPlatform::class); - - $connectionProphecy = $this->prophesize(Connection::class); - $connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy); - - $managerProphecy->getConnection()->willReturn($connectionProphecy); - } - - private function getMetadataProphecies(array $resourceClassesIdentifiers) - { - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - - foreach ($resourceClassesIdentifiers as $resourceClass => $identifiers) { - $nameCollection = ['foobar']; - - foreach ($identifiers as $identifier) { - $metadata = new PropertyMetadata(); - $metadata = $metadata->withIdentifier(true); - $propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata); - - $nameCollection[] = $identifier; - } - - // random property to prevent the use of non-identifiers metadata while looping - $propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata()); - - $propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection)); - } - - return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()]; - } - - private function getManagerRegistryProphecy(QueryBuilder $queryBuilder, array $identifiers, string $resourceClass) - { - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->getIdentifier()->willReturn($identifiers); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal()); - $managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass($resourceClass)->willReturn($managerProphecy->reveal()); - - return $managerRegistryProphecy->reveal(); - } - - public function testNotASubresource() - { - $this->expectException(ResourceClassNotSupportedException::class); - $this->expectExceptionMessage('The given resource class is not a subresource.'); - - $identifiers = ['id']; - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - $queryBuilder = $this->prophesize(QueryBuilder::class)->reveal(); - $managerRegistry = $this->getManagerRegistryProphecy($queryBuilder, $identifiers, Dummy::class); - - $dataProvider = new SubresourceDataProvider($managerRegistry, $propertyNameCollectionFactory, $propertyMetadataFactory, []); - - $dataProvider->getSubresource(Dummy::class, ['id' => 1], []); - } - - public function testGetSubresource() - { - $dql = 'SELECT relatedDummies_a2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a1 INNER JOIN id_a1.relatedDummies relatedDummies_a2 WHERE id_a1.id = :id_p1'; - - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getResult()->shouldBeCalled()->willReturn([]); - - $identifiers = ['id']; - $queryBuilder = $this->prophesize(QueryBuilder::class); - $queryBuilder->setParameter('id_p1', 1, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - $funcProphecy = $this->prophesize(Func::class); - $func = $funcProphecy->reveal(); - - $queryBuilder->andWhere($func)->shouldBeCalled()->willReturn($queryBuilder); - - $queryBuilder->getQuery()->shouldBeCalled()->willReturn($queryProphecy->reveal()); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->shouldBeCalled()->willReturn($queryBuilder->reveal()); - - $managerProphecy = $this->prophesize(EntityManager::class); - $managerProphecy->getRepository(RelatedDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($managerProphecy); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); - $classMetadataProphecy->getTypeOfField('id')->willReturn(Types::INTEGER)->shouldBeCalled(); - - $managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - - $qb = $this->prophesize(QueryBuilder::class); - $qb->select('relatedDummies_a2')->shouldBeCalled()->willReturn($qb); - $qb->from(Dummy::class, 'id_a1')->shouldBeCalled()->willReturn($qb); - $qb->innerJoin('id_a1.relatedDummies', 'relatedDummies_a2')->shouldBeCalled()->willReturn($qb); - $qb->andWhere('id_a1.id = :id_p1')->shouldBeCalled()->willReturn($qb); - $qb->getDQL()->shouldBeCalled()->willReturn($dql); - - $exprProphecy = $this->prophesize(Expr::class); - $exprProphecy->in('o', $dql)->willReturn($func)->shouldBeCalled(); - - $qb->expr()->shouldBeCalled()->willReturn($exprProphecy->reveal()); - - $managerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($qb->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'relatedDummies', 'identifiers' => ['id' => [Dummy::class, 'id']], 'collection' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals([], $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1]], $context)); - } - - public function testGetSubSubresourceItem() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $identifiers = ['id']; - $funcProphecy = $this->prophesize(Func::class); - $func = $funcProphecy->reveal(); - - // First manager (Dummy) - $dummyDQL = 'SELECT relatedDummies_a3 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a2 INNER JOIN id_a2.relatedDummies relatedDummies_a3 WHERE id_a2.id = :id_p2'; - - $qb = $this->prophesize(QueryBuilder::class); - $qb->select('relatedDummies_a3')->shouldBeCalled()->willReturn($qb); - $qb->from(Dummy::class, 'id_a2')->shouldBeCalled()->willReturn($qb); - $qb->innerJoin('id_a2.relatedDummies', 'relatedDummies_a3')->shouldBeCalled()->willReturn($qb); - $qb->andWhere('id_a2.id = :id_p2')->shouldBeCalled()->willReturn($qb); - - $dummyFunc = new Func('in', ['any']); - - $dummyExpProphecy = $this->prophesize(Expr::class); - $dummyExpProphecy->in('relatedDummies_a1', $dummyDQL)->willReturn($dummyFunc)->shouldBeCalled(); - - $qb->expr()->shouldBeCalled()->willReturn($dummyExpProphecy->reveal()); - - $qb->getDQL()->shouldBeCalled()->willReturn($dummyDQL); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); - $classMetadataProphecy->getTypeOfField('id')->willReturn(Types::INTEGER)->shouldBeCalled(); - - $dummyManagerProphecy = $this->prophesize(EntityManager::class); - $dummyManagerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($qb->reveal()); - $dummyManagerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($dummyManagerProphecy); - - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($dummyManagerProphecy->reveal()); - - // Second manager (RelatedDummy) - $relatedDQL = 'SELECT IDENTITY(relatedDummies_a1.thirdLevel) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy relatedDummies_a1 WHERE relatedDummies_a1.id = :id_p1 AND relatedDummies_a1 IN(SELECT relatedDummies_a3 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a2 INNER JOIN id_a2.relatedDummies relatedDummies_a3 WHERE id_a2.id = :id_p2)'; - - $rqb = $this->prophesize(QueryBuilder::class); - $rqb->select('IDENTITY(relatedDummies_a1.thirdLevel)')->shouldBeCalled()->willReturn($rqb); - $rqb->from(RelatedDummy::class, 'relatedDummies_a1')->shouldBeCalled()->willReturn($rqb); - $rqb->andWhere('relatedDummies_a1.id = :id_p1')->shouldBeCalled()->willReturn($rqb); - $rqb->andWhere($dummyFunc)->shouldBeCalled()->willReturn($rqb); - $rqb->getDQL()->shouldBeCalled()->willReturn($relatedDQL); - - $relatedExpProphecy = $this->prophesize(Expr::class); - $relatedExpProphecy->in('o', $relatedDQL)->willReturn($func)->shouldBeCalled(); - - $rqb->expr()->shouldBeCalled()->willReturn($relatedExpProphecy->reveal()); - - $rClassMetadataProphecy = $this->prophesize(ClassMetadata::class); - $rClassMetadataProphecy->hasAssociation('thirdLevel')->shouldBeCalled()->willReturn(true); - $rClassMetadataProphecy->getAssociationMapping('thirdLevel')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_ONE]); - $rClassMetadataProphecy->getTypeOfField('id')->willReturn(Types::INTEGER)->shouldBeCalled(); - - $rDummyManagerProphecy = $this->prophesize(EntityManager::class); - $rDummyManagerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($rqb->reveal()); - $rDummyManagerProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($rClassMetadataProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($rDummyManagerProphecy); - - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($rDummyManagerProphecy->reveal()); - - $result = new \stdClass(); - // Origin manager (ThirdLevel) - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->shouldBeCalled()->willReturn($result); - - $queryBuilder = $this->prophesize(QueryBuilder::class); - - $queryBuilder->andWhere($func)->shouldBeCalled()->willReturn($queryBuilder); - - $queryBuilder->getQuery()->shouldBeCalled()->willReturn($queryProphecy->reveal()); - $queryBuilder->setParameter('id_p1', 1, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - $queryBuilder->setParameter('id_p2', 1, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->shouldBeCalled()->willReturn($queryBuilder->reveal()); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getRepository(ThirdLevel::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(ThirdLevel::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers, RelatedDummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'thirdLevel', 'identifiers' => ['id' => [Dummy::class, 'id'], 'relatedDummies' => [RelatedDummy::class, 'id']], 'collection' => false, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals($result, $dataProvider->getSubresource(ThirdLevel::class, ['id' => ['id' => 1], 'relatedDummies' => ['id' => 1]], $context)); - } - - public function testGetSubresourceOneToOneOwningRelation() - { - // RelatedOwningDummy OneToOne Dummy - $dql = 'SELECT ownedDummy_a2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a1 INNER JOIN id_a1.ownedDummy ownedDummy_a2 WHERE id_a1.id = :id_p1'; - - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->shouldBeCalled()->willReturn([]); - - $identifiers = ['id']; - $queryBuilder = $this->prophesize(QueryBuilder::class); - $queryBuilder->setParameter('id_p1', 1, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - $funcProphecy = $this->prophesize(Func::class); - $func = $funcProphecy->reveal(); - $queryBuilder->andWhere($func)->shouldBeCalled()->willReturn($queryBuilder); - $queryBuilder->getQuery()->shouldBeCalled()->willReturn($queryProphecy->reveal()); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->shouldBeCalled()->willReturn($queryBuilder->reveal()); - - $managerProphecy = $this->prophesize(EntityManager::class); - $managerProphecy->getRepository(RelatedOwningDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($managerProphecy); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('ownedDummy')->willReturn(true)->shouldBeCalled(); - $classMetadataProphecy->getAssociationMapping('ownedDummy')->shouldBeCalled()->willReturn(['type' => ClassMetadata::ONE_TO_ONE]); - $classMetadataProphecy->getTypeOfField('id')->willReturn(Types::INTEGER)->shouldBeCalled(); - - $managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - - $qb = $this->prophesize(QueryBuilder::class); - $qb->select('IDENTITY(id_a1.ownedDummy)')->shouldBeCalled()->willReturn($qb); - $qb->from(Dummy::class, 'id_a1')->shouldBeCalled()->willReturn($qb); - $qb->andWhere('id_a1.id = :id_p1')->shouldBeCalled()->willReturn($qb); - $qb->getDQL()->shouldBeCalled()->willReturn($dql); - - $exprProphecy = $this->prophesize(Expr::class); - $exprProphecy->in('o', $dql)->willReturn($func)->shouldBeCalled(); - - $qb->expr()->shouldBeCalled()->willReturn($exprProphecy->reveal()); - - $managerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($qb->reveal()); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(RelatedOwningDummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'ownedDummy', 'identifiers' => ['id' => [Dummy::class, 'id']], 'collection' => false, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals([], $dataProvider->getSubresource(RelatedOwningDummy::class, ['id' => ['id' => 1]], $context)); - } - - public function testQueryResultExtension() - { - $dql = 'SELECT relatedDummies_a2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a1 INNER JOIN id_a1.relatedDummies relatedDummies_a2 WHERE id_a1.id = :id_p1'; - - $identifiers = ['id']; - $queryBuilder = $this->prophesize(QueryBuilder::class); - $queryBuilder->setParameter('id_p1', 1, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - $funcProphecy = $this->prophesize(Func::class); - $func = $funcProphecy->reveal(); - - $queryBuilder->andWhere($func)->shouldBeCalled()->willReturn($queryBuilder); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->shouldBeCalled()->willReturn($queryBuilder->reveal()); - - $managerProphecy = $this->prophesize(EntityManager::class); - $managerProphecy->getRepository(RelatedDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($managerProphecy); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); - $classMetadataProphecy->getTypeOfField('id')->willReturn(Types::INTEGER)->shouldBeCalled(); - - $managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($managerProphecy); - - $qb = $this->prophesize(QueryBuilder::class); - $qb->select('relatedDummies_a2')->shouldBeCalled()->willReturn($qb); - $qb->from(Dummy::class, 'id_a1')->shouldBeCalled()->willReturn($qb); - $qb->innerJoin('id_a1.relatedDummies', 'relatedDummies_a2')->shouldBeCalled()->willReturn($qb); - $qb->andWhere('id_a1.id = :id_p1')->shouldBeCalled()->willReturn($qb); - $qb->getDQL()->shouldBeCalled()->willReturn($dql); - - $exprProphecy = $this->prophesize(Expr::class); - $exprProphecy->in('o', $dql)->willReturn($func)->shouldBeCalled(); - - $qb->expr()->shouldBeCalled()->willReturn($exprProphecy->reveal()); - - $managerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($qb->reveal()); - $this->assertIdentifierManagerMethodCalls($managerProphecy); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $extensionProphecy = $this->prophesize(QueryResultCollectionExtensionInterface::class); - $extensionProphecy->applyToCollection($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), RelatedDummy::class, null, Argument::type('array'))->shouldBeCalled(); - $extensionProphecy->supportsResult(RelatedDummy::class, null, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $extensionProphecy->getResult($queryBuilder, RelatedDummy::class, null, Argument::type('array'))->willReturn([])->shouldBeCalled(); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - - $context = ['property' => 'relatedDummies', 'identifiers' => ['id' => [Dummy::class, 'id']], 'collection' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals([], $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1]], $context)); - } - - public function testCannotCreateQueryBuilder() - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The repository class must have a "createQueryBuilder" method.'); - - $identifiers = ['id']; - $repositoryProphecy = $this->prophesize(ObjectRepository::class); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled(); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - $dataProvider->getSubresource(Dummy::class, ['id' => 1], []); - } - - public function testThrowResourceClassNotSupportedException() - { - $this->expectException(ResourceClassNotSupportedException::class); - - $identifiers = ['id']; - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn(null)->shouldBeCalled(); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - $dataProvider->getSubresource(Dummy::class, ['id' => 1], []); - } - - /** - * @group legacy - */ - public function testGetSubSubresourceItemLegacy() - { - $this->expectDeprecation('Identifiers should match the convention introduced in ADR 0001-resource-identifiers, this behavior will be removed in 3.0.'); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $identifiers = ['id']; - $funcProphecy = $this->prophesize(Func::class); - $func = $funcProphecy->reveal(); - - // First manager (Dummy) - $dummyDQL = 'SELECT relatedDummies_a3 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a2 INNER JOIN id_a2.relatedDummies relatedDummies_a3 WHERE id_a2.id = :id_p2'; - - $qb = $this->prophesize(QueryBuilder::class); - $qb->select('relatedDummies_a3')->shouldBeCalled()->willReturn($qb); - $qb->from(Dummy::class, 'id_a2')->shouldBeCalled()->willReturn($qb); - $qb->innerJoin('id_a2.relatedDummies', 'relatedDummies_a3')->shouldBeCalled()->willReturn($qb); - $qb->andWhere('id_a2.id = :id_p2')->shouldBeCalled()->willReturn($qb); - - $dummyFunc = new Func('in', ['any']); - - $dummyExpProphecy = $this->prophesize(Expr::class); - $dummyExpProphecy->in('relatedDummies_a1', $dummyDQL)->willReturn($dummyFunc)->shouldBeCalled(); - - $qb->expr()->shouldBeCalled()->willReturn($dummyExpProphecy->reveal()); - - $qb->getDQL()->shouldBeCalled()->willReturn($dummyDQL); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn($identifiers); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); - $classMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn(Types::INTEGER); - - $dummyManagerProphecy = $this->prophesize(EntityManager::class); - $dummyManagerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($qb->reveal()); - $dummyManagerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($dummyManagerProphecy); - - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($dummyManagerProphecy->reveal()); - - // Second manager (RelatedDummy) - $relatedDQL = 'SELECT IDENTITY(relatedDummies_a1.thirdLevel) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy relatedDummies_a1 WHERE relatedDummies_a1.id = :id_p1 AND relatedDummies_a1 IN(SELECT relatedDummies_a3 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a2 INNER JOIN id_a2.relatedDummies relatedDummies_a3 WHERE id_a2.id = :id_p2)'; - - $rqb = $this->prophesize(QueryBuilder::class); - $rqb->select('IDENTITY(relatedDummies_a1.thirdLevel)')->shouldBeCalled()->willReturn($rqb); - $rqb->from(RelatedDummy::class, 'relatedDummies_a1')->shouldBeCalled()->willReturn($rqb); - $rqb->andWhere('relatedDummies_a1.id = :id_p1')->shouldBeCalled()->willReturn($rqb); - $rqb->andWhere($dummyFunc)->shouldBeCalled()->willReturn($rqb); - $rqb->getDQL()->shouldBeCalled()->willReturn($relatedDQL); - - $relatedExpProphecy = $this->prophesize(Expr::class); - $relatedExpProphecy->in('o', $relatedDQL)->willReturn($func)->shouldBeCalled(); - - $rqb->expr()->shouldBeCalled()->willReturn($relatedExpProphecy->reveal()); - - $rClassMetadataProphecy = $this->prophesize(ClassMetadata::class); - $rClassMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn($identifiers); - $rClassMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn(Types::INTEGER); - $rClassMetadataProphecy->hasAssociation('thirdLevel')->shouldBeCalled()->willReturn(true); - $rClassMetadataProphecy->getAssociationMapping('thirdLevel')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_ONE]); - - $rDummyManagerProphecy = $this->prophesize(EntityManager::class); - $rDummyManagerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($rqb->reveal()); - $rDummyManagerProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($rClassMetadataProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($rDummyManagerProphecy); - - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($rDummyManagerProphecy->reveal()); - - $result = new \stdClass(); - // Origin manager (ThirdLevel) - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->shouldBeCalled()->willReturn($result); - - $queryBuilder = $this->prophesize(QueryBuilder::class); - - $queryBuilder->andWhere($func)->shouldBeCalled()->willReturn($queryBuilder); - - $queryBuilder->getQuery()->shouldBeCalled()->willReturn($queryProphecy->reveal()); - $queryBuilder->setParameter('id_p1', 1, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - $queryBuilder->setParameter('id_p2', 1, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->shouldBeCalled()->willReturn($queryBuilder->reveal()); - - $managerProphecy = $this->prophesize(ObjectManager::class); - $managerProphecy->getRepository(ThirdLevel::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - $managerRegistryProphecy->getManagerForClass(ThirdLevel::class)->shouldBeCalled()->willReturn($managerProphecy->reveal()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers, RelatedDummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'thirdLevel', 'identifiers' => [['id', Dummy::class], ['relatedDummies', RelatedDummy::class]], 'collection' => false]; - - $this->assertEquals($result, $dataProvider->getSubresource(ThirdLevel::class, ['id' => 1, 'relatedDummies' => 1], $context)); - } - - public function testGetSubresourceCollectionItem() - { - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $identifiers = ['id']; - $funcProphecy = $this->prophesize(Func::class); - $func = $funcProphecy->reveal(); - - // First manager (Dummy) - $dummyDQL = 'dql'; - - $qb = $this->prophesize(QueryBuilder::class); - $qb->select('relatedDummies_a3')->shouldBeCalled()->willReturn($qb); - $qb->from(Dummy::class, 'id_a2')->shouldBeCalled()->willReturn($qb); - $qb->innerJoin('id_a2.relatedDummies', 'relatedDummies_a3')->shouldBeCalled()->willReturn($qb); - $qb->andWhere('id_a2.id = :id_p2')->shouldBeCalled()->willReturn($qb); - - $dummyFunc = new Func('in', ['any']); - - $dummyExpProphecy = $this->prophesize(Expr::class); - $dummyExpProphecy->in('relatedDummies_a1', $dummyDQL)->willReturn($dummyFunc)->shouldBeCalled(); - - $qb->expr()->shouldBeCalled()->willReturn($dummyExpProphecy->reveal()); - - $qb->getDQL()->shouldBeCalled()->willReturn($dummyDQL); - - $classMetadataProphecy = $this->prophesize(ClassMetadata::class); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); - $classMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn(Types::INTEGER); - - $dummyManagerProphecy = $this->prophesize(EntityManager::class); - $dummyManagerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($qb->reveal()); - $dummyManagerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($dummyManagerProphecy); - - $managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($dummyManagerProphecy->reveal()); - - // Second manager (RelatedDummy) - $relatedDQL = 'relateddql'; - - $rqb = $this->prophesize(QueryBuilder::class); - $rqb->select('relatedDummies_a1')->shouldBeCalled()->willReturn($rqb); - $rqb->from(RelatedDummy::class, 'relatedDummies_a1')->shouldBeCalled()->willReturn($rqb); - $rqb->andWhere('relatedDummies_a1.id = :id_p1')->shouldBeCalled()->willReturn($rqb); - $rqb->andWhere($dummyFunc)->shouldBeCalled()->willReturn($rqb); - $rqb->getDQL()->shouldBeCalled()->willReturn($relatedDQL); - - $relatedExpProphecy = $this->prophesize(Expr::class); - $relatedExpProphecy->in('o', $relatedDQL)->willReturn($func)->shouldBeCalled(); - - $rqb->expr()->shouldBeCalled()->willReturn($relatedExpProphecy->reveal()); - - $rClassMetadataProphecy = $this->prophesize(ClassMetadata::class); - $rClassMetadataProphecy->hasAssociation('id')->shouldBeCalled()->willReturn(false); - $rClassMetadataProphecy->isIdentifier('id')->shouldBeCalled()->willReturn(true); - $rClassMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn(Types::INTEGER); - - $rDummyManagerProphecy = $this->prophesize(EntityManager::class); - $rDummyManagerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($rqb->reveal()); - $rDummyManagerProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($rClassMetadataProphecy->reveal()); - $this->assertIdentifierManagerMethodCalls($rDummyManagerProphecy); - - $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->shouldBeCalled()->willReturn($rDummyManagerProphecy->reveal()); - - $result = new \stdClass(); - $queryProphecy = $this->prophesize(AbstractQuery::class); - $queryProphecy->getOneOrNullResult()->shouldBeCalled()->willReturn($result); - - $queryBuilder = $this->prophesize(QueryBuilder::class); - - $queryBuilder->andWhere($func)->shouldBeCalled()->willReturn($queryBuilder); - - $queryBuilder->getQuery()->shouldBeCalled()->willReturn($queryProphecy->reveal()); - $queryBuilder->setParameter('id_p1', 2, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - $queryBuilder->setParameter('id_p2', 1, Types::INTEGER)->shouldBeCalled()->willReturn($queryBuilder); - - $repositoryProphecy = $this->prophesize(EntityRepository::class); - $repositoryProphecy->createQueryBuilder('o')->shouldBeCalled()->willReturn($queryBuilder->reveal()); - - $rDummyManagerProphecy->getRepository(RelatedDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal()); - - [$propertyNameCollectionFactory, $propertyMetadataFactory] = $this->getMetadataProphecies([Dummy::class => $identifiers, RelatedDummy::class => $identifiers]); - - $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - - $context = ['property' => 'id', 'identifiers' => ['id' => [Dummy::class, 'id', true], 'relatedDummies' => [RelatedDummy::class, 'id', true]], 'collection' => false, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]; - - $this->assertEquals($result, $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1], 'relatedDummies' => ['id' => 2]], $context)); - } -} diff --git a/tests/Core/Bridge/Elasticsearch/Api/IdentifierExtractorTest.php b/tests/Core/Bridge/Elasticsearch/Api/IdentifierExtractorTest.php deleted file mode 100644 index a83edb0c647..00000000000 --- a/tests/Core/Bridge/Elasticsearch/Api/IdentifierExtractorTest.php +++ /dev/null @@ -1,70 +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\Tests\Bridge\Elasticsearch\Api; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractor; -use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface; -use ApiPlatform\Core\Bridge\Elasticsearch\Exception\NonUniqueIdentifierException; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositeRelation; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; - -class IdentifierExtractorTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - self::assertInstanceOf( - IdentifierExtractorInterface::class, - new IdentifierExtractor($this->prophesize(IdentifiersExtractorInterface::class)->reveal()) - ); - } - - public function testGetIdentifierFromResourceClass() - { - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); - - $identifierExtractor = new IdentifierExtractor($identifiersExtractorProphecy->reveal()); - - self::assertSame('id', $identifierExtractor->getIdentifierFromResourceClass(Dummy::class)); - } - - public function testGetIdentifierFromResourceClassWithCompositeIdentifiers() - { - $this->expectException(NonUniqueIdentifierException::class); - $this->expectExceptionMessage('Composite identifiers not supported.'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(CompositeRelation::class)->willReturn(['compositeItem', 'compositeLabel'])->shouldBeCalled(); - - $identifierExtractor = new IdentifierExtractor($identifiersExtractorProphecy->reveal()); - $identifierExtractor->getIdentifierFromResourceClass(CompositeRelation::class); - } - - public function testGetIdentifierFromResourceClassWithNoIdentifier() - { - $this->expectException(NonUniqueIdentifierException::class); - $this->expectExceptionMessage('Resource "ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy" has no identifiers.'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Dummy::class)->willReturn([])->shouldBeCalled(); - - $identifierExtractor = new IdentifierExtractor($identifiersExtractorProphecy->reveal()); - $identifierExtractor->getIdentifierFromResourceClass(Dummy::class); - } -} diff --git a/tests/Core/Bridge/Elasticsearch/DataProvider/CollectionDataProviderTest.php b/tests/Core/Bridge/Elasticsearch/DataProvider/CollectionDataProviderTest.php deleted file mode 100644 index e7c7f43b26c..00000000000 --- a/tests/Core/Bridge/Elasticsearch/DataProvider/CollectionDataProviderTest.php +++ /dev/null @@ -1,186 +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\Tests\Bridge\Elasticsearch\DataProvider; - -use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface; -use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\CollectionDataProvider; -use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension\RequestBodySearchCollectionExtensionInterface; -use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Paginator; -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\Pagination; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; -use ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException; -use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; -use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositeRelation; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCarColor; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Foo; -use Elasticsearch\Client; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -/** - * @group legacy - * @requires PHP >= 7.4 - */ -class CollectionDataProviderTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - self::assertInstanceOf( - CollectionDataProviderInterface::class, - new CollectionDataProvider( - $this->prophesize(Client::class)->reveal(), - $this->prophesize(DocumentMetadataFactoryInterface::class)->reveal(), - $this->prophesize(IdentifierExtractorInterface::class)->reveal(), - $this->prophesize(DenormalizerInterface::class)->reveal(), - new Pagination($this->prophesize(ResourceMetadataFactoryInterface::class)->reveal()), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() - ) - ); - } - - public function testSupports() - { - $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); - $documentMetadataFactoryProphecy->create(Foo::class)->willReturn(new DocumentMetadata('foo'))->shouldBeCalled(); - $documentMetadataFactoryProphecy->create(Dummy::class)->willThrow(new IndexNotFoundException())->shouldBeCalled(); - $documentMetadataFactoryProphecy->create(CompositeRelation::class)->willReturn(new DocumentMetadata('composite_relation'))->shouldBeCalled(); - - $identifierExtractorProphecy = $this->prophesize(IdentifierExtractorInterface::class); - $identifierExtractorProphecy->getIdentifierFromResourceClass(Foo::class)->willReturn('id')->shouldBeCalled(); - $identifierExtractorProphecy->getIdentifierFromResourceClass(CompositeRelation::class)->willThrow(new NonUniqueIdentifierException())->shouldBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Foo::class)->shouldBeCalled()->willReturn(new ResourceMetadata()); - $resourceMetadataFactoryProphecy->create(DummyCar::class)->shouldBeCalled()->willReturn((new ResourceMetadata())->withAttributes(['elasticsearch' => false])); - $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new ResourceMetadata()); - $resourceMetadataFactoryProphecy->create(CompositeRelation::class)->shouldBeCalled()->willReturn(new ResourceMetadata()); - $resourceMetadataFactoryProphecy->create(DummyCarColor::class)->shouldBeCalled()->willThrow(new ResourceClassNotFoundException()); - - $collectionDataProvider = new CollectionDataProvider( - $this->prophesize(Client::class)->reveal(), - $documentMetadataFactoryProphecy->reveal(), - $identifierExtractorProphecy->reveal(), - $this->prophesize(DenormalizerInterface::class)->reveal(), - new Pagination($this->prophesize(ResourceMetadataFactoryInterface::class)->reveal()), - $resourceMetadataFactoryProphecy->reveal() - ); - - self::assertTrue($collectionDataProvider->supports(Foo::class)); - self::assertFalse($collectionDataProvider->supports(Dummy::class)); - self::assertFalse($collectionDataProvider->supports(CompositeRelation::class)); - self::assertFalse($collectionDataProvider->supports(DummyCar::class)); - self::assertFalse($collectionDataProvider->supports(DummyCarColor::class)); - } - - public function testGetCollection() - { - $context = [ - 'groups' => ['custom'], - ]; - - $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); - $documentMetadataFactoryProphecy->create(Foo::class)->willReturn(new DocumentMetadata('foo'))->shouldBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Foo::class)->willReturn(new ResourceMetadata()); - - $documents = [ - 'took' => 15, - 'time_out' => false, - '_shards' => [ - 'total' => 5, - 'successful' => 5, - 'skipped' => 0, - 'failed' => 0, - ], - 'hits' => [ - 'total' => 4, - 'max_score' => 1, - 'hits' => [ - [ - '_index' => 'foo', - '_type' => '_doc', - '_id' => '1', - '_score' => 1, - '_source' => [ - 'id' => 1, - 'name' => 'Kilian', - 'bar' => 'Jornet', - ], - ], - [ - '_index' => 'foo', - '_type' => '_doc', - '_id' => '2', - '_score' => 1, - '_source' => [ - 'id' => 2, - 'name' => 'François', - 'bar' => 'D\'Haene', - ], - ], - ], - ], - ]; - - $clientProphecy = $this->prophesize(Client::class); - $clientProphecy - ->search( - Argument::allOf( - Argument::withEntry('index', 'foo'), - Argument::withEntry('body', Argument::allOf( - Argument::withEntry('size', 2), - Argument::withEntry('from', 0), - Argument::withEntry('query', Argument::allOf( - Argument::withEntry('match_all', Argument::type(\stdClass::class)), - Argument::size(1) - )), - Argument::size(3) - )), - Argument::size(2) - ) - ) - ->willReturn($documents) - ->shouldBeCalled(); - - $requestBodySearchCollectionExtensionProphecy = $this->prophesize(RequestBodySearchCollectionExtensionInterface::class); - $requestBodySearchCollectionExtensionProphecy->applyToCollection([], Foo::class, 'get', $context)->willReturn([])->shouldBeCalled(); - - $collectionDataProvider = new CollectionDataProvider( - $clientProphecy->reveal(), - $documentMetadataFactoryProphecy->reveal(), - $this->prophesize(IdentifierExtractorInterface::class)->reveal(), - $denormalizer = $this->prophesize(DenormalizerInterface::class)->reveal(), - new Pagination($resourceMetadataFactoryProphecy->reveal(), ['items_per_page' => 2]), - $resourceMetadataFactoryProphecy->reveal(), - [$requestBodySearchCollectionExtensionProphecy->reveal()] - ); - - self::assertEquals( - new Paginator($denormalizer, $documents, Foo::class, 2, 0, $context), - $collectionDataProvider->getCollection(Foo::class, 'get', $context) - ); - } -} diff --git a/tests/Core/Bridge/Elasticsearch/DataProvider/ItemDataProviderTest.php b/tests/Core/Bridge/Elasticsearch/DataProvider/ItemDataProviderTest.php deleted file mode 100644 index adfe2f6c3a2..00000000000 --- a/tests/Core/Bridge/Elasticsearch/DataProvider/ItemDataProviderTest.php +++ /dev/null @@ -1,149 +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\Tests\Bridge\Elasticsearch\DataProvider; - -use ApiPlatform\Core\Bridge\Elasticsearch\Api\IdentifierExtractorInterface; -use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\ItemDataProvider; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Elasticsearch\Exception\IndexNotFoundException; -use ApiPlatform\Elasticsearch\Exception\NonUniqueIdentifierException; -use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; -use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; -use ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositeRelation; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCarColor; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Foo; -use Elasticsearch\Client; -use Elasticsearch\Common\Exceptions\Missing404Exception; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -/** - * @group legacy - * @requires PHP >= 7.4 - */ -class ItemDataProviderTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - self::assertInstanceOf( - ItemDataProviderInterface::class, - new ItemDataProvider( - $this->prophesize(Client::class)->reveal(), - $this->prophesize(DocumentMetadataFactoryInterface::class)->reveal(), - $this->prophesize(IdentifierExtractorInterface::class)->reveal(), - $this->prophesize(DenormalizerInterface::class)->reveal(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() - ) - ); - } - - public function testSupports() - { - $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); - $documentMetadataFactoryProphecy->create(Foo::class)->willReturn(new DocumentMetadata('foo'))->shouldBeCalled(); - $documentMetadataFactoryProphecy->create(Dummy::class)->willThrow(new IndexNotFoundException())->shouldBeCalled(); - $documentMetadataFactoryProphecy->create(CompositeRelation::class)->willReturn(new DocumentMetadata('composite_relation'))->shouldBeCalled(); - - $identifierExtractorProphecy = $this->prophesize(IdentifierExtractorInterface::class); - $identifierExtractorProphecy->getIdentifierFromResourceClass(Foo::class)->willReturn('id')->shouldBeCalled(); - $identifierExtractorProphecy->getIdentifierFromResourceClass(CompositeRelation::class)->willThrow(new NonUniqueIdentifierException())->shouldBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Foo::class)->shouldBeCalled()->willReturn(new ResourceMetadata()); - $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new ResourceMetadata()); - $resourceMetadataFactoryProphecy->create(CompositeRelation::class)->shouldBeCalled()->willReturn(new ResourceMetadata()); - $resourceMetadataFactoryProphecy->create(DummyCar::class)->shouldBeCalled()->willReturn((new ResourceMetadata())->withAttributes(['elasticsearch' => false])); - $resourceMetadataFactoryProphecy->create(DummyCarColor::class)->shouldBeCalled()->willThrow(new ResourceClassNotFoundException()); - - $itemDataProvider = new ItemDataProvider( - $this->prophesize(Client::class)->reveal(), - $documentMetadataFactoryProphecy->reveal(), - $identifierExtractorProphecy->reveal(), - $this->prophesize(DenormalizerInterface::class)->reveal(), - $resourceMetadataFactoryProphecy->reveal() - ); - - self::assertTrue($itemDataProvider->supports(Foo::class)); - self::assertFalse($itemDataProvider->supports(Dummy::class)); - self::assertFalse($itemDataProvider->supports(CompositeRelation::class)); - self::assertFalse($itemDataProvider->supports(DummyCar::class)); - self::assertFalse($itemDataProvider->supports(DummyCarColor::class)); - } - - public function testGetItem() - { - $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); - $documentMetadataFactoryProphecy->create(Foo::class)->willReturn(new DocumentMetadata('foo'))->shouldBeCalled(); - - $identifierExtractorProphecy = $this->prophesize(IdentifierExtractorInterface::class); - $identifierExtractorProphecy->getIdentifierFromResourceClass(Foo::class)->willReturn('id')->shouldBeCalled(); - - $document = [ - '_index' => 'foo', - '_type' => '_doc', - '_id' => '1', - '_version' => 1, - 'found' => true, - '_source' => [ - 'id' => 1, - 'name' => 'Rossinière', - 'bar' => 'erèinissor', - ], - ]; - - $foo = new Foo(); - $foo->setName('Rossinière'); - $foo->setBar('erèinissor'); - - $clientProphecy = $this->prophesize(Client::class); - $clientProphecy->get(['index' => 'foo', 'id' => '1'])->willReturn($document)->shouldBeCalled(); - - $denormalizerProphecy = $this->prophesize(DenormalizerInterface::class); - $denormalizerProphecy->denormalize($document, Foo::class, DocumentNormalizer::FORMAT, [AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => true])->willReturn($foo)->shouldBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - - $itemDataProvider = new ItemDataProvider($clientProphecy->reveal(), $documentMetadataFactoryProphecy->reveal(), $identifierExtractorProphecy->reveal(), $denormalizerProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); - - self::assertSame($foo, $itemDataProvider->getItem(Foo::class, ['id' => 1])); - } - - public function testGetItemWithMissing404Exception() - { - $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); - $documentMetadataFactoryProphecy->create(Foo::class)->willReturn(new DocumentMetadata('foo'))->shouldBeCalled(); - - $identifierExtractorProphecy = $this->prophesize(IdentifierExtractorInterface::class); - $identifierExtractorProphecy->getIdentifierFromResourceClass(Foo::class)->willReturn('id')->shouldBeCalled(); - - $clientProphecy = $this->prophesize(Client::class); - $clientProphecy->get(['index' => 'foo', 'id' => '404'])->willThrow(new Missing404Exception())->shouldBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - - $itemDataProvider = new ItemDataProvider($clientProphecy->reveal(), $documentMetadataFactoryProphecy->reveal(), $identifierExtractorProphecy->reveal(), $this->prophesize(DenormalizerInterface::class)->reveal(), $resourceMetadataFactoryProphecy->reveal()); - - self::assertNull($itemDataProvider->getItem(Foo::class, ['id' => 404])); - } -} diff --git a/tests/Core/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactoryTest.php b/tests/Core/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactoryTest.php deleted file mode 100644 index 7ade6ced88f..00000000000 --- a/tests/Core/Bridge/Elasticsearch/Metadata/Resource/Factory/ElasticsearchOperationResourceMetadataFactoryTest.php +++ /dev/null @@ -1,64 +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\Tests\Bridge\Elasticsearch\Metadata\Resource\Factory; - -use ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Resource\Factory\ElasticsearchOperationResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Foo; -use PHPUnit\Framework\TestCase; - -/** - * @group legacy - */ -class ElasticsearchOperationResourceMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - self::assertInstanceOf( - ResourceMetadataFactoryInterface::class, - new ElasticsearchOperationResourceMetadataFactory($this->prophesize(ResourceMetadataFactoryInterface::class)->reveal()) - ); - } - - public function testCreate() - { - $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedProphecy->create(Foo::class)->willReturn(new ResourceMetadata())->shouldBeCalled(); - - $resourceMetadata = (new ElasticsearchOperationResourceMetadataFactory($decoratedProphecy->reveal()))->create(Foo::class); - - self::assertSame(['get' => ['method' => 'GET']], $resourceMetadata->getCollectionOperations()); - self::assertSame(['get' => ['method' => 'GET']], $resourceMetadata->getItemOperations()); - } - - public function testCreateWithExistingOperations() - { - $originalResourceMetadata = new ResourceMetadata(); - $originalResourceMetadata = $originalResourceMetadata->withItemOperations(['foo' => ['method' => 'GET']]); - $originalResourceMetadata = $originalResourceMetadata->withCollectionOperations(['bar' => ['method' => 'GET']]); - - $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedProphecy->create(Foo::class)->willReturn($originalResourceMetadata)->shouldBeCalled(); - - $resourceMetadata = (new ElasticsearchOperationResourceMetadataFactory($decoratedProphecy->reveal()))->create(Foo::class); - - self::assertSame($originalResourceMetadata, $resourceMetadata); - self::assertSame(['foo' => ['method' => 'GET']], $resourceMetadata->getItemOperations()); - self::assertSame(['bar' => ['method' => 'GET']], $resourceMetadata->getCollectionOperations()); - } -} diff --git a/tests/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProviderTest.php b/tests/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProviderTest.php deleted file mode 100644 index b3e1f2afee9..00000000000 --- a/tests/Core/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProviderTest.php +++ /dev/null @@ -1,569 +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\Tests\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider; - -use ApiPlatform\Api\FilterInterface; -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider\ApiPlatformProvider; -use ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser; -use ApiPlatform\Core\Bridge\Symfony\Routing\OperationMethodResolverInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Documentation\Documentation; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use Nelmio\ApiDocBundle\Annotation\ApiDoc; -use Nelmio\ApiDocBundle\Extractor\AnnotationsProviderInterface; -use Nelmio\ApiDocBundle\NelmioApiDocBundle; -use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; -use Symfony\Component\Routing\Route; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * @author Teoh Han Hui - * - * @group legacy - */ -class ApiPlatformProviderTest extends TestCase -{ - use ProphecyTrait; - - protected function setUp(): void - { - if (!class_exists(NelmioApiDocBundle::class)) { - $this->markTestSkipped('NelmioApiDocBundle is not installed.'); - } - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider\ApiPlatformProvider class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testConstruct() - { - $apiPlatformProvider = new ApiPlatformProvider( - $this->prophesize(ResourceNameCollectionFactoryInterface::class)->reveal(), - $this->prophesize(NormalizerInterface::class)->reveal(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), - $this->prophesize(ContainerInterface::class)->reveal(), - $this->prophesize(OperationMethodResolverInterface::class)->reveal() - ); - - $this->assertInstanceOf(AnnotationsProviderInterface::class, $apiPlatformProvider); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider\ApiPlatformProvider class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testGetAnnotations() - { - $dummySearchFilterProphecy = $this->prophesize(FilterInterface::class); - $dummySearchFilterProphecy->getDescription(Dummy::class)->willReturn([ - 'name' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => 'false', - 'strategy' => 'partial', - ], - ])->shouldBeCalled(); - - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $filterLocatorProphecy->has('my_dummy.search')->willReturn(true)->shouldBeCalled(); - $filterLocatorProphecy->get('my_dummy.search')->willReturn($dummySearchFilterProphecy->reveal())->shouldBeCalled(); - - $this->extractAnnotations($filterLocatorProphecy->reveal()); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider\ApiPlatformProvider class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. - */ - public function testGetAnnotationsWithDeprecatedFilterCollection() - { - $dummySearchFilterProphecy = $this->prophesize(FilterInterface::class); - $dummySearchFilterProphecy->getDescription(Dummy::class)->willReturn([ - 'name' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => 'false', - 'strategy' => 'partial', - ], - ])->shouldBeCalled(); - - $this->extractAnnotations(new FilterCollection(['my_dummy.search' => $dummySearchFilterProphecy->reveal()])); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Extractor\AnnotationsProvider\ApiPlatformProvider class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testConstructWithInvalidFilterLocator() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The "$filterLocator" argument is expected to be an implementation of the "Psr\\Container\\ContainerInterface" interface.'); - - new ApiPlatformProvider( - $this->prophesize(ResourceNameCollectionFactoryInterface::class)->reveal(), - $this->prophesize(NormalizerInterface::class)->reveal(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), - new \ArrayObject(), - $this->prophesize(OperationMethodResolverInterface::class)->reveal() - ); - } - - private function extractAnnotations($filterLocator) - { - $resourceNameCollection = new ResourceNameCollection([Dummy::class, RelatedDummy::class]); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn($resourceNameCollection)->shouldBeCalled(); - - $apiDocumentationBuilderProphecy = $this->prophesize(NormalizerInterface::class); - $apiDocumentationBuilderProphecy->normalize(new Documentation($resourceNameCollection))->willReturn($this->getHydraDoc())->shouldBeCalled(); - - $dummyResourceMetadata = (new ResourceMetadata()) - ->withShortName('Dummy') - ->withItemOperations([ - 'get' => [ - 'method' => 'GET', - ], - 'put' => [ - 'method' => 'PUT', - ], - 'delete' => [ - 'method' => 'DELETE', - ], - ]) - ->withCollectionOperations([ - 'get' => [ - 'filters' => [ - 'my_dummy.search', - ], - 'method' => 'GET', - ], - 'post' => [ - 'method' => 'POST', - ], - ]); - - $relatedDummyResourceMetadata = (new ResourceMetadata()) - ->withShortName('RelatedDummy'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyResourceMetadata)->shouldBeCalled(); - $resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedDummyResourceMetadata)->shouldBeCalled(); - - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->willReturn('GET')->shouldBeCalled(); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->willReturn('POST')->shouldBeCalled(); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->willReturn('GET')->shouldBeCalled(); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->willReturn('PUT')->shouldBeCalled(); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'delete')->willReturn('DELETE')->shouldBeCalled(); - $operationMethodResolverProphecy->getCollectionOperationRoute(Dummy::class, 'get')->willReturn((new Route('/dummies'))->setMethods(['GET']))->shouldBeCalled(); - $operationMethodResolverProphecy->getCollectionOperationRoute(Dummy::class, 'post')->willReturn((new Route('/dummies'))->setMethods(['POST']))->shouldBeCalled(); - $operationMethodResolverProphecy->getItemOperationRoute(Dummy::class, 'get')->willReturn((new Route('/dummies/{id}'))->setMethods(['GET']))->shouldBeCalled(); - $operationMethodResolverProphecy->getItemOperationRoute(Dummy::class, 'put')->willReturn((new Route('/dummies/{id}'))->setMethods(['PUT']))->shouldBeCalled(); - $operationMethodResolverProphecy->getItemOperationRoute(Dummy::class, 'delete')->willReturn((new Route('/dummies/{id}'))->setMethods(['DELETE']))->shouldBeCalled(); - - $apiPlatformProvider = new ApiPlatformProvider( - $resourceNameCollectionFactoryProphecy->reveal(), - $apiDocumentationBuilderProphecy->reveal(), - $resourceMetadataFactoryProphecy->reveal(), - $filterLocator, - $operationMethodResolverProphecy->reveal() - ); - - $annotations = $apiPlatformProvider->getAnnotations(); - - $this->assertCount(5, $annotations); - - $expectedResults = [ - [ - 'resource' => '/dummies', - 'description' => 'Retrieves the collection of Dummy resources.', - 'resource_description' => 'Dummy', - 'section' => 'Dummy', - 'input' => null, - 'output' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), - 'filters' => [ - 'name' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => 'false', - 'strategy' => 'partial', - ], - ], - 'path' => '/dummies', - 'methods' => ['GET'], - ], - [ - 'resource' => '/dummies', - 'description' => 'Creates a Dummy resource.', - 'resource_description' => 'Dummy', - 'section' => 'Dummy', - 'input' => sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, Dummy::class, 'post'), - 'output' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'post'), - 'filters' => [], - 'path' => '/dummies', - 'methods' => ['POST'], - ], - [ - 'resource' => '/dummies/{id}', - 'description' => 'Retrieves Dummy resource.', - 'resource_description' => 'Dummy', - 'section' => 'Dummy', - 'input' => null, - 'output' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), - 'filters' => [], - 'path' => '/dummies/{id}', - 'methods' => ['GET'], - ], - [ - 'resource' => '/dummies/{id}', - 'description' => 'Replaces the Dummy resource.', - 'resource_description' => 'Dummy', - 'section' => 'Dummy', - 'input' => sprintf('%s:%s:%s', ApiPlatformParser::IN_PREFIX, Dummy::class, 'put'), - 'output' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'put'), - 'filters' => [], - 'path' => '/dummies/{id}', - 'methods' => ['PUT'], - ], - [ - 'resource' => '/dummies/{id}', - 'description' => 'Deletes the Dummy resource.', - 'resource_description' => 'Dummy', - 'section' => 'Dummy', - 'input' => null, - 'output' => null, - 'filters' => [], - 'path' => '/dummies/{id}', - 'methods' => ['DELETE'], - ], - ]; - - foreach ($expectedResults as $i => $expected) { - /** @var ApiDoc $apiDoc */ - $apiDoc = $annotations[$i]; - - $this->assertInstanceOf(ApiDoc::class, $apiDoc); - $this->assertEquals($expected['resource'], $apiDoc->getResource()); - $this->assertEquals($expected['description'], $apiDoc->getDescription()); - $this->assertEquals($expected['resource_description'], $apiDoc->getResourceDescription()); - $this->assertEquals($expected['section'], $apiDoc->getSection()); - $this->assertEquals($expected['input'], $apiDoc->getInput()); - $this->assertEquals($expected['output'], $apiDoc->getOutput()); - $this->assertEquals($expected['filters'], $apiDoc->getFilters()); - $this->assertInstanceOf(Route::class, $apiDoc->getRoute()); - $this->assertEquals($expected['path'], $apiDoc->getRoute()->getPath()); - $this->assertEquals($expected['methods'], $apiDoc->getRoute()->getMethods()); - } - } - - private function getHydraDoc() - { - $hydraDocJson = <<<'JSON' - { - "hydra:supportedClass": [ - { - "@id": "#Dummy", - "hydra:title": "Dummy", - "hydra:supportedOperation": [ - { - "hydra:method": "GET", - "hydra:title": "Retrieves Dummy resource.", - "returns": "#Dummy" - }, - { - "expects": "#Dummy", - "hydra:method": "PUT", - "hydra:title": "Replaces the Dummy resource.", - "returns": "#Dummy" - }, - { - "hydra:method": "DELETE", - "hydra:title": "Deletes the Dummy resource.", - "returns": "owl:Nothing" - } - ] - }, - { - "@id": "#Entrypoint", - "hydra:supportedProperty": [ - { - "hydra:property": { - "@id": "#Entrypoint\/dummy", - "hydra:supportedOperation": [ - { - "hydra:method": "GET", - "hydra:title": "Retrieves the collection of Dummy resources.", - "returns": "hydra:PagedCollection" - }, - { - "expects": "#Dummy", - "hydra:method": "POST", - "hydra:title": "Creates a Dummy resource.", - "returns": "#Dummy" - } - ] - } - } - ] - } - ] - } -JSON; - - return json_decode($hydraDocJson, true); - } - - public function testGetAnnotationsWithEmptyHydraDoc() - { - $documentationNormalizerProphecy = $this->prophesize(NormalizerInterface::class); - $documentationNormalizerProphecy->normalize(new Documentation(new ResourceNameCollection([Dummy::class])))->willReturn([])->shouldBeCalled(); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $apiPlatformProvider = new ApiPlatformProvider( - $resourceNameCollectionFactoryProphecy->reveal(), - $documentationNormalizerProphecy->reveal(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), - $this->prophesize(ContainerInterface::class)->reveal(), - $this->prophesize(OperationMethodResolverInterface::class)->reveal() - ); - - $this->assertEquals([], $apiPlatformProvider->getAnnotations()); - } - - public function testGetAnnotationsWithInvalidHydraSupportedClass() - { - $hydraDoc = ['hydra:supportedClass' => 'not an array']; - - $documentationNormalizerProphecy = $this->prophesize(NormalizerInterface::class); - $documentationNormalizerProphecy->normalize(new Documentation(new ResourceNameCollection([Dummy::class])))->willReturn($hydraDoc)->shouldBeCalled(); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $apiPlatformProvider = new ApiPlatformProvider( - $resourceNameCollectionFactoryProphecy->reveal(), - $documentationNormalizerProphecy->reveal(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), - $this->prophesize(ContainerInterface::class)->reveal(), - $this->prophesize(OperationMethodResolverInterface::class)->reveal() - ); - - $this->assertEquals([], $apiPlatformProvider->getAnnotations()); - } - - public function testGetAnnotationsWithEmptyHydraSupportedClass() - { - $hydraDoc = ['hydra:supportedClass' => []]; - - $documentationNormalizerProphecy = $this->prophesize(NormalizerInterface::class); - $documentationNormalizerProphecy->normalize(new Documentation(new ResourceNameCollection([Dummy::class])))->willReturn($hydraDoc)->shouldBeCalled(); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $apiPlatformProvider = new ApiPlatformProvider( - $resourceNameCollectionFactoryProphecy->reveal(), - $documentationNormalizerProphecy->reveal(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), - $this->prophesize(ContainerInterface::class)->reveal(), - $this->prophesize(OperationMethodResolverInterface::class)->reveal() - ); - - $this->assertEquals([], $apiPlatformProvider->getAnnotations()); - } - - public function testGetAnnotationsWithInvalidHydraSupportedOperation() - { - $hydraDoc = ['hydra:supportedClass' => [ - ['@id' => '#Entrypoint'], - ['@id' => '#Dummy', 'hydra:supportedOperation' => 'not an array'], - ]]; - - $documentationNormalizerProphecy = $this->prophesize(NormalizerInterface::class); - $documentationNormalizerProphecy->normalize(new Documentation(new ResourceNameCollection([Dummy::class])))->willReturn($hydraDoc)->shouldBeCalled(); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $dummyResourceMetadata = (new ResourceMetadata()) - ->withShortName('Dummy') - ->withItemOperations([ - 'get' => [ - 'method' => 'GET', - ], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyResourceMetadata)->shouldBeCalled(); - - $route = (new Route('/dummies/{id}'))->setMethods(['GET']); - - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->willReturn('GET')->shouldBeCalled(); - $operationMethodResolverProphecy->getItemOperationRoute(Dummy::class, 'get')->willReturn($route)->shouldBeCalled(); - - $apiPlatformProvider = new ApiPlatformProvider( - $resourceNameCollectionFactoryProphecy->reveal(), - $documentationNormalizerProphecy->reveal(), - $resourceMetadataFactoryProphecy->reveal(), - $this->prophesize(ContainerInterface::class)->reveal(), - $operationMethodResolverProphecy->reveal() - ); - - $apiDoc = new ApiDoc(['resource' => $route->getPath(), 'description' => '', 'resourceDescription' => '', 'section' => '']); - $apiDoc->setRoute($route); - - $this->assertEquals([$apiDoc], $apiPlatformProvider->getAnnotations()); - } - - public function testGetAnnotationsWithEmptyHydraSupportedOperation() - { - $hydraDoc = ['hydra:supportedClass' => [ - ['@id' => '#Entrypoint'], - ['@id' => '#Dummy', 'hydra:supportedOperation' => []], - ]]; - - $documentationNormalizerProphecy = $this->prophesize(NormalizerInterface::class); - $documentationNormalizerProphecy->normalize(new Documentation(new ResourceNameCollection([Dummy::class])))->willReturn($hydraDoc)->shouldBeCalled(); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $dummyResourceMetadata = (new ResourceMetadata()) - ->withShortName('Dummy') - ->withItemOperations([ - 'get' => [ - 'method' => 'GET', - ], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyResourceMetadata)->shouldBeCalled(); - - $route = (new Route('/dummies/{id}'))->setMethods(['GET']); - - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->willReturn('GET')->shouldBeCalled(); - $operationMethodResolverProphecy->getItemOperationRoute(Dummy::class, 'get')->willReturn($route)->shouldBeCalled(); - - $apiPlatformProvider = new ApiPlatformProvider( - $resourceNameCollectionFactoryProphecy->reveal(), - $documentationNormalizerProphecy->reveal(), - $resourceMetadataFactoryProphecy->reveal(), - $this->prophesize(ContainerInterface::class)->reveal(), - $operationMethodResolverProphecy->reveal() - ); - - $apiDoc = new ApiDoc(['resource' => $route->getPath(), 'description' => '', 'resourceDescription' => '', 'section' => '']); - $apiDoc->setRoute($route); - - $this->assertEquals([$apiDoc], $apiPlatformProvider->getAnnotations()); - } - - public function testGetAnnotationsWithInvalidHydraSupportedProperty() - { - $hydraDoc = ['hydra:supportedClass' => [ - ['@id' => '#Entrypoint', 'hydra:supportedProperty' => 'not an array'], - ['@id' => '#Dummy'], - ]]; - - $documentationNormalizerProphecy = $this->prophesize(NormalizerInterface::class); - $documentationNormalizerProphecy->normalize(new Documentation(new ResourceNameCollection([Dummy::class])))->willReturn($hydraDoc)->shouldBeCalled(); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $dummyResourceMetadata = (new ResourceMetadata()) - ->withShortName('Dummy') - ->withCollectionOperations([ - 'get' => [ - 'method' => 'GET', - ], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyResourceMetadata)->shouldBeCalled(); - - $route = (new Route('/dummies'))->setMethods(['GET']); - - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->willReturn('GET')->shouldBeCalled(); - $operationMethodResolverProphecy->getCollectionOperationRoute(Dummy::class, 'get')->willReturn($route)->shouldBeCalled(); - - $apiPlatformProvider = new ApiPlatformProvider( - $resourceNameCollectionFactoryProphecy->reveal(), - $documentationNormalizerProphecy->reveal(), - $resourceMetadataFactoryProphecy->reveal(), - $this->prophesize(ContainerInterface::class)->reveal(), - $operationMethodResolverProphecy->reveal() - ); - - $apiDoc = new ApiDoc(['resource' => $route->getPath(), 'description' => '', 'resourceDescription' => '', 'section' => '', 'filters' => []]); - $apiDoc->setRoute($route); - - $this->assertEquals([$apiDoc], $apiPlatformProvider->getAnnotations()); - } - - public function testGetAnnotationsWithEmptyHydraSupportedProperty() - { - $hydraDoc = ['hydra:supportedClass' => [ - ['@id' => '#Entrypoint', 'hydra:supportedProperty' => []], - ['@id' => '#Dummy'], - ]]; - - $documentationNormalizerProphecy = $this->prophesize(NormalizerInterface::class); - $documentationNormalizerProphecy->normalize(new Documentation(new ResourceNameCollection([Dummy::class])))->willReturn($hydraDoc)->shouldBeCalled(); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $dummyResourceMetadata = (new ResourceMetadata()) - ->withShortName('Dummy') - ->withCollectionOperations([ - 'get' => [ - 'method' => 'GET', - ], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyResourceMetadata)->shouldBeCalled(); - - $route = (new Route('/dummies'))->setMethods(['GET']); - - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->willReturn('GET')->shouldBeCalled(); - $operationMethodResolverProphecy->getCollectionOperationRoute(Dummy::class, 'get')->willReturn($route)->shouldBeCalled(); - - $apiPlatformProvider = new ApiPlatformProvider( - $resourceNameCollectionFactoryProphecy->reveal(), - $documentationNormalizerProphecy->reveal(), - $resourceMetadataFactoryProphecy->reveal(), - $this->prophesize(ContainerInterface::class)->reveal(), - $operationMethodResolverProphecy->reveal() - ); - - $apiDoc = new ApiDoc(['resource' => $route->getPath(), 'description' => '', 'resourceDescription' => '', 'section' => '', 'filters' => []]); - $apiDoc->setRoute($route); - - $this->assertEquals([$apiDoc], $apiPlatformProvider->getAnnotations()); - } -} diff --git a/tests/Core/Bridge/NelmioApiDoc/Parser/ApiPlatformParserTest.php b/tests/Core/Bridge/NelmioApiDoc/Parser/ApiPlatformParserTest.php deleted file mode 100644 index 0b35c0c68f6..00000000000 --- a/tests/Core/Bridge/NelmioApiDoc/Parser/ApiPlatformParserTest.php +++ /dev/null @@ -1,540 +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\Tests\Bridge\NelmioApiDoc\Parser; - -use ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser; -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\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\UnknownDummy; -use ApiPlatform\Core\Tests\ProphecyTrait; -use Doctrine\Common\Collections\Collection; -use Nelmio\ApiDocBundle\DataTypes; -use Nelmio\ApiDocBundle\Parser\ParserInterface; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - -/** - * @author Teoh Han Hui - * - * @group legacy - */ -class ApiPlatformParserTest extends TestCase -{ - use ProphecyTrait; - - protected function setUp(): void - { - if (!class_exists(NelmioApiDocBundle::class)) { - $this->markTestSkipped('NelmioApiDocBundle is not installed.'); - } - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testConstruct() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $this->assertInstanceOf(ParserInterface::class, $apiPlatformParser); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testSupports() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $this->assertTrue($apiPlatformParser->supports([ - 'class' => sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class), - ])); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testNoOnDataFirstArray() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $this->assertFalse($apiPlatformParser->supports([ - 'class' => sprintf('%s', ApiPlatformParser::OUT_PREFIX), - ])); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testSupportsAttributeNormalization() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create('Acme\CustomAttributeDummy')->willReturn(new ResourceMetadata('dummy', 'dummy', null, [ - 'get' => ['method' => 'GET', 'normalization_context' => [AbstractNormalizer::GROUPS => ['custom_attr_dummy_get']]], - 'put' => ['method' => 'PUT', 'denormalization_context' => [AbstractNormalizer::GROUPS => ['custom_attr_dummy_put']]], - 'delete' => ['method' => 'DELETE'], - ], []))->shouldBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create('Acme\CustomAttributeDummy', Argument::cetera())->willReturn(new PropertyNameCollection([ - 'id', - 'name', - ]))->shouldBeCalled(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $idPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_INT, false)) - ->withDescription('The id.') - ->withReadable(true) - ->withWritable(false) - ->withRequired(true); - $propertyMetadataFactoryProphecy->create('Acme\CustomAttributeDummy', 'id')->willReturn($idPropertyMetadata)->shouldBeCalled(); - $namePropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_STRING, false)) - ->withDescription('The dummy name.') - ->withReadable(true) - ->withWritable(true) - ->withRequired(true); - $propertyMetadataFactoryProphecy->create('Acme\CustomAttributeDummy', 'name')->willReturn($namePropertyMetadata)->shouldBeCalled(); - - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $actual = $apiPlatformParser->parse([ - 'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, 'Acme\CustomAttributeDummy', 'get'), - ]); - - $this->assertEquals([ - 'id' => [ - 'dataType' => DataTypes::INTEGER, - 'required' => false, - 'description' => 'The id.', - 'readonly' => true, - ], - 'name' => [ - 'dataType' => DataTypes::STRING, - 'required' => true, - 'description' => 'The dummy name.', - 'readonly' => false, - ], - ], $actual); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testSupportsUnknownResource() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(UnknownDummy::class)->willThrow(ResourceClassNotFoundException::class)->shouldBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $this->assertFalse($apiPlatformParser->supports([ - 'class' => sprintf('%s:%s', ApiPlatformParser::OUT_PREFIX, UnknownDummy::class), - ])); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testSupportsUnsupportedClassFormat() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Argument::any())->shouldNotBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $this->assertFalse($apiPlatformParser->supports([ - 'class' => Dummy::class, - ])); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testParse() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [ - 'get' => ['method' => 'GET', 'normalization_context' => [AbstractNormalizer::GROUPS => ['custom_attr_dummy_get']]], - 'put' => ['method' => 'PUT', 'denormalization_context' => [AbstractNormalizer::GROUPS => ['custom_attr_dummy_put']]], - 'gerard' => ['method' => 'get', 'path' => '/gerard', 'denormalization_context' => [AbstractNormalizer::GROUPS => ['custom_attr_dummy_put']]], - 'delete' => ['method' => 'DELETE'], - ], []))->shouldBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([ - 'id', - 'name', - 'dummyPrice', - ]))->shouldBeCalled(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $idPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_INT, false)) - ->withDescription('The id.') - ->withReadable(true) - ->withWritable(false) - ->withRequired(true); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id')->willReturn($idPropertyMetadata)->shouldBeCalled(); - $namePropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_STRING, false)) - ->withDescription('The dummy name.') - ->withReadable(true) - ->withWritable(true) - ->withRequired(true); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name')->willReturn($namePropertyMetadata)->shouldBeCalled(); - $dummyPricePropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_FLOAT, true)) - ->withDescription('A dummy price.') - ->withReadable(true) - ->withWritable(true) - ->withRequired(false); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyPrice')->willReturn($dummyPricePropertyMetadata)->shouldBeCalled(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $actual = $apiPlatformParser->parse([ - 'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'gerard'), - ]); - - $this->assertEquals([ - 'id' => [ - 'dataType' => DataTypes::INTEGER, - 'required' => false, - 'description' => 'The id.', - 'readonly' => true, - ], - 'name' => [ - 'dataType' => DataTypes::STRING, - 'required' => true, - 'description' => 'The dummy name.', - 'readonly' => false, - ], - 'dummyPrice' => [ - 'dataType' => DataTypes::FLOAT, - 'required' => false, - 'description' => 'A dummy price.', - 'readonly' => false, - ], - ], $actual); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testParseDateTime() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [], []))->shouldBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([ - 'dummyDate', - ]))->shouldBeCalled(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $dummyDatePropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTime::class)) - ->withDescription('A dummy date.') - ->withReadable(true) - ->withWritable(true) - ->withRequired(false); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyDate')->willReturn($dummyDatePropertyMetadata)->shouldBeCalled(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $actual = $apiPlatformParser->parse([ - 'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), - ]); - - $this->assertEquals([ - 'dummyDate' => [ - 'dataType' => DataTypes::DATETIME, - 'required' => false, - 'description' => 'A dummy date.', - 'readonly' => false, - 'format' => sprintf('{DateTime %s}', \DateTime::RFC3339), - ], - ], $actual); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testParseRelation() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [], []))->shouldBeCalled(); - $resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([ - 'relatedDummy', - 'relatedDummies', - ]))->shouldBeCalled(); - $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([ - 'id', - 'name', - ]))->shouldBeCalled(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $relatedDummyPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_OBJECT, true, RelatedDummy::class)) - ->withDescription('A related dummy.') - ->withReadable(true) - ->withWritable(true) - ->withRequired(false); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy')->willReturn($relatedDummyPropertyMetadata)->shouldBeCalled(); - $relatedDummiesPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_OBJECT, false, Collection::class, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class))) - ->withDescription('Several dummies.') - ->withReadable(true) - ->withWritable(true) - ->withReadableLink(true) - ->withRequired(false); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies')->willReturn($relatedDummiesPropertyMetadata)->shouldBeCalled(); - $idPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_INT, false)) - ->withReadable(true) - ->withWritable(false) - ->withRequired(true); - $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id')->willReturn($idPropertyMetadata)->shouldBeCalled(); - $namePropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_STRING, false)) - ->withDescription('A name.') - ->withReadable(true) - ->withWritable(true) - ->withRequired(false); - $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name')->willReturn($namePropertyMetadata)->shouldBeCalled(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $actual = $apiPlatformParser->parse([ - 'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), - ]); - - $this->assertEquals([ - 'relatedDummy' => [ - 'dataType' => 'IRI', - 'required' => false, - 'description' => 'A related dummy.', - 'readonly' => false, - 'actualType' => DataTypes::STRING, - ], - 'relatedDummies' => [ - 'dataType' => null, - 'required' => false, - 'description' => 'Several dummies.', - 'readonly' => false, - 'actualType' => DataTypes::COLLECTION, - 'subType' => RelatedDummy::class, - 'children' => [ - 'id' => [ - 'dataType' => DataTypes::INTEGER, - 'required' => false, - 'description' => null, - 'readonly' => true, - ], - 'name' => [ - 'dataType' => DataTypes::STRING, - 'required' => false, - 'description' => 'A name.', - 'readonly' => false, - ], - ], - ], - ], $actual); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testParseWithNameConverter() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [], []))->shouldBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([ - 'nameConverted', - ]))->shouldBeCalled(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $nameConvertedPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_STRING, true)) - ->withDescription('A converted name') - ->withReadable(true) - ->withWritable(true) - ->withRequired(false); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'nameConverted')->willReturn($nameConvertedPropertyMetadata)->shouldBeCalled(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); - $nameConverterProphecy->normalize('nameConverted', Dummy::class)->willReturn('name_converted')->shouldBeCalled(); - $nameConverter = $nameConverterProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $nameConverter); - - $actual = $apiPlatformParser->parse([ - 'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), - ]); - - $this->assertEquals([ - 'name_converted' => [ - 'dataType' => DataTypes::STRING, - 'required' => false, - 'description' => 'A converted name', - 'readonly' => false, - ], - ], $actual); - } - - /** - * @expectedDeprecation The ApiPlatform\Core\Bridge\NelmioApiDoc\Parser\ApiPlatformParser class is deprecated since version 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform. - */ - public function testParseRecursive() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [], []))->shouldBeCalled(); - $resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled(); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([ - 'relatedDummy', - ]))->shouldBeCalled(); - $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([ - 'dummy', - ]))->shouldBeCalled(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $relatedDummyMetadatata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_OBJECT, true, RelatedDummy::class)) - ->withDescription('A related Dummy.') - ->withReadable(true) - ->withReadableLink(true) - ->withWritableLink(true) - ->withWritable(true) - ->withRequired(false); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy')->willReturn($relatedDummyMetadatata)->shouldBeCalled(); - $dummyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_OBJECT, true, Dummy::class)) - ->withDescription('A Dummy.') - ->withReadable(true) - ->withWritable(true) - ->withReadableLink(true) - ->withWritableLink(true) - ->withRequired(false); - $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'dummy')->willReturn($dummyMetadata)->shouldBeCalled(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory); - - $actual = $apiPlatformParser->parse([ - 'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), - ]); - - $this->assertEquals([ - 'relatedDummy' => [ - 'dataType' => null, - 'required' => false, - 'description' => 'A related Dummy.', - 'readonly' => false, - 'actualType' => 'model', - 'subType' => RelatedDummy::class, - 'children' => [ - 'dummy' => [ - 'dataType' => null, - 'required' => false, - 'description' => 'A Dummy.', - 'readonly' => false, - 'actualType' => 'model', - 'subType' => Dummy::class, - 'children' => [], - ], - ], - ], - ], $actual); - } -} diff --git a/tests/Core/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizerTest.php b/tests/Core/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizerTest.php deleted file mode 100644 index 3cd3cf99136..00000000000 --- a/tests/Core/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizerTest.php +++ /dev/null @@ -1,54 +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\Tests\Bridge\RamseyUuid\Identifier\Normalizer; - -use ApiPlatform\Core\Bridge\RamseyUuid\Identifier\Normalizer\UuidNormalizer; -use ApiPlatform\Exception\InvalidIdentifierException; -use PHPUnit\Framework\TestCase; -use Ramsey\Uuid\Uuid; - -class UuidNormalizerTest extends TestCase -{ - public function testDenormalizeUuid() - { - $uuid = Uuid::uuid4(); - $normalizer = new UuidNormalizer(); - $this->assertTrue($normalizer->supportsDenormalization($uuid->toString(), Uuid::class)); - $this->assertEquals($uuid, $normalizer->denormalize($uuid->toString(), Uuid::class)); - } - - public function testNoSupportDenormalizeUuid() - { - $uuid = 'notanuuid'; - $normalizer = new UuidNormalizer(); - $this->assertFalse($normalizer->supportsDenormalization($uuid, '')); - } - - public function testFailDenormalizeUuid() - { - $this->expectException(InvalidIdentifierException::class); - - $uuid = 'notanuuid'; - $normalizer = new UuidNormalizer(); - $this->assertTrue($normalizer->supportsDenormalization($uuid, Uuid::class)); - $normalizer->denormalize($uuid, Uuid::class); - } - - public function testDoNotSupportNotString() - { - $uuid = Uuid::uuid4(); - $normalizer = new UuidNormalizer(); - $this->assertFalse($normalizer->supportsDenormalization($uuid, Uuid::class)); - } -} diff --git a/tests/Core/Bridge/Symfony/Bundle/Command/SwaggerCommandTest.php b/tests/Core/Bridge/Symfony/Bundle/Command/SwaggerCommandTest.php deleted file mode 100644 index aef9ec5a63f..00000000000 --- a/tests/Core/Bridge/Symfony/Bundle/Command/SwaggerCommandTest.php +++ /dev/null @@ -1,137 +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\Tests\Bridge\Symfony\Bundle\Command; - -use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\Console\Exception\InvalidOptionException; -use Symfony\Component\Console\Tester\ApplicationTester; -use Symfony\Component\Yaml\Exception\ParseException; -use Symfony\Component\Yaml\Yaml; - -/** - * @author Amrouche Hamza - * @group legacy - */ -class SwaggerCommandTest extends KernelTestCase -{ - /** - * @var ApplicationTester - */ - private $tester; - - protected function setUp(): void - { - self::bootKernel(); - - $application = new Application(static::$kernel); - $application->setCatchExceptions(false); - $application->setAutoExit(false); - - $this->tester = new ApplicationTester($application); - - if (false === static::$kernel->getContainer()->getParameter('api_platform.metadata_backward_compatibility_layer')) { - $this->markTestSkipped( - 'Swagger is not compatible with the new metadata.' - ); - } - } - - /** - * @group legacy - * @expectedDeprecation The command "api:swagger:export" is deprecated for the spec version 3 use "api:openapi:export". - */ - public function testExecuteWithAliasVersion3() - { - $this->tester->run(['command' => 'api:swagger:export', '--spec-version' => 3]); - - $this->assertJson($this->tester->getDisplay()); - } - - /** - * @group legacy - * @expectedDeprecation The command "api:swagger:export" is deprecated for the spec version 3 use "api:openapi:export". - */ - public function testExecuteWithYamlVersion3() - { - $this->tester->run(['command' => 'api:swagger:export', '--yaml' => true, '--spec-version' => 3]); - - $result = $this->tester->getDisplay(); - $this->assertYaml($result); - - $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result, 'nested object should be present.'); - - $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result, 'arrays should be correctly formatted.'); - $this->assertStringContainsString('openapi: 3.0.2', $result); - - $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result, 'multiline formatting must be preserved (using literal style).'); - - $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result); - } - - public function testExecuteWithBadArguments() - { - $this->expectException(InvalidOptionException::class); - $this->expectExceptionMessage('This tool only supports versions 2, 3 of the OpenAPI specification ("foo" given).'); - $this->tester->run(['command' => 'api:swagger:export', '--spec-version' => 'foo', '--yaml' => true]); - } - - public function testWriteToFile() - { - /** @var string $tmpFile */ - $tmpFile = tempnam(sys_get_temp_dir(), 'test_write_to_file'); - - $this->tester->run(['command' => 'api:swagger:export', '--output' => $tmpFile]); - - $this->assertJson((string) @file_get_contents($tmpFile)); - @unlink($tmpFile); - } - - /** - * @param string $data - */ - private function assertYaml($data) - { - try { - Yaml::parse($data); - } catch (ParseException $exception) { - $this->fail('Is not valid YAML: '.$exception->getMessage()); - } - $this->addToAssertionCount(1); - } -} diff --git a/tests/Core/Bridge/Symfony/Bundle/Command/SwaggerCommandUnitTest.php b/tests/Core/Bridge/Symfony/Bundle/Command/SwaggerCommandUnitTest.php deleted file mode 100644 index e783dd213bd..00000000000 --- a/tests/Core/Bridge/Symfony/Bundle/Command/SwaggerCommandUnitTest.php +++ /dev/null @@ -1,75 +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\Tests\Bridge\Symfony\Bundle\Command; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\Command\SwaggerCommand; -use ApiPlatform\Documentation\Documentation; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use PHPUnit\Framework\MockObject\MockObject; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -class SwaggerCommandUnitTest extends KernelTestCase -{ - use ExpectDeprecationTrait; - /** @var MockObject&NormalizerInterface */ - private $normalizer; - - /** @var ResourceNameCollectionFactoryInterface&MockObject */ - private $resources; - - /** @var SwaggerCommand */ - private $command; - - protected function setUp(): void - { - $this->normalizer = $this->createMock(NormalizerInterface::class); - $this->resources = $this->createMock(ResourceNameCollectionFactoryInterface::class); - $this->command = new SwaggerCommand( - $this->normalizer, - $this->resources, - 'My API', - 'I told you already: it is my API', - 'one-zero-zero' - ); - - $this->resources->method('create') - ->willReturn(new ResourceNameCollection()); - } - - /** - * @group legacy - */ - public function testDocumentationJsonDoesNotUseEscapedSlashes(): void - { - $this->expectDeprecation('The command "api:swagger:export" is using pre-2.7 metadata, new metadata will not appear, use "api:openapi:export" instead.'); - $this->normalizer->method('normalize') - ->with(self::isInstanceOf(Documentation::class)) - ->willReturn(['a-jsonable-documentation' => 'containing/some/slashes']); - - $output = new BufferedOutput(); - - $this->command->run(new ArrayInput([]), $output); - - $jsonOutput = $output->fetch(); - - self::assertJson($jsonOutput); - self::assertStringNotContainsString('containing\/some\/slashes', $jsonOutput); - self::assertStringContainsString('containing/some/slashes', $jsonOutput); - } -} diff --git a/tests/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommandTest.php b/tests/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommandTest.php deleted file mode 100644 index a67f0904c4b..00000000000 --- a/tests/Core/Bridge/Symfony/Bundle/Command/UpgradeApiResourceCommandTest.php +++ /dev/null @@ -1,106 +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\Tests\Bridge\Symfony\Bundle\Command; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Bridge\Symfony\Bundle\Command\UpgradeApiResourceCommand; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Core\Upgrade\SubresourceTransformer; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use Doctrine\Common\Annotations\AnnotationReader; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Tester\CommandTester; - -class UpgradeApiResourceCommandTest extends TestCase -{ - use ProphecyTrait; - - private function getCommandTester(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, SubresourceOperationFactoryInterface $subresourceOperationFactory): CommandTester - { - $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); - - $application = new Application(); - $application->setCatchExceptions(false); - $application->setAutoExit(false); - - $application->add(new UpgradeApiResourceCommand($resourceNameCollectionFactory, $resourceMetadataFactory, $subresourceOperationFactory, new SubresourceTransformer(), $identifiersExtractor->reveal(), new AnnotationReader())); - - $command = $application->find('api:upgrade-resource'); - - return new CommandTester($command); - } - - /** - * @requires PHP 8.1 - */ - public function testDebugResource() - { - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([RelatedDummy::class])); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata()); - $subresourceOperationFactoryProphecy = $this->prophesize(SubresourceOperationFactoryInterface::class); - $subresourceOperationFactoryProphecy->create(RelatedDummy::class)->willReturn([[ - 'property' => 'id', - 'collection' => false, - 'resource_class' => RelatedDummy::class, - 'shortNames' => [ - 'RelatedDummy', - ], - 'legacy_filters' => [ - 'related_dummy.friends', - 'related_dummy.complex_sub_query', - ], - 'legacy_normalization_context' => [ - 'groups' => [ - 'friends', - ], - ], - 'legacy_type' => 'https://schema.org/Product', - 'identifiers' => [ - 'id' => [ - RelatedDummy::class, - 'id', - true, - ], - ], - 'operation_name' => 'id_get_subresource', - 'route_name' => 'api_related_dummies_id_get_subresource', - 'path' => '/related_dummies/{id}/id.{_format}', - ]]); - - $commandTester = $this->getCommandTester($resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), $subresourceOperationFactoryProphecy->reveal()); - $commandTester->execute([]); - - $expectedStrings = [ - '-use ApiPlatform\\Core\\Annotation\\ApiSubresource', - '-use ApiPlatform\\Core\\Annotation\\ApiResource', - '+use ApiPlatform\\Metadata\\ApiResource', - '+use ApiPlatform\\Metadata\\Get', - "+#[ApiResource(graphQlOperations: [new Query(name: 'item_query'), new Mutation(name: 'update', normalizationContext: ['groups' => ['chicago', 'fakemanytomany']], denormalizationContext: ['groups' => ['friends']])], types: ['https://schema.org/Product'], normalizationContext: ['groups' => ['friends']], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'])]", - "#[ApiResource(uriTemplate: '/related_dummies/{id}/id.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]", - ]; - - $display = $commandTester->getDisplay(); - foreach ($expectedStrings as $expectedString) { - $this->assertStringContainsString($expectedString, $display); - } - } -} diff --git a/tests/Core/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersisterTest.php b/tests/Core/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersisterTest.php deleted file mode 100644 index 79e28babcd4..00000000000 --- a/tests/Core/Bridge/Symfony/Bundle/DataPersister/TraceableChainDataPersisterTest.php +++ /dev/null @@ -1,185 +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\Tests\Bridge\Symfony\Bundle\DataPersister; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataPersister\TraceableChainDataPersister; -use ApiPlatform\Core\DataPersister\ChainDataPersister; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\DataPersister\ResumableDataPersisterInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use PHPUnit\Framework\TestCase; - -/** - * @author Anthony GRASSIOT - */ -class TraceableChainDataPersisterTest extends TestCase -{ - use ProphecyTrait; - - /** @dataProvider dataPersisterProvider */ - public function testPersist($persister, $expected) - { - $dataPersister = new TraceableChainDataPersister($persister); - $dataPersister->persist(''); - - $result = $dataPersister->getPersistersResponse(); - $this->assertCount(\count($expected), $result); - $this->assertEmpty(array_filter($result, function ($key) { - if (\PHP_VERSION_ID >= 80000) { - return !str_starts_with($key, DataPersisterInterface::class.'@anonymous'); - } - - return 0 !== strpos($key, 'class@anonymous'); - }, \ARRAY_FILTER_USE_KEY)); - $this->assertSame($expected, array_values($result)); - } - - /** @dataProvider dataPersisterProvider */ - public function testRemove($persister, $expected) - { - $dataPersister = new TraceableChainDataPersister($persister); - $dataPersister->remove(''); - - $result = $dataPersister->getPersistersResponse(); - $this->assertCount(\count($expected), $result); - $this->assertEmpty(array_filter($result, function ($key) { - if (\PHP_VERSION_ID >= 80000) { - return !str_starts_with($key, DataPersisterInterface::class.'@anonymous'); - } - - return 0 !== strpos($key, 'class@anonymous'); - }, \ARRAY_FILTER_USE_KEY)); - $this->assertSame($expected, array_values($result)); - } - - public function testSupports() - { - $context = ['ok' => true]; - $persister = $this->prophesize(DataPersisterInterface::class); - $persister->supports('', $context)->willReturn(true)->shouldBeCalled(); - $chain = new ChainDataPersister([$persister->reveal()]); - $persister->persist('', $context)->shouldBeCalled(); - $dataPersister = new TraceableChainDataPersister($chain); - $dataPersister->persist('', $context); - } - - public function dataPersisterProvider(): iterable - { - yield [ - new ChainDataPersister([]), - [], - ]; - - yield [ - new ChainDataPersister([ - new class() implements DataPersisterInterface { - public function supports($data): bool - { - return false; - } - - public function persist($data) - { - } - - public function remove($data) - { - } - }, - new class() implements DataPersisterInterface { - public function supports($data): bool - { - return true; - } - - public function persist($data) - { - } - - public function remove($data) - { - } - }, - new class() implements DataPersisterInterface { - public function supports($data): bool - { - return true; - } - - public function persist($data) - { - } - - public function remove($data) - { - } - }, - ]), - [false, true, false], - ]; - - yield [ - new ChainDataPersister([ - new class() implements DataPersisterInterface, ResumableDataPersisterInterface { - public function supports($data): bool - { - return true; - } - - public function resumable(array $context = []): bool - { - return true; - } - - public function persist($data) - { - } - - public function remove($data) - { - } - }, - new class() implements DataPersisterInterface { - public function supports($data): bool - { - return false; - } - - public function persist($data) - { - } - - public function remove($data) - { - } - }, - new class() implements DataPersisterInterface { - public function supports($data): bool - { - return true; - } - - public function persist($data) - { - } - - public function remove($data) - { - } - }, - ]), - [true, false, true], - ]; - } -} diff --git a/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataCollectorTest.php b/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataCollectorTest.php deleted file mode 100644 index 82c4282773c..00000000000 --- a/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainCollectionDataCollectorTest.php +++ /dev/null @@ -1,149 +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\Tests\Bridge\Symfony\Bundle\DataProvider; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainCollectionDataProvider; -use ApiPlatform\Core\DataProvider\ChainCollectionDataProvider; -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use PHPUnit\Framework\TestCase; - -/** - * @author Anthony GRASSIOT - */ -class TraceableChainCollectionDataCollectorTest extends TestCase -{ - /** @dataProvider dataProviderProvider */ - public function testGetCollection($provider, $context, $expected) - { - $dataProvider = new TraceableChainCollectionDataProvider($provider); - $dataProvider->getCollection('', null, $context); - - $result = $dataProvider->getProvidersResponse(); - $this->assertCount(\count($expected), $result); - $this->assertEmpty(array_filter($result, function ($key) { - if (\PHP_VERSION_ID >= 80000) { - return !str_starts_with($key, CollectionDataProviderInterface::class.'@anonymous'); - } - - return 0 !== strpos($key, 'class@anonymous'); - }, \ARRAY_FILTER_USE_KEY)); - $this->assertSame($expected, array_values($result)); - $this->assertSame($context, $dataProvider->getContext()); - } - - /** - * @dataProvider deprecatedDataProviderProvider - * @group legacy - * - * @param mixed $provider - * @param mixed $context - * @param mixed $expected - */ - public function testDeprecatedGetCollection($provider, $context, $expected) - { - $dataProvider = new TraceableChainCollectionDataProvider($provider); - $dataProvider->getCollection('', null, $context); - - $result = $dataProvider->getProvidersResponse(); - $this->assertCount(\count($expected), $result); - $this->assertEmpty(array_filter($result, function ($key) { - if (\PHP_VERSION_ID >= 80000) { - return !str_starts_with($key, CollectionDataProviderInterface::class.'@anonymous'); - } - - return 0 !== strpos($key, 'class@anonymous'); - }, \ARRAY_FILTER_USE_KEY)); - $this->assertSame($expected, array_values($result)); - $this->assertSame($context, $dataProvider->getContext()); - } - - public function dataProviderProvider(): iterable - { - yield 'Not a ChainCollectionDataProvider' => [ - new class() implements CollectionDataProviderInterface { - public function getCollection(string $resourceClass, string $operationName = null) - { - return []; - } - }, - ['some_context'], - [], - ]; - - yield 'Empty ChainCollectionDataProvider' => [ - new ChainCollectionDataProvider([]), - ['some_context'], - [], - ]; - - yield 'ChainCollectionDataProvider' => [ - new ChainCollectionDataProvider([ - new class() implements CollectionDataProviderInterface, RestrictedDataProviderInterface { - public function getCollection(string $resourceClass, string $operationName = null) - { - return []; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return false; - } - }, - new class() implements CollectionDataProviderInterface, RestrictedDataProviderInterface { - public function getCollection(string $resourceClass, string $operationName = null) - { - return []; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return true; - } - }, - new class() implements CollectionDataProviderInterface { - public function getCollection(string $resourceClass, string $operationName = null) - { - return []; - } - }, - ]), - ['some_context'], - [false, true, null], - ]; - } - - public function deprecatedDataProviderProvider(): iterable - { - yield 'ChainCollectionDataProvider' => [ - new ChainCollectionDataProvider([ - new class() implements CollectionDataProviderInterface { - public function getCollection(string $resourceClass, string $operationName = null) - { - throw new ResourceClassNotSupportedException('nope'); - } - }, - new class() implements CollectionDataProviderInterface { - public function getCollection(string $resourceClass, string $operationName = null) - { - return []; - } - }, - ]), - ['some_context'], - [false, true], - ]; - } -} diff --git a/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataCollectorTest.php b/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataCollectorTest.php deleted file mode 100644 index d8cfb0c387a..00000000000 --- a/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainItemDataCollectorTest.php +++ /dev/null @@ -1,150 +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\Tests\Bridge\Symfony\Bundle\DataProvider; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainItemDataProvider; -use ApiPlatform\Core\DataProvider\ChainItemDataProvider; -use ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use PHPUnit\Framework\TestCase; - -/** - * @author Anthony GRASSIOT - */ -class TraceableChainItemDataCollectorTest extends TestCase -{ - /** @dataProvider dataProviderProvider */ - public function testGetItem($provider, $context, $expected) - { - $dataProvider = new TraceableChainItemDataProvider($provider); - $dataProvider->getItem('', '', null, $context); - - $result = $dataProvider->getProvidersResponse(); - $this->assertCount(\count($expected), $result); - $this->assertEmpty(array_filter($result, function ($key) { - if (\PHP_VERSION_ID >= 80000) { - return !str_starts_with($key, ItemDataProviderInterface::class.'@anonymous'); - } - - return 0 !== strpos($key, 'class@anonymous'); - }, \ARRAY_FILTER_USE_KEY)); - $this->assertSame($expected, array_values($result)); - $this->assertSame($context, $dataProvider->getContext()); - } - - /** - * @dataProvider deprecatedDataProviderProvider - * @group legacy - * - * @param mixed $provider - * @param mixed $context - * @param mixed $expected - */ - public function testDeprecatedGetItem($provider, $context, $expected) - { - $dataProvider = new TraceableChainItemDataProvider($provider); - $dataProvider->getItem('', '', null, $context); - - $result = $dataProvider->getProvidersResponse(); - $this->assertCount(\count($expected), $result); - $this->assertEmpty(array_filter($result, function ($key) { - if (\PHP_VERSION_ID >= 80000) { - return !str_starts_with($key, ItemDataProviderInterface::class.'@anonymous'); - } - - return 0 !== strpos($key, 'class@anonymous'); - }, \ARRAY_FILTER_USE_KEY)); - $this->assertSame($expected, array_values($result)); - $this->assertSame($context, $dataProvider->getContext()); - } - - public function dataProviderProvider(): iterable - { - yield 'Not a ChainItemDataProvider' => [ - new class() implements ItemDataProviderInterface { - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - return null; - } - }, - ['some_context'], - [], - ]; - - yield 'Empty ChainItemDataProvider' => [ - new ChainItemDataProvider([]), - ['some_context'], - [], - ]; - - yield 'ChainItemDataProvider' => [ - new ChainItemDataProvider([ - new class() implements ItemDataProviderInterface, RestrictedDataProviderInterface { - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return false; - } - - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - return null; - } - }, - new class() implements ItemDataProviderInterface, RestrictedDataProviderInterface, DenormalizedIdentifiersAwareItemDataProviderInterface { - public function getItem(string $resourceClass, /* array */ $id, string $operationName = null, array $context = []) - { - return null; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return true; - } - }, - new class() implements ItemDataProviderInterface { - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - return null; - } - }, - ]), - ['some_context'], - [false, true, null], - ]; - } - - public function deprecatedDataProviderProvider(): iterable - { - yield 'deprecated ChainItemDataProvider - ResourceClassNotSupportedException' => [ - new ChainItemDataProvider([ - new class() implements ItemDataProviderInterface { - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - throw new ResourceClassNotSupportedException('nope'); - } - }, - new class() implements ItemDataProviderInterface { - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) - { - return null; - } - }, - ]), - ['some_context'], - [false, true], - ]; - } -} diff --git a/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataCollectorTest.php b/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataCollectorTest.php deleted file mode 100644 index 41bc0d2f2e0..00000000000 --- a/tests/Core/Bridge/Symfony/Bundle/DataProvider/TraceableChainSubresourceDataCollectorTest.php +++ /dev/null @@ -1,149 +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\Tests\Bridge\Symfony\Bundle\DataProvider; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataProvider\TraceableChainSubresourceDataProvider; -use ApiPlatform\Core\DataProvider\ChainSubresourceDataProvider; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use PHPUnit\Framework\TestCase; - -/** - * @author Anthony GRASSIOT - */ -class TraceableChainSubresourceDataCollectorTest extends TestCase -{ - /** @dataProvider dataProviderProvider */ - public function testGetSubresource($provider, $context, $expected) - { - $dataProvider = new TraceableChainSubresourceDataProvider($provider); - $dataProvider->getSubresource('', [], $context); - - $result = $dataProvider->getProvidersResponse(); - $this->assertCount(\count($expected), $result); - $this->assertEmpty(array_filter($result, function ($key) { - if (\PHP_VERSION_ID >= 80000) { - return !str_starts_with($key, SubresourceDataProviderInterface::class.'@anonymous'); - } - - return 0 !== strpos($key, 'class@anonymous'); - }, \ARRAY_FILTER_USE_KEY)); - $this->assertSame($expected, array_values($result)); - $this->assertSame($context, $dataProvider->getContext()); - } - - /** - * @dataProvider deprecatedDataProviderProvider - * @group legacy - * - * @param mixed $provider - * @param mixed $context - * @param mixed $expected - */ - public function testDeprecatedGetSubResource($provider, $context, $expected) - { - $dataProvider = new TraceableChainSubresourceDataProvider($provider); - $dataProvider->getSubresource('', [], $context); - - $result = $dataProvider->getProvidersResponse(); - $this->assertCount(\count($expected), $result); - $this->assertEmpty(array_filter($result, function ($key) { - if (\PHP_VERSION_ID >= 80000) { - return !str_starts_with($key, SubresourceDataProviderInterface::class.'@anonymous'); - } - - return 0 !== strpos($key, 'class@anonymous'); - }, \ARRAY_FILTER_USE_KEY)); - $this->assertSame($expected, array_values($result)); - $this->assertSame($context, $dataProvider->getContext()); - } - - public function dataProviderProvider(): iterable - { - yield 'Not a ChainSubresourceDataProvider' => [ - new class() implements SubresourceDataProviderInterface { - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - return null; - } - }, - ['some_context'], - [], - ]; - - yield 'Empty ChainSubresourceDataProvider' => [ - new ChainSubresourceDataProvider([]), - ['some_context'], - [], - ]; - - yield 'ChainSubresourceDataProvider' => [ - new ChainSubresourceDataProvider([ - new class() implements SubresourceDataProviderInterface, RestrictedDataProviderInterface { - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - return null; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return false; - } - }, - new class() implements SubresourceDataProviderInterface, RestrictedDataProviderInterface { - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - return null; - } - - public function supports(string $resourceClass, string $operationName = null, array $context = []): bool - { - return true; - } - }, - new class() implements SubresourceDataProviderInterface { - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - return null; - } - }, - ]), - ['some_context'], - [false, true, null], - ]; - } - - public function deprecatedDataProviderProvider(): iterable - { - yield 'deprecated ChainSubresourceDataProvider - ResourceClassNotSupportedException' => [ - new ChainSubresourceDataProvider([ - new class() implements SubresourceDataProviderInterface { - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - throw new ResourceClassNotSupportedException('nope'); - } - }, - new class() implements SubresourceDataProviderInterface { - public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) - { - return null; - } - }, - ]), - ['some_context'], - [false, true], - ]; - } -} diff --git a/tests/Core/Bridge/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php b/tests/Core/Bridge/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php deleted file mode 100644 index d4b84ac03fa..00000000000 --- a/tests/Core/Bridge/Symfony/Bundle/Twig/ApiPlatformProfilerPanelTest.php +++ /dev/null @@ -1,299 +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\Tests\Bridge\Symfony\Bundle\Twig; - -use ApiPlatform\Core\Bridge\Doctrine\Common\DataPersister; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\CollectionDataProvider as OdmCollectionDataProvider; -use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\ItemDataProvider as OdmItemDataProvider; -use ApiPlatform\Core\Bridge\Doctrine\Orm\CollectionDataProvider; -use ApiPlatform\Core\Bridge\Doctrine\Orm\ItemDataProvider; -use ApiPlatform\Tests\Fixtures\TestBundle\DataProvider\ContainNonResourceItemDataProvider; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy as DocumentDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Tools\SchemaTool; -use Doctrine\Persistence\ManagerRegistry; -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; - -/** - * @author Anthony GRASSIOT - */ -class ApiPlatformProfilerPanelTest extends WebTestCase -{ - /** - * @var EntityManagerInterface - */ - private $manager; - private $schemaTool; - private $env; - private $legacy; - - protected function setUp(): void - { - parent::setUp(); - $kernel = self::bootKernel(); - $this->env = $kernel->getEnvironment(); - $this->legacy = $kernel->getContainer()->getParameter('api_platform.metadata_backward_compatibility_layer'); - - /** @var ManagerRegistry $doctrine */ - $doctrine = $kernel->getContainer()->get('doctrine'); - /** @var EntityManagerInterface $manager */ - $manager = $doctrine->getManager(); - $this->manager = $manager; - $this->schemaTool = new SchemaTool($this->manager); - /** @var \Doctrine\ORM\Mapping\ClassMetadata[] $classes */ - $classes = $this->manager->getMetadataFactory()->getAllMetadata(); - $this->schemaTool->dropSchema($classes); - $this->manager->clear(); - $this->schemaTool->createSchema($classes); - - $this->ensureKernelShutdown(); - } - - protected function tearDown(): void - { - $this->schemaTool->dropSchema($this->manager->getMetadataFactory()->getAllMetadata()); - $this->manager->clear(); - parent::tearDown(); - } - - public function testDebugBarContentNotResourceClass() - { - if (!$this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $client = static::createClient(); - $client->enableProfiler(); - // Using html to get default Swagger UI - $client->request('GET', '/'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - /** @var string $token */ - $token = $client->getResponse()->headers->get('X-Debug-Token'); - $crawler = $client->request('GET', "/_wdt/$token"); - - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - $block = $crawler->filter('div[class*=sf-toolbar-block-api_platform]'); - - // Check extra info content - $this->assertStringContainsString('sf-toolbar-status-default', $block->attr('class'), 'The toolbar block should have the default color.'); - $this->assertSame('Not an API Platform resource', $block->filterXPath('//div[@class="sf-toolbar-info-piece"][./b[contains(., "Resource Class")]]/span')->html()); - } - - public function testDebugBarContent() - { - if (!$this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $client = static::createClient(); - $client->enableProfiler(); - $client->request('GET', '/dummies', [], [], ['HTTP_ACCEPT' => 'application/ld+json']); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - /** @var string $token */ - $token = $client->getResponse()->headers->get('X-Debug-Token'); - - $crawler = $client->request('GET', "/_wdt/$token"); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - $block = $crawler->filter('div[class*=sf-toolbar-block-api_platform]'); - - // Check extra info content - $this->assertStringContainsString('sf-toolbar-status-default', $block->attr('class'), 'The toolbar block should have the default color.'); - $this->assertSame('mongodb' === $this->env ? DocumentDummy::class : Dummy::class, $block->filterXPath('//div[@class="sf-toolbar-info-piece"][./b[contains(., "Resource Class")]]/span')->html()); - } - - public function testProfilerGeneralLayoutNotResourceClass() - { - if (!$this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $client = static::createClient(); - $client->enableProfiler(); - // Using html to get default Swagger UI - $client->request('GET', '/'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - $crawler = $client->request('GET', '/_profiler/latest?panel=api_platform.data_collector.request', [], [], []); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - // Check that the Api-Platform sidebar link is active - $this->assertNotEmpty($menuLink = $crawler->filter('a[href$="panel=api_platform.data_collector.request"]')); - $this->assertNotEmpty($menuLink->filter('.disabled'), 'The sidebar menu should be disabled.'); - - $metrics = $crawler->filter('.metrics'); - $this->assertCount(1, $metrics->filter('.metric'), 'The should be one metric displayed (resource class).'); - $this->assertSame('Not an API Platform resource', $metrics->filter('span.value')->html()); - - $this->assertEmpty($crawler->filter('.sf-tabs .tab'), 'Tabs must not be presents on the panel.'); - } - - public function testProfilerGeneralLayout() - { - if (!$this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $client = static::createClient(); - $client->enableProfiler(); - $client->request('GET', '/dummies', [], [], ['HTTP_ACCEPT' => 'application/ld+json']); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - $crawler = $client->request('GET', '/_profiler/latest?panel=api_platform.data_collector.request', [], [], []); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - // Check that the Api-Platform sidebar link is active - $this->assertNotEmpty($menuLink = $crawler->filter('a[href$="panel=api_platform.data_collector.request"]')); - $this->assertEmpty($menuLink->filter('.disabled'), 'The sidebar menu should not be disabled.'); - - $metrics = $crawler->filter('.metrics'); - $this->assertCount(1, $metrics->filter('.metric'), 'The should be one metric displayed (resource class).'); - $this->assertSame('mongodb' === $this->env ? DocumentDummy::class : Dummy::class, $metrics->filter('span.value')->html()); - - $this->assertCount(3, $crawler->filter('.sf-tabs .tab'), 'Tabs must be presents on the panel.'); - - // Metadata tab - $this->assertSame('Resource Metadata', $crawler->filter('.tab:nth-of-type(1) .tab-title')->html()); - $tabContent = $crawler->filter('.tab:nth-of-type(1) .tab-content'); - $this->assertStringEndsWith('"Dummy"', $tabContent->filter('h3')->html(), 'the resource shortname should be displayed.'); - - $this->assertCount(4, $tabContent->filter('table')); - $this->assertSame('Item operations', $tabContent->filter('table:first-of-type thead th:first-of-type')->html()); - $this->assertSame('Collection operations', $tabContent->filter('table:nth-of-type(2) thead th:first-of-type')->html()); - $this->assertSame('Filters', $tabContent->filter('table:nth-of-type(3) thead th:first-of-type')->html()); - $this->assertSame('Attributes', $tabContent->filter('table:last-of-type thead th:first-of-type')->html()); - - // Data providers tab - $this->assertSame('Data Providers', $crawler->filter('.tab:nth-of-type(2) .tab-title')->html()); - $this->assertNotEmpty($crawler->filter('.tab:nth-of-type(2) .tab-content')); - - // Data persisters tab - $this->assertSame('Data Persisters', $crawler->filter('.tab:last-child .tab-title')->html()); - $this->assertNotEmpty($crawler->filter('.tab:nth-of-type(3) .tab-content')); - } - - public function testGetCollectionProfiler() - { - if (!$this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $client = static::createClient(); - $client->enableProfiler(); - $client->request('GET', '/dummies', [], [], ['HTTP_ACCEPT' => 'application/ld+json']); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - $crawler = $client->request('GET', '/_profiler/latest?panel=api_platform.data_collector.request'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - // Metadata tab - $tabContent = $crawler->filter('.tab:nth-of-type(1) .tab-content'); - $this->assertSame('get', $tabContent->filter('table:nth-of-type(2) th.status-success')->html(), 'The actual operation should be highlighted.'); - $this->assertEmpty($tabContent->filter('table:not(:nth-of-type(2)) .status-success'), 'Only the actual operation should be highlighted.'); - - // Data provider tab - $tabContent = $crawler->filter('.tab:nth-of-type(2) .tab-content'); - $this->assertSame('TRUE', $tabContent->filter('table tbody .status-success')->html()); - $this->assertStringContainsString('mongodb' === $this->env ? OdmCollectionDataProvider::class : CollectionDataProvider::class, $tabContent->filter('table tbody')->html()); - - $this->assertStringContainsString('No calls to item data provider have been recorded.', $tabContent->html()); - $this->assertStringContainsString('No calls to subresource data provider have been recorded.', $tabContent->html()); - - // Data persiters tab - $this->assertStringContainsString('No calls to data persister have been recorded.', $crawler->filter('.tab:nth-of-type(3) .tab-content .empty')->html()); - } - - public function testPostCollectionProfiler() - { - if (!$this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $client = static::createClient(); - $client->enableProfiler(); - $client->request('POST', '/dummies', [], [], ['HTTP_ACCEPT' => 'application/ld+json', 'CONTENT_TYPE' => 'application/ld+json'], '{"name": "foo"}'); - $this->assertEquals(201, $client->getResponse()->getStatusCode()); - $crawler = $client->request('get', '/_profiler/latest?panel=api_platform.data_collector.request'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - // Metadata tab - $tabContent = $crawler->filter('.tab:nth-of-type(1) .tab-content'); - $this->assertSame('post', $tabContent->filter('table:nth-of-type(2) th.status-success')->html(), 'The actual operation should be highlighted.'); - $this->assertEmpty($tabContent->filter('table:not(:nth-of-type(2)) .status-success'), 'Only the actual operation should be highlighted.'); - - // Data provider tab - $tabContent = $crawler->filter('.tab:nth-of-type(2) .tab-content'); - $this->assertStringContainsString('No calls to collection data provider have been recorded.', $tabContent->html()); - $this->assertStringContainsString('No calls to item data provider have been recorded.', $tabContent->html()); - $this->assertStringContainsString('No calls to subresource data provider have been recorded.', $tabContent->html()); - - // Data persiters tab - $tabContent = $crawler->filter('.tab:nth-of-type(3) .tab-content'); - $this->assertSame('TRUE', $tabContent->filter('table tbody .status-success')->html()); - $this->assertStringContainsString(DataPersister::class, $tabContent->filter('table tbody')->html()); - } - - /** - * @group legacy - * Group legacy is due ApiPlatform\Core\Exception\ResourceClassNotSupportedException, the annotation could be removed in 3.0 but the test should stay - */ - public function testGetItemProfiler() - { - if (!$this->legacy) { - $this->markTestSkipped('Legacy test.'); - - return; - } - - $dummy = new Dummy(); - $dummy->setName('bar'); - $this->manager->persist($dummy); - $this->manager->flush(); - - $client = static::createClient(); - $client->enableProfiler(); - $client->request('GET', '/dummies/1', [], [], ['HTTP_ACCEPT' => 'application/ld+json', 'CONTENT_TYPE' => 'application/ld+json'], '{"name": "foo"}'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - $crawler = $client->request('get', '/_profiler/latest?panel=api_platform.data_collector.request'); - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - - // Metadata tab - $tabContent = $crawler->filter('.tab:nth-of-type(1) .tab-content'); - $this->assertSame('get', $tabContent->filter('table:nth-of-type(1) th.status-success')->html(), 'The actual operation should be highlighted.'); - $this->assertEmpty($tabContent->filter('table:not(:nth-of-type(1)) .status-success'), 'Only the actual operation should be highlighted.'); - - // Data provider tab - $tabContent = $crawler->filter('.tab:nth-of-type(2) .tab-content'); - $this->assertSame('FALSE', $tabContent->filter('table tbody tr:first-of-type .status-error')->html()); - $this->assertSame(ContainNonResourceItemDataProvider::class, $tabContent->filter('table tbody tr:first-of-type td:nth-of-type(3)')->html()); - - $this->assertSame('TRUE', $tabContent->filter('table tbody .status-success')->html()); - $this->assertStringContainsString('mongodb' === $this->env ? OdmItemDataProvider::class : ItemDataProvider::class, $tabContent->filter('table tbody')->html()); - - $this->assertStringContainsString('No calls to collection data provider have been recorded.', $tabContent->html()); - $this->assertStringContainsString('No calls to subresource data provider have been recorded.', $tabContent->html()); - - // Data persiters tab - $this->assertStringContainsString('No calls to data persister have been recorded.', $crawler->filter('.tab:nth-of-type(3) .tab-content .empty')->html()); - } -} diff --git a/tests/Core/Bridge/Symfony/Identifier/Normalizer/UlidNormalizerTest.php b/tests/Core/Bridge/Symfony/Identifier/Normalizer/UlidNormalizerTest.php deleted file mode 100644 index 170fab26d65..00000000000 --- a/tests/Core/Bridge/Symfony/Identifier/Normalizer/UlidNormalizerTest.php +++ /dev/null @@ -1,62 +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\Tests\Bridge\Symfony\Identifier\Normalizer; - -use ApiPlatform\Core\Bridge\Symfony\Identifier\Normalizer\UlidNormalizer; -use ApiPlatform\Exception\InvalidIdentifierException; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Uid\AbstractUid; -use Symfony\Component\Uid\Ulid; - -final class UlidNormalizerTest extends TestCase -{ - protected function setUp(): void - { - if (!class_exists(AbstractUid::class)) { - $this->markTestSkipped(); - } - } - - public function testDenormalizeUlid() - { - $ulid = new Ulid(); - $normalizer = new UlidNormalizer(); - $this->assertTrue($normalizer->supportsDenormalization($ulid->__toString(), Ulid::class)); - $this->assertEquals($ulid, $normalizer->denormalize($ulid->__toString(), Ulid::class)); - } - - public function testNoSupportDenormalizeUlid() - { - $ulid = 'notanulid'; - $normalizer = new UlidNormalizer(); - $this->assertFalse($normalizer->supportsDenormalization($ulid, '')); - } - - public function testFailDenormalizeUlid() - { - $this->expectException(InvalidIdentifierException::class); - - $ulid = 'notanulid'; - $normalizer = new UlidNormalizer(); - $this->assertTrue($normalizer->supportsDenormalization($ulid, Ulid::class)); - $normalizer->denormalize($ulid, Ulid::class); - } - - public function testDoNotSupportNotString() - { - $ulid = new Ulid(); - $normalizer = new UlidNormalizer(); - $this->assertFalse($normalizer->supportsDenormalization($ulid, Ulid::class)); - } -} diff --git a/tests/Core/Bridge/Symfony/Identifier/Normalizer/UuidNormalizerTest.php b/tests/Core/Bridge/Symfony/Identifier/Normalizer/UuidNormalizerTest.php deleted file mode 100644 index 32c124a72ad..00000000000 --- a/tests/Core/Bridge/Symfony/Identifier/Normalizer/UuidNormalizerTest.php +++ /dev/null @@ -1,62 +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\Tests\Bridge\Symfony\Identifier\Normalizer; - -use ApiPlatform\Core\Bridge\Symfony\Identifier\Normalizer\UuidNormalizer; -use ApiPlatform\Exception\InvalidIdentifierException; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Uid\AbstractUid; -use Symfony\Component\Uid\Uuid; - -final class UuidNormalizerTest extends TestCase -{ - protected function setUp(): void - { - if (!class_exists(AbstractUid::class)) { - $this->markTestSkipped(); - } - } - - public function testDenormalizeUuid() - { - $uuid = Uuid::v4(); - $normalizer = new UuidNormalizer(); - $this->assertTrue($normalizer->supportsDenormalization($uuid->__toString(), Uuid::class)); - $this->assertEquals($uuid, $normalizer->denormalize($uuid->__toString(), Uuid::class)); - } - - public function testNoSupportDenormalizeUuid() - { - $uuid = 'notanuuid'; - $normalizer = new UuidNormalizer(); - $this->assertFalse($normalizer->supportsDenormalization($uuid, '')); - } - - public function testFailDenormalizeUuid() - { - $this->expectException(InvalidIdentifierException::class); - - $uuid = 'notanuuid'; - $normalizer = new UuidNormalizer(); - $this->assertTrue($normalizer->supportsDenormalization($uuid, Uuid::class)); - $normalizer->denormalize($uuid, Uuid::class); - } - - public function testDoNotSupportNotString() - { - $uuid = Uuid::v4(); - $normalizer = new UuidNormalizer(); - $this->assertFalse($normalizer->supportsDenormalization($uuid, Uuid::class)); - } -} diff --git a/tests/Core/Bridge/Symfony/Maker/MakeDataPersisterTest.php b/tests/Core/Bridge/Symfony/Maker/MakeDataPersisterTest.php deleted file mode 100644 index 12cc2aa2785..00000000000 --- a/tests/Core/Bridge/Symfony/Maker/MakeDataPersisterTest.php +++ /dev/null @@ -1,187 +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); - -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; -use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\Filesystem\Filesystem; - -class MakeDataPersisterTest extends KernelTestCase -{ - protected function tearDown(): void - { - (new Filesystem())->remove(self::tempDir()); - parent::tearDown(); - } - - /** @dataProvider dataPersisterProvider */ - public function testMakeDataPersister(array $commandInputs, array $userInputs, string $expected) - { - $this->assertFileDoesNotExist(self::tempFile('src/DataPersister/CustomDataPersister.php')); - - $tester = new CommandTester((new Application(self::bootKernel()))->find('make:data-persister')); - $tester->setInputs($userInputs); - $tester->execute($commandInputs); - - $this->assertFileExists(self::tempFile('src/DataPersister/CustomDataPersister.php')); - - // Unify line endings - $expected = preg_replace('~\R~u', "\r\n", $expected); - $result = preg_replace('~\R~u', "\r\n", file_get_contents(self::tempFile('src/DataPersister/CustomDataPersister.php'))); - $this->assertSame($expected, $result); - - $display = $tester->getDisplay(); - $this->assertStringContainsString('Success!', $display); - - if (!isset($commandInputs['name'])) { - $this->assertStringContainsString('Choose a class name for your data persister (e.g. AwesomeDataPersister):', $display); - } else { - $this->assertStringNotContainsString('Choose a class name for your data persister (e.g. AwesomeDataPersister):', $display); - } - if (!isset($commandInputs['resource-class'])) { - $this->assertStringContainsString('Choose a Resource class:', $display); - } else { - $this->assertStringNotContainsString('Choose a Resource class:', $display); - } - $this->assertStringContainsString(<< [ - [], - ['CustomDataPersister', ''], - \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': object', '', $expected), - ]; - - $expected = <<<'EOF' - [ - [], - ['CustomDataPersister', Question::class], - $expected, - ]; - - yield 'Generate data persister with resource class not interactively' => [ - ['name' => 'CustomDataPersister', 'resource-class' => Question::class], - [], - $expected, - ]; - } - - private static function tempDir(): string - { - return __DIR__.'/../../../../Fixtures/app/var/tmp'; - } - - private static function tempFile(string $path): string - { - return sprintf('%s/%s', self::tempDir(), $path); - } -} diff --git a/tests/Core/Bridge/Symfony/Maker/MakeDataProviderTest.php b/tests/Core/Bridge/Symfony/Maker/MakeDataProviderTest.php deleted file mode 100644 index 5dfc87c52d0..00000000000 --- a/tests/Core/Bridge/Symfony/Maker/MakeDataProviderTest.php +++ /dev/null @@ -1,332 +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); - -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; -use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; -use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\Filesystem\Filesystem; - -class MakeDataProviderTest extends KernelTestCase -{ - protected function tearDown(): void - { - (new Filesystem())->remove(self::tempDir()); - parent::tearDown(); - } - - /** @dataProvider dataProviderProvider */ - public function testMakeDataProvider(array $commandInputs, array $userInputs, string $expected) - { - $this->assertFileDoesNotExist(self::tempFile('src/DataProvider/CustomDataProvider.php')); - - $tester = new CommandTester((new Application(self::bootKernel()))->find('make:data-provider')); - $tester->setInputs($userInputs); - $tester->execute($commandInputs); - - $this->assertFileExists(self::tempFile('src/DataProvider/CustomDataProvider.php')); - - // Unify line endings - $expected = preg_replace('~\R~u', "\r\n", $expected); - $result = preg_replace('~\R~u', "\r\n", file_get_contents(self::tempFile('src/DataProvider/CustomDataProvider.php'))); - $this->assertSame($expected, $result); - - $display = $tester->getDisplay(); - $this->assertStringContainsString('Success!', $display); - - if (!isset($commandInputs['name'])) { - $this->assertStringContainsString('Choose a class name for your data provider (e.g. AwesomeDataProvider):', $display); - } else { - $this->assertStringNotContainsString('Choose a class name for your data provider (e.g. AwesomeDataProvider):', $display); - } - if (!isset($commandInputs['resource-class'])) { - $this->assertStringContainsString(' Choose a Resource class:', $display); - } else { - $this->assertStringNotContainsString('Choose a Resource class:', $display); - } - - $this->assertStringContainsString(<< [ - [], - ['CustomDataProvider', ''], - \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': ?object', '', $expected), - ]; - - $expected = <<<'EOF' - [ - [], - ['CustomDataProvider', Question::class], - $expected, - ]; - - yield 'Generate all with resource class not interactively' => [ - ['name' => 'CustomDataProvider', 'resource-class' => Question::class], - [], - $expected, - ]; - - $expected = <<<'EOF' - [ - ['--item-only' => true], - ['CustomDataProvider', ''], - \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': ?object', '', $expected), - ]; - - $expected = <<<'EOF' - [ - ['--item-only' => true], - ['CustomDataProvider', Question::class], - $expected, - ]; - - yield 'Generate an item data provider with a resource class not interactively' => [ - ['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--item-only' => true], - [], - $expected, - ]; - - $expected = <<<'EOF' - [ - ['--collection-only' => true], - ['CustomDataProvider', ''], - $expected, - ]; - - $expected = <<<'EOF' - [ - ['--collection-only' => true], - ['CustomDataProvider', Question::class], - $expected, - ]; - - yield 'Generate a collection data provider with a resource class not interactively' => [ - ['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--collection-only' => true], - [], - $expected, - ]; - } - - public function testMakeDataProviderThrows() - { - $tester = new CommandTester((new Application(self::bootKernel()))->find('make:data-provider')); - $this->expectException(RuntimeCommandException::class); - $this->expectExceptionMessage('You should at least generate an item or a collection data provider'); - - $tester->execute(['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--collection-only' => true, '--item-only' => true]); - } - - private static function tempDir(): string - { - return __DIR__.'/../../../../Fixtures/app/var/tmp'; - } - - private static function tempFile(string $path): string - { - return sprintf('%s/%s', self::tempDir(), $path); - } -} diff --git a/tests/Core/Bridge/Symfony/Messenger/DataPersisterTest.php b/tests/Core/Bridge/Symfony/Messenger/DataPersisterTest.php deleted file mode 100644 index 983fa78db75..00000000000 --- a/tests/Core/Bridge/Symfony/Messenger/DataPersisterTest.php +++ /dev/null @@ -1,107 +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\Tests\Bridge\Symfony\Messenger; - -use ApiPlatform\Core\Bridge\Symfony\Messenger\DataPersister; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Symfony\Messenger\ContextStamp; -use ApiPlatform\Symfony\Messenger\RemoveStamp; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Component\Messenger\Envelope; -use Symfony\Component\Messenger\MessageBusInterface; -use Symfony\Component\Messenger\Stamp\HandledStamp; - -/** - * @author Kévin Dunglas - * @group legacy - */ -class DataPersisterTest extends TestCase -{ - use ProphecyTrait; - - public function testSupport() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => true])); - - $dataPersister = new DataPersister($metadataFactoryProphecy->reveal(), $this->prophesize(MessageBusInterface::class)->reveal()); - $this->assertTrue($dataPersister->supports(new Dummy())); - } - - public function testSupportWithContext() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => true])); - $metadataFactoryProphecy->create(DummyCar::class)->willThrow(new ResourceClassNotFoundException()); - - $dataPersister = new DataPersister($metadataFactoryProphecy->reveal(), $this->prophesize(MessageBusInterface::class)->reveal()); - $this->assertTrue($dataPersister->supports(new DummyCar(), ['resource_class' => Dummy::class])); - $this->assertFalse($dataPersister->supports(new DummyCar())); - } - - public function testPersist() - { - $dummy = new Dummy(); - - $messageBus = $this->prophesize(MessageBusInterface::class); - $messageBus->dispatch(Argument::that(function (Envelope $envelope) use ($dummy) { - return $dummy === $envelope->getMessage() && null !== $envelope->last(ContextStamp::class); - }))->willReturn(new Envelope($dummy))->shouldBeCalled(); - - $dataPersister = new DataPersister($this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), $messageBus->reveal()); - $this->assertSame($dummy, $dataPersister->persist($dummy)); - } - - public function testRemove() - { - $dummy = new Dummy(); - - $messageBus = $this->prophesize(MessageBusInterface::class); - - $messageBus->dispatch(Argument::that(function (Envelope $envelope) use ($dummy) { - return $dummy === $envelope->getMessage() && null !== $envelope->last(RemoveStamp::class); - }))->willReturn(new Envelope($dummy))->shouldBeCalled(); - - $dataPersister = new DataPersister($this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), $messageBus->reveal()); - $dataPersister->remove($dummy); - } - - public function testHandle() - { - $dummy = new Dummy(); - - $messageBus = $this->prophesize(MessageBusInterface::class); - $messageBus->dispatch(Argument::that(function (Envelope $envelope) use ($dummy) { - return $dummy === $envelope->getMessage() && null !== $envelope->last(ContextStamp::class); - }))->willReturn((new Envelope($dummy))->with(new HandledStamp($dummy, 'DummyHandler::__invoke')))->shouldBeCalled(); - - $dataPersister = new DataPersister($this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), $messageBus->reveal()); - $this->assertSame($dummy, $dataPersister->persist($dummy)); - } - - public function testSupportWithGraphqlContext() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn((new ResourceMetadata(null, null, null, null, null, []))->withGraphQl(['create' => ['messenger' => 'input']])); - - $dataPersister = new DataPersister($metadataFactoryProphecy->reveal(), $this->prophesize(MessageBusInterface::class)->reveal()); - $this->assertTrue($dataPersister->supports(new DummyCar(), ['resource_class' => Dummy::class, 'graphql_operation_name' => 'create'])); - } -} diff --git a/tests/Core/Bridge/Symfony/Messenger/DataTransformerTest.php b/tests/Core/Bridge/Symfony/Messenger/DataTransformerTest.php deleted file mode 100644 index d7b7ab5e78d..00000000000 --- a/tests/Core/Bridge/Symfony/Messenger/DataTransformerTest.php +++ /dev/null @@ -1,101 +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\Tests\Symfony\Messenger; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Bridge\Symfony\Messenger\DataTransformer; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; - -/** - * @author Antoine Bluchet - * @group legacy - */ -class DataTransformerTest extends TestCase -{ - use ProphecyTrait; - - public function testSupport() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input'])); - - $dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal()); - $this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth']])); - } - - public function testSupportWithinRequest() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, ['foo' => ['messenger' => 'input']], null, [])); - - $dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal()); - $this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'operation_type' => OperationType::ITEM, 'item_operation_name' => 'foo'])); - } - - public function testNoSupport() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => true])); - - $dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal()); - $this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth']])); - } - - public function testNoSupportWithinRequest() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, ['foo' => ['messenger' => true]], null, [])); - - $dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal()); - $this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'operation_type' => OperationType::ITEM, 'item_operation_name' => 'foo'])); - } - - public function testNoSupportWithoutInput() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input'])); - - $dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal()); - $this->assertFalse($dataTransformer->supportsTransformation([], Dummy::class, [])); - } - - public function testNoSupportWithObject() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['messenger' => 'input'])); - - $dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal()); - $this->assertFalse($dataTransformer->supportsTransformation(new Dummy(), Dummy::class, [])); - } - - public function testTransform() - { - $dummy = new Dummy(); - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal()); - $this->assertSame($dummy, $dataTransformer->transform($dummy, Dummy::class)); - } - - public function testSupportWithGraphqlContext() - { - $metadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $metadataFactoryProphecy->create(Dummy::class)->willReturn((new ResourceMetadata(null, null, null, null, null, []))->withGraphQl(['create' => ['messenger' => 'input']])); - $dataTransformer = new DataTransformer($metadataFactoryProphecy->reveal()); - $this->assertTrue($dataTransformer->supportsTransformation([], Dummy::class, ['input' => ['class' => 'smth'], 'graphql_operation_name' => 'create'])); - } -} diff --git a/tests/Core/Bridge/Symfony/Routing/ApiLoaderTest.php b/tests/Core/Bridge/Symfony/Routing/ApiLoaderTest.php deleted file mode 100644 index a38b5033507..00000000000 --- a/tests/Core/Bridge/Symfony/Routing/ApiLoaderTest.php +++ /dev/null @@ -1,361 +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\Tests\Bridge\Symfony\Routing; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactory; -use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidResourceException; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\PathResolver\CustomOperationPathResolver; -use ApiPlatform\PathResolver\OperationPathResolver; -use ApiPlatform\Symfony\Routing\ApiLoader; -use ApiPlatform\Tests\Fixtures\DummyEntity; -use ApiPlatform\Tests\Fixtures\RelatedDummyEntity; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Routing\Route; - -/** - * @author Antoine Bluchet - * @author Amrouche Hamza - * - * @group legacy - */ -class ApiLoaderTest extends TestCase -{ - use ProphecyTrait; - - public function testLegacyApiLoader() - { - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withShortName('dummy'); - $resourceMetadata = $resourceMetadata->withAttributes(['identifiers' => 'id']); - // default operation based on OperationResourceMetadataFactory - $resourceMetadata = $resourceMetadata->withItemOperations([ - 'get' => ['method' => 'GET', 'requirements' => ['id' => '\d+'], 'defaults' => ['my_default' => 'default_value', '_controller' => 'should_not_be_overriden'], 'stateless' => null], - 'put' => ['method' => 'PUT', 'stateless' => null], - 'delete' => ['method' => 'DELETE', 'stateless' => null], - ]); - // custom operations - $resourceMetadata = $resourceMetadata->withCollectionOperations([ - 'my_op' => ['method' => 'GET', 'controller' => 'some.service.name', 'requirements' => ['_format' => 'a valid format'], 'defaults' => ['my_default' => 'default_value', '_format' => 'a valid format'], 'condition' => "request.headers.get('User-Agent') matches '/firefox/i'", 'stateless' => null], // with controller - 'my_second_op' => ['method' => 'POST', 'options' => ['option' => 'option_value'], 'host' => '{subdomain}.api-platform.com', 'schemes' => ['https'], 'stateless' => null], // without controller, takes the default one - 'my_path_op' => ['method' => 'GET', 'path' => 'some/custom/path', 'stateless' => null], // custom path - 'my_stateless_op' => ['method' => 'GET', 'stateless' => true], - ]); - $resourceMetadata = $resourceMetadata->withSubresourceOperations([ - 'subresources_get_subresource' => ['stateless' => true], - ]); - - $routeCollection = $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); - - $this->assertEquals( - $this->getRoute('/dummies/{id}.{_format}', 'api_platform.action.get_item', DummyEntity::class, 'api_dummies_get_item', ['GET'], false, ['id' => '\d+'], ['my_default' => 'default_value', '_stateless' => null]), - $routeCollection->get('api_dummies_get_item') - ); - - $this->assertEquals( - $this->getRoute('/dummies/{id}.{_format}', 'api_platform.action.delete_item', DummyEntity::class, 'api_dummies_delete_item', ['DELETE']), - $routeCollection->get('api_dummies_delete_item') - ); - - $this->assertEquals( - $this->getRoute('/dummies/{id}.{_format}', 'api_platform.action.put_item', DummyEntity::class, 'api_dummies_put_item', ['PUT']), - $routeCollection->get('api_dummies_put_item') - ); - - $this->assertEquals( - $this->getRoute('/dummies.{_format}', 'some.service.name', DummyEntity::class, 'api_dummies_my_op_collection', ['GET'], true, ['_format' => 'a valid format'], ['my_default' => 'default_value', '_format' => 'a valid format', '_stateless' => null], [], '', [], "request.headers.get('User-Agent') matches '/firefox/i'"), - $routeCollection->get('api_dummies_my_op_collection') - ); - - $this->assertEquals( - $this->getRoute('/dummies.{_format}', 'api_platform.action.post_collection', DummyEntity::class, 'api_dummies_my_second_op_collection', ['POST'], true, [], ['_stateless' => null], ['option' => 'option_value'], '{subdomain}.api-platform.com', ['https']), - $routeCollection->get('api_dummies_my_second_op_collection') - ); - - $this->assertEquals( - $this->getRoute('/some/custom/path', 'api_platform.action.get_collection', DummyEntity::class, 'api_dummies_my_path_op_collection', ['GET'], true), - $routeCollection->get('api_dummies_my_path_op_collection') - ); - - $this->assertEquals( - $this->getRoute('/dummies.{_format}', 'api_platform.action.get_collection', DummyEntity::class, 'api_dummies_my_stateless_op_collection', ['GET'], true, [], ['_stateless' => true]), - $routeCollection->get('api_dummies_my_stateless_op_collection') - ); - - $this->assertEquals( - $this->getSubresourceRoute('/dummies/{id}/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'api_dummies_subresources_get_subresource', ['property' => 'subresource', 'identifiers' => ['id' => [DummyEntity::class, 'id', true]], 'collection' => true, 'operationId' => 'api_dummies_subresources_get_subresource'], [], ['_stateless' => true]), - $routeCollection->get('api_dummies_subresources_get_subresource') - ); - } - - public function testApiLoaderWithPrefix() - { - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withShortName('dummy'); - $resourceMetadata = $resourceMetadata->withItemOperations([ - 'get' => ['method' => 'GET', 'requirements' => ['id' => '\d+'], 'defaults' => ['my_default' => 'default_value', '_controller' => 'should_not_be_overriden'], 'stateless' => null], - 'put' => ['method' => 'PUT', 'stateless' => null], - 'delete' => ['method' => 'DELETE', 'stateless' => null], - ]); - $resourceMetadata = $resourceMetadata->withAttributes(['route_prefix' => '/foobar-prefix', 'identifiers' => 'id']); - - $routeCollection = $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); - - $this->assertEquals( - $this->getRoute('/foobar-prefix/dummies/{id}.{_format}', 'api_platform.action.get_item', DummyEntity::class, 'api_dummies_get_item', ['GET'], false, ['id' => '\d+'], ['my_default' => 'default_value', '_stateless' => null]), - $routeCollection->get('api_dummies_get_item') - ); - - $this->assertEquals( - $this->getRoute('/foobar-prefix/dummies/{id}.{_format}', 'api_platform.action.delete_item', DummyEntity::class, 'api_dummies_delete_item', ['DELETE']), - $routeCollection->get('api_dummies_delete_item') - ); - - $this->assertEquals( - $this->getRoute('/foobar-prefix/dummies/{id}.{_format}', 'api_platform.action.put_item', DummyEntity::class, 'api_dummies_put_item', ['PUT']), - $routeCollection->get('api_dummies_put_item') - ); - } - - public function testNoMethodApiLoader() - { - $this->expectException(\RuntimeException::class); - - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withShortName('dummy'); - - $resourceMetadata = $resourceMetadata->withItemOperations([ - 'get' => [], - ]); - - $resourceMetadata = $resourceMetadata->withCollectionOperations([ - 'get' => ['method' => 'GET', 'stateless' => null], - ]); - - $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); - } - - public function testWrongMethodApiLoader() - { - $this->expectException(\RuntimeException::class); - - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withShortName('dummy'); - - $resourceMetadata = $resourceMetadata->withItemOperations([ - 'post' => ['method' => 'POST', 'stateless' => null], - ]); - - $resourceMetadata = $resourceMetadata->withCollectionOperations([ - 'get' => ['method' => 'GET', 'stateless' => null], - ]); - - $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); - } - - public function testNoShortNameApiLoader() - { - $this->expectException(InvalidResourceException::class); - - $this->getApiLoaderWithResourceMetadata(new ResourceMetadata())->load(null); - } - - public function testRecursiveSubresource() - { - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withShortName('dummy'); - $resourceMetadata = $resourceMetadata->withItemOperations([ - 'get' => ['method' => 'GET', 'stateless' => null], - 'put' => ['method' => 'PUT', 'stateless' => null], - 'delete' => ['method' => 'DELETE', 'stateless' => null], - ]); - $resourceMetadata = $resourceMetadata->withCollectionOperations([ - 'my_op' => ['method' => 'GET', 'controller' => 'some.service.name', 'stateless' => null], // with controller - 'my_second_op' => ['method' => 'POST', 'stateless' => null], // without controller, takes the default one - 'my_path_op' => ['method' => 'GET', 'path' => 'some/custom/path', 'stateless' => null], // custom path - ]); - - $routeCollection = $this->getApiLoaderWithResourceMetadata($resourceMetadata, true)->load(null); - - $this->assertEquals( - $this->getSubresourceRoute('/dummies/{id}/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'api_dummies_subresources_get_subresource', ['property' => 'subresource', 'identifiers' => ['id' => [DummyEntity::class, 'id', true]], 'collection' => true, 'operationId' => 'api_dummies_subresources_get_subresource']), - $routeCollection->get('api_dummies_subresources_get_subresource') - ); - - $this->assertEquals( - $this->getSubresourceRoute('/related_dummies/{id}/recursivesubresource/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'api_related_dummies_recursivesubresource_subresources_get_subresource', ['property' => 'subresource', 'identifiers' => ['id' => [RelatedDummyEntity::class, 'id', true], 'recursivesubresource' => [DummyEntity::class, 'id', false]], 'collection' => true, 'operationId' => 'api_related_dummies_recursivesubresource_subresources_get_subresource']), - $routeCollection->get('api_related_dummies_recursivesubresource_subresources_get_subresource') - ); - - $this->assertEquals( - $this->getSubresourceRoute('/related_dummies/{id}/recursivesubresource.{_format}', 'dummy_controller', DummyEntity::class, 'api_related_dummies_recursivesubresource_get_subresource', ['property' => 'recursivesubresource', 'identifiers' => ['id' => [RelatedDummyEntity::class, 'id', true]], 'collection' => false, 'operationId' => 'api_related_dummies_recursivesubresource_get_subresource']), - $routeCollection->get('api_related_dummies_recursivesubresource_get_subresource') - ); - - $this->assertEquals( - $this->getSubresourceRoute('/dummies/{id}/subresources/{subresource}/recursivesubresource.{_format}', 'api_platform.action.get_subresource', DummyEntity::class, 'api_dummies_subresources_recursivesubresource_get_subresource', ['property' => 'recursivesubresource', 'identifiers' => ['id' => [DummyEntity::class, 'id', true], 'subresource' => [RelatedDummyEntity::class, 'id', true]], 'collection' => false, 'operationId' => 'api_dummies_subresources_recursivesubresource_get_subresource']), - $routeCollection->get('api_dummies_subresources_recursivesubresource_get_subresource') - ); - - $this->assertEquals( - $this->getSubresourceRoute('/related_dummies/{id}/secondrecursivesubresource/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'api_related_dummies_secondrecursivesubresource_subresources_get_subresource', ['property' => 'subresource', 'identifiers' => ['id' => [RelatedDummyEntity::class, 'id', true], 'secondrecursivesubresource' => [DummyEntity::class, 'id', false]], 'collection' => true, 'operationId' => 'api_related_dummies_secondrecursivesubresource_subresources_get_subresource']), - $routeCollection->get('api_related_dummies_secondrecursivesubresource_subresources_get_subresource') - ); - - $this->assertEquals( - $this->getSubresourceRoute('/related_dummies/{id}/secondrecursivesubresource.{_format}', 'api_platform.action.get_subresource', DummyEntity::class, 'api_related_dummies_secondrecursivesubresource_get_subresource', ['property' => 'secondrecursivesubresource', 'identifiers' => ['id' => [RelatedDummyEntity::class, 'id', true]], 'collection' => false, 'operationId' => 'api_related_dummies_secondrecursivesubresource_get_subresource']), - $routeCollection->get('api_related_dummies_secondrecursivesubresource_get_subresource') - ); - } - - private function getApiLoaderWithResourceMetadata(ResourceMetadata $resourceMetadata, $recursiveSubresource = false): ApiLoader - { - $routingConfig = __DIR__.'/../../../../../src/Symfony/Bundle/Resources/config/routing'; - - $kernelProphecy = $this->prophesize(KernelInterface::class); - $kernelProphecy->locateResource(Argument::any())->willReturn($routingConfig); - $possibleArguments = [ - 'api_platform.action.get_collection', - 'api_platform.action.post_collection', - 'api_platform.action.get_item', - 'api_platform.action.put_item', - 'api_platform.action.delete_item', - 'api_platform.action.get_subresource', - ]; - $containerProphecy = $this->prophesize(ContainerInterface::class); - - foreach ($possibleArguments as $possibleArgument) { - $containerProphecy->has($possibleArgument)->willReturn(true); - } - - $containerProphecy->has(Argument::type('string'))->willReturn(false); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->willReturn($resourceMetadata); - - $relatedDummyEntityMetadata = (new ResourceMetadata())->withShortName('related_dummies')->withSubresourceOperations([ - 'recursivesubresource_get_subresource' => [ - 'controller' => 'dummy_controller', - ], - ]); - - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->willReturn($relatedDummyEntityMetadata); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([DummyEntity::class, RelatedDummyEntity::class])); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->willReturn(new PropertyNameCollection(['id', 'subresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->willReturn(new PropertyNameCollection(['id', 'recursivesubresource', 'secondrecursivesubresource'])); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata()); - - $relatedType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummyEntity::class); - - $subResourcePropertyMetadata = (new PropertyMetadata()) - ->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, true)) - ->withType(new Type(Type::BUILTIN_TYPE_ARRAY, false, \ArrayObject::class, true, null, $relatedType)); - - if (false === $recursiveSubresource) { - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'recursivesubresource', Argument::type('array'))->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'secondrecursivesubresource', Argument::type('array'))->willReturn(new PropertyMetadata()); - } else { - $dummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyEntity::class); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'recursivesubresource', Argument::type('array')) - ->willReturn((new PropertyMetadata()) - ->withSubresource(new SubresourceMetadata(DummyEntity::class, false)) - ->withType($dummyType)); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'secondrecursivesubresource', Argument::type('array')) - ->willReturn((new PropertyMetadata()) - ->withSubresource(new SubresourceMetadata(DummyEntity::class, false)) - ->withType($dummyType)); - } - - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->willReturn($subResourcePropertyMetadata); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - $identifiersExtractor = $identifiersExtractorProphecy->reveal(); - - $subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactory, $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), new UnderscorePathSegmentNameGenerator(), $identifiersExtractor); - - return new ApiLoader($kernelProphecy->reveal(), $resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactory, $operationPathResolver, $containerProphecy->reveal(), ['jsonld' => ['application/ld+json']], [], $subresourceOperationFactory, false, true, true, false, false, $identifiersExtractor); - } - - private function getRoute(string $path, string $controller, string $resourceClass, string $operationName, array $methods, bool $collection = false, array $requirements = [], array $extraDefaults = ['_stateless' => null], array $options = [], string $host = '', array $schemes = [], string $condition = ''): Route - { - $legacyOperationName = str_replace('api_dummies_', '', str_replace($collection ? '_collection' : '_item', '', $operationName)); - - return new Route( - $path, - [ - '_controller' => $controller, - '_format' => $extraDefaults['_format'] ?? null, - '_api_resource_class' => $resourceClass, - '_api_identifiers' => ['id'], - '_api_has_composite_identifier' => false, - sprintf('_api_%s_operation_name', $collection ? 'collection' : 'item') => $legacyOperationName, - '_api_operation_name' => $operationName, - ] + $extraDefaults, - $requirements, - $options, - $host, - $schemes, - $methods, - $condition - ); - } - - private function getSubresourceRoute(string $path, string $controller, string $resourceClass, string $operationName, array $context, array $requirements = [], array $extraDefaults = ['_stateless' => null]): Route - { - return new Route( - $path, - [ - '_controller' => $controller, - '_format' => $extraDefaults['_format'] ?? null, - '_api_resource_class' => $resourceClass, - '_api_subresource_operation_name' => $operationName, - '_api_subresource_context' => $context, - '_api_identifiers' => $context['identifiers'], - '_api_has_composite_identifier' => false, - ] + $extraDefaults, - $requirements, - [], - '', - [], - ['GET'] - ); - } -} diff --git a/tests/Core/Bridge/Symfony/Routing/CachedRouteNameResolverTest.php b/tests/Core/Bridge/Symfony/Routing/CachedRouteNameResolverTest.php deleted file mode 100644 index 1c4b0f59252..00000000000 --- a/tests/Core/Bridge/Symfony/Routing/CachedRouteNameResolverTest.php +++ /dev/null @@ -1,178 +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\Tests\Bridge\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Bridge\Symfony\Routing\CachedRouteNameResolver; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolverInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidArgumentException; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Psr\Cache\CacheException; -use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; - -/** - * @author Teoh Han Hui - */ -class CachedRouteNameResolverTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - - $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - - $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); - - $this->assertInstanceOf(RouteNameResolverInterface::class, $cachedRouteNameResolver); - } - - public function testGetRouteNameForItemRouteWithNoMatchingRoute() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No item route associated with the type "AppBundle\\Entity\\User".'); - - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->willReturn(false)->shouldBeCalled(); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem(Argument::type('string'))->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldNotBeCalled(); - - $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', OperationType::ITEM, []) - ->willThrow(new InvalidArgumentException('No item route associated with the type "AppBundle\Entity\User".')) - ->shouldBeCalled(); - - $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); - $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', OperationType::ITEM); - } - - public function testGetRouteNameForItemRouteOnCacheMiss() - { - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->willReturn(false)->shouldBeCalledTimes(1); - $cacheItemProphecy->set('some_item_route')->shouldBeCalledTimes(1)->willReturn($cacheItemProphecy->reveal()); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem(Argument::type('string'))->shouldBeCalledTimes(1)->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldBeCalledTimes(1)->willReturn(true); - - $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', false, [])->willReturn('some_item_route')->shouldBeCalledTimes(1); - - $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); - - $this->assertSame('some_item_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', false)); - $this->assertSame('some_item_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', false), 'Trigger the local cache'); - } - - public function testGetRouteNameForItemRouteOnCacheHit() - { - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->shouldBeCalledTimes(1)->willReturn(true); - $cacheItemProphecy->get()->shouldBeCalledTimes(1)->willReturn('some_item_route'); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem(Argument::type('string'))->shouldBeCalledTimes(1)->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldNotBeCalled(); - - $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName(Argument::cetera())->shouldNotBeCalled(); - - $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); - - $this->assertSame('some_item_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', OperationType::ITEM)); - $this->assertSame('some_item_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', OperationType::ITEM), 'Trigger the local cache'); - } - - public function testGetRouteNameForCollectionRouteWithNoMatchingRoute() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No collection route associated with the type "AppBundle\\Entity\\User".'); - - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->willReturn(false)->shouldBeCalled(); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem(Argument::type('string'))->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldNotBeCalled(); - - $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', OperationType::COLLECTION, []) - ->willThrow(new InvalidArgumentException('No collection route associated with the type "AppBundle\Entity\User".')) - ->shouldBeCalled(); - - $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); - $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', OperationType::COLLECTION); - } - - public function testGetRouteNameForCollectionRouteOnCacheMiss() - { - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->shouldBeCalledTimes(1)->willReturn(false); - $cacheItemProphecy->set('some_collection_route')->shouldBeCalledTimes(1)->willReturn($cacheItemProphecy->reveal()); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem(Argument::type('string'))->shouldBeCalledTimes(1)->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldBeCalledTimes(1)->willReturn(true); - - $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', true, [])->willReturn('some_collection_route')->shouldBeCalledTimes(1); - - $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); - - $this->assertSame('some_collection_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', true)); - $this->assertSame('some_collection_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', true), 'Trigger the local cache'); - } - - public function testGetRouteNameForCollectionRouteOnCacheHit() - { - $cacheItemProphecy = $this->prophesize(CacheItemInterface::class); - $cacheItemProphecy->isHit()->willReturn(true)->shouldBeCalledTimes(1); - $cacheItemProphecy->get()->willReturn('some_collection_route')->shouldBeCalledTimes(1); - - $cacheItemPoolProphecy = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPoolProphecy->getItem(Argument::type('string'))->shouldBeCalledTimes(1)->willReturn($cacheItemProphecy); - $cacheItemPoolProphecy->save($cacheItemProphecy)->shouldNotBeCalled(); - - $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName(Argument::cetera())->shouldNotBeCalled(); - - $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPoolProphecy->reveal(), $decoratedProphecy->reveal()); - - $this->assertSame('some_collection_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', OperationType::COLLECTION)); - $this->assertSame('some_collection_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', OperationType::COLLECTION), 'Trigger the local cache'); - } - - public function testGetRouteNameWithCacheItemThrowsCacheException() - { - $cacheException = new class() extends \Exception implements CacheException {}; - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem(Argument::type('string'))->shouldBeCalledTimes(1)->willThrow($cacheException); - - $decoratedProphecy = $this->prophesize(RouteNameResolverInterface::class); - $decoratedProphecy->getRouteName('AppBundle\Entity\User', OperationType::ITEM, [])->willReturn('some_item_route')->shouldBeCalledTimes(1); - - $cachedRouteNameResolver = new CachedRouteNameResolver($cacheItemPool->reveal(), $decoratedProphecy->reveal()); - - $this->assertSame('some_item_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', OperationType::ITEM)); - $this->assertSame('some_item_route', $cachedRouteNameResolver->getRouteName('AppBundle\Entity\User', OperationType::ITEM), 'Trigger the local cache'); - } -} diff --git a/tests/Core/Bridge/Symfony/Routing/IriConverterTest.php b/tests/Core/Bridge/Symfony/Routing/IriConverterTest.php deleted file mode 100644 index f215880d50a..00000000000 --- a/tests/Core/Bridge/Symfony/Routing/IriConverterTest.php +++ /dev/null @@ -1,453 +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\Tests\Bridge\Symfony\Routing; - -use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Api\IdentifiersExtractor; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Bridge\Symfony\Routing\IriConverter; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolverInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -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\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Exception\InvalidIdentifierException; -use ApiPlatform\Exception\ItemNotFoundException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\Routing\Exception\RouteNotFoundException; -use Symfony\Component\Routing\RouterInterface; - -/** - * @author Vincent Chalamon - * @group legacy - */ -class IriConverterTest extends TestCase -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - public function testGetItemFromIriNoRouteException() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No route matches "/users/3".'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3')->willThrow(new RouteNotFoundException())->shouldBeCalledTimes(1); - $converter = $this->getIriConverter($routerProphecy); - $converter->getItemFromIri('/users/3'); - } - - public function testGetItemFromIriNoResourceException() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No resource associated to "/users/3".'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3')->willReturn([])->shouldBeCalledTimes(1); - - $converter = $this->getIriConverter($routerProphecy); - $converter->getItemFromIri('/users/3'); - } - - public function testGetItemFromIriCollectionRouteException() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The iri "/users" references a collection not an item.'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users')->willReturn([ - '_api_resource_class' => Dummy::class, - '_api_collection_operation_name' => 'get', - '_api_identifiers' => ['id'], - ])->shouldBeCalledTimes(1); - - $converter = $this->getIriConverter($routerProphecy); - $converter->getItemFromIri('/users'); - } - - public function testGetItemFromIriItemNotFoundException() - { - $this->expectException(ItemNotFoundException::class); - $this->expectExceptionMessage('Item not found for "/users/3".'); - - $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy - ->getItem(Dummy::class, ['id' => 3], 'get', [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]) - ->shouldBeCalled()->willReturn(null); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3')->willReturn([ - '_api_resource_class' => Dummy::class, - '_api_item_operation_name' => 'get', - '_api_identifiers' => ['id'], - 'id' => 3, - ])->shouldBeCalledTimes(1); - - $converter = $this->getIriConverter($routerProphecy, null, $itemDataProviderProphecy); - $converter->getItemFromIri('/users/3'); - } - - public function testGetItemFromIri() - { - $item = new \stdClass(); - $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy->getItem(Dummy::class, ['id' => 3], 'get', ['fetch_data' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true])->shouldBeCalled()->willReturn($item); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3')->willReturn([ - '_api_resource_class' => Dummy::class, - '_api_item_operation_name' => 'get', - '_api_identifiers' => ['id'], - 'id' => 3, - ])->shouldBeCalledTimes(1); - - $converter = $this->getIriConverter($routerProphecy, null, $itemDataProviderProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3', ['fetch_data' => true]), $item); - } - - public function testGetItemFromIriWithOperationName() - { - $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy->getItem('AppBundle\Entity\User', ['id' => 3], 'operation_name', ['fetch_data' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true]) - ->willReturn('foo') - ->shouldBeCalledTimes(1); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3')->willReturn([ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_item_operation_name' => 'operation_name', - '_api_identifiers' => ['id'], - 'id' => 3, - ])->shouldBeCalledTimes(1); - - $converter = $this->getIriConverter($routerProphecy, null, $itemDataProviderProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3', ['fetch_data' => true]), 'foo'); - } - - public function testGetIriFromResourceClass() - { - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::COLLECTION)->willReturn('dummies'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->generate('dummies', [], UrlGeneratorInterface::ABS_PATH)->willReturn('/dummies'); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy); - $this->assertEquals($converter->getIriFromResourceClass(Dummy::class), '/dummies'); - } - - public function testGetIriFromResourceClassAbsoluteUrl() - { - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::COLLECTION)->willReturn('dummies'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->generate('dummies', [], UrlGeneratorInterface::ABS_URL)->willReturn('http://example.com/dummies'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('', '', '', [], [], ['url_generation_strategy' => UrlGeneratorInterface::ABS_URL])); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, null, null, $resourceMetadataFactoryProphecy->reveal()); - $this->assertEquals($converter->getIriFromResourceClass(Dummy::class), 'http://example.com/dummies'); - } - - public function testNotAbleToGenerateGetIriFromResourceClass() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unable to generate an IRI for "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy"'); - - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::COLLECTION)->willReturn('dummies'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->generate('dummies', [], UrlGeneratorInterface::ABS_PATH)->willThrow(new RouteNotFoundException()); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy); - $converter->getIriFromResourceClass(Dummy::class); - } - - /** - * @group legacy - */ - public function testGetSubresourceIriFromResourceClass() - { - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::SUBRESOURCE, Argument::type('array'))->willReturn('api_dummies_related_dummies_get_subresource'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->generate('api_dummies_related_dummies_get_subresource', ['id' => 1], UrlGeneratorInterface::ABS_PATH)->willReturn('/dummies/1/related_dummies'); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy); - $this->assertEquals($converter->getSubresourceIriFromResourceClass(Dummy::class, ['subresource_identifiers' => ['id' => 1], 'subresource_resources' => [RelatedDummy::class => 1]]), '/dummies/1/related_dummies'); - } - - /** - * @group legacy - */ - public function testNotAbleToGenerateGetSubresourceIriFromResourceClass() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unable to generate an IRI for "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy"'); - - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::SUBRESOURCE, Argument::type('array'))->willReturn('dummies'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->generate('dummies', ['id' => 1], UrlGeneratorInterface::ABS_PATH)->willThrow(new RouteNotFoundException()); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy); - $converter->getSubresourceIriFromResourceClass(Dummy::class, ['subresource_identifiers' => ['id' => 1], 'subresource_resources' => [RelatedDummy::class => 1]]); - } - - /** - * @group legacy - */ - public function testGetItemIriFromResourceClass() - { - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::ITEM)->willReturn('api_dummies_get_item'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->generate('api_dummies_get_item', ['id' => 1], UrlGeneratorInterface::ABS_PATH)->willReturn('/dummies/1'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn((new ResourceMetadata())->withAttributes(['composite_identifier' => true])); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, null, null, $resourceMetadataFactoryProphecy->reveal()); - $this->assertEquals($converter->getItemIriFromResourceClass(Dummy::class, ['id' => 1]), '/dummies/1'); - } - - /** - * @group legacy - */ - public function testGetItemIriFromResourceClassAbsoluteUrl() - { - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::ITEM)->willReturn('api_dummies_get_item'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->generate('api_dummies_get_item', ['id' => 1], UrlGeneratorInterface::ABS_URL)->willReturn('http://example.com/dummies/1'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('', '', '', [], [], ['url_generation_strategy' => UrlGeneratorInterface::ABS_URL])); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, null, null, $resourceMetadataFactoryProphecy->reveal()); - $this->assertEquals($converter->getItemIriFromResourceClass(Dummy::class, ['id' => 1]), 'http://example.com/dummies/1'); - } - - /** - * @group legacy - */ - public function testNotAbleToGenerateGetItemIriFromResourceClass() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unable to generate an IRI for "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy"'); - - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::ITEM)->willReturn('dummies'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->generate('dummies', ['id' => 1], UrlGeneratorInterface::ABS_PATH)->willThrow(new RouteNotFoundException()); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn((new ResourceMetadata())->withAttributes(['composite_identifier' => true])); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, null, null, $resourceMetadataFactoryProphecy->reveal()); - $converter->getItemIriFromResourceClass(Dummy::class, ['id' => 1]); - } - - public function testGetItemFromIriWithIdentifierConverter() - { - $item = new \stdClass(); - $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy->getItem(Dummy::class, ['id' => 3], 'get', ['fetch_data' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true])->shouldBeCalled()->willReturn($item); - $identifierConverterProphecy = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverterProphecy->convert(['id' => '3'], Dummy::class)->shouldBeCalled()->willReturn(['id' => 3]); - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3')->willReturn([ - '_api_resource_class' => Dummy::class, - '_api_item_operation_name' => 'get', - '_api_identifiers' => ['id' => [Dummy::class, 'id']], - 'id' => 3, - ])->shouldBeCalledTimes(1); - - $converter = $this->getIriConverter($routerProphecy, null, $itemDataProviderProphecy, null, $identifierConverterProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3', ['fetch_data' => true]), $item); - } - - public function testGetItemFromIriWithSubresourceDataProvider() - { - $item = new \stdClass(); - $subresourceContext = ['identifiers' => ['id' => [Dummy::class, 'id', true]]]; - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3/adresses')->willReturn([ - '_api_resource_class' => Dummy::class, - '_api_subresource_context' => $subresourceContext, - '_api_subresource_operation_name' => 'get_subresource', - '_api_identifiers' => $subresourceContext['identifiers'], - 'id' => 3, - ])->shouldBeCalledTimes(1); - $subresourceDataProviderProphecy = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProviderProphecy->getSubresource(Dummy::class, ['id' => ['id' => 3]], $subresourceContext + ['fetch_data' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true], 'get_subresource')->shouldBeCalled()->willReturn($item); - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, $subresourceDataProviderProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3/adresses', ['fetch_data' => true]), $item); - } - - public function testGetItemFromIriWithSubresourceDataProviderNotFound() - { - $this->expectException(ItemNotFoundException::class); - $this->expectExceptionMessage('Item not found for "/users/3/adresses".'); - - $subresourceContext = ['identifiers' => ['id' => [Dummy::class, 'id', true]]]; - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3/adresses')->willReturn([ - '_api_resource_class' => Dummy::class, - '_api_subresource_context' => $subresourceContext, - '_api_subresource_operation_name' => 'get_subresource', - '_api_identifiers' => $subresourceContext['identifiers'], - 'id' => 3, - ])->shouldBeCalledTimes(1); - $identifierConverterProphecy = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverterProphecy->convert(['id' => '3'], Dummy::class)->shouldBeCalled()->willReturn(['id' => 3]); - $subresourceDataProviderProphecy = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProviderProphecy->getSubresource(Dummy::class, ['id' => ['id' => 3]], $subresourceContext + ['fetch_data' => true, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true], 'get_subresource')->shouldBeCalled()->willReturn(null); - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, $subresourceDataProviderProphecy, $identifierConverterProphecy); - $converter->getItemFromIri('/users/3/adresses', ['fetch_data' => true]); - } - - public function testGetItemFromIriBadIdentifierException() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Item not found for "/users/3".'); - - $item = new \stdClass(); - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->match('/users/3')->willReturn([ - '_api_resource_class' => Dummy::class, - '_api_item_operation_name' => 'get_subresource', - '_api_identifiers' => ['id'], - 'id' => 3, - ])->shouldBeCalledTimes(1); - $identifierConverterProphecy = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverterProphecy->convert(['id' => '3'], Dummy::class)->shouldBeCalled()->willThrow(new InvalidIdentifierException('Item not found for "/users/3".')); - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, null, $identifierConverterProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3', ['fetch_data' => true]), $item); - } - - public function testNoIdentifiersException() - { - $this->markTestSkipped('The method "generateIdentifiersUrl" has been removed.'); - /* @phpstan-ignore-next-line */ - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('No identifiers defined for resource of type "\App\Entity\Sample"'); - - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $routerProphecy = $this->prophesize(RouterInterface::class); - - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy); - - $method = new \ReflectionMethod(IriConverter::class, 'generateIdentifiersUrl'); - $method->setAccessible(true); - $method->invoke($converter, [], '\App\Entity\Sample'); - } - - /** - * @group legacy - */ - public function testLegacyConstructor() - { - $this->expectDeprecation('Not injecting ApiPlatform\Core\Api\ResourceClassResolverInterface in the IdentifiersExtractor might introduce cache issues with object identifiers.'); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $routerProphecy = $this->prophesize(RouterInterface::class); - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - - new IriConverter( - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $itemDataProviderProphecy->reveal(), - $routeNameResolverProphecy->reveal(), - $routerProphecy->reveal(), - null - ); - } - - private function getResourceClassResolver() - { - $resourceClassResolver = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolver->isResourceClass(Argument::type('string'))->will(function ($args) { - return true; - }); - - $resourceClassResolver->getResourceClass(Argument::cetera())->will(function ($args) { - return \get_class($args[0]); - }); - - return $resourceClassResolver->reveal(); - } - - private function getIriConverter($routerProphecy = null, $routeNameResolverProphecy = null, $itemDataProviderProphecy = null, $subresourceDataProviderProphecy = null, $identifierConverterProphecy = null, $resourceMetadataFactory = null) - { - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - - if (!$routerProphecy) { - $routerProphecy = $this->prophesize(RouterInterface::class); - } - - if (!$routeNameResolverProphecy) { - $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); - } - - $itemDataProvider = $itemDataProviderProphecy ?: $this->prophesize(ItemDataProviderInterface::class); - - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - if (null === $identifierConverterProphecy) { - $identifierConverterProphecy = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverterProphecy->convert(Argument::type('array'), Argument::type('string'))->will(function ($args) { - return $args[0]; - }); - } - - return new IriConverter( - $propertyNameCollectionFactory, - $propertyMetadataFactory, - $itemDataProvider->reveal(), - $routeNameResolverProphecy->reveal(), - $routerProphecy->reveal(), - null, - new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()), - $subresourceDataProviderProphecy ? $subresourceDataProviderProphecy->reveal() : null, - $identifierConverterProphecy->reveal(), - null, - $resourceMetadataFactory - ); - } -} diff --git a/tests/Core/Bridge/Symfony/Routing/RouteNameGeneratorTest.php b/tests/Core/Bridge/Symfony/Routing/RouteNameGeneratorTest.php deleted file mode 100644 index 0d3663e7669..00000000000 --- a/tests/Core/Bridge/Symfony/Routing/RouteNameGeneratorTest.php +++ /dev/null @@ -1,48 +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\Tests\Bridge\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameGenerator; -use ApiPlatform\Exception\InvalidArgumentException; -use PHPUnit\Framework\TestCase; - -/** - * @author Baptiste Meyer - */ -class RouteNameGeneratorTest extends TestCase -{ - public function testGenerate() - { - $this->assertEquals('api_foos_get_collection', RouteNameGenerator::generate('get', 'Foo', OperationType::COLLECTION)); - $this->assertEquals('api_bars_custom_operation_item', RouteNameGenerator::generate('custom_operation', 'Bar', OperationType::ITEM)); - } - - /** - * @group legacy - * @expectedDeprecation Using a boolean for the Operation Type is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3 - */ - public function testLegacyGenerate() - { - $this->assertEquals('api_foos_get_collection', RouteNameGenerator::generate('get', 'Foo', true)); - } - - public function testGenerateWithSubresource() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Subresource operations are not supported by the RouteNameGenerator.'); - - $this->assertEquals('api_foos_bar_get_subresource', RouteNameGenerator::generate('get', 'Foo', OperationType::SUBRESOURCE)); - } -} diff --git a/tests/Core/Bridge/Symfony/Routing/RouteNameResolverTest.php b/tests/Core/Bridge/Symfony/Routing/RouteNameResolverTest.php deleted file mode 100644 index 7f7ba96d33b..00000000000 --- a/tests/Core/Bridge/Symfony/Routing/RouteNameResolverTest.php +++ /dev/null @@ -1,195 +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\Tests\Bridge\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolver; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolverInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Routing\Route; -use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\RouterInterface; - -/** - * @author Teoh Han Hui - */ -class RouteNameResolverTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - $routerProphecy = $this->prophesize(RouterInterface::class); - - $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - - $this->assertInstanceOf(RouteNameResolverInterface::class, $routeNameResolver); - } - - public function testGetRouteNameForItemRouteWithNoMatchingRoute() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('No item route associated with the type "AppBundle\\Entity\\User".'); - - $routeCollection = new RouteCollection(); - $routeCollection->add('some_collection_route', new Route('/some/collection/path', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_collection_operation_name' => 'some_collection_op', - ])); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - $routeNameResolver->getRouteName('AppBundle\Entity\User', OperationType::ITEM); - } - - /** - * @group legacy - * @expectedDeprecation Using a boolean for the Operation Type is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3 - */ - public function testGetRouteNameForItemRouteLegacy() - { - $routeCollection = new RouteCollection(); - $routeCollection->add('some_collection_route', new Route('/some/collection/path', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_collection_operation_name' => 'some_collection_op', - ])); - $routeCollection->add('some_item_route', new Route('/some/item/path/{id}', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_item_operation_name' => 'some_item_op', - ])); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - $actual = $routeNameResolver->getRouteName('AppBundle\Entity\User', false); - - $this->assertSame('some_item_route', $actual); - } - - public function testGetRouteNameForItemRoute() - { - $routeCollection = new RouteCollection(); - $routeCollection->add('some_collection_route', new Route('/some/collection/path', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_collection_operation_name' => 'some_collection_op', - ])); - $routeCollection->add('some_item_route', new Route('/some/item/path/{id}', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_item_operation_name' => 'some_item_op', - ])); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - $actual = $routeNameResolver->getRouteName('AppBundle\Entity\User', OperationType::ITEM); - - $this->assertSame('some_item_route', $actual); - } - - public function testGetRouteNameForCollectionRouteWithNoMatchingRoute() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('No collection route associated with the type "AppBundle\\Entity\\User".'); - - $routeCollection = new RouteCollection(); - $routeCollection->add('some_item_route', new Route('/some/item/path/{id}', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_item_operation_name' => 'some_item_op', - ])); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - $routeNameResolver->getRouteName('AppBundle\Entity\User', OperationType::COLLECTION); - } - - /** - * @group legacy - * @expectedDeprecation Using a boolean for the Operation Type is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3 - */ - public function testGetRouteNameForCollectionRouteLegacy() - { - $routeCollection = new RouteCollection(); - $routeCollection->add('some_item_route', new Route('/some/item/path/{id}', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_item_operation_name' => 'some_item_op', - ])); - $routeCollection->add('some_collection_route', new Route('/some/collection/path', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_collection_operation_name' => 'some_collection_op', - ])); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - $actual = $routeNameResolver->getRouteName('AppBundle\Entity\User', true); - - $this->assertSame('some_collection_route', $actual); - } - - public function testGetRouteNameForCollectionRoute() - { - $routeCollection = new RouteCollection(); - $routeCollection->add('some_item_route', new Route('/some/item/path/{id}', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_item_operation_name' => 'some_item_op', - ])); - $routeCollection->add('some_collection_route', new Route('/some/collection/path', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_collection_operation_name' => 'some_collection_op', - ])); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - $actual = $routeNameResolver->getRouteName('AppBundle\Entity\User', OperationType::COLLECTION); - - $this->assertSame('some_collection_route', $actual); - } - - public function testGetRouteNameForSubresourceRoute() - { - $routeCollection = new RouteCollection(); - $routeCollection->add('a_some_subresource_route', new Route('/a/some/item/path/{id}', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_subresource_operation_name' => 'some_other_item_op', - '_api_subresource_context' => ['identifiers' => ['id' => ['Bar', 'id']]], - ])); - $routeCollection->add('b_some_subresource_route', new Route('/b/some/item/path/{id}', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_subresource_operation_name' => 'some_item_op', - '_api_subresource_context' => ['identifiers' => ['id' => ['Foo', 'id']]], - ])); - $routeCollection->add('some_collection_route', new Route('/some/collection/path', [ - '_api_resource_class' => 'AppBundle\Entity\User', - '_api_collection_operation_name' => 'some_collection_op', - ])); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $routeNameResolver = new RouteNameResolver($routerProphecy->reveal()); - $actual = $routeNameResolver->getRouteName('AppBundle\Entity\User', OperationType::SUBRESOURCE, ['subresource_resources' => ['Foo' => 1]]); - - $this->assertSame('b_some_subresource_route', $actual); - } -} diff --git a/tests/Core/Bridge/Symfony/Routing/RouterOperationPathResolverTest.php b/tests/Core/Bridge/Symfony/Routing/RouterOperationPathResolverTest.php deleted file mode 100644 index c8c72e99afc..00000000000 --- a/tests/Core/Bridge/Symfony/Routing/RouterOperationPathResolverTest.php +++ /dev/null @@ -1,98 +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\Tests\Bridge\Symfony\Routing; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameGenerator; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouterOperationPathResolver; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\PathResolver\OperationPathResolverInterface; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Routing\Route; -use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\RouterInterface; - -/** - * @author Baptiste Meyer - */ -class RouterOperationPathResolverTest extends TestCase -{ - use ProphecyTrait; - - public function testResolveOperationPath() - { - $routeCollection = new RouteCollection(); - $routeCollection->add('foos', new Route('/foos')); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection)->shouldBeCalled(); - - $operationPathResolver = new RouterOperationPathResolver($routerProphecy->reveal(), $this->prophesize(OperationPathResolverInterface::class)->reveal()); - - $this->assertEquals('/foos', $operationPathResolver->resolveOperationPath('Foo', ['route_name' => 'foos'], OperationType::COLLECTION, 'get')); - } - - public function testResolveOperationPathWithSubresource() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Subresource operations are not supported by the RouterOperationPathResolver without a route name.'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - - $operationPathResolver = new RouterOperationPathResolver($routerProphecy->reveal(), $this->prophesize(OperationPathResolverInterface::class)->reveal()); - - $operationPathResolver->resolveOperationPath('Foo', ['property' => 'bar', 'collection' => true, 'resource_class' => 'Foo'], OperationType::SUBRESOURCE, 'get'); - } - - public function testResolveOperationPathWithRouteNameGeneration() - { - $routeCollection = new RouteCollection(); - $routeCollection->add(RouteNameGenerator::generate('get', 'Foo', OperationType::COLLECTION), new Route('/foos')); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection)->shouldBeCalled(); - - $operationPathResolver = new RouterOperationPathResolver($routerProphecy->reveal(), $this->prophesize(OperationPathResolverInterface::class)->reveal()); - - $this->assertEquals('/foos', $operationPathResolver->resolveOperationPath('Foo', [], OperationType::COLLECTION, 'get')); - } - - public function testResolveOperationPathWithRouteNotFound() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The route "api_foos_get_collection" of the resource "Foo" was not found.'); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn(new RouteCollection())->shouldBeCalled(); - - $operationPathResolver = new RouterOperationPathResolver($routerProphecy->reveal(), $this->prophesize(OperationPathResolverInterface::class)->reveal()); - $operationPathResolver->resolveOperationPath('Foo', [], OperationType::COLLECTION, 'get'); - } - - /** - * @group legacy - * @expectedDeprecation Method ApiPlatform\Core\Bridge\Symfony\Routing\RouterOperationPathResolver::resolveOperationPath() will have a 4th `string $operationName` argument in version 3.0. Not defining it is deprecated since 2.1. - * @expectedDeprecation Using a boolean for the Operation Type is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3 - */ - public function testLegacyResolveOperationPath() - { - $operationPathResolverProphecy = $this->prophesize(OperationPathResolverInterface::class); - $operationPathResolverProphecy->resolveOperationPath('Foo', [], OperationType::ITEM, null)->willReturn('/foos/{id}.{_format}')->shouldBeCalled(); - - $operationPathResolver = new RouterOperationPathResolver($this->prophesize(RouterInterface::class)->reveal(), $operationPathResolverProphecy->reveal()); - - $this->assertEquals('/foos/{id}.{_format}', $operationPathResolver->resolveOperationPath('Foo', [], false)); - } -} diff --git a/tests/Core/Bridge/Symfony/Validator/EventListener/ValidateListenerTest.php b/tests/Core/Bridge/Symfony/Validator/EventListener/ValidateListenerTest.php deleted file mode 100644 index 6fd2b4f3073..00000000000 --- a/tests/Core/Bridge/Symfony/Validator/EventListener/ValidateListenerTest.php +++ /dev/null @@ -1,212 +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\Tests\Bridge\Symfony\Validator\EventListener; - -use ApiPlatform\Core\Bridge\Symfony\Validator\EventListener\ValidateListener; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\DummyEntity; -use ApiPlatform\Validator\Exception\ValidationException; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Psr\Container\ContainerInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Event\ViewEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\Validator\ConstraintViolationListInterface; -use Symfony\Component\Validator\Validator\ValidatorInterface; - -/** - * @author Samuel ROZE - * - * @group legacy - */ -class ValidateListenerTest extends TestCase -{ - use ProphecyTrait; - - public function testNotAnApiPlatformRequest() - { - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); - $validator = $validatorProphecy->reveal(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $request = new Request(); - $request->setMethod('POST'); - - $listener = new ValidateListener($validator, $resourceMetadataFactory); - - $event = new ViewEvent($this->prophesize(HttpKernelInterface::class)->reveal(), $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, []); - $listener->onKernelView($event); - } - - public function testValidatorIsCalled() - { - $data = new DummyEntity(); - $expectedValidationGroups = ['a', 'b', 'c']; - - $constraintViolationList = $this->prophesize(ConstraintViolationListInterface::class); - $constraintViolationList->count()->willReturn(0); - - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, null, $expectedValidationGroups)->willReturn($constraintViolationList)->shouldBeCalled(); - $validator = $validatorProphecy->reveal(); - - $containerProphecy = $this->prophesize(ContainerInterface::class); - $containerProphecy->has(Argument::any())->shouldNotBeCalled(); - - [$resourceMetadataFactory, $event] = $this->createEventObject($expectedValidationGroups, $data); - - $validationViewListener = new ValidateListener($validator, $resourceMetadataFactory, $containerProphecy->reveal()); - $validationViewListener->onKernelView($event); - } - - public function testGetGroupsFromCallable() - { - $data = new DummyEntity(); - $expectedValidationGroups = ['a', 'b', 'c']; - - $constraintViolationList = $this->prophesize(ConstraintViolationListInterface::class); - $constraintViolationList->count()->willReturn(0); - - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, null, $expectedValidationGroups)->willReturn($constraintViolationList)->shouldBeCalled(); - $validator = $validatorProphecy->reveal(); - - $closure = function ($data) use ($expectedValidationGroups): array { - return $data instanceof DummyEntity ? $expectedValidationGroups : []; - }; - - [$resourceMetadataFactory, $event] = $this->createEventObject($closure, $data); - - $validationViewListener = new ValidateListener($validator, $resourceMetadataFactory); - $validationViewListener->onKernelView($event); - } - - public function testGetGroupsFromService() - { - $data = new DummyEntity(); - - $constraintViolationList = $this->prophesize(ConstraintViolationListInterface::class); - $constraintViolationList->count()->willReturn(0); - - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, null, ['a', 'b', 'c'])->willReturn($constraintViolationList)->shouldBeCalled(); - $validator = $validatorProphecy->reveal(); - - [$resourceMetadataFactory, $event] = $this->createEventObject('groups_builder', $data); - - $containerProphecy = $this->prophesize(ContainerInterface::class); - $containerProphecy->has('groups_builder')->willReturn(true)->shouldBeCalled(); - $containerProphecy->get('groups_builder')->willReturn(new class() { - public function __invoke($data): array - { - return $data instanceof DummyEntity ? ['a', 'b', 'c'] : []; - } - } - )->shouldBeCalled(); - - $validationViewListener = new ValidateListener($validator, $resourceMetadataFactory, $containerProphecy->reveal()); - $validationViewListener->onKernelView($event); - } - - public function testValidatorWithScalarGroup() - { - $data = new DummyEntity(); - $expectedValidationGroups = ['foo']; - - $constraintViolationList = $this->prophesize(ConstraintViolationListInterface::class); - $constraintViolationList->count()->willReturn(0); - - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, null, $expectedValidationGroups)->willreturn($constraintViolationList)->shouldBeCalled(); - - $containerProphecy = $this->prophesize(ContainerInterface::class); - $containerProphecy->has('foo')->willReturn(false)->shouldBeCalled(); - - [$resourceMetadataFactory, $event] = $this->createEventObject('foo', $data); - - $validationViewListener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactory, $containerProphecy->reveal()); - $validationViewListener->onKernelView($event); - } - - public function testDoNotCallWhenReceiveFlagIsFalse() - { - $data = new DummyEntity(); - $expectedValidationGroups = ['a', 'b', 'c']; - - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, null, $expectedValidationGroups)->shouldNotBeCalled(); - $validator = $validatorProphecy->reveal(); - - [$resourceMetadataFactory, $event] = $this->createEventObject($expectedValidationGroups, $data, false); - - $validationViewListener = new ValidateListener($validator, $resourceMetadataFactory); - $validationViewListener->onKernelView($event); - } - - public function testThrowsValidationExceptionWithViolationsFound() - { - $this->expectException(ValidationException::class); - - $data = new DummyEntity(); - $expectedValidationGroups = ['a', 'b', 'c']; - - $violationsProphecy = $this->prophesize(ConstraintViolationListInterface::class); - $violationsProphecy->rewind()->shouldBeCalled(); - $violationsProphecy->valid()->shouldBeCalled(); - $violationsProphecy->count()->willReturn(1)->shouldBeCalled(); - $violations = $violationsProphecy->reveal(); - - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, null, $expectedValidationGroups)->willReturn($violations)->shouldBeCalled(); - $validator = $validatorProphecy->reveal(); - - [$resourceMetadataFactory, $event] = $this->createEventObject($expectedValidationGroups, $data); - - $validationViewListener = new ValidateListener($validator, $resourceMetadataFactory); - $validationViewListener->onKernelView($event); - } - - private function createEventObject($expectedValidationGroups, $data, bool $receive = true): array - { - $resourceMetadata = new ResourceMetadata(null, null, null, [ - 'create' => ['validation_groups' => $expectedValidationGroups], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - if ($receive) { - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->willReturn($resourceMetadata)->shouldBeCalled(); - } - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $kernel = $this->prophesize(HttpKernelInterface::class)->reveal(); - $request = new Request([], [], [ - '_api_resource_class' => DummyEntity::class, - '_api_item_operation_name' => 'create', - '_api_format' => 'json', - '_api_mime_type' => 'application/json', - '_api_receive' => $receive, - ]); - - $request->setMethod('POST'); - $event = new ViewEvent($kernel, $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, $data); - - return [$resourceMetadataFactory, $event]; - } -} diff --git a/tests/Core/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php b/tests/Core/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php deleted file mode 100644 index b261e167ff3..00000000000 --- a/tests/Core/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php +++ /dev/null @@ -1,398 +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\Tests\Bridge\Symfony\Validator\Metadata\Property; - -use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaFormat; -use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaLengthRestriction; -use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaOneOfRestriction; -use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRegexRestriction; -use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\ValidatorPropertyMetadataFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\DummyAtLeastOneOfValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyIriWithValidationEntity; -use ApiPlatform\Tests\Fixtures\DummySequentiallyValidatedEntity; -use ApiPlatform\Tests\Fixtures\DummyValidatedEntity; -use Doctrine\Common\Annotations\AnnotationReader; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Intl\Countries; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Validator\Constraints\AtLeastOneOf; -use Symfony\Component\Validator\Constraints\Sequentially; -use Symfony\Component\Validator\Mapping\ClassMetadata; -use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; -use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; - -/** - * @author Baptiste Meyer - */ -class ValidatorPropertyMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - private $validatorClassMetadata; - - protected function setUp(): void - { - $this->validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class); - (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($this->validatorClassMetadata); - } - - public function testCreateWithPropertyWithRequiredConstraints() - { - $propertyMetadata = new PropertyMetadata(null, 'A dummy', true, true, null, null, null, false); - $expectedPropertyMetadata = $propertyMetadata->withRequired(true); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummy', [])->willReturn($propertyMetadata)->shouldBeCalled(); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled(); - - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [] - ); - $resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummy'); - - $this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata); - } - - public function testCreateWithPropertyWithNotRequiredConstraints() - { - $propertyMetadata = new PropertyMetadata(null, 'A dummy date', true, true, null, null, null, false); - $expectedPropertyMetadata = $propertyMetadata->withRequired(false); - $expectedPropertyMetadata = $expectedPropertyMetadata->withIri('http://schema.org/Date'); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyDate', [])->willReturn($propertyMetadata)->shouldBeCalled(); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled(); - - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [] - ); - $resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyDate'); - - $this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata); - } - - public function testCreateWithPropertyWithoutConstraints() - { - $propertyMetadata = new PropertyMetadata(null, 'A dummy id', true, true, null, null, null, true); - $expectedPropertyMetadata = $propertyMetadata->withRequired(false); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyId', [])->willReturn($propertyMetadata)->shouldBeCalled(); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled(); - - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [] - ); - $resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyId'); - - $this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata); - } - - public function testCreateWithPropertyWithRightValidationGroupsAndRequiredConstraints() - { - $propertyMetadata = new PropertyMetadata(null, 'A dummy group', true, true, null, null, null, false); - $expectedPropertyMetadata = $propertyMetadata->withRequired(true); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => ['dummy']])->willReturn($propertyMetadata)->shouldBeCalled(); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled(); - - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [] - ); - $resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => ['dummy']]); - - $this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata); - } - - public function testCreateWithPropertyWithBadValidationGroupsAndRequiredConstraints() - { - $propertyMetadata = new PropertyMetadata(null, 'A dummy group', true, true, null, null, null, false); - $expectedPropertyMetadata = $propertyMetadata->withRequired(false); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => ['ymmud']])->willReturn($propertyMetadata)->shouldBeCalled(); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled(); - - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [] - ); - $resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => ['ymmud']]); - - $this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata); - } - - public function testCreateWithPropertyWithNonStringValidationGroupsAndRequiredConstraints() - { - $propertyMetadata = new PropertyMetadata(null, 'A dummy group', true, true, null, null, null, false); - $expectedPropertyMetadata = $propertyMetadata->withRequired(false); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => [1312]])->willReturn($propertyMetadata)->shouldBeCalled(); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled(); - - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [] - ); - $resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => [1312]]); - - $this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata); - } - - public function testCreateWithRequiredByDecorated() - { - $propertyMetadata = new PropertyMetadata(null, 'A dummy date', true, true, null, null, true, false, 'foo:bar'); - $expectedPropertyMetadata = clone $propertyMetadata; - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyDate', [])->willReturn($propertyMetadata)->shouldBeCalled(); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled(); - - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [] - ); - $resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyDate'); - - $this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata); - } - - public function testCreateWithPropertyWithValidationConstraints() - { - if (!class_exists(Countries::class)) { - $this->markTestSkipped('symfony/intl not installed'); - } - - $validatorClassMetadata = new ClassMetadata(DummyIriWithValidationEntity::class); - (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata); - - $types = [ - 'dummyUrl' => 'http://schema.org/url', - 'dummyEmail' => 'http://schema.org/email', - 'dummyUuid' => 'http://schema.org/identifier', - 'dummyCardScheme' => 'http://schema.org/identifier', - 'dummyBic' => 'http://schema.org/identifier', - 'dummyIban' => 'http://schema.org/identifier', - 'dummyDate' => 'http://schema.org/Date', - 'dummyDateTime' => 'http://schema.org/DateTime', - 'dummyTime' => 'http://schema.org/Time', - 'dummyImage' => 'http://schema.org/image', - 'dummyFile' => 'http://schema.org/MediaObject', - 'dummyCurrency' => 'http://schema.org/priceCurrency', - 'dummyIsbn' => 'http://schema.org/isbn', - 'dummyIssn' => 'http://schema.org/issn', - ]; - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - foreach ($types as $property => $iri) { - $decoratedPropertyMetadataFactory->create(DummyIriWithValidationEntity::class, $property, [])->willReturn(new PropertyMetadata())->shouldBeCalled(); - } - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyIriWithValidationEntity::class)->willReturn($validatorClassMetadata)->shouldBeCalled(); - - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [] - ); - - foreach ($types as $property => $iri) { - $resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyIriWithValidationEntity::class, $property); - $this->assertSame($iri, $resultedPropertyMetadata->getIri()); - } - } - - public function testCreateWithPropertyLengthRestriction(): void - { - $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class); - (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class) - ->willReturn($validatorClassMetadata) - ->shouldBeCalled(); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $property = 'dummy'; - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, $property, [])->willReturn( - new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)) - )->shouldBeCalled(); - - $lengthRestrictions = new PropertySchemaLengthRestriction(); - $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal(), [$lengthRestrictions] - ); - - $schema = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, $property)->getSchema(); - $this->assertNotNull($schema); - $this->assertArrayHasKey('minLength', $schema); - $this->assertArrayHasKey('maxLength', $schema); - } - - public function testCreateWithPropertyRegexRestriction(): void - { - $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class); - (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class) - ->willReturn($validatorClassMetadata) - ->shouldBeCalled(); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummy', [])->willReturn( - new PropertyMetadata() - )->shouldBeCalled(); - - $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal(), - [new PropertySchemaRegexRestriction()] - ); - - $schema = $validationPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummy')->getSchema(); - $this->assertNotNull($schema); - $this->assertArrayHasKey('pattern', $schema); - $this->assertEquals('^(dummy)$', $schema['pattern']); - } - - public function testCreateWithPropertyFormatRestriction(): void - { - $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class); - (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class) - ->willReturn($validatorClassMetadata) - ->shouldBeCalled(); - $formats = [ - 'dummyEmail' => 'email', - 'dummyUuid' => 'uuid', - 'dummyIpv4' => 'ipv4', - 'dummyIpv6' => 'ipv6', - ]; - - foreach ($formats as $property => $format) { - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, $property, [])->willReturn( - new PropertyMetadata() - )->shouldBeCalled(); - $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [new PropertySchemaFormat()] - ); - $schema = $validationPropertyMetadataFactory->create(DummyValidatedEntity::class, $property)->getSchema(); - $this->assertNotNull($schema); - $this->assertArrayHasKey('format', $schema); - $this->assertEquals($format, $schema['format']); - } - } - - public function testCreateWithSequentiallyConstraint(): void - { - if (!class_exists(Sequentially::class)) { - $this->markTestSkipped(); - } - - $validatorClassMetadata = new ClassMetadata(DummySequentiallyValidatedEntity::class); - (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummySequentiallyValidatedEntity::class) - ->willReturn($validatorClassMetadata) - ->shouldBeCalled(); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummySequentiallyValidatedEntity::class, 'dummy', [])->willReturn( - new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)) - )->shouldBeCalled(); - $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [new PropertySchemaLengthRestriction(), new PropertySchemaRegexRestriction()] - ); - $schema = $validationPropertyMetadataFactory->create(DummySequentiallyValidatedEntity::class, 'dummy')->getSchema(); - - $this->assertNotNull($schema); - $this->assertArrayHasKey('minLength', $schema); - $this->assertArrayHasKey('maxLength', $schema); - $this->assertArrayHasKey('pattern', $schema); - } - - public function testCreateWithAtLeastOneOfConstraint(): void - { - if (!class_exists(AtLeastOneOf::class)) { - $this->markTestSkipped(); - } - - $validatorClassMetadata = new ClassMetadata(DummyAtLeastOneOfValidatedEntity::class); - (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata); - - $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyAtLeastOneOfValidatedEntity::class) - ->willReturn($validatorClassMetadata) - ->shouldBeCalled(); - - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyAtLeastOneOfValidatedEntity::class, 'dummy', [])->willReturn( - new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)) - )->shouldBeCalled(); - $restrictionsMetadata = [new PropertySchemaLengthRestriction(), new PropertySchemaRegexRestriction()]; - $restrictionsMetadata = [new PropertySchemaOneOfRestriction($restrictionsMetadata), new PropertySchemaLengthRestriction(), new PropertySchemaRegexRestriction()]; - $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - $restrictionsMetadata - ); - $schema = $validationPropertyMetadataFactory->create(DummyAtLeastOneOfValidatedEntity::class, 'dummy')->getSchema(); - - $this->assertNotNull($schema); - $this->assertArrayHasKey('oneOf', $schema); - $this->assertSame([ - ['pattern' => '^(.*#.*)$'], - ['minLength' => 10], - ], $schema['oneOf']); - } -} diff --git a/tests/Core/DataPersister/ChainDataPersisterTest.php b/tests/Core/DataPersister/ChainDataPersisterTest.php deleted file mode 100644 index 4248a3f6feb..00000000000 --- a/tests/Core/DataPersister/ChainDataPersisterTest.php +++ /dev/null @@ -1,122 +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\Tests\DataPersister; - -use ApiPlatform\Core\DataPersister\ChainDataPersister; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\DataPersister\ResumableDataPersisterInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; - -/** - * @author Baptiste Meyer - */ -class ChainDataPersisterTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - $this->assertInstanceOf(DataPersisterInterface::class, new ChainDataPersister([$this->prophesize(DataPersisterInterface::class)->reveal()])); - } - - public function testSupports() - { - $dummy = new Dummy(); - - $persisterProphecy = $this->prophesize(DataPersisterInterface::class); - $persisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - - $this->assertTrue((new ChainDataPersister([$persisterProphecy->reveal()]))->supports($dummy)); - } - - public function testDoesNotSupport() - { - $dummy = new Dummy(); - - $persisterProphecy = $this->prophesize(DataPersisterInterface::class); - $persisterProphecy->supports($dummy, Argument::type('array'))->willReturn(false)->shouldBeCalled(); - - $this->assertFalse((new ChainDataPersister([$persisterProphecy->reveal()]))->supports($dummy)); - } - - public function testPersist() - { - $dummy = new Dummy(); - - $fooPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $fooPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(false)->shouldBeCalled(); - $fooPersisterProphecy->persist($dummy, Argument::type('array'))->shouldNotBeCalled(); - - $barPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $barPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $barPersisterProphecy->persist($dummy, Argument::type('array'))->shouldBeCalled(); - - $foobarPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $foobarPersisterProphecy->supports($dummy, Argument::type('array'))->shouldNotBeCalled(); - $foobarPersisterProphecy->persist($dummy, Argument::type('array'))->shouldNotBeCalled(); - - (new ChainDataPersister([$fooPersisterProphecy->reveal(), $barPersisterProphecy->reveal(), $foobarPersisterProphecy->reveal()]))->persist($dummy); - } - - public function testRemove() - { - $dummy = new Dummy(); - - $fooPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $fooPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(false)->shouldBeCalled(); - $fooPersisterProphecy->remove($dummy, Argument::type('array'))->shouldNotBeCalled(); - - $barPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $barPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $barPersisterProphecy->remove($dummy, Argument::type('array'))->shouldBeCalled(); - - $foobarPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $foobarPersisterProphecy->supports($dummy, Argument::type('array'))->shouldNotBeCalled(); - $foobarPersisterProphecy->remove($dummy, Argument::type('array'))->shouldNotBeCalled(); - - (new ChainDataPersister([$fooPersisterProphecy->reveal(), $barPersisterProphecy->reveal(), $foobarPersisterProphecy->reveal()]))->remove($dummy); - } - - public function testResumable() - { - $dummy = new Dummy(); - $fooPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $fooPersisterProphecy->willImplement(ResumableDataPersisterInterface::class); - $fooPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $fooPersisterProphecy->persist($dummy, Argument::type('array'))->willReturn($dummy)->shouldBeCalled(); - $fooPersisterProphecy->remove($dummy, Argument::type('array'))->shouldBeCalled(); - $fooPersisterProphecy->resumable(Argument::type('array'))->willReturn(true)->shouldBeCalled(); - - $foo2PersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $foo2PersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(false)->shouldBeCalled(); - $foo2PersisterProphecy->persist($dummy, Argument::type('array'))->shouldNotBeCalled(); - $foo2PersisterProphecy->remove($dummy, Argument::type('array'))->shouldNotBeCalled(); - - $barPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $barPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $barPersisterProphecy->persist($dummy, Argument::type('array'))->willReturn($dummy)->shouldBeCalled(); - $barPersisterProphecy->remove($dummy, Argument::type('array'))->shouldBeCalled(); - - $foobarPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $foobarPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(false)->shouldNotBeCalled(); - $foobarPersisterProphecy->persist($dummy, Argument::type('array'))->shouldNotBeCalled(); - $foobarPersisterProphecy->remove($dummy, Argument::type('array'))->shouldNotBeCalled(); - - (new ChainDataPersister([$fooPersisterProphecy->reveal(), $foo2PersisterProphecy->reveal(), $barPersisterProphecy->reveal(), $foobarPersisterProphecy->reveal()]))->persist($dummy); - (new ChainDataPersister([$fooPersisterProphecy->reveal(), $foo2PersisterProphecy->reveal(), $barPersisterProphecy->reveal(), $foobarPersisterProphecy->reveal()]))->remove($dummy); - } -} diff --git a/tests/Core/DataProvider/ChainCollectionDataProviderTest.php b/tests/Core/DataProvider/ChainCollectionDataProviderTest.php deleted file mode 100644 index 4788181528a..00000000000 --- a/tests/Core/DataProvider/ChainCollectionDataProviderTest.php +++ /dev/null @@ -1,126 +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\Tests\DataProvider; - -use ApiPlatform\Core\DataProvider\ChainCollectionDataProvider; -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; - -/** - * Retrieves items from a persistence layer. - * - * @author Kévin Dunglas - */ -class ChainCollectionDataProviderTest extends TestCase -{ - use ProphecyTrait; - - public function testGetCollection() - { - $dummy = new Dummy(); - $dummy->setName('Rosa'); - $dummy2 = new Dummy(); - $dummy2->setName('Parks'); - - $firstDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $firstDataProvider->willImplement(RestrictedDataProviderInterface::class); - $firstDataProvider->supports(Dummy::class, null, [])->willReturn(false); - - $secondDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $secondDataProvider->willImplement(RestrictedDataProviderInterface::class); - $secondDataProvider->supports(Dummy::class, null, [])->willReturn(true); - $secondDataProvider->getCollection(Dummy::class, null, []) - ->willReturn([$dummy, $dummy2]); - - $thirdDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $thirdDataProvider->willImplement(RestrictedDataProviderInterface::class); - $thirdDataProvider->supports(Dummy::class, null, [])->willReturn(true); - $thirdDataProvider->getCollection(Dummy::class, null, [])->willReturn([$dummy]); - - $chainItemDataProvider = new ChainCollectionDataProvider([ - $firstDataProvider->reveal(), - $secondDataProvider->reveal(), - $thirdDataProvider->reveal(), - ]); - - $this->assertEquals( - [$dummy, $dummy2], - $chainItemDataProvider->getCollection(Dummy::class) - ); - } - - public function testGetCollectionNotSupported() - { - $firstDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $firstDataProvider->willImplement(RestrictedDataProviderInterface::class); - $firstDataProvider->supports('notfound', 'op', [])->willReturn(false); - - $collection = (new ChainCollectionDataProvider([$firstDataProvider->reveal()]))->getCollection('notfound', 'op'); - - $this->assertTrue(is_iterable($collection)); - $this->assertEmpty($collection); - } - - /** - * @group legacy - * @expectedDeprecation Throwing a "ApiPlatform\Exception\ResourceClassNotSupportedException" in a data provider is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface" - */ - public function testLegacyGetCollection() - { - $dummy = new Dummy(); - $dummy->setName('Rosa'); - $dummy2 = new Dummy(); - $dummy2->setName('Parks'); - - $firstDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $firstDataProvider->getCollection(Dummy::class, null, [])->willThrow(ResourceClassNotSupportedException::class); - - $secondDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $secondDataProvider->getCollection(Dummy::class, null, [])->willReturn([$dummy, $dummy2]); - - $thirdDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $thirdDataProvider->getCollection(Dummy::class, null, [])->willReturn([$dummy]); - - $chainItemDataProvider = new ChainCollectionDataProvider([$firstDataProvider->reveal(), $secondDataProvider->reveal(), $thirdDataProvider->reveal()]); - - $this->assertEquals([$dummy, $dummy2], $chainItemDataProvider->getCollection(Dummy::class)); - } - - /** - * @group legacy - * @expectedDeprecation Throwing a "ApiPlatform\Exception\ResourceClassNotSupportedException" in a data provider is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface" - */ - public function testLegacyGetCollectionExceptions() - { - $firstDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $firstDataProvider->getCollection('notfound', 'op', [])->willThrow(ResourceClassNotSupportedException::class); - - $collection = (new ChainCollectionDataProvider([$firstDataProvider->reveal()]))->getCollection('notfound', 'op'); - - $this->assertTrue(is_iterable($collection)); - $this->assertEmpty($collection); - } - - public function testGetCollectionWithEmptyDataProviders() - { - $collection = (new ChainCollectionDataProvider([]))->getCollection(Dummy::class); - - $this->assertTrue(is_iterable($collection)); - $this->assertEmpty($collection); - } -} diff --git a/tests/Core/DataProvider/ChainItemDataProviderTest.php b/tests/Core/DataProvider/ChainItemDataProviderTest.php deleted file mode 100644 index a28aad91635..00000000000 --- a/tests/Core/DataProvider/ChainItemDataProviderTest.php +++ /dev/null @@ -1,158 +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\Tests\DataProvider; - -use ApiPlatform\Core\DataProvider\ChainItemDataProvider; -use ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositePrimitiveItem; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; - -/** - * Retrieves items from a persistence layer. - * - * @author Kévin Dunglas - */ -class ChainItemDataProviderTest extends TestCase -{ - use ProphecyTrait; - - public function testGetItem() - { - $dummy = new Dummy(); - $dummy->setName('Lucie'); - - $firstDataProvider = $this->prophesize(DenormalizedIdentifiersAwareItemDataProviderInterface::class); - $firstDataProvider->willImplement(RestrictedDataProviderInterface::class); - $firstDataProvider->supports(Dummy::class, null, [])->willReturn(false); - - $secondDataProvider = $this->prophesize(DenormalizedIdentifiersAwareItemDataProviderInterface::class); - $secondDataProvider->willImplement(RestrictedDataProviderInterface::class); - $secondDataProvider->supports(Dummy::class, null, [])->willReturn(true); - $secondDataProvider->getItem(Dummy::class, ['id' => 1], null, [])->willReturn($dummy); - - $thirdDataProvider = $this->prophesize(DenormalizedIdentifiersAwareItemDataProviderInterface::class); - $thirdDataProvider->willImplement(RestrictedDataProviderInterface::class); - $thirdDataProvider->supports(Dummy::class, null, [])->willReturn(true); - $thirdDataProvider->getItem(Dummy::class, ['id' => 1], null, [])->willReturn(new \stdClass()); - - $chainItemDataProvider = new ChainItemDataProvider([ - $firstDataProvider->reveal(), - $secondDataProvider->reveal(), - $thirdDataProvider->reveal(), - ]); - - $this->assertEquals($dummy, $chainItemDataProvider->getItem(Dummy::class, ['id' => 1])); - } - - public function testGetItemWithoutDenormalizedIdentifiers() - { - $dummy = new Dummy(); - $dummy->setName('Lucie'); - - $firstDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $firstDataProvider->willImplement(RestrictedDataProviderInterface::class); - $firstDataProvider->supports(Dummy::class, null, [])->willReturn(false); - - $secondDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $secondDataProvider->willImplement(RestrictedDataProviderInterface::class); - $secondDataProvider->supports(Dummy::class, null, [])->willReturn(true); - $secondDataProvider->getItem(Dummy::class, '1', null, [])->willReturn($dummy); - - $thirdDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $thirdDataProvider->willImplement(RestrictedDataProviderInterface::class); - $thirdDataProvider->supports(Dummy::class, null, [])->willReturn(true); - $thirdDataProvider->getItem(Dummy::class, 1, null, [])->willReturn(new \stdClass()); - - $chainItemDataProvider = new ChainItemDataProvider([ - $firstDataProvider->reveal(), - $secondDataProvider->reveal(), - $thirdDataProvider->reveal(), - ]); - - $this->assertEquals($dummy, $chainItemDataProvider->getItem(Dummy::class, ['id' => 1])); - } - - public function testGetItemExceptions() - { - $firstDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $firstDataProvider->willImplement(RestrictedDataProviderInterface::class); - $firstDataProvider->supports('notfound', null, [])->willReturn(false); - - $chainItemDataProvider = new ChainItemDataProvider([$firstDataProvider->reveal()]); - - $this->assertEquals('', $chainItemDataProvider->getItem('notfound', 1)); - } - - /** - * @group legacy - * @expectedDeprecation Throwing a "ApiPlatform\Exception\ResourceClassNotSupportedException" is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface" - */ - public function testLegacyGetItem() - { - $dummy = new Dummy(); - $dummy->setName('Lucie'); - - $firstDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $firstDataProvider->getItem(Dummy::class, 1, null, [])->willThrow(ResourceClassNotSupportedException::class); - - $secondDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $secondDataProvider->getItem(Dummy::class, 1, null, [])->willReturn($dummy); - - $thirdDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $thirdDataProvider->getItem(Dummy::class, 1, null, [])->willReturn(new \stdClass()); - - $chainItemDataProvider = new ChainItemDataProvider([$firstDataProvider->reveal(), $secondDataProvider->reveal(), $thirdDataProvider->reveal()]); - - $chainItemDataProvider->getItem(Dummy::class, 1); - } - - /** - * @group legacy - * @expectedDeprecation Receiving "$id" as non-array in an item data provider is deprecated in 2.3 in favor of implementing "ApiPlatform\Core\DataProvider\DenormalizedIdentifiersAwareItemDataProviderInterface". - */ - public function testLegacyGetItemWithoutDenormalizedIdentifiersAndCompositeIdentifier() - { - $dummy = new CompositePrimitiveItem('Lucie', 1984); - - $dataProvider = $this->prophesize(ItemDataProviderInterface::class); - $dataProvider->willImplement(RestrictedDataProviderInterface::class); - $dataProvider->supports(CompositePrimitiveItem::class, null, [])->willReturn(true); - $dataProvider->getItem(CompositePrimitiveItem::class, 'name=Lucie;year=1984', null, [])->willReturn($dummy); - - $chainItemDataProvider = new ChainItemDataProvider([ - $dataProvider->reveal(), - ]); - - $this->assertEquals($dummy, $chainItemDataProvider->getItem(CompositePrimitiveItem::class, ['name' => 'Lucie', 'year' => 1984])); - } - - /** - * @group legacy - * @expectedDeprecation Throwing a "ApiPlatform\Exception\ResourceClassNotSupportedException" is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface" - */ - public function testLegacyGetItemExceptions() - { - $firstDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $firstDataProvider->getItem('notfound', 1, null, [])->willThrow(ResourceClassNotSupportedException::class); - - $chainItemDataProvider = new ChainItemDataProvider([$firstDataProvider->reveal()]); - - $this->assertEquals('', $chainItemDataProvider->getItem('notfound', 1)); - } -} diff --git a/tests/Core/DataProvider/ChainSubresourcedataProviderTest.php b/tests/Core/DataProvider/ChainSubresourcedataProviderTest.php deleted file mode 100644 index c451d62194a..00000000000 --- a/tests/Core/DataProvider/ChainSubresourcedataProviderTest.php +++ /dev/null @@ -1,122 +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\Tests\DataProvider; - -use ApiPlatform\Core\DataProvider\ChainSubresourceDataProvider; -use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\ResourceClassNotSupportedException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; - -/** - * Retrieves items from a persistence layer. - */ -class ChainSubresourcedataProviderTest extends TestCase -{ - use ProphecyTrait; - - public function testGetSubresource() - { - $dummy = new Dummy(); - $dummy->setName('Rosa'); - $dummy2 = new Dummy(); - $dummy2->setName('Parks'); - - $context = ['identifiers' => ['id' => Dummy::class], 'property' => 'relatedDummies']; - $firstDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $firstDataProvider->willImplement(RestrictedDataProviderInterface::class); - $firstDataProvider->supports(Dummy::class, 'get', $context)->willReturn(false); - $firstDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')->willReturn([$dummy, $dummy2])->willThrow(ResourceClassNotSupportedException::class); - - $secondDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $secondDataProvider->willImplement(RestrictedDataProviderInterface::class); - $secondDataProvider->supports(Dummy::class, 'get', $context)->willReturn(true); - $secondDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')->willReturn([$dummy, $dummy2]); - - $thirdDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $thirdDataProvider->willImplement(RestrictedDataProviderInterface::class); - $thirdDataProvider->supports(Dummy::class, 'get', $context)->willReturn(true); - $thirdDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')->willReturn([$dummy]); - - $chainSubresourceDataProvider = new ChainSubresourceDataProvider([$firstDataProvider->reveal(), $secondDataProvider->reveal(), $thirdDataProvider->reveal()]); - - $this->assertEquals([$dummy, $dummy2], $chainSubresourceDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')); - } - - public function testGetSubresourceExceptionsItem() - { - $context = ['collection' => false]; - $firstDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $firstDataProvider->willImplement(RestrictedDataProviderInterface::class); - $firstDataProvider->supports(Dummy::class, 'get', $context)->willReturn(false); - - $chainSubresourceDataProvider = new ChainSubresourceDataProvider([$firstDataProvider->reveal()]); - - $this->assertNull($chainSubresourceDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')); - } - - public function testGetSubresourceExceptionsCollection() - { - $context = ['collection' => true]; - $firstDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $firstDataProvider->willImplement(RestrictedDataProviderInterface::class); - $firstDataProvider->supports(Dummy::class, 'get', $context)->willReturn(false); - - $chainSubresourceDataProvider = new ChainSubresourceDataProvider([$firstDataProvider->reveal()]); - - $this->assertEquals([], $chainSubresourceDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')); - } - - /** - * @group legacy - * @expectedDeprecation Throwing a "ApiPlatform\Exception\ResourceClassNotSupportedException" in a data provider is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface" - */ - public function testLegacyGetSubresource() - { - $dummy = new Dummy(); - $dummy->setName('Rosa'); - $dummy2 = new Dummy(); - $dummy2->setName('Parks'); - - $context = ['identifiers' => ['id' => Dummy::class], 'property' => 'relatedDummies']; - $firstDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $firstDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')->willReturn([$dummy, $dummy2])->willThrow(ResourceClassNotSupportedException::class); - - $secondDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $secondDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')->willReturn([$dummy, $dummy2]); - - $thirdDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $thirdDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')->willReturn([$dummy]); - - $chainSubresourceDataProvider = new ChainSubresourceDataProvider([$firstDataProvider->reveal(), $secondDataProvider->reveal(), $thirdDataProvider->reveal()]); - - $this->assertEquals([$dummy, $dummy2], $chainSubresourceDataProvider->getSubresource(Dummy::class, ['id' => 1], $context, 'get')); - } - - /** - * @group legacy - * @expectedDeprecation Throwing a "ApiPlatform\Exception\ResourceClassNotSupportedException" in a data provider is deprecated in favor of implementing "ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface" - */ - public function testLegacyGetCollectionExceptions() - { - $firstDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $firstDataProvider->getSubresource('notfound', ['id' => 1], [], 'get')->willThrow(ResourceClassNotSupportedException::class); - - $chainItemDataProvider = new ChainSubresourceDataProvider([$firstDataProvider->reveal()]); - - $this->assertEquals('', $chainItemDataProvider->getSubresource('notfound', ['id' => 1], [], 'get')); - } -} diff --git a/tests/Core/EventListener/ReadListenerTest.php b/tests/Core/EventListener/ReadListenerTest.php deleted file mode 100644 index 08b9596545f..00000000000 --- a/tests/Core/EventListener/ReadListenerTest.php +++ /dev/null @@ -1,409 +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\Tests\EventListener; - -use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; -use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\EventListener\ReadListener; -use ApiPlatform\Core\Identifier\IdentifierConverterInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidIdentifierException; -use ApiPlatform\Exception\RuntimeException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -/** - * @author Kévin Dunglas - * @group legacy - */ -class ReadListenerTest extends TestCase -{ - use ProphecyTrait; - - public function testNotAnApiPlatformRequest() - { - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn(new Request())->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - /** - * @group legacy - */ - public function testLegacyConstructor() - { - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn(new Request())->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - public function testDoNotReadWhenReceiveFlagIsFalse() - { - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection(Argument::cetera())->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem(Argument::cetera())->shouldNotBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource(Argument::cetera())->shouldNotBeCalled(); - - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - - $request = new Request([], [], ['id' => 1, 'data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'put', '_api_receive' => false]); - $request->setMethod('PUT'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - public function testDoNotReadWhenDisabledInOperationAttribute() - { - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection(Argument::cetera())->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem(Argument::cetera())->shouldNotBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource(Argument::cetera())->shouldNotBeCalled(); - - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - - $resourceMetadata = new ResourceMetadata('Dummy', null, null, [ - 'put' => [ - 'read' => false, - ], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($resourceMetadata); - - $request = new Request([], [], ['id' => 1, 'data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'put']); - $request->setMethod('PUT'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal(), $resourceMetadataFactoryProphecy->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - public function testRetrieveCollectionPost() - { - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'post', '_api_format' => 'json', '_api_mime_type' => 'application/json'], [], [], [], '{}'); - $request->setMethod('POST'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - - $this->assertFalse($request->attributes->has('data')); - $this->assertFalse($request->attributes->has('previous_data')); - } - - public function testRetrieveCollectionGet() - { - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection('Foo', 'get', ['filters' => ['foo' => 'bar']])->willReturn([])->shouldBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_collection_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json'], [], [], ['QUERY_STRING' => 'foo=bar']); - $request->setMethod('GET'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - - $this->assertSame([], $request->attributes->get('data')); - $this->assertFalse($request->attributes->has('previous_data')); - } - - public function testRetrieveItem() - { - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverter->convert(['id' => '1'], 'Foo')->shouldBeCalled()->willReturn(['id' => '1']); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $data = new \stdClass(); - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem('Foo', ['id' => '1'], 'get', [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true])->willReturn($data)->shouldBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); - - $request = new Request([], [], ['id' => '1', '_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); - $request->setMethod('GET'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - - $this->assertSame($data, $request->attributes->get('data')); - $this->assertEquals($data, $request->attributes->get('previous_data')); - } - - public function testRetrieveItemNoIdentifier() - { - $this->expectException(NotFoundHttpException::class); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); - $request->setMethod('GET'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); - $listener->onKernelRequest($event->reveal()); - - $request->attributes->get('data'); - } - - public function testRetrieveSubresource() - { - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverter->convert(['id' => '1'], 'Foo')->shouldBeCalled()->willReturn(['id' => '1']); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $data = [new \stdClass()]; - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource('Foo', ['id' => ['id' => '1']], ['identifiers' => [['id', 'Bar', true, ['id']]], 'property' => 'bar', IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true], 'get')->willReturn($data)->shouldBeCalled(); - - $request = new Request([], [], ['id' => '1', '_api_resource_class' => 'Foo', '_api_subresource_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json', '_api_subresource_context' => ['identifiers' => [['id', 'Bar', true, ['id']]], 'property' => 'bar']]); - $request->setMethod('GET'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - - $this->assertSame($data, $request->attributes->get('data')); - $this->assertSame($data, $request->attributes->get('previous_data')); - } - - public function testRetrieveSubresourceNoDataProvider() - { - $this->expectException(RuntimeException::class); - - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverter->convert(['id' => '1'], 'Foo')->shouldBeCalled()->willReturn(['id' => '1']); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $request = new Request([], [], ['id' => '1', '_api_resource_class' => 'Foo', '_api_subresource_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json', '_api_subresource_context' => ['identifiers' => [['id', 'Bar', true, ['id']]], 'property' => 'bar']]); - $request->setMethod('GET'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), null, null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - - $request->attributes->get('data'); - } - - public function testRetrieveSubresourceNotFound() - { - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverter->convert(['id' => '1'], 'Foo')->willThrow(new InvalidIdentifierException())->shouldBeCalled(); - $this->expectException(NotFoundHttpException::class); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $request = new Request([], [], ['id' => '1', '_api_resource_class' => 'Foo', '_api_subresource_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json', '_api_subresource_context' => ['identifiers' => [['id', 'Bar', true, ['id']]], 'property' => 'bar']]); - $request->setMethod('GET'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $this->prophesize(SubresourceDataProviderInterface::class)->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - public function testRetrieveItemNotFound() - { - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverter->convert(['id' => '22'], 'Foo')->shouldBeCalled()->willReturn(['id' => 22]); - $this->expectException(NotFoundHttpException::class); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem('Foo', ['id' => 22], 'get', [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true])->willReturn(null)->shouldBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - - $request = new Request([], [], ['id' => '22', '_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); - $request->setMethod('GET'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - public function testRetrieveBadItemNormalizedIdentifiers() - { - $this->expectException(NotFoundHttpException::class); - - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverter->convert(['id' => '1'], 'Foo')->shouldBeCalled()->willThrow(new InvalidIdentifierException()); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - - $request = new Request([], [], ['id' => '1', '_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); - $request->setMethod(Request::METHOD_GET); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - public function testRetrieveBadSubresourceNormalizedIdentifiers() - { - $this->expectException(NotFoundHttpException::class); - - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverter->convert(Argument::type('array'), Argument::type('string'))->shouldBeCalled()->willThrow(new InvalidIdentifierException()); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - $collectionDataProvider->getCollection()->shouldNotBeCalled(); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem()->shouldNotBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); - - $request = new Request([], [], ['id' => '1', '_api_resource_class' => 'Foo', '_api_subresource_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json', '_api_subresource_context' => ['identifiers' => [['id', 'Bar', true, ['id']]], 'property' => 'bar']]); - $request->setMethod(Request::METHOD_GET); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - public function testRetrieveItemWithCompositeIdentifiersCallsIdentifierConverter() - { - $identifierConverter = $this->prophesize(IdentifierConverterInterface::class); - $identifierConverter->convert(['foo' => '22', 'bar' => 'test'], 'DummyWithCompositeIdentifier')->shouldBeCalled()->willReturn(['foo' => 22, 'bar' => 'test']); - $this->expectException(NotFoundHttpException::class); - - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); - - $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem('DummyWithCompositeIdentifier', ['foo' => 22, 'bar' => 'test'], 'get', [IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true])->willReturn(null)->shouldBeCalled(); - - $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - - $request = new Request([], [], ['id' => 'foo=22;bar=test', '_api_resource_class' => 'DummyWithCompositeIdentifier', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json', '_api_identifiers' => ['foo', 'bar'], '_api_has_composite_identifier' => true]); - $request->setMethod('GET'); - - $event = $this->prophesize(RequestEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierConverter->reveal()); - $listener->onKernelRequest($event->reveal()); - } -} diff --git a/tests/Core/EventListener/WriteListenerTest.php b/tests/Core/EventListener/WriteListenerTest.php deleted file mode 100644 index 94743b941a8..00000000000 --- a/tests/Core/EventListener/WriteListenerTest.php +++ /dev/null @@ -1,400 +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\Tests\EventListener; - -use ApiPlatform\Api\IriConverterInterface; -use ApiPlatform\Core\DataPersister\DataPersisterInterface; -use ApiPlatform\Core\EventListener\WriteListener; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ConcreteDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\ViewEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * @author Baptiste Meyer - * @group legacy - */ -class WriteListenerTest extends TestCase -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - public function testOnKernelViewWithControllerResultAndPersist() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->willReturn($dummy)->shouldBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy)->willReturn('/dummy/1')->shouldBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => Dummy::class]); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) { - $request->setMethod($httpMethod); - $request->attributes->set(sprintf('_api_%s_operation_name', 'POST' === $httpMethod ? 'collection' : 'item'), strtolower($httpMethod)); - - (new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event); - $this->assertSame($dummy, $event->getControllerResult()); - $this->assertEquals('/dummy/1', $request->attributes->get('_api_write_item_iri')); - } - } - - /** - * @group legacy - * @expectedDeprecation Not returning an object from ApiPlatform\Core\DataPersister\DataPersisterInterface::persist() is deprecated since API Platform 2.3 and will not be supported in API Platform 3. - */ - public function testOnKernelViewWithControllerResultAndPersistReturningVoid() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->shouldBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy)->willReturn('/dummy/1'); - - $request = new Request([], [], ['_api_resource_class' => Dummy::class]); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) { - $request->setMethod($httpMethod); - $request->attributes->set(sprintf('_api_%s_operation_name', 'POST' === $httpMethod ? 'collection' : 'item'), strtolower($httpMethod)); - - (new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event); - $this->assertSame($dummy, $event->getControllerResult()); - } - } - - /** - * @see https://github.com/api-platform/core/issues/1799 - * @see https://github.com/api-platform/core/issues/2692 - */ - public function testOnKernelViewWithControllerResultAndPersistWithImmutableResource() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dummy2 = new Dummy(); - $dummy2->setId(2); - $dummy2->setName('Dummyferoce'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true); - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->willReturn($dummy2); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy2)->willReturn('/dummy/2'); - - $writeListener = new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()); - - $request = new Request([], [], ['_api_resource_class' => Dummy::class]); - - foreach (['PATCH', 'PUT', 'POST'] as $httpMethod) { - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - $request->setMethod($httpMethod); - $request->attributes->set(sprintf('_api_%s_operation_name', 'POST' === $httpMethod ? 'collection' : 'item'), strtolower($httpMethod)); - - $writeListener->onKernelView($event); - - $this->assertSame($dummy2, $event->getControllerResult()); - $this->assertEquals('/dummy/2', $request->attributes->get('_api_write_item_iri')); - } - } - - public function testOnKernelViewDoNotCallIriConverterWhenOutputClassDisabled() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy)->shouldNotBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['output' => ['class' => null]])); - - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->willReturn($dummy)->shouldBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post']); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - (new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()))->onKernelView($event); - } - - public function testOnKernelViewWithControllerResultAndRemove() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $dataPersisterProphecy->remove($dummy, Argument::type('array'))->shouldBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy)->shouldNotBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'delete']); - $request->setMethod('DELETE'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - (new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event); - } - - public function testOnKernelViewWithSafeMethod() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->shouldNotBeCalled(); - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->shouldNotBeCalled(); - $dataPersisterProphecy->remove($dummy, Argument::type('array'))->shouldNotBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy)->shouldNotBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => Dummy::class, '_api_item_operation_name' => 'head']); - $request->setMethod('HEAD'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - (new WriteListener($dataPersisterProphecy->reveal()))->onKernelView($event); - } - - public function testDoNotWriteWhenControllerResultIsResponse() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports(Argument::cetera())->shouldNotBeCalled(); - $dataPersisterProphecy->persist(Argument::cetera())->shouldNotBeCalled(); - $dataPersisterProphecy->remove(Argument::cetera())->shouldNotBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - - $request = new Request(); - - $response = new Response(); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $response - ); - - $listener = new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()); - $listener->onKernelView($event); - } - - public function testDoNotWriteWhenPersistFlagIsFalse() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports(Argument::cetera())->shouldNotBeCalled(); - $dataPersisterProphecy->persist(Argument::cetera())->shouldNotBeCalled(); - $dataPersisterProphecy->remove(Argument::cetera())->shouldNotBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - - $request = new Request([], [], ['data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post', '_api_persist' => false]); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - $listener = new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()); - $listener->onKernelView($event); - } - - public function testDoNotWriteWhenDisabledInOperationAttribute() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports(Argument::cetera())->shouldNotBeCalled(); - $dataPersisterProphecy->persist(Argument::cetera())->shouldNotBeCalled(); - $dataPersisterProphecy->remove(Argument::cetera())->shouldNotBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - - $resourceMetadata = new ResourceMetadata('Dummy', null, null, [], [ - 'post' => [ - 'write' => false, - ], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($resourceMetadata); - - $request = new Request([], [], ['data' => new Dummy(), '_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'post']); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - $listener = new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); - $listener->onKernelView($event); - } - - public function testOnKernelViewWithNoResourceClass() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->shouldNotBeCalled(); - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->shouldNotBeCalled(); - $dataPersisterProphecy->remove($dummy, Argument::type('array'))->shouldNotBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy)->shouldNotBeCalled(); - - $request = new Request(); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - (new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event); - } - - public function testOnKernelViewWithParentResourceClass() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new ConcreteDummy(); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(true)->shouldBeCalled(); - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->willReturn($dummy)->shouldBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy)->willReturn('/dummy/1')->shouldBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => ConcreteDummy::class, '_api_item_operation_name' => 'put', '_api_persist' => true]); - $request->setMethod('PUT'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - (new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event); - } - - public function testOnKernelViewWithNoDataPersisterSupport() - { - $this->expectDeprecation('Since api-platform/core 2.7: The listener "ApiPlatform\Core\EventListener\WriteListener" is deprecated and will be removed in 3.0, use "ApiPlatform\Symfony\EventListener\WriteListener" instead'); - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $dataPersisterProphecy = $this->prophesize(DataPersisterInterface::class); - $dataPersisterProphecy->supports($dummy, Argument::type('array'))->willReturn(false)->shouldBeCalled(); - $dataPersisterProphecy->persist($dummy, Argument::type('array'))->shouldNotBeCalled(); - $dataPersisterProphecy->remove($dummy, Argument::type('array'))->shouldNotBeCalled(); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy)->shouldNotBeCalled(); - - $request = new Request([], [], ['_api_resource_class' => 'Dummy', '_api_collection_operation_name' => 'post']); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - (new WriteListener($dataPersisterProphecy->reveal(), $iriConverterProphecy->reveal()))->onKernelView($event); - } -} diff --git a/tests/Core/Identifier/IdentifierConverterTest.php b/tests/Core/Identifier/IdentifierConverterTest.php deleted file mode 100644 index c2a39234f6d..00000000000 --- a/tests/Core/Identifier/IdentifierConverterTest.php +++ /dev/null @@ -1,147 +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\Tests\Identifier; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Identifier\IdentifierConverter; -use ApiPlatform\Core\Identifier\Normalizer\DateTimeIdentifierDenormalizer; -use ApiPlatform\Core\Identifier\Normalizer\IntegerDenormalizer; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -/** - * @author Antoine Bluchet - */ -class IdentifierConverterTest extends TestCase -{ - use ProphecyTrait; - - public function testCompositeIdentifier() - { - $identifiers = [ - 'a' => '1', - 'c' => '2', - 'd' => '2015-04-05', - ]; - - $class = 'Dummy'; - - $integerPropertyMetadata = (new PropertyMetadata())->withIdentifier(true)->withType(new Type(Type::BUILTIN_TYPE_INT)); - $identifierPropertyMetadata = (new PropertyMetadata())->withIdentifier(true); - $dateIdentifierPropertyMetadata = (new PropertyMetadata())->withIdentifier(true)->withType(new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTime::class)); - - $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory->create($class, 'a')->shouldBeCalled()->willReturn($integerPropertyMetadata); - $propertyMetadataFactory->create($class, 'c')->shouldBeCalled()->willReturn($identifierPropertyMetadata); - $propertyMetadataFactory->create($class, 'd')->shouldBeCalled()->willReturn($dateIdentifierPropertyMetadata); - - $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractor->getIdentifiersFromResourceClass($class)->willReturn(['a', 'c', 'd']); - - $identifierDenormalizers = [new IntegerDenormalizer(), new DateTimeIdentifierDenormalizer()]; - - $identifierDenormalizer = new IdentifierConverter($identifiersExtractor->reveal(), $propertyMetadataFactory->reveal(), $identifierDenormalizers); - - $result = $identifierDenormalizer->convert($identifiers, $class); - $this->assertEquals(['a' => 1, 'c' => '2', 'd' => new \DateTime('2015-04-05')], $result); - $this->assertSame(1, $result['a']); - } - - public function testSingleDateIdentifier() - { - $identifier = ['funkyid' => '2015-04-05']; - $class = 'Dummy'; - - $dateIdentifierPropertyMetadata = (new PropertyMetadata())->withIdentifier(true)->withType(new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTime::class)); - - $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory->create($class, 'funkyid')->shouldBeCalled()->willReturn($dateIdentifierPropertyMetadata); - - $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractor->getIdentifiersFromResourceClass($class)->willReturn(['funkyid']); - - $identifierDenormalizers = [new DateTimeIdentifierDenormalizer()]; - $identifierDenormalizer = new IdentifierConverter($identifiersExtractor->reveal(), $propertyMetadataFactory->reveal(), $identifierDenormalizers); - - $this->assertEquals($identifierDenormalizer->convert($identifier, $class), ['funkyid' => new \DateTime('2015-04-05')]); - } - - public function testIntegerIdentifier() - { - $identifier = ['id' => '42']; - $class = 'Dummy'; - - $integerIdentifierPropertyMetadata = (new PropertyMetadata())->withIdentifier(true)->withType(new Type(Type::BUILTIN_TYPE_INT)); - - $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory->create($class, 'id')->shouldBeCalled()->willReturn($integerIdentifierPropertyMetadata); - - $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractor->getIdentifiersFromResourceClass($class)->willReturn(['id']); - - $identifierDenormalizers = [new IntegerDenormalizer()]; - $identifierDenormalizer = new IdentifierConverter($identifiersExtractor->reveal(), $propertyMetadataFactory->reveal(), $identifierDenormalizers); - - $this->assertSame(['id' => 42], $identifierDenormalizer->convert($identifier, $class)); - } - - public function testShouldBreakAfterTransforming() - { - $identifier = ['id' => '42']; - $class = 'Dummy'; - - $integerIdentifierPropertyMetadata = (new PropertyMetadata())->withIdentifier(true)->withType(new Type(Type::BUILTIN_TYPE_INT)); - - $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory->create($class, 'id')->shouldBeCalled()->willReturn($integerIdentifierPropertyMetadata); - - $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractor->getIdentifiersFromResourceClass($class)->willReturn(['id']); - - $shouldNotBeCalled = $this->prophesize(DenormalizerInterface::class); - $shouldNotBeCalled->supportsDenormalization()->shouldNotBeCalled(); - - $identifierDenormalizers = [new IntegerDenormalizer(), $shouldNotBeCalled->reveal()]; - $identifierDenormalizer = new IdentifierConverter($identifiersExtractor->reveal(), $propertyMetadataFactory->reveal(), $identifierDenormalizers); - - $this->assertSame(['id' => 42], $identifierDenormalizer->convert($identifier, $class)); - } - - public function testWithContextAndMultipleIdentifiers() - { - $identifier = ['id' => '42', 'book' => '21']; - - $integerIdentifierPropertyMetadata = (new PropertyMetadata())->withIdentifier(true)->withType(new Type(Type::BUILTIN_TYPE_INT)); - - $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory->create('Author', 'id')->shouldBeCalled()->willReturn($integerIdentifierPropertyMetadata); - $propertyMetadataFactory->create('Book', 'id')->shouldBeCalled()->willReturn($integerIdentifierPropertyMetadata); - - $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractor->getIdentifiersFromResourceClass('Book')->willReturn(['id']); - $identifiersExtractor->getIdentifiersFromResourceClass('Author')->willReturn(['id']); - - $shouldNotBeCalled = $this->prophesize(DenormalizerInterface::class); - $shouldNotBeCalled->supportsDenormalization()->shouldNotBeCalled(); - - $identifierDenormalizers = [new IntegerDenormalizer(), $shouldNotBeCalled->reveal()]; - $identifierDenormalizer = new IdentifierConverter($identifiersExtractor->reveal(), $propertyMetadataFactory->reveal(), $identifierDenormalizers); - - $this->assertSame(['id' => 42, 'book' => 21], $identifierDenormalizer->convert($identifier, 'Book', ['identifiers' => ['id' => ['Author', 'id'], 'book' => ['Book', 'id']]])); - } -} diff --git a/tests/Core/Identifier/Normalizer/DateTimeIdentifierNormalizerTest.php b/tests/Core/Identifier/Normalizer/DateTimeIdentifierNormalizerTest.php deleted file mode 100644 index ccd386d93ad..00000000000 --- a/tests/Core/Identifier/Normalizer/DateTimeIdentifierNormalizerTest.php +++ /dev/null @@ -1,36 +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\Tests\Identifier\Normalizer; - -use ApiPlatform\Core\Identifier\Normalizer\DateTimeIdentifierDenormalizer; -use PHPUnit\Framework\TestCase; - -/** - * @author Kévin Dunglas - */ -class DateTimeIdentifierNormalizerTest extends TestCase -{ - public function testDenormalize() - { - $this->expectException(\ApiPlatform\Exception\InvalidIdentifierException::class); - - $normalizer = new DateTimeIdentifierDenormalizer(); - $normalizer->denormalize('not valid', \DateTimeImmutable::class); - } - - public function testHasCacheableSupportsMethod() - { - $this->assertTrue((new DateTimeIdentifierDenormalizer())->hasCacheableSupportsMethod()); - } -} diff --git a/tests/Core/Identifier/Normalizer/IntegerDenormalizerTest.php b/tests/Core/Identifier/Normalizer/IntegerDenormalizerTest.php deleted file mode 100644 index a900c24f7ca..00000000000 --- a/tests/Core/Identifier/Normalizer/IntegerDenormalizerTest.php +++ /dev/null @@ -1,37 +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\Tests\Identifier\Normalizer; - -use ApiPlatform\Core\Identifier\Normalizer\IntegerDenormalizer; -use PHPUnit\Framework\TestCase; - -/** - * @author Kévin Dunglas - */ -class IntegerDenormalizerTest extends TestCase -{ - public function testDenormalize() - { - $this->assertSame(2, (new IntegerDenormalizer())->denormalize('2', 'int')); - } - - public function testSupportsDenormalization() - { - $normalizer = new IntegerDenormalizer(); - $this->assertTrue($normalizer->supportsDenormalization('1', 'int')); - $this->assertFalse($normalizer->supportsDenormalization([], 'int')); - $this->assertFalse($normalizer->supportsDenormalization('1', 'foo')); - $this->assertTrue($normalizer->hasCacheableSupportsMethod()); - } -} diff --git a/tests/Core/Metadata/Extractor/ExtractorTestCase.php b/tests/Core/Metadata/Extractor/ExtractorTestCase.php deleted file mode 100644 index 61d95c152cd..00000000000 --- a/tests/Core/Metadata/Extractor/ExtractorTestCase.php +++ /dev/null @@ -1,614 +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\Tests\Metadata\Extractor; - -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Metadata\Extractor\ResourceExtractorInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FileConfigDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedOwnedDummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; - -/** - * @author Théo Fidry - */ -abstract class ExtractorTestCase extends TestCase -{ - use ProphecyTrait; - - protected $extractorClass; - - final public function testEmptyResources() - { - $resources = $this->createExtractor([$this->getEmptyResourcesFile()])->getResources(); - - $this->assertEmpty($resources); - } - - final public function testEmptyOperation() - { - $resources = $this->createExtractor([$this->getEmptyOperationFile()])->getResources(); - - $this->assertSame(['filters' => ['greeting.search_filter']], $resources['App\Entity\Greeting']['collectionOperations']['get']); - // There is a difference between XML & YAML here for example, one will parse `null` or the lack of value as `null` - // whilst the other will parse it as an empty array. Since it doesn't affect the processing of those values, there is no - // real need to fix this. - $this->assertEmpty($resources['App\Entity\Greeting']['collectionOperations']['post']); - $this->assertEmpty($resources['App\Entity\Greeting']['itemOperations']['get']); - $this->assertEmpty($resources['App\Entity\Greeting']['itemOperations']['put']); - } - - final public function testCorrectResources() - { - $resources = $this->createExtractor([$this->getCorrectResourceFile()])->getResources(); - - $this->assertSame([ - Dummy::class => [ - 'shortName' => null, - 'description' => null, - 'iri' => null, - 'itemOperations' => null, - 'collectionOperations' => null, - 'subresourceOperations' => null, - 'graphql' => null, - 'attributes' => null, - 'properties' => null, - ], - FileConfigDummy::class => [ - 'shortName' => 'thedummyshortname', - 'description' => 'Dummy resource', - 'iri' => 'someirischema', - 'itemOperations' => [ - 'my_op_name' => [ - 'method' => 'GET', - ], - 'my_other_op_name' => [ - 'method' => 'POST', - ], - ], - 'collectionOperations' => [ - 'my_collection_op' => [ - 'method' => 'POST', - 'path' => 'the/collection/path', - ], - ], - 'subresourceOperations' => [ - 'my_collection_subresource' => [ - 'path' => 'the/subresource/path', - ], - ], - 'graphql' => [ - 'query' => [ - 'normalization_context' => [ - 'groups' => [ - 'graphql', - ], - ], - ], - ], - 'attributes' => [ - 'normalization_context' => [ - 'groups' => [ - 'default', - ], - ], - 'denormalization_context' => [ - 'groups' => [ - 'default', - ], - ], - 'hydra_context' => [ - '@type' => 'hydra:Operation', - '@hydra:title' => 'File config Dummy', - ], - 'stateless' => true, - ], - 'properties' => [ - 'foo' => [ - 'description' => 'The dummy foo', - 'readable' => true, - 'writable' => true, - 'readableLink' => false, - 'writableLink' => false, - 'required' => true, - 'identifier' => null, - 'iri' => null, - 'attributes' => [ - 'foo' => [ - 'Foo', - ], - 'bar' => [ - [ - 'Bar', - ], - 'baz' => 'Baz', - ], - 'baz' => 'Baz', - ], - 'subresource' => [ - 'collection' => true, - 'resourceClass' => 'Foo', - 'maxDepth' => 1, - ], - ], - 'name' => [ - 'description' => 'The dummy name', - 'readable' => null, - 'writable' => null, - 'readableLink' => null, - 'writableLink' => null, - 'required' => null, - 'identifier' => null, - 'iri' => null, - 'attributes' => [], - 'subresource' => null, - ], - ], - ], - ], $resources); - } - - final public function testResourcesParametersResolution() - { - $containerProphecy = $this->prophesize(ContainerInterface::class); - $containerProphecy->get('dummy_class')->willReturn(Dummy::class); - $containerProphecy->get('dummy_related_owned_class')->willReturn(RelatedOwnedDummy::class); - $containerProphecy->get('file_config_dummy_class')->willReturn(FileConfigDummy::class); - - $resources = $this->createExtractor([$this->getResourceWithParametersFile()], $containerProphecy->reveal())->getResources(); - - $this->assertSame([ - Dummy::class => [ - 'shortName' => null, - 'description' => null, - 'iri' => null, - 'itemOperations' => null, - 'collectionOperations' => null, - 'subresourceOperations' => null, - 'graphql' => null, - 'attributes' => null, - 'properties' => [ - 'relatedOwnedDummy' => [ - 'description' => null, - 'readable' => null, - 'writable' => null, - 'readableLink' => null, - 'writableLink' => null, - 'required' => null, - 'identifier' => null, - 'iri' => null, - 'attributes' => [], - 'subresource' => [ - 'collection' => null, - 'resourceClass' => RelatedOwnedDummy::class, - 'maxDepth' => null, - ], - ], - ], - ], - 'ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyBis' => [ - 'shortName' => null, - 'description' => null, - 'iri' => null, - 'itemOperations' => null, - 'collectionOperations' => null, - 'subresourceOperations' => null, - 'graphql' => null, - 'attributes' => null, - 'properties' => null, - ], - FileConfigDummy::class => [ - 'shortName' => 'thedummyshortname', - 'description' => 'Dummy resource', - 'iri' => 'someirischema', - 'itemOperations' => [ - 'my_op_name' => [ - 'method' => 'GET', - ], - 'my_other_op_name' => [ - 'method' => 'POST', - ], - ], - 'collectionOperations' => [ - 'my_collection_op' => [ - 'method' => 'POST', - 'path' => 'the/collection/path', - ], - ], - 'subresourceOperations' => [ - 'my_collection_subresource' => [ - 'path' => 'the/subresource/path', - ], - ], - 'graphql' => [ - 'query' => [ - 'normalization_context' => [ - 'groups' => [ - 'graphql', - ], - ], - ], - ], - 'attributes' => [ - 'normalization_context' => [ - 'groups' => [ - 'default', - ], - ], - 'denormalization_context' => [ - 'groups' => [ - 'default', - ], - ], - 'hydra_context' => [ - '@type' => 'hydra:Operation', - '@hydra:title' => 'File config Dummy', - ], - ], - 'properties' => [ - 'foo' => [ - 'description' => 'The dummy foo', - 'readable' => true, - 'writable' => true, - 'readableLink' => false, - 'writableLink' => false, - 'required' => true, - 'identifier' => null, - 'iri' => null, - 'attributes' => [ - 'foo' => [ - 'Foo', - ], - 'bar' => [ - [ - 'Bar', - ], - 'baz' => 'Baz', - ], - 'baz' => 'Baz', - 'const' => 0, - ], - 'subresource' => [ - 'collection' => true, - 'resourceClass' => 'Foo', - 'maxDepth' => 1, - ], - ], - 'name' => [ - 'description' => 'The dummy name', - 'readable' => null, - 'writable' => null, - 'readableLink' => null, - 'writableLink' => null, - 'required' => null, - 'identifier' => null, - 'iri' => null, - 'attributes' => [], - 'subresource' => null, - ], - ], - ], - ], $resources); - - $containerProphecy->get(Argument::cetera())->shouldHaveBeenCalledTimes(3); - } - - final public function testResourcesParametersResolutionWithTheSymfonyContainer() - { - $containerProphecy = $this->prophesize(SymfonyContainerInterface::class); - $containerProphecy->getParameter('dummy_class')->willReturn(Dummy::class); - $containerProphecy->getParameter('dummy_related_owned_class')->willReturn(RelatedOwnedDummy::class); - $containerProphecy->getParameter('file_config_dummy_class')->willReturn(FileConfigDummy::class); - - $resources = $this->createExtractor([$this->getResourceWithParametersFile()], $containerProphecy->reveal())->getResources(); - - $this->assertSame([ - Dummy::class => [ - 'shortName' => null, - 'description' => null, - 'iri' => null, - 'itemOperations' => null, - 'collectionOperations' => null, - 'subresourceOperations' => null, - 'graphql' => null, - 'attributes' => null, - 'properties' => [ - 'relatedOwnedDummy' => [ - 'description' => null, - 'readable' => null, - 'writable' => null, - 'readableLink' => null, - 'writableLink' => null, - 'required' => null, - 'identifier' => null, - 'iri' => null, - 'attributes' => [], - 'subresource' => [ - 'collection' => null, - 'resourceClass' => RelatedOwnedDummy::class, - 'maxDepth' => null, - ], - ], - ], - ], - 'ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyBis' => [ - 'shortName' => null, - 'description' => null, - 'iri' => null, - 'itemOperations' => null, - 'collectionOperations' => null, - 'subresourceOperations' => null, - 'graphql' => null, - 'attributes' => null, - 'properties' => null, - ], - FileConfigDummy::class => [ - 'shortName' => 'thedummyshortname', - 'description' => 'Dummy resource', - 'iri' => 'someirischema', - 'itemOperations' => [ - 'my_op_name' => [ - 'method' => 'GET', - ], - 'my_other_op_name' => [ - 'method' => 'POST', - ], - ], - 'collectionOperations' => [ - 'my_collection_op' => [ - 'method' => 'POST', - 'path' => 'the/collection/path', - ], - ], - 'subresourceOperations' => [ - 'my_collection_subresource' => [ - 'path' => 'the/subresource/path', - ], - ], - 'graphql' => [ - 'query' => [ - 'normalization_context' => [ - 'groups' => [ - 'graphql', - ], - ], - ], - ], - 'attributes' => [ - 'normalization_context' => [ - 'groups' => [ - 'default', - ], - ], - 'denormalization_context' => [ - 'groups' => [ - 'default', - ], - ], - 'hydra_context' => [ - '@type' => 'hydra:Operation', - '@hydra:title' => 'File config Dummy', - ], - ], - 'properties' => [ - 'foo' => [ - 'description' => 'The dummy foo', - 'readable' => true, - 'writable' => true, - 'readableLink' => false, - 'writableLink' => false, - 'required' => true, - 'identifier' => null, - 'iri' => null, - 'attributes' => [ - 'foo' => [ - 'Foo', - ], - 'bar' => [ - [ - 'Bar', - ], - 'baz' => 'Baz', - ], - 'baz' => 'Baz', - 'const' => 0, - ], - 'subresource' => [ - 'collection' => true, - 'resourceClass' => 'Foo', - 'maxDepth' => 1, - ], - ], - 'name' => [ - 'description' => 'The dummy name', - 'readable' => null, - 'writable' => null, - 'readableLink' => null, - 'writableLink' => null, - 'required' => null, - 'identifier' => null, - 'iri' => null, - 'attributes' => [], - 'subresource' => null, - ], - ], - ], - ], $resources); - - $containerProphecy->getParameter(Argument::cetera())->shouldHaveBeenCalledTimes(3); - } - - final public function testResourcesParametersResolutionWithoutAContainer() - { - $resources = $this->createExtractor([$this->getResourceWithParametersFile()])->getResources(); - - $this->assertSame([ - '%dummy_class%' => [ - 'shortName' => null, - 'description' => null, - 'iri' => null, - 'itemOperations' => null, - 'collectionOperations' => null, - 'subresourceOperations' => null, - 'graphql' => null, - 'attributes' => null, - 'properties' => [ - 'relatedOwnedDummy' => [ - 'description' => null, - 'readable' => null, - 'writable' => null, - 'readableLink' => null, - 'writableLink' => null, - 'required' => null, - 'identifier' => null, - 'iri' => null, - 'attributes' => [], - 'subresource' => [ - 'collection' => null, - 'resourceClass' => '%dummy_related_owned_class%', - 'maxDepth' => null, - ], - ], - ], - ], - '%dummy_class%Bis' => [ - 'shortName' => null, - 'description' => null, - 'iri' => null, - 'itemOperations' => null, - 'collectionOperations' => null, - 'subresourceOperations' => null, - 'graphql' => null, - 'attributes' => null, - 'properties' => null, - ], - '%file_config_dummy_class%' => [ - 'shortName' => 'thedummyshortname', - 'description' => 'Dummy resource', - 'iri' => 'someirischema', - 'itemOperations' => [ - 'my_op_name' => [ - 'method' => 'GET', - ], - 'my_other_op_name' => [ - 'method' => 'POST', - ], - ], - 'collectionOperations' => [ - 'my_collection_op' => [ - 'method' => 'POST', - 'path' => 'the/collection/path', - ], - ], - 'subresourceOperations' => [ - 'my_collection_subresource' => [ - 'path' => 'the/subresource/path', - ], - ], - 'graphql' => [ - 'query' => [ - 'normalization_context' => [ - 'groups' => [ - 'graphql', - ], - ], - ], - ], - 'attributes' => [ - 'normalization_context' => [ - 'groups' => [ - 'default', - ], - ], - 'denormalization_context' => [ - 'groups' => [ - 'default', - ], - ], - 'hydra_context' => [ - '@type' => 'hydra:Operation', - '@hydra:title' => 'File config Dummy', - ], - ], - 'properties' => [ - 'foo' => [ - 'description' => 'The dummy foo', - 'readable' => true, - 'writable' => true, - 'readableLink' => false, - 'writableLink' => false, - 'required' => true, - 'identifier' => null, - 'iri' => null, - 'attributes' => [ - 'foo' => [ - 'Foo', - ], - 'bar' => [ - [ - 'Bar', - ], - 'baz' => 'Baz', - ], - 'baz' => 'Baz', - 'const' => 0, - ], - 'subresource' => [ - 'collection' => true, - 'resourceClass' => 'Foo', - 'maxDepth' => 1, - ], - ], - 'name' => [ - 'description' => 'The dummy name', - 'readable' => null, - 'writable' => null, - 'readableLink' => null, - 'writableLink' => null, - 'required' => null, - 'identifier' => null, - 'iri' => null, - 'attributes' => [], - 'subresource' => null, - ], - ], - ], - ], $resources); - } - - /** - * @param string[] $paths - */ - final protected function createExtractor(array $paths, ContainerInterface $container = null): ResourceExtractorInterface - { - $extractorClass = $this->getExtractorClass(); - - $this->assertTrue(is_a($extractorClass, ResourceExtractorInterface::class, true)); - - return new $extractorClass($paths, $container); - } - - abstract protected function getExtractorClass(): string; - - abstract protected function getEmptyResourcesFile(): string; - - abstract protected function getEmptyOperationFile(): string; - - abstract protected function getCorrectResourceFile(): string; - - abstract protected function getResourceWithParametersFile(): string; -} diff --git a/tests/Core/Metadata/Extractor/XmlExtractorTest.php b/tests/Core/Metadata/Extractor/XmlExtractorTest.php deleted file mode 100644 index cfa45257756..00000000000 --- a/tests/Core/Metadata/Extractor/XmlExtractorTest.php +++ /dev/null @@ -1,63 +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\Tests\Metadata\Extractor; - -use ApiPlatform\Core\Metadata\Extractor\XmlExtractor; - -/** - * @author Kévin Dunglas - * @author Théo Fidry - */ -class XmlExtractorTest extends ExtractorTestCase -{ - /** - * {@inheritdoc} - */ - protected function getExtractorClass(): string - { - return XmlExtractor::class; - } - - /** - * {@inheritdoc} - */ - protected function getEmptyResourcesFile(): string - { - return __DIR__.'/../../../Fixtures/FileConfigurations/resources_empty.xml'; - } - - /** - * {@inheritdoc} - */ - protected function getEmptyOperationFile(): string - { - return __DIR__.'/../../../Fixtures/FileConfigurations/empty-operation.xml'; - } - - /** - * {@inheritdoc} - */ - protected function getCorrectResourceFile(): string - { - return __DIR__.'/../../../Fixtures/FileConfigurations/resources.xml'; - } - - /** - * {@inheritdoc} - */ - protected function getResourceWithParametersFile(): string - { - return __DIR__.'/../../../Fixtures/FileConfigurations/resources_with_parameters.xml'; - } -} diff --git a/tests/Core/Metadata/Extractor/YamlExtractorTest.php b/tests/Core/Metadata/Extractor/YamlExtractorTest.php deleted file mode 100644 index a949062dbe6..00000000000 --- a/tests/Core/Metadata/Extractor/YamlExtractorTest.php +++ /dev/null @@ -1,121 +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\Tests\Metadata\Extractor; - -use ApiPlatform\Core\Metadata\Extractor\YamlExtractor; -use ApiPlatform\Exception\InvalidArgumentException; -use Generator; - -/** - * @author Kévin Dunglas - * @author Théo Fidry - */ -class YamlExtractorTest extends ExtractorTestCase -{ - public function testInvalidProperty() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The property "shortName" must be a "string", "integer" given.'); - - (new YamlExtractor([__DIR__.'/../../../Fixtures/FileConfigurations/badpropertytype.yml']))->getResources(); - } - - public function testParseException() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/Unable to parse in ".+\\/\\.\\.\\/\\.\\.\\/Fixtures\\/FileConfigurations\\/parse_exception.yml"/'); - - (new YamlExtractor([__DIR__.'/../../../Fixtures/FileConfigurations/parse_exception.yml']))->getResources(); - } - - /** - * @dataProvider provideInvalidResources - */ - public function testInvalidResources(string $path, string $exceptionRegex) - { - try { - (new YamlExtractor([$path]))->getResources(); - - $this->fail('Expected exception to be thrown.'); - } catch (\InvalidArgumentException $exception) { - $this->assertMatchesRegularExpression( - $exceptionRegex, - $exception->getMessage() - ); - } - } - - public function provideInvalidResources(): Generator - { - yield [ - __DIR__.'/../../../Fixtures/FileConfigurations/resourcesinvalid.yml', - '/^"resources" setting is expected to be null or an array, string given in ".*resourcesinvalid\.yml"\.$/', - ]; - - yield [ - __DIR__.'/../../../Fixtures/FileConfigurations/resourcesinvalid_2.yml', - '/^"Foo" setting is expected to be null or an array, string given in ".*resourcesinvalid_2\.yml"\.$/', - ]; - - yield [ - __DIR__.'/../../../Fixtures/FileConfigurations/resourcesinvalid_3.yml', - '/^"properties" setting is expected to be null or an array, string given in ".*resourcesinvalid_3\.yml"\.$/', - ]; - - yield [ - __DIR__.'/../../../Fixtures/FileConfigurations/resourcesinvalid_4.yml', - '/^"myprop" setting is expected to be null or an array, string given in ".*resourcesinvalid_4\.yml"\.$/', - ]; - } - - /** - * {@inheritdoc} - */ - protected function getExtractorClass(): string - { - return YamlExtractor::class; - } - - /** - * {@inheritdoc} - */ - protected function getEmptyResourcesFile(): string - { - return __DIR__.'/../../../Fixtures/FileConfigurations/resources_empty.yml'; - } - - /** - * {@inheritdoc} - */ - protected function getEmptyOperationFile(): string - { - return __DIR__.'/../../../Fixtures/FileConfigurations/empty-operation.yml'; - } - - /** - * {@inheritdoc} - */ - protected function getCorrectResourceFile(): string - { - return __DIR__.'/../../../Fixtures/FileConfigurations/resources.yml'; - } - - /** - * {@inheritdoc} - */ - protected function getResourceWithParametersFile(): string - { - return __DIR__.'/../../../Fixtures/FileConfigurations/resources_with_parameters.yml'; - } -} diff --git a/tests/Core/Metadata/Property/Factory/AnnotationPropertyMetadataFactoryTest.php b/tests/Core/Metadata/Property/Factory/AnnotationPropertyMetadataFactoryTest.php deleted file mode 100644 index 680426ce8f5..00000000000 --- a/tests/Core/Metadata/Property/Factory/AnnotationPropertyMetadataFactoryTest.php +++ /dev/null @@ -1,163 +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\Tests\Metadata\Property\Factory; - -use ApiPlatform\Core\Annotation\ApiProperty; -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Metadata\Property\Factory\AnnotationPropertyMetadataFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPhp8; -use Doctrine\Common\Annotations\Reader; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; - -/** - * @author Kévin Dunglas - */ -class AnnotationPropertyMetadataFactoryTest extends TestCase -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - /** - * @dataProvider dependenciesProvider - * - * @param mixed $reader - * @param mixed $decorated - * @group legacy - */ - public function testCreateProperty($reader, $decorated, string $description) - { - $factory = new AnnotationPropertyMetadataFactory($reader->reveal(), $decorated ? $decorated->reveal() : null); - $metadata = $factory->create(Dummy::class, 'name'); - - $this->assertEquals($description, $metadata->getDescription()); - $this->assertTrue($metadata->isReadable()); - $this->assertTrue($metadata->isWritable()); - $this->assertFalse($metadata->isReadableLink()); - $this->assertFalse($metadata->isWritableLink()); - $this->assertFalse($metadata->isIdentifier()); - $this->assertTrue($metadata->isRequired()); - $this->assertEquals('foo', $metadata->getIri()); - $this->assertEquals(['foo' => 'bar'], $metadata->getAttributes()); - } - - /** - * @requires PHP 8.0 - * @group legacy - */ - public function testCreateAttribute() - { - $this->expectDeprecation('Since api-platform/core 2.7: Decorating the legacy ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface is deprecated, use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface instead.'); - $factory = new AnnotationPropertyMetadataFactory(); - - $metadata = $factory->create(DummyPhp8::class, 'id'); - $this->assertTrue($metadata->isIdentifier()); - $this->assertSame('the identifier', $metadata->getDescription()); - - $metadata = $factory->create(DummyPhp8::class, 'foo'); - $this->assertSame('a foo', $metadata->getDescription()); - } - - public function dependenciesProvider(): array - { - $annotation = new ApiProperty(); - $annotation->description = 'description'; - $annotation->readable = true; - $annotation->writable = true; - $annotation->readableLink = false; - $annotation->writableLink = false; - $annotation->identifier = false; - $annotation->required = true; - $annotation->iri = 'foo'; - $annotation->attributes = ['foo' => 'bar']; - - $propertyReaderProphecy = $this->prophesize(Reader::class); - $propertyReaderProphecy->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiProperty::class)->willReturn($annotation)->shouldBeCalled(); - - $getterReaderProphecy = $this->prophesize(Reader::class); - $getterReaderProphecy->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiProperty::class)->willReturn(null)->shouldBeCalled(); - $getterReaderProphecy->getMethodAnnotation(Argument::type(\ReflectionMethod::class), ApiProperty::class)->willReturn($annotation)->shouldBeCalled(); - - $setterReaderProphecy = $this->prophesize(Reader::class); - $setterReaderProphecy->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiProperty::class)->willReturn(null)->shouldBeCalled(); - $setterReaderProphecy->getMethodAnnotation(Argument::type(\ReflectionMethod::class), ApiProperty::class)->willReturn(null)->shouldBeCalled(); - $setterReaderProphecy->getMethodAnnotation(Argument::type(\ReflectionMethod::class), ApiProperty::class)->willReturn($annotation)->shouldBeCalled(); - - $decoratedThrowNotFoundProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedThrowNotFoundProphecy->create(Dummy::class, 'name', [])->willThrow(new PropertyNotFoundException())->shouldBeCalled(); - - $decoratedReturnProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedReturnProphecy->create(Dummy::class, 'name', [])->willReturn(new PropertyMetadata(null, 'Hi'))->shouldBeCalled(); - - return [ - [$propertyReaderProphecy, null, 'description'], - [$getterReaderProphecy, $decoratedThrowNotFoundProphecy, 'description'], - [$setterReaderProphecy, $decoratedThrowNotFoundProphecy, 'description'], - [$setterReaderProphecy, $decoratedReturnProphecy, 'description'], - ]; - } - - /** - * @group legacy - */ - public function testClassNotFound() - { - $this->expectDeprecation('Since api-platform/core 2.7: Decorating the legacy ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface is deprecated, use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface instead.'); - $this->expectException(PropertyNotFoundException::class); - $this->expectExceptionMessage('Property "foo" of class "\\DoNotExist" not found.'); - - $factory = new AnnotationPropertyMetadataFactory($this->prophesize(Reader::class)->reveal()); - $factory->create('\DoNotExist', 'foo'); - } - - /** - * @group legacy - */ - public function testClassNotFoundButParentFound() - { - $this->expectDeprecation('Since api-platform/core 2.7: Decorating the legacy ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface is deprecated, use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface instead.'); - $propertyMetadata = new PropertyMetadata(); - - $decoratedProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedProphecy->create('\DoNotExist', 'foo', [])->willReturn($propertyMetadata); - - $factory = new AnnotationPropertyMetadataFactory($this->prophesize(Reader::class)->reveal(), $decoratedProphecy->reveal()); - $this->assertEquals($propertyMetadata, $factory->create('\DoNotExist', 'foo')); - } - - public function testSkipDeprecation() - { - $annotation = new ApiProperty(); - $annotation->description = 'description'; - $annotation->readable = true; - $annotation->writable = true; - $annotation->readableLink = false; - $annotation->writableLink = false; - $annotation->identifier = false; - $annotation->required = true; - $annotation->iri = 'foo'; - $annotation->attributes = ['foo' => 'bar']; - - $propertyReaderProphecy = $this->prophesize(Reader::class); - $propertyReaderProphecy->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiProperty::class)->willReturn($annotation)->shouldBeCalled(); - - $factory = new AnnotationPropertyMetadataFactory($propertyReaderProphecy->reveal()); - $metadata = $factory->create(Dummy::class, 'name', ['deprecate' => false]); - } -} diff --git a/tests/Core/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactoryTest.php b/tests/Core/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactoryTest.php deleted file mode 100644 index 9b284d864c4..00000000000 --- a/tests/Core/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactoryTest.php +++ /dev/null @@ -1,126 +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\Tests\Metadata\Property\Factory; - -use ApiPlatform\Core\Annotation\ApiProperty; -use ApiPlatform\Core\Metadata\Property\Factory\AnnotationPropertyNameCollectionFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPhp8; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\UpperCaseIdentifierDummy; -use Doctrine\Common\Annotations\Reader; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; - -/** - * @author Kévin Dunglas - * @group legacy - */ -class AnnotationPropertyNameCollectionFactoryTest extends TestCase -{ - use ProphecyTrait; - - /** - * @dataProvider dependenciesProvider - * - * @param mixed $decorated - */ - public function testCreate($decorated, array $results) - { - $reader = $this->prophesize(Reader::class); - $reader->getPropertyAnnotation(new \ReflectionProperty(Dummy::class, 'name'), ApiProperty::class)->willReturn(new ApiProperty())->shouldBeCalled(); - $reader->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiProperty::class)->willReturn(null)->shouldBeCalled(); - $reader->getMethodAnnotation(new \ReflectionMethod(Dummy::class, 'getName'), ApiProperty::class)->willReturn(new ApiProperty())->shouldBeCalled(); - $reader->getMethodAnnotation(new \ReflectionMethod(Dummy::class, 'getAlias'), ApiProperty::class)->willReturn(new ApiProperty())->shouldBeCalled(); - $reader->getMethodAnnotation(new \ReflectionMethod(Dummy::class, 'staticMethod'), ApiProperty::class)->shouldNotBeCalled(); - $reader->getMethodAnnotation(Argument::type(\ReflectionMethod::class), ApiProperty::class)->willReturn(null)->shouldBeCalled(); - - $factory = new AnnotationPropertyNameCollectionFactory($reader->reveal(), $decorated ? $decorated->reveal() : null); - $metadata = $factory->create(Dummy::class); - - $this->assertEquals($results, iterator_to_array($metadata)); - } - - public function dependenciesProvider(): array - { - $decoratedThrowsNotFound = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $decoratedThrowsNotFound->create(Dummy::class, [])->willThrow(new ResourceClassNotFoundException())->shouldBeCalled(); - - $decoratedReturnParent = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $decoratedReturnParent->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['foo']))->shouldBeCalled(); - - return [ - [null, ['name', 'alias']], - [$decoratedThrowsNotFound, ['name', 'alias']], - [$decoratedReturnParent, ['name', 'alias', 'foo']], - ]; - } - - /** - * @requires PHP 8.0 - */ - public function testCreateAttribute() - { - $factory = new AnnotationPropertyNameCollectionFactory(); - $metadata = $factory->create(DummyPhp8::class); - - $this->assertSame(['id', 'foo'], iterator_to_array($metadata)); - } - - /** - * @dataProvider upperCaseDependenciesProvider - * - * @param mixed $decorated - */ - public function testUpperCaseCreate($decorated, array $results) - { - $reader = $this->prophesize(Reader::class); - $reader->getPropertyAnnotation(new \ReflectionProperty(UpperCaseIdentifierDummy::class, 'name'), ApiProperty::class)->willReturn(new ApiProperty())->shouldBeCalled(); - $reader->getPropertyAnnotation(new \ReflectionProperty(UpperCaseIdentifierDummy::class, 'Uuid'), ApiProperty::class)->willReturn(new ApiProperty())->shouldBeCalled(); - $reader->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiProperty::class)->willReturn(null)->shouldBeCalled(); - $reader->getMethodAnnotation(new \ReflectionMethod(UpperCaseIdentifierDummy::class, 'getName'), ApiProperty::class)->willReturn(new ApiProperty())->shouldBeCalled(); - $reader->getMethodAnnotation(new \ReflectionMethod(UpperCaseIdentifierDummy::class, 'getUuid'), ApiProperty::class)->willReturn(new ApiProperty())->shouldBeCalled(); - $reader->getMethodAnnotation(Argument::type(\ReflectionMethod::class), ApiProperty::class)->willReturn(null)->shouldBeCalled(); - - $factory = new AnnotationPropertyNameCollectionFactory($reader->reveal(), $decorated ? $decorated->reveal() : null); - $metadata = $factory->create(UpperCaseIdentifierDummy::class); - - $this->assertEquals($results, iterator_to_array($metadata)); - } - - public function upperCaseDependenciesProvider(): array - { - $decoratedThrowsNotFound = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $decoratedThrowsNotFound->create(UpperCaseIdentifierDummy::class, [])->willThrow(new ResourceClassNotFoundException())->shouldBeCalled(); - - return [ - [null, ['Uuid', 'name']], - [$decoratedThrowsNotFound, ['Uuid', 'name']], - ]; - } - - public function testClassDoesNotExist() - { - $this->expectException(ResourceClassNotFoundException::class); - $this->expectExceptionMessage('The resource class "\\DoNotExist" does not exist.'); - - $reader = $this->prophesize(Reader::class); - - $factory = new AnnotationPropertyNameCollectionFactory($reader->reveal()); - $factory->create('\DoNotExist'); - } -} diff --git a/tests/Core/Metadata/Property/Factory/AnnotationSubresourceMetadataFactoryTest.php b/tests/Core/Metadata/Property/Factory/AnnotationSubresourceMetadataFactoryTest.php deleted file mode 100644 index 6f6081f1cb2..00000000000 --- a/tests/Core/Metadata/Property/Factory/AnnotationSubresourceMetadataFactoryTest.php +++ /dev/null @@ -1,86 +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\Tests\Metadata\Property\Factory; - -use ApiPlatform\Core\Annotation\ApiSubresource; -use ApiPlatform\Core\Metadata\Property\Factory\AnnotationSubresourceMetadataFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidResourceException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use Doctrine\Common\Annotations\Reader; -use Doctrine\Common\Collections\ArrayCollection; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Component\PropertyInfo\Type; - -/** - * @group legacy - */ -class AnnotationSubresourceMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - /** - * @dataProvider dependenciesProvider - * - * @param mixed $reader - * @param mixed $decorated - */ - public function testCreateProperty($reader, $decorated) - { - $factory = new AnnotationSubresourceMetadataFactory($reader->reveal(), $decorated->reveal()); - $metadata = $factory->create(Dummy::class, 'relatedDummies'); - - $this->assertEquals(new SubresourceMetadata(RelatedDummy::class, true, null), $metadata->getSubresource()); - } - - public function dependenciesProvider(): array - { - $annotation = new ApiSubresource(); - - $propertyReaderProphecy = $this->prophesize(Reader::class); - $propertyReaderProphecy->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiSubresource::class)->willReturn($annotation)->shouldBeCalled(); - - $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class); - $subresourceType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType); - - $decoratedReturnProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedReturnProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new PropertyMetadata())->withType($subresourceType)->withDescription('Several dummies'))->shouldBeCalled(); - - return [ - [$propertyReaderProphecy, $decoratedReturnProphecy], - ]; - } - - public function testCreatePropertyUnknownType() - { - $this->expectException(InvalidResourceException::class); - $this->expectExceptionMessage('Property "relatedDummies" on resource "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy" is declared as a subresource, but its type could not be determined.'); - - $annotation = new ApiSubresource(); - - $propertyReaderProphecy = $this->prophesize(Reader::class); - $propertyReaderProphecy->getPropertyAnnotation(Argument::type(\ReflectionProperty::class), ApiSubresource::class)->willReturn($annotation)->shouldBeCalled(); - - $decoratedReturnProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedReturnProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new PropertyMetadata())->withDescription('Several dummies'))->shouldBeCalled(); - - $factory = new AnnotationSubresourceMetadataFactory($propertyReaderProphecy->reveal(), $decoratedReturnProphecy->reveal()); - $factory->create(Dummy::class, 'relatedDummies'); - } -} diff --git a/tests/Core/Metadata/Property/Factory/ExtractorPropertyMetadataFactoryTest.php b/tests/Core/Metadata/Property/Factory/ExtractorPropertyMetadataFactoryTest.php deleted file mode 100644 index 4653ed682cb..00000000000 --- a/tests/Core/Metadata/Property/Factory/ExtractorPropertyMetadataFactoryTest.php +++ /dev/null @@ -1,259 +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\Tests\Metadata\Property\Factory; - -use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Exception\PropertyNotFoundException; -use ApiPlatform\Core\Metadata\Extractor\XmlExtractor; -use ApiPlatform\Core\Metadata\Extractor\YamlExtractor; -use ApiPlatform\Core\Metadata\Property\Factory\ExtractorPropertyMetadataFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\DummyResourceInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FileConfigDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use Doctrine\Common\Collections\ArrayCollection; -use Symfony\Component\PropertyInfo\Type; - -/** - * @author Baptiste Meyer - * @group legacy - */ -class ExtractorPropertyMetadataFactoryTest extends FileConfigurationMetadataFactoryProvider -{ - use ProphecyTrait; - - /** - * @dataProvider propertyMetadataProvider - */ - public function testCreateXml(PropertyMetadata $expectedPropertyMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.xml'; - - $propertyMetadataFactory = new ExtractorPropertyMetadataFactory(new XmlExtractor([$configPath])); - $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo'); - - $this->assertEquals($expectedPropertyMetadata, $propertyMetadata); - } - - /** - * @dataProvider decoratedPropertyMetadataProvider - */ - public function testCreateWithParentPropertyMetadataFactoryXml(PropertyMetadata $expectedPropertyMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.xml'; - - $decorated = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decorated - ->create(FileConfigDummy::class, 'foo', []) - ->willReturn(new PropertyMetadata(null, null, null, null, true, null, null, false, null, null, ['Foo'], new SubresourceMetadata('Foo', false))) - ->shouldBeCalled(); - - $propertyMetadataFactory = new ExtractorPropertyMetadataFactory(new XmlExtractor([$configPath]), $decorated->reveal()); - $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo'); - - $this->assertEquals($expectedPropertyMetadata, $propertyMetadata); - } - - public function testCreateWithNonexistentResourceXml() - { - $this->expectException(PropertyNotFoundException::class); - $this->expectExceptionMessage('Property "foo" of the resource class "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\ThisDoesNotExist" not found.'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcenotfound.xml'; - - (new ExtractorPropertyMetadataFactory(new XmlExtractor([$configPath])))->create('ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist', 'foo'); - } - - public function testCreateWithNonexistentPropertyXml() - { - $this->expectException(PropertyNotFoundException::class); - $this->expectExceptionMessage('Property "bar" of the resource class "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\FileConfigDummy" not found.'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.xml'; - - (new ExtractorPropertyMetadataFactory(new XmlExtractor([$configPath])))->create(FileConfigDummy::class, 'bar'); - } - - public function testCreateWithInvalidXml() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('#.+Element \'\\{https://api-platform.com/schema/metadata\\}foo\': This element is not expected\\..+#'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/propertyinvalid.xml'; - - (new ExtractorPropertyMetadataFactory(new XmlExtractor([$configPath])))->create(FileConfigDummy::class, 'foo'); - } - - /** - * @dataProvider propertyMetadataProvider - */ - public function testCreateYaml(PropertyMetadata $expectedPropertyMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.yml'; - - $propertyMetadataFactory = new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])); - $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo'); - - $this->assertEquals($expectedPropertyMetadata, $propertyMetadata); - } - - /** - * @dataProvider decoratedPropertyMetadataProvider - */ - public function testCreateWithParentPropertyMetadataFactoryYaml(PropertyMetadata $expectedPropertyMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.yml'; - - $decorated = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decorated - ->create(FileConfigDummy::class, 'foo', []) - ->willReturn(new PropertyMetadata(null, null, null, null, true, null, null, false, null, null, ['Foo'], new SubresourceMetadata('Foo', false))) - ->shouldBeCalled(); - - $propertyMetadataFactory = new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath]), $decorated->reveal()); - $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo'); - - $this->assertEquals($expectedPropertyMetadata, $propertyMetadata); - } - - /** - * @dataProvider decoratedPropertyMetadataProvider - */ - public function testCreateWithCollectionTypedParentPropertyMetadataFactoryYaml(PropertyMetadata $expectedPropertyMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.yml'; - - $collectionType = new Type(Type::BUILTIN_TYPE_OBJECT, - false, - ArrayCollection::class, - true, - new Type(Type::BUILTIN_TYPE_INT), - new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class) - ); - - $expectedPropertyMetadata = $expectedPropertyMetadata->withType($collectionType); - $expectedPropertyMetadata = $expectedPropertyMetadata->withSubresource(new SubresourceMetadata(RelatedDummy::class, true, 1)); - - $decorated = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decorated - ->create(FileConfigDummy::class, 'foo', []) - ->willReturn(new PropertyMetadata($collectionType, null, null, null, true, null, null, false, null, null, ['Foo'], null)) - ->shouldBeCalled(); - - $propertyMetadataFactory = new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath]), $decorated->reveal()); - $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo'); - - $this->assertEquals($expectedPropertyMetadata, $propertyMetadata); - } - - /** - * @dataProvider decoratedPropertyMetadataProvider - */ - public function testCreateWithTypedParentPropertyMetadataFactoryYaml(PropertyMetadata $expectedPropertyMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.yml'; - - $type = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class); - - $expectedPropertyMetadata = $expectedPropertyMetadata->withType($type); - $expectedPropertyMetadata = $expectedPropertyMetadata->withSubresource(new SubresourceMetadata(RelatedDummy::class, false, 1)); - - $decorated = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decorated - ->create(FileConfigDummy::class, 'foo', []) - ->willReturn(new PropertyMetadata($type, null, null, null, true, null, null, false, null, null, ['Foo'], null)) - ->shouldBeCalled(); - - $propertyMetadataFactory = new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath]), $decorated->reveal()); - $propertyMetadata = $propertyMetadataFactory->create(FileConfigDummy::class, 'foo'); - - $this->assertEquals($expectedPropertyMetadata, $propertyMetadata); - } - - public function testCreateWithNonexistentResourceYaml() - { - $this->expectException(PropertyNotFoundException::class); - $this->expectExceptionMessage('Property "foo" of the resource class "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\ThisDoesNotExist" not found.'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcenotfound.yml'; - - (new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])))->create('ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThisDoesNotExist', 'foo'); - } - - public function testCreateWithNonexistentPropertyYaml() - { - $this->expectException(PropertyNotFoundException::class); - $this->expectExceptionMessage('Property "bar" of the resource class "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\FileConfigDummy" not found.'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.yml'; - - (new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])))->create(FileConfigDummy::class, 'bar'); - } - - public function testCreateWithMalformedResourcesSettingYaml() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/"resources" setting is expected to be null or an array, string given in ".+\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/Fixtures\\/FileConfigurations\\/resourcesinvalid\\.yml"\\./'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcesinvalid.yml'; - - (new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])))->create(FileConfigDummy::class, 'foo'); - } - - public function testCreateWithMalformedPropertiesSettingYaml() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/"properties" setting is expected to be null or an array, string given in ".+\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/Fixtures\\/FileConfigurations\\/propertiesinvalid\\.yml"\\./'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/propertiesinvalid.yml'; - - (new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])))->create(FileConfigDummy::class, 'foo'); - } - - public function testCreateWithMalformedPropertySettingYaml() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/"foo" setting is expected to be null or an array, string given in ".+\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/Fixtures\\/FileConfigurations\\/propertyinvalid\\.yml"\\./'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/propertyinvalid.yml'; - - (new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])))->create(FileConfigDummy::class, 'foo'); - } - - public function testCreateWithMalformedYaml() - { - $this->expectException(InvalidArgumentException::class); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/parse_exception.yml'; - - (new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])))->create(FileConfigDummy::class, 'foo'); - } - - public function testItExtractPropertiesFromInterfaceResources() - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/interface_resource.yml'; - - $propertyMetadataFactory = new ExtractorPropertyMetadataFactory(new YamlExtractor([$configPath])); - $metadataSomething = $propertyMetadataFactory->create(DummyResourceInterface::class, 'something'); - $metadataSomethingElse = $propertyMetadataFactory->create(DummyResourceInterface::class, 'somethingElse'); - - $this->assertInstanceOf(PropertyMetadata::class, $metadataSomething); - $this->assertInstanceOf(PropertyMetadata::class, $metadataSomethingElse); - $this->assertTrue($metadataSomething->isIdentifier()); - $this->assertFalse($metadataSomethingElse->isWritable()); - } -} diff --git a/tests/Core/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php b/tests/Core/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php deleted file mode 100644 index 431d04b8853..00000000000 --- a/tests/Core/Metadata/Property/Factory/FileConfigurationMetadataFactoryProvider.php +++ /dev/null @@ -1,79 +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\Tests\Metadata\Property\Factory; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use PHPUnit\Framework\TestCase; - -/** - * Property metadata provider for file configured factories tests. - * - * @author Baptiste Meyer - * @group legacy - */ -abstract class FileConfigurationMetadataFactoryProvider extends TestCase -{ - public function propertyMetadataProvider() - { - $metadata = [ - 'description' => 'The dummy foo', - 'readable' => true, - 'writable' => true, - 'readableLink' => false, - 'writableLink' => false, - 'required' => true, - 'attributes' => [ - 'foo' => ['Foo'], - 'bar' => [['Bar'], 'baz' => 'Baz'], - 'baz' => 'Baz', - ], - 'subresource' => new SubresourceMetadata('Foo', true, 1), - ]; - - return [[$this->getPropertyMetadata($metadata)]]; - } - - public function decoratedPropertyMetadataProvider() - { - $metadata = [ - 'description' => 'The dummy foo', - 'readable' => true, - 'writable' => true, - 'readableLink' => false, - 'writableLink' => false, - 'required' => true, - 'identifier' => false, - 'attributes' => [ - 'foo' => ['Foo'], - 'bar' => [['Bar'], 'baz' => 'Baz'], - 'baz' => 'Baz', - ], - 'subresource' => new SubresourceMetadata('Foo', false, null), - ]; - - return [[$this->getPropertyMetadata($metadata)]]; - } - - private function getPropertyMetadata(array $metadata): PropertyMetadata - { - $propertyMetadata = new PropertyMetadata(); - - foreach ($metadata as $propertyName => $propertyValue) { - $propertyMetadata = $propertyMetadata->{'with'.ucfirst($propertyName)}($propertyValue); - } - - return $propertyMetadata; - } -} diff --git a/tests/Core/Metadata/Property/Factory/InheritedPropertyMetadataFactoryTest.php b/tests/Core/Metadata/Property/Factory/InheritedPropertyMetadataFactoryTest.php deleted file mode 100644 index 84396146240..00000000000 --- a/tests/Core/Metadata/Property/Factory/InheritedPropertyMetadataFactoryTest.php +++ /dev/null @@ -1,50 +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\Tests\Metadata\Property\Factory; - -use ApiPlatform\Core\Metadata\Property\Factory\InheritedPropertyMetadataFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritance; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritanceChild; -use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; - -/** - * @group legacy - */ -class InheritedPropertyMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testCreate() - { - $resourceNameCollectionFactory = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactory->create()->willReturn(new ResourceNameCollection([DummyTableInheritance::class, DummyTableInheritanceChild::class]))->shouldBeCalled(); - - $type = new Type(Type::BUILTIN_TYPE_STRING); - $nicknameMetadata = (new PropertyMetadata())->withType($type)->withDescription('nickname')->withReadable(true)->withWritable(true)->withWritableLink(false)->withReadableLink(false)->withRequired(true)->withIdentifier(false)->withIri('http://example.com/foo')->withAttributes(['foo' => 'bar']); - $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactory->create(DummyTableInheritance::class, 'nickname', [])->willReturn($nicknameMetadata)->shouldBeCalled(); - $propertyMetadataFactory->create(DummyTableInheritanceChild::class, 'nickname', [])->willReturn($nicknameMetadata)->shouldBeCalled(); - - $factory = new InheritedPropertyMetadataFactory($resourceNameCollectionFactory->reveal(), $propertyMetadataFactory->reveal()); - $metadata = $factory->create(DummyTableInheritance::class, 'nickname'); - - $this->assertEquals($metadata, $nicknameMetadata->withChildInherited(DummyTableInheritanceChild::class)); - } -} diff --git a/tests/Core/Metadata/Property/Factory/InheritedPropertyNameCollectionFactoryTest.php b/tests/Core/Metadata/Property/Factory/InheritedPropertyNameCollectionFactoryTest.php deleted file mode 100644 index a828bf2801b..00000000000 --- a/tests/Core/Metadata/Property/Factory/InheritedPropertyNameCollectionFactoryTest.php +++ /dev/null @@ -1,62 +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\Tests\Metadata\Property\Factory; - -use ApiPlatform\Core\Metadata\Property\Factory\InheritedPropertyNameCollectionFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritance; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritanceChild; -use PHPUnit\Framework\TestCase; - -/** - * @group legacy - */ -class InheritedPropertyNameCollectionFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testCreateOnParent() - { - $resourceNameCollectionFactory = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactory->create()->willReturn(new ResourceNameCollection([DummyTableInheritance::class, DummyTableInheritanceChild::class]))->shouldBeCalled(); - - $propertyNameCollectionFactory = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactory->create(DummyTableInheritance::class, [])->willReturn(new PropertyNameCollection(['name']))->shouldBeCalled(); - $propertyNameCollectionFactory->create(DummyTableInheritanceChild::class, [])->shouldNotBeCalled(); - - $factory = new InheritedPropertyNameCollectionFactory($resourceNameCollectionFactory->reveal(), $propertyNameCollectionFactory->reveal()); - $metadata = $factory->create(DummyTableInheritance::class); - - $this->assertSame((array) new PropertyNameCollection(['name']), (array) $metadata); - } - - public function testCreateOnChild() - { - $resourceNameCollectionFactory = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactory->create()->willReturn(new ResourceNameCollection([DummyTableInheritance::class, DummyTableInheritanceChild::class]))->shouldBeCalled(); - - $propertyNameCollectionFactory = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactory->create(DummyTableInheritance::class, [])->willReturn(new PropertyNameCollection(['name']))->shouldBeCalled(); - $propertyNameCollectionFactory->create(DummyTableInheritanceChild::class, [])->willReturn(new PropertyNameCollection(['nickname', '169']))->shouldBeCalled(); - - $factory = new InheritedPropertyNameCollectionFactory($resourceNameCollectionFactory->reveal(), $propertyNameCollectionFactory->reveal()); - $metadata = $factory->create(DummyTableInheritanceChild::class); - - $this->assertSame((array) new PropertyNameCollection(['nickname', '169', 'name']), (array) $metadata); - } -} diff --git a/tests/Core/Metadata/Property/Factory/SerializerPropertyMetadataFactoryTest.php b/tests/Core/Metadata/Property/Factory/SerializerPropertyMetadataFactoryTest.php deleted file mode 100644 index 7ab32e44c47..00000000000 --- a/tests/Core/Metadata/Property/Factory/SerializerPropertyMetadataFactoryTest.php +++ /dev/null @@ -1,187 +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\Tests\Metadata\Property\Factory; - -use ApiPlatform\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\SerializerPropertyMetadataFactory; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritance; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritanceChild; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Mapping\AttributeMetadata as SerializerAttributeMetadata; -use Symfony\Component\Serializer\Mapping\ClassMetadata as SerializerClassMetadata; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface as SerializerClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - -/** - * @group legacy - */ -class SerializerPropertyMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testConstruct() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $serializerClassMetadataFactoryProphecy = $this->prophesize(SerializerClassMetadataFactoryInterface::class); - $serializerClassMetadataFactory = $serializerClassMetadataFactoryProphecy->reveal(); - - $decoratedProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decorated = $decoratedProphecy->reveal(); - - $serializerPropertyMetadataFactory = new SerializerPropertyMetadataFactory($resourceMetadataFactory, $serializerClassMetadataFactory, $decorated); - - $this->assertInstanceOf(PropertyMetadataFactoryInterface::class, $serializerPropertyMetadataFactory); - } - - /** - * @dataProvider groupsProvider - * - * @param mixed $readGroups - * @param mixed $writeGroups - */ - public function testCreateLegacy($readGroups, $writeGroups, ?string $relatedOutputClass = null) - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $dummyResourceMetadata = (new ResourceMetadata()) - ->withAttributes([ - 'normalization_context' => [ - AbstractNormalizer::GROUPS => $readGroups, - ], - 'denormalization_context' => [ - AbstractNormalizer::GROUPS => $writeGroups, - ], - ]); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyResourceMetadata); - $relatedDummyResourceMetadata = new ResourceMetadata(); - if ($relatedOutputClass) { - $relatedDummyResourceMetadata = $relatedDummyResourceMetadata->withAttributes([ - 'output' => ['class' => $relatedOutputClass], - ]); - } - $resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedDummyResourceMetadata); - - $serializerClassMetadataFactoryProphecy = $this->prophesize(SerializerClassMetadataFactoryInterface::class); - $dummySerializerClassMetadata = new SerializerClassMetadata(Dummy::class); - $fooSerializerAttributeMetadata = new SerializerAttributeMetadata('foo'); - $fooSerializerAttributeMetadata->addGroup('dummy_read'); - $fooSerializerAttributeMetadata->addGroup('dummy_write'); - $dummySerializerClassMetadata->addAttributeMetadata($fooSerializerAttributeMetadata); - $relatedDummySerializerAttributeMetadata = new SerializerAttributeMetadata('relatedDummy'); - $relatedDummySerializerAttributeMetadata->addGroup('dummy_read'); - $relatedDummySerializerAttributeMetadata->addGroup('dummy_write'); - $dummySerializerClassMetadata->addAttributeMetadata($relatedDummySerializerAttributeMetadata); - $nameConvertedSerializerAttributeMetadata = new SerializerAttributeMetadata('nameConverted'); - $dummySerializerClassMetadata->addAttributeMetadata($nameConvertedSerializerAttributeMetadata); - $serializerClassMetadataFactoryProphecy->getMetadataFor(Dummy::class)->willReturn($dummySerializerClassMetadata); - $relatedDummySerializerClassMetadata = new SerializerClassMetadata(RelatedDummy::class); - $nameSerializerAttributeMetadata = new SerializerAttributeMetadata('name'); - $nameSerializerAttributeMetadata->addGroup('dummy_read'); - $relatedDummySerializerClassMetadata->addAttributeMetadata($nameSerializerAttributeMetadata); - $serializerClassMetadataFactoryProphecy->getMetadataFor(RelatedDummy::class)->willReturn($relatedDummySerializerClassMetadata); - $dummyCarSerializerClassMetadata = new SerializerClassMetadata(DummyCar::class); - $nameSerializerAttributeMetadata = new SerializerAttributeMetadata('name'); - $nameSerializerAttributeMetadata->addGroup('dummy_car_read'); - $nameSerializerAttributeMetadata->addGroup('dummy_write'); - $dummyCarSerializerClassMetadata->addAttributeMetadata($nameSerializerAttributeMetadata); - $serializerClassMetadataFactoryProphecy->getMetadataFor(DummyCar::class)->willReturn($dummyCarSerializerClassMetadata); - - $decoratedProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $fooPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_ARRAY, true)) - ->withReadable(false) - ->withWritable(true); - $decoratedProphecy->create(Dummy::class, 'foo', [])->willReturn($fooPropertyMetadata); - $relatedDummyPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_OBJECT, true, RelatedDummy::class)); - $decoratedProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn($relatedDummyPropertyMetadata); - $nameConvertedPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_STRING, true)); - $decoratedProphecy->create(Dummy::class, 'nameConverted', [])->willReturn($nameConvertedPropertyMetadata); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class); - - $serializerPropertyMetadataFactory = new SerializerPropertyMetadataFactory($resourceMetadataFactoryProphecy->reveal(), $serializerClassMetadataFactoryProphecy->reveal(), $decoratedProphecy->reveal(), $resourceClassResolverProphecy->reveal()); - - $actual = []; - $actual[] = $serializerPropertyMetadataFactory->create(Dummy::class, 'foo'); - $actual[] = $serializerPropertyMetadataFactory->create(Dummy::class, 'relatedDummy'); - $actual[] = $serializerPropertyMetadataFactory->create(Dummy::class, 'nameConverted'); - - $this->assertInstanceOf(PropertyMetadata::class, $actual[0]); - $this->assertFalse($actual[0]->isReadable()); - $this->assertTrue($actual[0]->isWritable()); - - $this->assertInstanceOf(PropertyMetadata::class, $actual[1]); - $this->assertTrue($actual[1]->isReadable()); - $this->assertTrue($actual[1]->isWritable()); - if ($relatedOutputClass) { - $this->assertFalse($actual[1]->isReadableLink()); - $this->assertTrue($actual[1]->isWritableLink()); - } else { - $this->assertTrue($actual[1]->isReadableLink()); - $this->assertFalse($actual[1]->isWritableLink()); - } - - $this->assertInstanceOf(PropertyMetadata::class, $actual[2]); - $this->assertFalse($actual[2]->isReadable()); - $this->assertFalse($actual[2]->isWritable()); - } - - public function groupsProvider(): array - { - return [ - [['dummy_read'], ['dummy_write']], - ['dummy_read', 'dummy_write'], - ['dummy_read', 'dummy_write', DummyCar::class], - ]; - } - - /** - * @group legacy - */ - public function testCreateInherited(): void - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyTableInheritanceChild::class)->willReturn(new ResourceMetadata()); - - $serializerClassMetadataFactoryProphecy = $this->prophesize(SerializerClassMetadataFactoryInterface::class); - $dummySerializerClassMetadata = new SerializerClassMetadata(DummyTableInheritanceChild::class); - $serializerClassMetadataFactoryProphecy->getMetadataFor(DummyTableInheritanceChild::class)->willReturn($dummySerializerClassMetadata); - - $decoratedProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $fooPropertyMetadata = (new PropertyMetadata()) - ->withType(new Type(Type::BUILTIN_TYPE_ARRAY, true)) - ->withChildInherited(DummyTableInheritanceChild::class); - $decoratedProphecy->create(DummyTableInheritance::class, 'nickname', [])->willReturn($fooPropertyMetadata); - - $serializerPropertyMetadataFactory = new SerializerPropertyMetadataFactory($resourceMetadataFactoryProphecy->reveal(), $serializerClassMetadataFactoryProphecy->reveal(), $decoratedProphecy->reveal()); - - $actual = $serializerPropertyMetadataFactory->create(DummyTableInheritance::class, 'nickname'); - - $this->assertEquals($actual->getChildInherited(), DummyTableInheritanceChild::class); - } -} diff --git a/tests/Core/Metadata/Property/PropertyMetadataTest.php b/tests/Core/Metadata/Property/PropertyMetadataTest.php deleted file mode 100644 index d1c0d0a3add..00000000000 --- a/tests/Core/Metadata/Property/PropertyMetadataTest.php +++ /dev/null @@ -1,115 +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\Tests\Metadata\Property; - -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; - -/** - * @author Kévin Dunglas - */ -class PropertyMetadataTest extends TestCase -{ - public function testValueObject() - { - $type = new Type(Type::BUILTIN_TYPE_STRING); - $metadata = new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'desc', true, true, false, false, true, false, 'http://example.com/foo', null, ['foo' => 'bar']); - $this->assertEquals($type, $metadata->getType()); - $this->assertEquals('desc', $metadata->getDescription()); - $this->assertTrue($metadata->isReadable()); - $this->assertTrue($metadata->isWritable()); - $this->assertFalse($metadata->isReadableLink()); - $this->assertFalse($metadata->isWritableLink()); - $this->assertTrue($metadata->isRequired()); - $this->assertFalse($metadata->isIdentifier()); - $this->assertEquals('http://example.com/foo', $metadata->getIri()); - $this->assertEquals(['foo' => 'bar'], $metadata->getAttributes()); - - $newType = new Type(Type::BUILTIN_TYPE_BOOL); - $newMetadata = $metadata->withType($newType); - $this->assertNotSame($metadata, $newMetadata); - $this->assertEquals($newType, $newMetadata->getType()); - - $newMetadata = $metadata->withDescription('description'); - $this->assertNotSame($metadata, $newMetadata); - $this->assertEquals('description', $newMetadata->getDescription()); - - $newMetadata = $metadata->withReadable(false); - $this->assertNotSame($metadata, $newMetadata); - $this->assertFalse($newMetadata->isReadable()); - - $newMetadata = $metadata->withWritable(false); - $this->assertNotSame($metadata, $newMetadata); - $this->assertFalse($newMetadata->isWritable()); - - $newMetadata = $metadata->withReadableLink(true); - $this->assertNotSame($metadata, $newMetadata); - $this->assertTrue($newMetadata->isReadableLink()); - - $newMetadata = $metadata->withWritableLink(true); - $this->assertNotSame($metadata, $newMetadata); - $this->assertTrue($newMetadata->isWritableLink()); - - $newMetadata = $metadata->withRequired(false); - $this->assertNotSame($metadata, $newMetadata); - $this->assertFalse($newMetadata->isRequired()); - - $newMetadata = $metadata->withIdentifier(true); - $this->assertNotSame($metadata, $newMetadata); - $this->assertTrue($newMetadata->isIdentifier()); - - $newMetadata = $metadata->withIri('foo:bar'); - $this->assertNotSame($metadata, $newMetadata); - $this->assertEquals('foo:bar', $newMetadata->getIri()); - - $newMetadata = $metadata->withAttributes(['a' => 'b']); - $this->assertNotSame($metadata, $newMetadata); - $this->assertEquals(['a' => 'b'], $newMetadata->getAttributes()); - $this->assertEquals('b', $newMetadata->getAttribute('a')); - - $newMetadata = $metadata->withInitializable(true); - $this->assertNotSame($metadata, $newMetadata); - $this->assertTrue($newMetadata->isInitializable()); - - $newMetadata = $metadata->withDefault('foobar'); - $this->assertNotSame($metadata, $newMetadata); - $this->assertEquals('foobar', $newMetadata->getDefault()); - - $newMetadata = $metadata->withExample('foobarexample'); - $this->assertNotSame($metadata, $newMetadata); - $this->assertEquals('foobarexample', $newMetadata->getExample()); - } - - public function testShouldReturnRequiredFalseWhenRequiredTrueIsSetButMaskedByWritableFalse() - { - $metadata = new PropertyMetadata(); - - $metadata = $metadata->withRequired(true); - $metadata = $metadata->withWritable(false); - - $this->assertFalse($metadata->isRequired()); - } - - public function testShouldReturnPreviouslySetRequiredTrueWhenWritableFalseUnmasked() - { - $metadata = new PropertyMetadata(); - - $metadata = $metadata->withRequired(true); - $metadata = $metadata->withWritable(false); - $metadata = $metadata->withWritable(true); - - $this->assertTrue($metadata->isRequired()); - } -} diff --git a/tests/Core/Metadata/Resource/Factory/AnnotationResourceFilterMetadataFactoryTest.php b/tests/Core/Metadata/Resource/Factory/AnnotationResourceFilterMetadataFactoryTest.php deleted file mode 100644 index 75f35871ff7..00000000000 --- a/tests/Core/Metadata/Resource/Factory/AnnotationResourceFilterMetadataFactoryTest.php +++ /dev/null @@ -1,55 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Annotation\ApiFilter; -use ApiPlatform\Core\Metadata\Resource\Factory\AnnotationResourceFilterMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Doctrine\Orm\Filter\DummyFilter; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use Doctrine\Common\Annotations\Reader; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; - -/** - * @author Antoine Bluchet - * @group legacy - */ -class AnnotationResourceFilterMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testCreate() - { - $decorated = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decorated->create(Dummy::class)->willReturn(new ResourceMetadata('hello', 'blabla'))->shouldBeCalled(); - - $reader = $this->prophesize(Reader::class); - $reader->getClassAnnotations(Argument::type(\ReflectionClass::class))->shouldBeCalled()->willReturn([ // @phpstan-ignore-next-line - new ApiFilter(['value' => DummyFilter::class]), - ]); - - $reader->getPropertyAnnotations(Argument::type(\ReflectionProperty::class))->shouldBeCalled()->willReturn([]); - - $factory = new AnnotationResourceFilterMetadataFactory($reader->reveal(), $decorated->reveal()); - - $metadata = $factory->create(Dummy::class); - - $this->assertEquals(['filters' => [ - 'annotated_api_platform_tests_fixtures_test_bundle_entity_dummy_api_platform_tests_fixtures_test_bundle_doctrine_orm_filter_dummy_filter', - ]], $metadata->getAttributes()); - } -} diff --git a/tests/Core/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php b/tests/Core/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php deleted file mode 100644 index bfd09e60dd2..00000000000 --- a/tests/Core/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php +++ /dev/null @@ -1,156 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Annotation\ApiResource; -use ApiPlatform\Core\Metadata\Resource\Factory\AnnotationResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPhp8; -use Doctrine\Common\Annotations\Reader; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; - -/** - * @author Kévin Dunglas - * @group legacy - */ -class AnnotationResourceMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - /** - * @dataProvider getCreateDependencies - * - * @param mixed $reader - * @param mixed $decorated - */ - public function testCreate($reader, $decorated, string $expectedShortName, ?string $expectedDescription) - { - $factory = new AnnotationResourceMetadataFactory($reader->reveal(), $decorated ? $decorated->reveal() : null); - $metadata = $factory->create(Dummy::class); - - $this->assertEquals($expectedShortName, $metadata->getShortName()); - $this->assertEquals($expectedDescription, $metadata->getDescription()); - $this->assertEquals('http://example.com', $metadata->getIri()); - $this->assertEquals(['foo' => ['bar' => true]], $metadata->getItemOperations()); - $this->assertEquals(['baz' => ['tab' => false]], $metadata->getCollectionOperations()); - $this->assertEquals(['sub' => ['bus' => false]], $metadata->getSubresourceOperations()); - $this->assertEquals(['a' => 1, 'route_prefix' => '/foobar', 'stateless' => false], $metadata->getAttributes()); - $this->assertEquals(['foo' => 'bar'], $metadata->getGraphql()); - } - - /** - * @requires PHP 8.0 - */ - public function testCreateAttribute() - { - $factory = new AnnotationResourceMetadataFactory(); - $metadata = $factory->create(DummyPhp8::class); - - $this->assertSame('Hey PHP 8', $metadata->getDescription()); - } - - public function testCreateWithDefaults() - { - $defaults = [ - 'shortName' => 'Default shortname should not be ignored', - 'description' => 'CHANGEME!', - 'collection_operations' => ['get'], - 'item_operations' => ['get', 'put'], - 'attributes' => [ - 'pagination_items_per_page' => 4, - 'pagination_maximum_items_per_page' => 6, - 'stateless' => true, - ], - ]; - - $annotation = new ApiResource([ - 'itemOperations' => ['get', 'delete'], - 'attributes' => [ - 'pagination_client_enabled' => true, - 'pagination_maximum_items_per_page' => 10, - 'stateless' => null, - ], - ]); - $reader = $this->prophesize(Reader::class); - $reader->getClassAnnotation(Argument::type(\ReflectionClass::class), ApiResource::class)->willReturn($annotation)->shouldBeCalled(); - $factory = new AnnotationResourceMetadataFactory($reader->reveal(), null, $defaults); - $metadata = $factory->create(Dummy::class); - - $this->assertNull($metadata->getShortName()); - $this->assertEquals('CHANGEME!', $metadata->getDescription()); - $this->assertEquals(['get'], $metadata->getCollectionOperations()); - $this->assertEquals(['get', 'delete'], $metadata->getItemOperations()); - $this->assertTrue($metadata->getAttribute('pagination_client_enabled')); - $this->assertEquals(4, $metadata->getAttribute('pagination_items_per_page')); - $this->assertEquals(10, $metadata->getAttribute('pagination_maximum_items_per_page')); - $this->assertTrue($metadata->getAttribute('stateless')); - } - - public function testCreateWithoutAttributes() - { - $annotation = new ApiResource([]); - $reader = $this->prophesize(Reader::class); - $reader->getClassAnnotation(Argument::type(\ReflectionClass::class), ApiResource::class)->willReturn($annotation)->shouldBeCalled(); - $factory = new AnnotationResourceMetadataFactory($reader->reveal(), null); - $metadata = $factory->create(Dummy::class); - - $this->assertNull($metadata->getAttributes()); - } - - public function getCreateDependencies() - { - $resourceData = [ - 'shortName' => 'shortName', - 'description' => 'description', - 'iri' => 'http://example.com', - 'itemOperations' => ['foo' => ['bar' => true]], - 'collectionOperations' => ['baz' => ['tab' => false]], - 'subresourceOperations' => ['sub' => ['bus' => false]], - 'attributes' => ['a' => 1, 'route_prefix' => '/foobar'], - 'graphql' => ['foo' => 'bar'], - 'stateless' => false, - ]; - $annotationFull = new ApiResource($resourceData); - - $reader = $this->prophesize(Reader::class); - $reader->getClassAnnotation(Argument::type(\ReflectionClass::class), ApiResource::class)->willReturn($annotationFull)->shouldBeCalled(); - - $decoratedThrow = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedThrow->create(Dummy::class)->willThrow(ResourceClassNotFoundException::class); - - $decoratedReturn = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedReturn->create(Dummy::class)->willReturn(new ResourceMetadata('hello', 'blabla'))->shouldBeCalled(); - - $resourceData['description'] = null; - $annotationWithNull = new ApiResource($resourceData); - - $decoratedReturnWithNull = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedReturnWithNull->create(Dummy::class)->willReturn(new ResourceMetadata('hello'))->shouldBeCalled(); - - $readerWithNull = $this->prophesize(Reader::class); - $readerWithNull->getClassAnnotation(Argument::type(\ReflectionClass::class), ApiResource::class)->willReturn($annotationWithNull)->shouldBeCalled(); - - return [ - [$reader, $decoratedThrow, 'shortName', 'description'], - [$reader, null, 'shortName', 'description'], - [$reader, $decoratedReturn, 'hello', 'blabla'], - [$readerWithNull, $decoratedReturnWithNull, 'hello', null], - ]; - } -} diff --git a/tests/Core/Metadata/Resource/Factory/AnnotationResourceNameCollectionFactoryTest.php b/tests/Core/Metadata/Resource/Factory/AnnotationResourceNameCollectionFactoryTest.php deleted file mode 100644 index 97f62394e43..00000000000 --- a/tests/Core/Metadata/Resource/Factory/AnnotationResourceNameCollectionFactoryTest.php +++ /dev/null @@ -1,52 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\Factory\AnnotationResourceNameCollectionFactory; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use Doctrine\Common\Annotations\Reader; -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; - -/** - * @author Antoine Bluchet - */ -class AnnotationResourceNameCollectionFactoryTest extends TestCase -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - public function testCreate() - { - $decorated = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $decorated->create()->willReturn(new ResourceNameCollection(['foo', 'bar']))->shouldBeCalled(); - - $reader = $this->prophesize(Reader::class); - - $metadata = new AnnotationResourceNameCollectionFactory($reader->reveal(), [], $decorated->reveal()); - - $this->assertEquals(new ResourceNameCollection(['foo', 'bar']), $metadata->create()); - } - - public function testCreateAttribute() - { - $decorated = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $decorated->create()->willReturn(new ResourceNameCollection(['foo', 'bar']))->shouldBeCalled(); - - $metadata = new AnnotationResourceNameCollectionFactory(null, [], $decorated->reveal()); - $this->assertEquals(new ResourceNameCollection(['foo', 'bar']), $metadata->create()); - } -} diff --git a/tests/Core/Metadata/Resource/Factory/CachedResourceMetadataFactoryTest.php b/tests/Core/Metadata/Resource/Factory/CachedResourceMetadataFactoryTest.php deleted file mode 100644 index d972c6a3101..00000000000 --- a/tests/Core/Metadata/Resource/Factory/CachedResourceMetadataFactoryTest.php +++ /dev/null @@ -1,96 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\Factory\CachedResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; -use Psr\Cache\CacheException; -use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; - -/** - * @author Baptiste Meyer - * @group legacy - */ -class CachedResourceMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testCreateWithItemHit() - { - $cacheItem = $this->prophesize(CacheItemInterface::class); - $cacheItem->isHit()->willReturn(true)->shouldBeCalled(); - $cacheItem->get()->willReturn(new ResourceMetadata(null, 'Dummy.'))->shouldBeCalled(); - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem($this->generateCacheKey())->willReturn($cacheItem->reveal())->shouldBeCalled(); - - $decoratedResourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); - - $cachedResourceMetadataFactory = new CachedResourceMetadataFactory($cacheItemPool->reveal(), $decoratedResourceMetadataFactory->reveal()); - $resultedResourceMetadata = $cachedResourceMetadataFactory->create(Dummy::class); - - $this->assertEquals(new ResourceMetadata(null, 'Dummy.'), $resultedResourceMetadata); - } - - public function testCreateWithItemNotHit() - { - $propertyMetadata = new ResourceMetadata(null, 'Dummy.'); - - $decoratedResourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedResourceMetadataFactory->create(Dummy::class)->willReturn($propertyMetadata)->shouldBeCalled(); - - $cacheItem = $this->prophesize(CacheItemInterface::class); - $cacheItem->isHit()->willReturn(false)->shouldBeCalled(); - $cacheItem->set($propertyMetadata)->willReturn($cacheItem->reveal())->shouldBeCalled(); - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem($this->generateCacheKey())->willReturn($cacheItem->reveal())->shouldBeCalled(); - $cacheItemPool->save($cacheItem->reveal())->willReturn(true)->shouldBeCalled(); - - $cachedResourceMetadataFactory = new CachedResourceMetadataFactory($cacheItemPool->reveal(), $decoratedResourceMetadataFactory->reveal()); - $resultedResourceMetadata = $cachedResourceMetadataFactory->create(Dummy::class); - - $expectedResult = new ResourceMetadata(null, 'Dummy.'); - $this->assertEquals($expectedResult, $resultedResourceMetadata); - $this->assertEquals($expectedResult, $cachedResourceMetadataFactory->create(Dummy::class), 'Trigger the local cache'); - } - - public function testCreateWithGetCacheItemThrowsCacheException() - { - $decoratedResourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedResourceMetadataFactory->create(Dummy::class)->willReturn(new ResourceMetadata(null, 'Dummy.'))->shouldBeCalled(); - - $cacheException = new class() extends \Exception implements CacheException {}; - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem($this->generateCacheKey())->willThrow($cacheException)->shouldBeCalled(); - - $cachedResourceMetadataFactory = new CachedResourceMetadataFactory($cacheItemPool->reveal(), $decoratedResourceMetadataFactory->reveal()); - $resultedResourceMetadata = $cachedResourceMetadataFactory->create(Dummy::class); - - $expectedResult = new ResourceMetadata(null, 'Dummy.'); - $this->assertEquals($expectedResult, $resultedResourceMetadata); - $this->assertEquals($expectedResult, $cachedResourceMetadataFactory->create(Dummy::class), 'Trigger the local cache'); - } - - private function generateCacheKey(string $resourceClass = Dummy::class) - { - return CachedResourceMetadataFactory::CACHE_KEY_PREFIX.md5($resourceClass); - } -} diff --git a/tests/Core/Metadata/Resource/Factory/CachedResourceNameCollectionFactoryTest.php b/tests/Core/Metadata/Resource/Factory/CachedResourceNameCollectionFactoryTest.php deleted file mode 100644 index 3965c3375f0..00000000000 --- a/tests/Core/Metadata/Resource/Factory/CachedResourceNameCollectionFactoryTest.php +++ /dev/null @@ -1,92 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Metadata\Resource\Factory\CachedResourceNameCollectionFactory; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; -use Psr\Cache\CacheException; -use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; - -/** - * @author Baptiste Meyer - */ -class CachedResourceNameCollectionFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testCreateWithItemHit() - { - $cacheItem = $this->prophesize(CacheItemInterface::class); - $cacheItem->isHit()->willReturn(true)->shouldBeCalled(); - $cacheItem->get()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem(CachedResourceNameCollectionFactory::CACHE_KEY)->willReturn($cacheItem->reveal())->shouldBeCalled(); - - $decoratedResourceNameCollectionFactory = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - - $cachedResourceNameCollectionFactory = new CachedResourceNameCollectionFactory($cacheItemPool->reveal(), $decoratedResourceNameCollectionFactory->reveal()); - $resultedResourceNameCollection = $cachedResourceNameCollectionFactory->create(); - - $expectedResult = new ResourceNameCollection([Dummy::class]); - $this->assertEquals($expectedResult, $resultedResourceNameCollection); - $this->assertEquals($expectedResult, $cachedResourceNameCollectionFactory->create(), 'Trigger the local cache'); - } - - public function testCreateWithItemNotHit() - { - $resourceNameCollection = new ResourceNameCollection([Dummy::class]); - - $decoratedResourceNameCollectionFactory = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $decoratedResourceNameCollectionFactory->create()->willReturn($resourceNameCollection)->shouldBeCalled(); - - $cacheItem = $this->prophesize(CacheItemInterface::class); - $cacheItem->isHit()->willReturn(false)->shouldBeCalled(); - $cacheItem->set($resourceNameCollection)->willReturn($cacheItem->reveal())->shouldBeCalled(); - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem(CachedResourceNameCollectionFactory::CACHE_KEY)->willReturn($cacheItem->reveal())->shouldBeCalled(); - $cacheItemPool->save($cacheItem->reveal())->willReturn(true)->shouldBeCalled(); - - $cachedResourceNameCollectionFactory = new CachedResourceNameCollectionFactory($cacheItemPool->reveal(), $decoratedResourceNameCollectionFactory->reveal()); - $resultedResourceNameCollection = $cachedResourceNameCollectionFactory->create(); - - $expectedResult = new ResourceNameCollection([Dummy::class]); - $this->assertEquals($expectedResult, $resultedResourceNameCollection); - $this->assertEquals($expectedResult, $cachedResourceNameCollectionFactory->create(), 'Trigger the local cache'); - } - - public function testCreateWithGetCacheItemThrowsCacheException() - { - $decoratedResourceNameCollectionFactory = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $decoratedResourceNameCollectionFactory->create()->willReturn(new ResourceNameCollection([Dummy::class]))->shouldBeCalled(); - - $cacheException = new class() extends \Exception implements CacheException {}; - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem(CachedResourceNameCollectionFactory::CACHE_KEY)->willThrow($cacheException)->shouldBeCalled(); - - $cachedResourceNameCollectionFactory = new CachedResourceNameCollectionFactory($cacheItemPool->reveal(), $decoratedResourceNameCollectionFactory->reveal()); - $resultedResourceNameCollection = $cachedResourceNameCollectionFactory->create(); - - $expectedResult = new ResourceNameCollection([Dummy::class]); - $this->assertEquals($expectedResult, $resultedResourceNameCollection); - $this->assertEquals($expectedResult, $cachedResourceNameCollectionFactory->create(), 'Trigger the local cache'); - } -} diff --git a/tests/Core/Metadata/Resource/Factory/ExtractorResourceMetadataFactoryTest.php b/tests/Core/Metadata/Resource/Factory/ExtractorResourceMetadataFactoryTest.php deleted file mode 100644 index 629aee72ef3..00000000000 --- a/tests/Core/Metadata/Resource/Factory/ExtractorResourceMetadataFactoryTest.php +++ /dev/null @@ -1,361 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Extractor\ExtractorInterface; -use ApiPlatform\Core\Metadata\Extractor\XmlExtractor; -use ApiPlatform\Core\Metadata\Extractor\YamlExtractor; -use ApiPlatform\Core\Metadata\Resource\Factory\ExtractorResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\Factory\ShortNameResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Exception\ResourceClassNotFoundException; -use ApiPlatform\Metadata\Resource\Factory\ExtractorResourceNameCollectionFactory; -use ApiPlatform\Tests\Fixtures\DummyResourceInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FileConfigDummy; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; - -/** - * Tests extractor resource metadata factory. - * - * @author Antoine Bluchet - * @group legacy - */ -class ExtractorResourceMetadataFactoryTest extends FileConfigurationMetadataFactoryProvider -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - /** - * @dataProvider resourceMetadataProvider - * - * @param mixed $expectedResourceMetadata - */ - public function testXmlCreateResourceMetadata($expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.xml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath])); - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - public function testXmlDoesNotExistMetadataFactory() - { - $this->expectException(ResourceClassNotFoundException::class); - $this->expectExceptionMessage('Resource "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\ThisDoesNotExist" not found.'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcenotfound.xml'; - $factory = new ExtractorResourceNameCollectionFactory(new XmlExtractor([$configPath])); - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath])); - - foreach ($factory->create() as $resourceName) { - $resourceMetadataFactory->create($resourceName); - } - } - - /** - * @dataProvider optionalResourceMetadataProvider - * - * @param mixed $expectedResourceMetadata - */ - public function testXmlOptionalResourceMetadata($expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcesoptional.xml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath])); - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - /** - * @expectedDeprecation Configuring "%s" tags without using a parent "%ss" tag is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3 - * @group legacy - * @dataProvider legacyOperationsResourceMetadataProvider - * - * @param mixed $expectedResourceMetadata - */ - public function testLegacyOperationsResourceMetadata($expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/legacyoperations.xml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath])); - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - /** - * @dataProvider noCollectionOperationsResourceMetadataProvider - * - * @param mixed $expectedResourceMetadata - */ - public function testXmlNoCollectionOperationsResourceMetadata($expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/nocollectionoperations.xml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath])); - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - /** - * @dataProvider noItemOperationsResourceMetadataProvider - * - * @param mixed $expectedResourceMetadata - */ - public function testXmlNoItemOperationsResourceMetadata($expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/noitemoperations.xml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath])); - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - public function testInvalidXmlResourceMetadataFactory() - { - $this->expectException(InvalidArgumentException::class); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcesinvalid.xml'; - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath])); - - $resourceMetadataFactory->create(FileConfigDummy::class); - } - - /** - * @dataProvider optionalResourceMetadataProvider - */ - public function testXmlParentResourceMetadataFactory(ResourceMetadata $expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcesoptional.xml'; - - $decorated = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decorated->create(FileConfigDummy::class)->willReturn(new ResourceMetadata(null, 'test'))->shouldBeCalled(); - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath]), $decorated->reveal()); - - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - $expectedResourceMetadata = $expectedResourceMetadata->withDescription('test'); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - /** - * @dataProvider resourceMetadataProvider - */ - public function testXmlExistingParentResourceMetadataFactory(ResourceMetadata $expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.xml'; - - $decorated = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decorated->create(FileConfigDummy::class)->willReturn($expectedResourceMetadata)->shouldBeCalled(); - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new XmlExtractor([$configPath]), $decorated->reveal()); - - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - /** - * @dataProvider resourceMetadataProvider - */ - public function testYamlCreateResourceMetadata(ResourceMetadata $expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.yml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath])); - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - public function testYamlDoesNotExistMetadataFactory() - { - $this->expectException(ResourceClassNotFoundException::class); - $this->expectExceptionMessage('Resource "ApiPlatform\\Tests\\Fixtures\\TestBundle\\Entity\\ThisDoesNotExist" not found.'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcenotfound.yml'; - $factory = new ExtractorResourceNameCollectionFactory(new YamlExtractor([$configPath])); - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath])); - - foreach ($factory->create() as $resourceName) { - $resourceMetadataFactory->create($resourceName); - } - } - - /** - * @dataProvider optionalResourceMetadataProvider - * - * @param mixed $expectedResourceMetadata - */ - public function testYamlOptionalResourceMetadata($expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcesoptional.yml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath])); - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - /** - * @dataProvider resourceMetadataProvider - */ - public function testYamlSingleResourceMetadata(ResourceMetadata $expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/single_resource.yml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath])); - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - /** - * @dataProvider optionalResourceMetadataProvider - */ - public function testYamlParentResourceMetadataFactory(ResourceMetadata $expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcesoptional.yml'; - - $decorated = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decorated->create(FileConfigDummy::class)->willReturn(new ResourceMetadata(null, 'test'))->shouldBeCalled(); - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath]), $decorated->reveal()); - - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - $expectedResourceMetadata = $expectedResourceMetadata->withDescription('test'); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - /** - * @dataProvider resourceMetadataProvider - */ - public function testYamlExistingParentResourceMetadataFactory(ResourceMetadata $expectedResourceMetadata) - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.yml'; - - $decorated = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decorated->create(FileConfigDummy::class)->willReturn($expectedResourceMetadata)->shouldBeCalled(); - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath]), $decorated->reveal()); - - $resourceMetadata = $resourceMetadataFactory->create(FileConfigDummy::class); - - $this->assertEquals($expectedResourceMetadata, $resourceMetadata); - } - - public function testCreateWithMalformedYaml() - { - $this->expectException(InvalidArgumentException::class); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/parse_exception.yml'; - - (new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath])))->create(FileConfigDummy::class); - } - - public function testCreateWithBadDeclaration() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/"ApiPlatform\\\\Tests\\\\Fixtures\\\\TestBundle\\\\Entity\\\\Dummy" setting is expected to be null or an array, string given in ".+\\/Fixtures\\/FileConfigurations\\/bad_declaration\\.yml"\\./'); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/bad_declaration.yml'; - - (new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath])))->create(FileConfigDummy::class); - } - - public function testCreateShortNameResourceMetadataForClassWithoutNamespace() - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourceswithoutnamespace.yml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath])); - $shortNameResourceMetadataFactory = new ShortNameResourceMetadataFactory($resourceMetadataFactory); - - $resourceMetadata = $shortNameResourceMetadataFactory->create(\DateTime::class); - $this->assertSame(\DateTime::class, $resourceMetadata->getShortName()); - } - - public function testItSupportsInterfaceAsAResource() - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/interface_resource.yml'; - - $resourceMetadataFactory = new ExtractorResourceMetadataFactory(new YamlExtractor([$configPath])); - $shortNameResourceMetadataFactory = new ShortNameResourceMetadataFactory($resourceMetadataFactory); - - $resourceMetadata = $shortNameResourceMetadataFactory->create(DummyResourceInterface::class); - $this->assertSame('DummyResourceInterface', $resourceMetadata->getShortName()); - } - - public function testItFallbacksToDefaultConfiguration() - { - $defaults = [ - 'shortName' => 'Default shortname should not be ignored', - 'description' => 'CHANGEME!', - 'collection_operations' => ['get'], - 'item_operations' => ['get', 'put'], - 'attributes' => [ - 'pagination_items_per_page' => 4, - 'pagination_maximum_items_per_page' => 6, - 'stateless' => true, - ], - ]; - $resourceConfiguration = [ - Dummy::class => [ - 'shortName' => null, - 'description' => null, - 'subresourceOperations' => null, - 'itemOperations' => ['get', 'delete'], - 'attributes' => [ - 'pagination_items_per_page' => null, - 'pagination_maximum_items_per_page' => 10, - 'stateless' => false, - ], - ], - ]; - - $extractor = new class($resourceConfiguration) implements ExtractorInterface { - private $resources; - - public function __construct(array $resources) - { - $this->resources = $resources; - } - - public function getResources(): array - { - return $this->resources; - } - }; - $factory = new ExtractorResourceMetadataFactory($extractor, null, $defaults); - $metadata = $factory->create(Dummy::class); - - $this->assertNull($metadata->getShortName()); - $this->assertEquals('CHANGEME!', $metadata->getDescription()); - $this->assertEquals(['get'], $metadata->getCollectionOperations()); - $this->assertEquals(['get', 'delete'], $metadata->getItemOperations()); - $this->assertEquals(4, $metadata->getAttribute('pagination_items_per_page')); - $this->assertEquals(10, $metadata->getAttribute('pagination_maximum_items_per_page')); - $this->assertFalse($metadata->getAttribute('stateless')); - } -} diff --git a/tests/Core/Metadata/Resource/Factory/ExtractorResourceNameCollectionFactoryTest.php b/tests/Core/Metadata/Resource/Factory/ExtractorResourceNameCollectionFactoryTest.php deleted file mode 100644 index a8617b5216a..00000000000 --- a/tests/Core/Metadata/Resource/Factory/ExtractorResourceNameCollectionFactoryTest.php +++ /dev/null @@ -1,79 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Extractor\XmlExtractor; -use ApiPlatform\Core\Metadata\Extractor\YamlExtractor; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\Metadata\Resource\Factory\ExtractorResourceNameCollectionFactory; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FileConfigDummy; -use PHPUnit\Framework\TestCase; - -/** - * Tests extractor resource name collection factory. - * - * @author Antoine Bluchet - */ -class ExtractorResourceNameCollectionFactoryTest extends TestCase -{ - public function testXmlResourceName() - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.xml'; - $factory = new ExtractorResourceNameCollectionFactory(new XmlExtractor([$configPath])); - - $this->assertEquals($factory->create(), new ResourceNameCollection([ - Dummy::class, - FileConfigDummy::class, - ])); - } - - public function testInvalidExtractorResourceNameCollectionFactory() - { - $this->expectException(InvalidArgumentException::class); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resourcesinvalid.xml'; - $factory = new ExtractorResourceNameCollectionFactory(new XmlExtractor([$configPath])); - $factory->create(); - } - - public function testYamlResourceName() - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/resources.yml'; - $factory = new ExtractorResourceNameCollectionFactory(new YamlExtractor([$configPath])); - - $this->assertEquals($factory->create(), new ResourceNameCollection([ - Dummy::class, - FileConfigDummy::class, - ])); - } - - public function testYamlSingleResourceName() - { - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/single_resource.yml'; - $factory = new ExtractorResourceNameCollectionFactory(new YamlExtractor([$configPath])); - - $this->assertEquals($factory->create(), new ResourceNameCollection([FileConfigDummy::class])); - } - - public function testCreateWithMalformedYaml() - { - $this->expectException(InvalidArgumentException::class); - - $configPath = __DIR__.'/../../../../Fixtures/FileConfigurations/parse_exception.yml'; - - (new ExtractorResourceNameCollectionFactory(new YamlExtractor([$configPath])))->create(); - } -} diff --git a/tests/Core/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php b/tests/Core/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php deleted file mode 100644 index 39c81511c39..00000000000 --- a/tests/Core/Metadata/Resource/Factory/FileConfigurationMetadataFactoryProvider.php +++ /dev/null @@ -1,114 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - -/** - * Resource metadata provider for file configured factories tests. - * - * @author Antoine Bluchet - */ -abstract class FileConfigurationMetadataFactoryProvider extends TestCase -{ - public function resourceMetadataProvider() - { - $resourceMetadata = new ResourceMetadata(); - - $metadata = [ - 'shortName' => 'thedummyshortname', - 'description' => 'Dummy resource', - 'itemOperations' => [ - 'my_op_name' => ['method' => 'GET'], - 'my_other_op_name' => ['method' => 'POST'], - ], - 'collectionOperations' => [ - 'my_collection_op' => ['method' => 'POST', 'path' => 'the/collection/path'], - ], - 'subresourceOperations' => [ - 'my_collection_subresource' => ['path' => 'the/subresource/path'], - ], - 'graphql' => [ - 'query' => [ - 'normalization_context' => [ - AbstractNormalizer::GROUPS => ['graphql'], - ], - ], - ], - 'iri' => 'someirischema', - 'attributes' => [ - 'normalization_context' => [ - AbstractNormalizer::GROUPS => ['default'], - ], - 'denormalization_context' => [ - AbstractNormalizer::GROUPS => ['default'], - ], - 'hydra_context' => [ - '@type' => 'hydra:Operation', - '@hydra:title' => 'File config Dummy', - ], - 'stateless' => true, - ], - ]; - - foreach (['shortName', 'description', 'itemOperations', 'collectionOperations', 'subresourceOperations', 'graphql', 'iri', 'attributes'] as $property) { - $wither = 'with'.ucfirst($property); - $resourceMetadata = $resourceMetadata->{$wither}($metadata[$property]); - } - - return [[$resourceMetadata]]; - } - - public function optionalResourceMetadataProvider() - { - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withItemOperations(['my_op_name' => ['method' => 'POST']]); - - return [[$resourceMetadata]]; - } - - public function noCollectionOperationsResourceMetadataProvider() - { - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withItemOperations(['my_op_name' => ['method' => 'POST']]); - $resourceMetadata = $resourceMetadata->withCollectionOperations([]); - - return [[$resourceMetadata]]; - } - - public function noItemOperationsResourceMetadataProvider() - { - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withCollectionOperations(['my_op_name' => ['method' => 'POST']]); - $resourceMetadata = $resourceMetadata->withItemOperations([]); - - return [[$resourceMetadata]]; - } - - public function legacyOperationsResourceMetadataProvider() - { - $resourceMetadata = new ResourceMetadata(); - $resourceMetadata = $resourceMetadata->withItemOperations([ - 'my_op_name' => ['method' => 'POST'], - 'my_other_op_name' => ['method' => 'GET'], - ]); - $resourceMetadata = $resourceMetadata->withCollectionOperations([ - 'my_op_name' => ['method' => 'POST'], - ]); - - return [[$resourceMetadata]]; - } -} diff --git a/tests/Core/Metadata/Resource/Factory/FormatsResourceMetadataFactoryTest.php b/tests/Core/Metadata/Resource/Factory/FormatsResourceMetadataFactoryTest.php deleted file mode 100644 index 6f247ff792f..00000000000 --- a/tests/Core/Metadata/Resource/Factory/FormatsResourceMetadataFactoryTest.php +++ /dev/null @@ -1,193 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\Factory\FormatsResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Exception\InvalidArgumentException; -use PHPUnit\Framework\TestCase; - -/** - * @group legacy - */ -class FormatsResourceMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - /** - * @dataProvider createProvider - */ - public function testCreate(ResourceMetadata $previous, ResourceMetadata $expected, array $formats = [], array $patchFormats = []): void - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn($previous); - - $actual = (new FormatsResourceMetadataFactory($resourceMetadataFactoryProphecy->reveal(), $formats, $patchFormats))->create('Foo'); - $this->assertEquals($expected, $actual); - } - - public function createProvider(): iterable - { - yield [ - new ResourceMetadata( - null, - null, - null, - ['get' => []], - ['get' => []], - ['formats' => 'json'], - ['get' => []] - ), - new ResourceMetadata( - null, - null, - null, - ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]], - ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]], - ['formats' => 'json'], - ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]] - ), - ['json' => ['application/json']], - ]; - - yield [ - new ResourceMetadata( - null, - null, - null, - ['get' => []], - ['get' => []], - ['formats' => ['json' => ['application/json']]], - ['get' => []] - ), - new ResourceMetadata( - null, - null, - null, - ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]], - ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]], - ['formats' => ['json' => ['application/json']]], - ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]] - ), - ]; - - yield [ - new ResourceMetadata( - null, - null, - null, - ['get' => []], - ['get' => []], - ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => 'text/csv']], - ['get' => []] - ), - new ResourceMetadata( - null, - null, - null, - ['get' => ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => ['text/csv']]]], - ['get' => ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => ['text/csv']]]], - ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => 'text/csv']], - ['get' => ['input_formats' => ['json' => ['application/json'], 'xml' => ['text/xml', 'application/xml']], 'output_formats' => ['csv' => ['text/csv']]]] - ), - ]; - - yield [ - new ResourceMetadata( - null, - null, - null, - ['patch' => ['method' => 'PATCH']] - ), - new ResourceMetadata( - null, - null, - null, - ['patch' => ['method' => 'PATCH', 'input_formats' => ['json' => ['application/merge-patch+json']], 'output_formats' => []]] - ), - [], - ['json' => ['application/merge-patch+json']], - ]; - - yield [ - new ResourceMetadata( - null, - null, - null, - ['get' => ['formats' => 'json']] - ), - new ResourceMetadata( - null, - null, - null, - ['get' => ['formats' => ['json' => ['application/json']], 'input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]] - ), - ['json' => ['application/json']], - ]; - - yield [ - new ResourceMetadata( - null, - null, - null, - ['get' => ['input_formats' => 'json', 'output_formats' => 'json']] - ), - new ResourceMetadata( - null, - null, - null, - ['get' => ['input_formats' => ['json' => ['application/json']], 'output_formats' => ['json' => ['application/json']]]] - ), - ['json' => ['application/json']], - ]; - } - - public function testInvalidFormatType(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The \'formats\' attributes value must be a string when trying to include an already configured format, object given.'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( - null, - null, - null, - null, - null, - ['formats' => [new \stdClass()]] - )); - - (new FormatsResourceMetadataFactory($resourceMetadataFactoryProphecy->reveal(), [], []))->create('Foo'); - } - - public function testNotConfiguredFormat(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('You either need to add the format \'xml\' to your project configuration or declare a mime type for it in your annotation.'); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata( - null, - null, - null, - null, - null, - ['formats' => ['xml']] - )); - - (new FormatsResourceMetadataFactory($resourceMetadataFactoryProphecy->reveal(), [], []))->create('Foo'); - } -} diff --git a/tests/Core/Metadata/Resource/Factory/InputOutputResourceMetadataFactoryTest.php b/tests/Core/Metadata/Resource/Factory/InputOutputResourceMetadataFactoryTest.php deleted file mode 100644 index 1d3f4ffa862..00000000000 --- a/tests/Core/Metadata/Resource/Factory/InputOutputResourceMetadataFactoryTest.php +++ /dev/null @@ -1,77 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\Factory\InputOutputResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\DummyEntity; -use PHPUnit\Framework\TestCase; - -/** - * @group legacy - */ -class InputOutputResourceMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - /** - * @dataProvider getAttributes - * - * @param mixed $attributes - * @param mixed $expected - */ - public function testInputOutputMetadata($attributes, $expected) - { - $resourceMetadata = (new ResourceMetadata(null))->withAttributes($attributes); - $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - $decorated = $decoratedProphecy->reveal(); - - $factory = new InputOutputResourceMetadataFactory($decorated); - $this->assertSame($expected, $factory->create('Foo')->getAttributes()['input']); - } - - /** - * @dataProvider getAttributes - * - * @param mixed $attributes - * @param mixed $expected - */ - public function testInputOutputViaGraphQlMetadata($attributes, $expected) - { - $resourceMetadata = (new ResourceMetadata(null))->withGraphQl(['create' => $attributes]); - $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - $decorated = $decoratedProphecy->reveal(); - - $factory = new InputOutputResourceMetadataFactory($decorated); - $this->assertSame($expected, $factory->create('Foo')->getGraphqlAttribute('create', 'input')); - } - - public function getAttributes(): array - { - return [ - // no input class defined - [[], null], - // input is a string - [['input' => DummyEntity::class], ['class' => DummyEntity::class, 'name' => 'DummyEntity']], - // input is false - [['input' => false], ['class' => null]], - // input is an array - [['input' => ['class' => DummyEntity::class, 'type' => 'Foo']], ['class' => DummyEntity::class, 'type' => 'Foo', 'name' => 'DummyEntity']], - ]; - } -} diff --git a/tests/Core/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php b/tests/Core/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php deleted file mode 100644 index 517ff91d407..00000000000 --- a/tests/Core/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php +++ /dev/null @@ -1,79 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\Factory\OperationResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; - -/** - * @author Kévin Dunglas - * @group legacy - */ -class OperationResourceMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - /** - * @dataProvider getMetadata - */ - public function testCreateOperation(ResourceMetadata $before, ResourceMetadata $after, array $formats = []): void - { - $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($before); - - $this->assertEquals($after, (new OperationResourceMetadataFactory($decoratedProphecy->reveal(), $formats))->create(Dummy::class)); - } - - public function getMetadata(): iterable - { - $jsonapi = ['jsonapi' => ['application/vnd.api+json']]; - - // Item operations - yield [new ResourceMetadata(null, null, null, null, [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['get', 'put', 'delete']), [], null, [], [])]; - yield [new ResourceMetadata(null, null, null, null, [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['get', 'put', 'patch', 'delete']), [], null, [], []), $jsonapi]; - yield [new ResourceMetadata(null, null, null, ['get'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['get']), [], null, [], [])]; - yield [new ResourceMetadata(null, null, null, ['put'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['put']), [], null, [], [])]; - yield [new ResourceMetadata(null, null, null, ['delete'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['delete']), [], null, [], [])]; - yield [new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch']], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch', 'stateless' => null]], [], null, [], [])]; - yield [new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch']], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch', 'stateless' => null]], [], null, [], []), $jsonapi]; - yield [new ResourceMetadata(null, null, null, ['untouched' => ['method' => 'GET']], [], null, [], []), new ResourceMetadata(null, null, null, ['untouched' => ['method' => 'GET', 'stateless' => null]], [], null, [], []), $jsonapi]; - yield [new ResourceMetadata(null, null, null, ['untouched_custom' => ['route_name' => 'custom_route']], [], null, [], []), new ResourceMetadata(null, null, null, ['untouched_custom' => ['route_name' => 'custom_route', 'stateless' => null]], [], null, [], []), $jsonapi]; - yield [new ResourceMetadata(null, null, null, ['stateless_operation' => ['method' => 'GET', 'stateless' => true]], [], null, [], []), new ResourceMetadata(null, null, null, ['stateless_operation' => ['method' => 'GET', 'stateless' => true]], [], null, [], []), $jsonapi]; - yield [new ResourceMetadata(null, null, null, ['statefull_attribute' => ['method' => 'GET']], [], ['stateless' => false], [], []), new ResourceMetadata(null, null, null, ['statefull_attribute' => ['method' => 'GET', 'stateless' => false]], [], ['stateless' => false], [], []), $jsonapi]; - - // Collection operations - yield [new ResourceMetadata(null, null, null, [], null, null, [], []), new ResourceMetadata(null, null, null, [], $this->getOperations(['get', 'post']), null, [], [])]; - yield [new ResourceMetadata(null, null, null, [], ['get'], null, [], []), new ResourceMetadata(null, null, null, [], $this->getOperations(['get']), null, [], [])]; - yield [new ResourceMetadata(null, null, null, [], ['post'], null, [], []), new ResourceMetadata(null, null, null, [], $this->getOperations(['post']), null, [], [])]; - yield [new ResourceMetadata(null, null, null, [], ['options' => ['method' => 'OPTIONS', 'route_name' => 'options']], null, [], []), new ResourceMetadata(null, null, null, [], ['options' => ['route_name' => 'options', 'method' => 'OPTIONS', 'stateless' => null]], null, [], [])]; - yield [new ResourceMetadata(null, null, null, [], ['untouched' => ['method' => 'GET']], null, [], []), new ResourceMetadata(null, null, null, [], ['untouched' => ['method' => 'GET', 'stateless' => null]], null, [], [])]; - yield [new ResourceMetadata(null, null, null, [], ['untouched_custom' => ['route_name' => 'custom_route']], null, [], []), new ResourceMetadata(null, null, null, [], ['untouched_custom' => ['route_name' => 'custom_route', 'stateless' => null]], null, [], [])]; - yield [new ResourceMetadata(null, null, null, [], ['statefull_operation' => ['method' => 'GET', 'stateless' => false]], null, null, []), new ResourceMetadata(null, null, null, [], ['statefull_operation' => ['method' => 'GET', 'stateless' => false]], null, null, []), $jsonapi]; - yield [new ResourceMetadata(null, null, null, [], ['stateless_attribute' => ['method' => 'GET']], ['stateless' => true], null, []), new ResourceMetadata(null, null, null, [], ['stateless_attribute' => ['method' => 'GET', 'stateless' => true]], ['stateless' => true], null, []), $jsonapi]; - } - - private function getOperations(array $names): array - { - $operations = []; - foreach ($names as $name) { - $operations[$name] = ['method' => strtoupper($name), 'stateless' => null]; - } - - return $operations; - } -} diff --git a/tests/Core/Metadata/Resource/Factory/PhpDocResourceMetadataFactoryTest.php b/tests/Core/Metadata/Resource/Factory/PhpDocResourceMetadataFactoryTest.php deleted file mode 100644 index 7f75db1f485..00000000000 --- a/tests/Core/Metadata/Resource/Factory/PhpDocResourceMetadataFactoryTest.php +++ /dev/null @@ -1,62 +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\Tests\Metadata\Resource\Factory; - -use ApiPlatform\Core\Metadata\Resource\Factory\PhpDocResourceMetadataFactory; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\ClassWithNoDocBlock; -use ApiPlatform\Tests\Fixtures\DummyEntity; -use PHPUnit\Framework\TestCase; - -/** - * @group legacy - */ -class PhpDocResourceMetadataFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testExistingDescription() - { - $resourceMetadata = new ResourceMetadata(null, 'My desc'); - $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedProphecy->create('Foo')->willReturn($resourceMetadata)->shouldBeCalled(); - $decorated = $decoratedProphecy->reveal(); - - $factory = new PhpDocResourceMetadataFactory($decorated); - $this->assertSame($resourceMetadata, $factory->create('Foo')); - } - - public function testNoDocBlock() - { - $resourceMetadata = new ResourceMetadata(); - $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedProphecy->create(ClassWithNoDocBlock::class)->willReturn($resourceMetadata)->shouldBeCalled(); - $decorated = $decoratedProphecy->reveal(); - - $factory = new PhpDocResourceMetadataFactory($decorated); - $this->assertSame($resourceMetadata, $factory->create(ClassWithNoDocBlock::class)); - } - - public function testExtractDescription() - { - $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $decoratedProphecy->create(DummyEntity::class)->willReturn(new ResourceMetadata())->shouldBeCalled(); - $decorated = $decoratedProphecy->reveal(); - - $factory = new PhpDocResourceMetadataFactory($decorated); - $this->assertSame('My dummy entity.', $factory->create(DummyEntity::class)->getDescription()); - } -} diff --git a/tests/Core/Metadata/Resource/ResourceMetadataTest.php b/tests/Core/Metadata/Resource/ResourceMetadataTest.php deleted file mode 100644 index 863f5a447f4..00000000000 --- a/tests/Core/Metadata/Resource/ResourceMetadataTest.php +++ /dev/null @@ -1,111 +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\Tests\Metadata\Resource; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use PHPUnit\Framework\TestCase; - -/** - * @author Kévin Dunglas - */ -class ResourceMetadataTest extends TestCase -{ - public function testValueObject() - { - $metadata = new ResourceMetadata('shortName', 'desc', 'http://example.com/foo', ['iop1' => ['foo' => 'a'], 'iop2' => ['bar' => 'b']], ['cop1' => ['foo' => 'c'], 'cop2' => ['bar' => 'd']], ['baz' => 'bar'], ['sop1' => ['sub' => 'bus']], ['query' => ['foo' => 'graphql']]); - $this->assertSame('shortName', $metadata->getShortName()); - $this->assertSame('desc', $metadata->getDescription()); - $this->assertSame('http://example.com/foo', $metadata->getIri()); - $this->assertSame(['iop1' => ['foo' => 'a'], 'iop2' => ['bar' => 'b']], $metadata->getItemOperations()); - $this->assertSame('a', $metadata->getItemOperationAttribute('iop1', 'foo', 'z')); - $this->assertSame('a', $metadata->getTypedOperationAttribute(OperationType::ITEM, 'iop1', 'foo', 'z')); - $this->assertSame('bar', $metadata->getItemOperationAttribute('iop1', 'baz', 'z', true)); - $this->assertSame('bar', $metadata->getItemOperationAttribute(null, 'baz', 'z', true)); - $this->assertSame('z', $metadata->getItemOperationAttribute('iop1', 'notExist', 'z', true)); - $this->assertSame('z', $metadata->getItemOperationAttribute('notExist', 'notExist', 'z', true)); - $this->assertSame(['cop1' => ['foo' => 'c'], 'cop2' => ['bar' => 'd']], $metadata->getCollectionOperations()); - $this->assertSame('c', $metadata->getCollectionOperationAttribute('cop1', 'foo', 'z')); - $this->assertSame('c', $metadata->getTypedOperationAttribute(OperationType::COLLECTION, 'cop1', 'foo', 'z')); - $this->assertSame('bar', $metadata->getCollectionOperationAttribute('cop1', 'baz', 'z', true)); - $this->assertSame('bar', $metadata->getCollectionOperationAttribute(null, 'baz', 'z', true)); - $this->assertSame('z', $metadata->getCollectionOperationAttribute('cop1', 'notExist', 'z', true)); - $this->assertSame('z', $metadata->getCollectionOperationAttribute('notExist', 'notExist', 'z', true)); - $this->assertSame(['baz' => 'bar'], $metadata->getAttributes()); - $this->assertSame('bar', $metadata->getAttribute('baz')); - $this->assertSame('z', $metadata->getAttribute('notExist', 'z')); - $this->assertSame(['sop1' => ['sub' => 'bus']], $metadata->getSubresourceOperations()); - $this->assertSame('bus', $metadata->getSubresourceOperationAttribute('sop1', 'sub')); - $this->assertSame('bus', $metadata->getTypedOperationAttribute(OperationType::SUBRESOURCE, 'sop1', 'sub')); - $this->assertSame('sub', $metadata->getSubresourceOperationAttribute('sop1', 'bus', 'sub')); - $this->assertSame('bar', $metadata->getSubresourceOperationAttribute('sop1', 'baz', 'sub', true)); - $this->assertSame('graphql', $metadata->getGraphqlAttribute('query', 'foo')); - $this->assertSame('bar', $metadata->getGraphqlAttribute('query', 'baz', null, true)); - $this->assertSame('hey', $metadata->getGraphqlAttribute('query', 'notExist', 'hey', true)); - - $newMetadata = $metadata->withShortName('name'); - $this->assertNotSame($metadata, $newMetadata); - $this->assertSame('name', $newMetadata->getShortName()); - - $newMetadata = $metadata->withDescription('description'); - $this->assertNotSame($metadata, $newMetadata); - $this->assertSame('description', $newMetadata->getDescription()); - - $newMetadata = $metadata->withIri('foo:bar'); - $this->assertNotSame($metadata, $newMetadata); - $this->assertSame('foo:bar', $newMetadata->getIri()); - - $newMetadata = $metadata->withItemOperations(['a' => ['b' => 'c']]); - $this->assertNotSame($metadata, $newMetadata); - $this->assertSame(['a' => ['b' => 'c']], $newMetadata->getItemOperations()); - } - - /** - * @dataProvider getWithMethods - * - * @param mixed $value - */ - public function testWithMethods(string $name, $value) - { - $metadata = new ResourceMetadata(); - $newMetadata = $metadata->{"with$name"}($value); - $this->assertNotSame($metadata, $newMetadata); - $this->assertSame($value, $newMetadata->{"get$name"}()); - } - - public function testGetOperationAttributeFallback() - { - $metadata = new ResourceMetadata(); - $this->assertSame('okay', $metadata->getOperationAttribute([], 'doh', 'okay')); - } - - public function testGetOperationAttributeFallbackToResourceAttribute() - { - $metadata = new ResourceMetadata(null, null, null, null, null, ['doh' => 'nuts']); - $this->assertSame('nuts', $metadata->getOperationAttribute([], 'doh', 'okay', true)); - } - - public function getWithMethods(): array - { - return [ - ['ShortName', 'shortName'], - ['Description', 'description'], - ['Iri', 'iri'], - ['ItemOperations', ['a' => ['b' => 'c']]], - ['CollectionOperations', ['a' => ['b' => 'c']]], - ['Attributes', ['a' => ['b' => 'c']]], - ['Graphql', ['query' => ['b' => 'c']]], - ]; - } -} diff --git a/tests/Core/Metadata/Resource/ResourceNameCollectionTest.php b/tests/Core/Metadata/Resource/ResourceNameCollectionTest.php deleted file mode 100644 index a36905990eb..00000000000 --- a/tests/Core/Metadata/Resource/ResourceNameCollectionTest.php +++ /dev/null @@ -1,32 +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\Tests\Metadata\Resource; - -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use PHPUnit\Framework\TestCase; - -/** - * @author Kévin Dunglas - */ -class ResourceNameCollectionTest extends TestCase -{ - public function testValueObject() - { - $collection = new ResourceNameCollection(['foo', 'bar']); - $this->assertInstanceOf(\Countable::class, $collection); - $this->assertInstanceOf(\IteratorAggregate::class, $collection); - $this->assertCount(2, $collection); - $this->assertInstanceOf(\ArrayIterator::class, $collection->getIterator()); - } -} diff --git a/tests/Core/Metadata/schema/XmlSchemaTest.php b/tests/Core/Metadata/schema/XmlSchemaTest.php deleted file mode 100644 index a54fa18e6dc..00000000000 --- a/tests/Core/Metadata/schema/XmlSchemaTest.php +++ /dev/null @@ -1,38 +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\Tests\Metadata\schema; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Config\Util\XmlUtils; - -/** - * @author Grégoire Hébert - */ -class XmlSchemaTest extends TestCase -{ - public function testSchema(): void - { - $fixtures = __DIR__.'/../../../Fixtures/Metadata/schema/'; - $schema = __DIR__.'/../../../../src/Core/Metadata/schema/metadata.xsd'; - - try { - XmlUtils::loadFile($fixtures.'invalid.xml', $schema); - $this->fail(); - } catch (\InvalidArgumentException $e) { - $this->assertStringContainsString('ERROR 1845', $e->getMessage()); - } - - $this->assertInstanceOf(\DOMDocument::class, XmlUtils::loadFile($fixtures.'valid.xml', $schema)); - } -} diff --git a/tests/Core/OpenApi/Factory/OpenApiFactoryTest.php b/tests/Core/OpenApi/Factory/OpenApiFactoryTest.php deleted file mode 100644 index 12f9403e244..00000000000 --- a/tests/Core/OpenApi/Factory/OpenApiFactoryTest.php +++ /dev/null @@ -1,818 +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\Tests\OpenApi\Factory; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouterOperationPathResolver; -use ApiPlatform\Core\JsonSchema\SchemaFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\OpenApi\Factory\OpenApiFactory; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactory; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; -use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\JsonSchema\Schema; -use ApiPlatform\JsonSchema\TypeFactory; -use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\OpenApi\Model; -use ApiPlatform\OpenApi\OpenApi; -use ApiPlatform\OpenApi\Options; -use ApiPlatform\OpenApi\Serializer\OpenApiNormalizer; -use ApiPlatform\PathResolver\CustomOperationPathResolver; -use ApiPlatform\PathResolver\OperationPathResolver; -use ApiPlatform\State\Pagination\PaginationOptions; -use ApiPlatform\Tests\Fixtures\DummyFilter; -use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Answer; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Psr\Container\ContainerInterface; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Routing\Route; -use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\RouterInterface; -use Symfony\Component\Serializer\Encoder\JsonEncoder; -use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; - -/** - * @group legacy - */ -class OpenApiFactoryTest extends TestCase -{ - use ProphecyTrait; - - private const OPERATION_FORMATS = [ - 'input_formats' => ['jsonld' => ['application/ld+json']], - 'output_formats' => ['jsonld' => ['application/ld+json']], - ]; - - public function testInvoke(): void - { - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT'] + self::OPERATION_FORMATS, - 'delete' => ['method' => 'DELETE'] + self::OPERATION_FORMATS, - 'custom' => ['method' => 'HEAD', 'path' => '/foo/{id}', 'openapi_context' => [ - 'x-visibility' => 'hide', - 'description' => 'Custom description', - 'parameters' => [ - ['description' => 'Test parameter', 'name' => 'param', 'in' => 'path', 'required' => true], - ['description' => 'Replace parameter', 'name' => 'id', 'in' => 'path', 'required' => true, 'schema' => ['type' => 'string', 'format' => 'uuid']], - ], - 'tags' => ['Dummy', 'Profile'], - 'responses' => [ - '202' => [ - 'description' => 'Success', - 'content' => [ - 'application/json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'headers' => [ - 'Foo' => ['description' => 'A nice header', 'schema' => ['type' => 'integer']], - ], - 'links' => [ - 'Foo' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - '205' => [], - ], - 'requestBody' => [ - 'required' => true, - 'description' => 'Custom request body', - 'content' => [ - 'multipart/form-data' => [ - 'schema' => [ - 'type' => 'object', - 'properties' => [ - 'file' => [ - 'type' => 'string', - 'format' => 'binary', - ], - ], - ], - ], - ], - ], - ]] + self::OPERATION_FORMATS, - 'formats' => ['method' => 'PUT', 'path' => '/formatted/{id}', 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]], - ], - [ - 'get' => ['method' => 'GET', 'openapi_context' => [ - 'parameters' => [ - ['description' => 'Test modified collection page number', 'name' => 'page', 'in' => 'query', 'required' => false, 'schema' => ['type' => 'integer', 'default' => 1], 'allowEmptyValue' => true], - ], - ]] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - 'filtered' => ['method' => 'GET', 'filters' => ['f1', 'f2', 'f3', 'f4', 'f5'], 'path' => '/filtered'] + self::OPERATION_FORMATS, - 'paginated' => ['method' => 'GET', 'pagination_client_enabled' => true, 'pagination_client_items_per_page' => true, 'pagination_items_per_page' => 20, 'pagination_maximum_items_per_page' => 80, 'path' => '/paginated'] + self::OPERATION_FORMATS, - ], - [ - 'pagination_client_items_per_page' => true, - 'output' => ['class' => OutputDto::class], - ] - ); - - $subresourceOperationFactoryProphecy = $this->prophesize(SubresourceOperationFactoryInterface::class); - $subresourceOperationFactoryProphecy->create(Argument::any())->willReturn([]); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class])); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate', 'enum'])); - $propertyNameCollectionFactoryProphecy->create(OutputDto::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate', 'enum'])); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false, null, null, null, true, null, null, null, null, null, null, null)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [], null, null, null, null, ['minLength' => 3, 'maxLength' => 20, 'pattern' => '^dummyPattern$'])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'description', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an initializable but not writable property.', true, false, true, true, false, false, null, null, [], null, true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyDate', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTime::class), 'This is a \DateTimeInterface object.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'enum', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an enum.', true, true, true, true, false, false, null, null, ['openapi_context' => ['type' => 'string', 'enum' => ['one', 'two'], 'example' => 'one']])); - $propertyMetadataFactoryProphecy->create(OutputDto::class, 'id', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false, null, null, null, true, null, null, null, null, null, null, null)); - $propertyMetadataFactoryProphecy->create(OutputDto::class, 'name', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [], null, null, null, null, ['minLength' => 3, 'maxLength' => 20, 'pattern' => '^dummyPattern$'])); - $propertyMetadataFactoryProphecy->create(OutputDto::class, 'description', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an initializable but not writable property.', true, false, true, true, false, false, null, null, [], null, true)); - $propertyMetadataFactoryProphecy->create(OutputDto::class, 'dummyDate', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTime::class), 'This is a \DateTimeInterface object.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(OutputDto::class, 'enum', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an enum.', true, true, true, true, false, false, null, null, ['openapi_context' => ['type' => 'string', 'enum' => ['one', 'two'], 'example' => 'one']])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $filters = [ - 'f1' => new DummyFilter(['name' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => true, - 'strategy' => 'exact', - 'openapi' => ['example' => 'bar', 'deprecated' => true, 'allowEmptyValue' => true, 'allowReserved' => true, 'explode' => true], - ]]), - 'f2' => new DummyFilter(['ha' => [ - 'property' => 'foo', - 'type' => 'int', - 'required' => false, - 'strategy' => 'partial', - ]]), - 'f3' => new DummyFilter(['toto' => [ - 'property' => 'name', - 'type' => 'array', - 'is_collection' => true, - 'required' => true, - 'strategy' => 'exact', - ]]), - 'f4' => new DummyFilter(['order[name]' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => false, - 'schema' => [ - 'type' => 'string', - 'enum' => ['asc', 'desc'], - ], - ]]), - ]; - - foreach ($filters as $filterId => $filter) { - $filterLocatorProphecy->has($filterId)->willReturn(true)->shouldBeCalled(); - $filterLocatorProphecy->get($filterId)->willReturn($filter)->shouldBeCalled(); - } - - $filterLocatorProphecy->has('f5')->willReturn(false)->shouldBeCalled(); - - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $typeFactory = new TypeFactory(); - $schemaFactory = new SchemaFactory($typeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); - $typeFactory->setSchemaFactory($schemaFactory); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $factory = new OpenApiFactory( - $resourceNameCollectionFactoryProphecy->reveal(), - $resourceMetadataFactory, - $propertyNameCollectionFactory, - $propertyMetadataFactory, - $schemaFactory, - $typeFactory, - $operationPathResolver, - $filterLocatorProphecy->reveal(), - $subresourceOperationFactoryProphecy->reveal(), - $identifiersExtractorProphecy->reveal(), - [], - new Options('Test API', 'This is a test API.', '1.2.3', true, 'oauth2', 'authorizationCode', '/oauth/v2/token', '/oauth/v2/auth', '/oauth/v2/refresh', ['scope param'], [ - 'header' => [ - 'type' => 'header', - 'name' => 'Authorization', - ], - 'query' => [ - 'type' => 'query', - 'name' => 'key', - ], - ]), - new PaginationOptions(true, 'page', true, 'itemsPerPage', true, 'pagination') - ); - - $dummySchema = new Schema('openapi'); - // $dummySchema = new Model\Schema(false, null, false, false, null, ['url' => 'http://schema.example.com/Dummy']); - $dummySchema->setDefinitions(new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - 'minLength' => 3, - 'maxLength' => 20, - 'pattern' => '^dummyPattern$', - ]), - 'description' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is an initializable but not writable property.', - ]), - 'dummy_date' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a \DateTimeInterface object.', - 'format' => 'date-time', - 'nullable' => true, - ]), - 'enum' => new \ArrayObject([ - 'type' => 'string', - 'enum' => ['one', 'two'], - 'example' => 'one', - 'description' => 'This is an enum.', - ]), - ], - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - ])); - - $openApi = $factory(['base_url' => '/app_dev.php/']); - - $this->assertInstanceOf(OpenApi::class, $openApi); - $this->assertEquals($openApi->getInfo(), new Model\Info('Test API', '1.2.3', 'This is a test API.')); - $this->assertEquals($openApi->getServers(), [new Model\Server('/app_dev.php/')]); - - $components = $openApi->getComponents(); - $this->assertInstanceOf(Model\Components::class, $components); - - $this->assertEquals($components->getSchemas(), new \ArrayObject(['Dummy' => $dummySchema->getDefinitions(), 'Dummy.OutputDto' => $dummySchema->getDefinitions()])); - - $this->assertEquals($components->getSecuritySchemes(), new \ArrayObject([ - 'oauth' => new Model\SecurityScheme('oauth2', 'OAuth 2.0 authorization code Grant', null, null, null, null, new Model\OAuthFlows(null, null, null, new Model\OAuthFlow('/oauth/v2/auth', '/oauth/v2/token', '/oauth/v2/refresh', new \ArrayObject(['scope param'])))), - 'header' => new Model\SecurityScheme('apiKey', 'Value for the Authorization header parameter.', 'Authorization', 'header'), - 'query' => new Model\SecurityScheme('apiKey', 'Value for the key query parameter.', 'key', 'query'), - ])); - - $this->assertSame([ - ['oauth' => []], - ['header' => []], - ['query' => []], - ], $openApi->getSecurity()); - - $paths = $openApi->getPaths(); - $dummiesPath = $paths->getPath('/dummies'); - $this->assertNotNull($dummiesPath); - foreach (['Put', 'Head', 'Trace', 'Delete', 'Options', 'Patch'] as $method) { - $this->assertNull($dummiesPath->{'get'.$method}()); - } - - $this->assertEquals($dummiesPath->getGet(), new Model\Operation( - 'getDummyCollection', - ['Dummy'], - [ - '200' => new Model\Response('Dummy collection', new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject(new \ArrayObject([ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy.OutputDto'], - ]))), - ])), - ], - 'Retrieves the collection of Dummy resources.', - 'Retrieves the collection of Dummy resources.', - null, - [ - new Model\Parameter('page', 'query', 'Test modified collection page number', false, false, true, [ - 'type' => 'integer', - 'default' => 1, - ]), - new Model\Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [ - 'type' => 'integer', - 'default' => 30, - 'minimum' => 0, - ]), - new Model\Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [ - 'type' => 'boolean', - ]), - ] - )); - - $this->assertEquals($dummiesPath->getPost(), new Model\Operation( - 'postDummyCollection', - ['Dummy'], - [ - '201' => new Model\Response( - 'Dummy resource created', - new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject(new \ArrayObject(['$ref' => '#/components/schemas/Dummy.OutputDto']))), - ]), - null, - new \ArrayObject(['GetDummyItem' => new Model\Link('getDummyItem', new \ArrayObject(['id' => '$response.body#/id']), null, 'The `id` value returned in the response can be used as the `id` parameter in `GET /dummies/{id}`.')]) - ), - '400' => new Model\Response('Invalid input'), - '422' => new Model\Response('Unprocessable entity'), - ], - 'Creates a Dummy resource.', - 'Creates a Dummy resource.', - null, - [], - new Model\RequestBody( - 'The new Dummy resource', - new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject(new \ArrayObject(['$ref' => '#/components/schemas/Dummy']))), - ]), - true - ) - )); - - $dummyPath = $paths->getPath('/dummies/{id}'); - $this->assertNotNull($dummyPath); - foreach (['Post', 'Head', 'Trace', 'Options', 'Patch'] as $method) { - $this->assertNull($dummyPath->{'get'.$method}()); - } - - $this->assertEquals($dummyPath->getGet(), new Model\Operation( - 'getDummyItem', - ['Dummy'], - [ - '200' => new Model\Response( - 'Dummy resource', - new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject(new \ArrayObject(['$ref' => '#/components/schemas/Dummy.OutputDto']))), - ]) - ), - '404' => new Model\Response('Resource not found'), - ], - 'Retrieves a Dummy resource.', - 'Retrieves a Dummy resource.', - null, - [new Model\Parameter('id', 'path', 'Resource identifier', true, false, false, ['type' => 'string'])] - )); - - $this->assertEquals($dummyPath->getPut(), new Model\Operation( - 'putDummyItem', - ['Dummy'], - [ - '200' => new Model\Response( - 'Dummy resource updated', - new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject(['$ref' => '#/components/schemas/Dummy.OutputDto'])), - ]), - null, - new \ArrayObject(['GetDummyItem' => new Model\Link('getDummyItem', new \ArrayObject(['id' => '$response.body#/id']), null, 'The `id` value returned in the response can be used as the `id` parameter in `GET /dummies/{id}`.')]) - ), - '400' => new Model\Response('Invalid input'), - '422' => new Model\Response('Unprocessable entity'), - '404' => new Model\Response('Resource not found'), - ], - 'Replaces the Dummy resource.', - 'Replaces the Dummy resource.', - null, - [new Model\Parameter('id', 'path', 'Resource identifier', true, false, false, ['type' => 'string'])], - new Model\RequestBody( - 'The updated Dummy resource', - new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject(['$ref' => '#/components/schemas/Dummy'])), - ]), - true - ) - )); - - $this->assertEquals($dummyPath->getDelete(), new Model\Operation( - 'deleteDummyItem', - ['Dummy'], - [ - '204' => new Model\Response('Dummy resource deleted'), - '404' => new Model\Response('Resource not found'), - ], - 'Removes the Dummy resource.', - 'Removes the Dummy resource.', - null, - [new Model\Parameter('id', 'path', 'Resource identifier', true, false, false, ['type' => 'string'])] - )); - - $customPath = $paths->getPath('/foo/{id}'); - $this->assertEquals($customPath->getHead(), new Model\Operation( - 'customDummyItem', - ['Dummy', 'Profile'], - [ - '202' => new Model\Response('Success', new \ArrayObject([ - 'application/json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ]), new \ArrayObject([ - 'Foo' => ['description' => 'A nice header', 'schema' => ['type' => 'integer']], - ]), new \ArrayObject([ - 'Foo' => ['$ref' => '#/components/schemas/Dummy'], - ])), - '205' => new Model\Response(), - '404' => new Model\Response('Resource not found'), - ], - 'Dummy', - 'Custom description', - null, - [new Model\Parameter('param', 'path', 'Test parameter', true), new Model\Parameter('id', 'path', 'Replace parameter', true, false, false, ['type' => 'string', 'format' => 'uuid'])], - new Model\RequestBody('Custom request body', new \ArrayObject([ - 'multipart/form-data' => [ - 'schema' => [ - 'type' => 'object', - 'properties' => [ - 'file' => [ - 'type' => 'string', - 'format' => 'binary', - ], - ], - ], - ], - ]), true), - null, - false, - null, - null, - ['x-visibility' => 'hide'] - )); - - $formattedPath = $paths->getPath('/formatted/{id}'); - $this->assertEquals($formattedPath->getPut(), new Model\Operation( - 'formatsDummyItem', - ['Dummy'], - [ - '200' => new Model\Response( - 'Dummy resource updated', - new \ArrayObject([ - 'application/json' => new Model\MediaType(new \ArrayObject(['$ref' => '#/components/schemas/Dummy.OutputDto'])), - 'text/csv' => new Model\MediaType(new \ArrayObject(['$ref' => '#/components/schemas/Dummy.OutputDto'])), - ]), - null, - new \ArrayObject(['GetDummyItem' => new Model\Link('getDummyItem', new \ArrayObject(['id' => '$response.body#/id']), null, 'The `id` value returned in the response can be used as the `id` parameter in `GET /dummies/{id}`.')]) - ), - '400' => new Model\Response('Invalid input'), - '422' => new Model\Response('Unprocessable entity'), - '404' => new Model\Response('Resource not found'), - ], - 'Replaces the Dummy resource.', - 'Replaces the Dummy resource.', - null, - [new Model\Parameter('id', 'path', 'Resource identifier', true, false, false, ['type' => 'string'])], - new Model\RequestBody( - 'The updated Dummy resource', - new \ArrayObject([ - 'application/json' => new Model\MediaType(new \ArrayObject(['$ref' => '#/components/schemas/Dummy'])), - 'text/csv' => new Model\MediaType(new \ArrayObject(['$ref' => '#/components/schemas/Dummy'])), - ]), - true - ) - )); - - $filteredPath = $paths->getPath('/filtered'); - $this->assertEquals($filteredPath->getGet(), new Model\Operation( - 'filteredDummyCollection', - ['Dummy'], - [ - '200' => new Model\Response('Dummy collection', new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject([ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy.OutputDto'], - ])), - ])), - ], - 'Retrieves the collection of Dummy resources.', - 'Retrieves the collection of Dummy resources.', - null, - [ - new Model\Parameter('page', 'query', 'The collection page number', false, false, true, [ - 'type' => 'integer', - 'default' => 1, - ]), - new Model\Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [ - 'type' => 'integer', - 'default' => 30, - 'minimum' => 0, - ]), - new Model\Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [ - 'type' => 'boolean', - ]), - new Model\Parameter('name', 'query', '', true, true, true, [ - 'type' => 'string', - ], 'form', true, true, 'bar'), - new Model\Parameter('ha', 'query', '', false, false, true, [ - 'type' => 'integer', - ]), - new Model\Parameter('toto', 'query', '', true, false, true, [ - 'type' => 'array', - 'items' => ['type' => 'string'], - ], 'deepObject', true), - new Model\Parameter('order[name]', 'query', '', false, false, true, [ - 'type' => 'string', - 'enum' => ['asc', 'desc'], - ]), - ] - )); - - $paginatedPath = $paths->getPath('/paginated'); - $this->assertEquals($paginatedPath->getGet(), new Model\Operation( - 'paginatedDummyCollection', - ['Dummy'], - [ - '200' => new Model\Response('Dummy collection', new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject([ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy.OutputDto'], - ])), - ])), - ], - 'Retrieves the collection of Dummy resources.', - 'Retrieves the collection of Dummy resources.', - null, - [ - new Model\Parameter('page', 'query', 'The collection page number', false, false, true, [ - 'type' => 'integer', - 'default' => 1, - ]), - new Model\Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [ - 'type' => 'integer', - 'default' => 20, - 'minimum' => 0, - 'maximum' => 80, - ]), - new Model\Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [ - 'type' => 'boolean', - ]), - ] - )); - } - - public function testOverrideDocumentation() - { - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT'] + self::OPERATION_FORMATS, - 'delete' => ['method' => 'DELETE'] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ], - [] - ); - - $subresourceOperationFactoryProphecy = $this->prophesize(SubresourceOperationFactoryInterface::class); - $subresourceOperationFactoryProphecy->create(Argument::any())->willReturn([]); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class])); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate'])); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false, null, null, null, true, null, null, null, null, null, null, null)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [], null, null, null, null, ['minLength' => 3, 'maxLength' => 20, 'pattern' => '^dummyPattern$'])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'description', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an initializable but not writable property.', true, false, true, true, false, false, null, null, [], null, true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyDate', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTime::class), 'This is a \DateTimeInterface object.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $typeFactory = new TypeFactory(); - $schemaFactory = new SchemaFactory($typeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); - $typeFactory->setSchemaFactory($schemaFactory); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $factory = new OpenApiFactory( - $resourceNameCollectionFactoryProphecy->reveal(), - $resourceMetadataFactory, - $propertyNameCollectionFactory, - $propertyMetadataFactory, - $schemaFactory, - $typeFactory, - $operationPathResolver, - $filterLocatorProphecy->reveal(), - $subresourceOperationFactoryProphecy->reveal(), - $identifiersExtractorProphecy->reveal(), - [], - new Options('Test API', 'This is a test API.', '1.2.3', true, 'oauth2', 'authorizationCode', '/oauth/v2/token', '/oauth/v2/auth', '/oauth/v2/refresh', ['scope param'], [ - 'header' => [ - 'type' => 'header', - 'name' => 'Authorization', - ], - 'query' => [ - 'type' => 'query', - 'name' => 'key', - ], - ]), - new PaginationOptions(true, 'page', true, 'itemsPerPage', true, 'pagination') - ); - - $openApi = $factory(['base_url' => '/app_dev.php/']); - - $pathItem = $openApi->getPaths()->getPath('/dummies/{id}'); - $operation = $pathItem->getGet(); - - $openApi->getPaths()->addPath('/dummies/{id}', $pathItem->withGet( - $operation->withParameters(array_merge( - $operation->getParameters(), - [new Model\Parameter('fields', 'query', 'Fields to remove of the output')] - )) - )); - - $openApi = $openApi->withInfo((new Model\Info('New Title', 'v2', 'Description of my custom API'))->withExtensionProperty('info-key', 'Info value')); - $openApi = $openApi->withExtensionProperty('key', 'Custom x-key value'); - $openApi = $openApi->withExtensionProperty('x-value', 'Custom x-value value'); - - $this->assertEquals($openApi->getInfo()->getExtensionProperties(), ['x-info-key' => 'Info value']); - $this->assertEquals($openApi->getExtensionProperties(), ['x-key' => 'Custom x-key value', 'x-value' => 'Custom x-value value']); - } - - public function testSubresourceDocumentation() - { - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Question::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['answer'])); - $propertyNameCollectionFactoryProphecy->create(Answer::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['content'])); - - $questionMetadata = new ResourceMetadata( - 'Question', - 'This is a question.', - 'http://schema.example.com/Question', - ['get' => ['method' => 'GET', 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]]] - ); - $answerMetadata = new ResourceMetadata( - 'Answer', - 'This is an answer.', - 'http://schema.example.com/Answer', - [], - ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS], - [], - ['get' => ['method' => 'GET', 'input_formats' => ['xml' => ['text/xml']], 'output_formats' => ['xml' => ['text/xml']]]] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Question::class)->shouldBeCalled()->willReturn($questionMetadata); - $resourceMetadataFactoryProphecy->create(Answer::class)->shouldBeCalled()->willReturn($answerMetadata); - - $subresourceMetadata = new SubresourceMetadata(Answer::class, false); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Question::class, 'answer', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, false, Question::class, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, Answer::class)), 'This is a name.', true, true, true, true, false, false, null, null, [], $subresourceMetadata)); - - $propertyMetadataFactoryProphecy->create(Answer::class, 'content', Argument::any())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, false, Question::class, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, Answer::class)), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $routeCollection = new RouteCollection(); - $routeCollection->add('api_answers_get_collection', new Route('/api/answers.{_format}')); - $routeCollection->add('api_questions_answer_get_subresource', new Route('/api/questions/{id}/answer.{_format}')); - $routeCollection->add('api_questions_get_item', new Route('/api/questions/{id}.{_format}')); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->shouldBeCalled()->willReturn($routeCollection); - - $operationPathResolver = new RouterOperationPathResolver($routerProphecy->reveal(), new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator()))); - - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - $identifiersExtractor = $identifiersExtractorProphecy->reveal(); - - $subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new UnderscorePathSegmentNameGenerator(), $identifiersExtractor); - - $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); - $resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Question::class, Answer::class])); - - $typeFactory = new TypeFactory(); - $schemaFactory = new SchemaFactory($typeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); - $typeFactory->setSchemaFactory($schemaFactory); - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - - $factory = new OpenApiFactory( - $resourceNameCollectionFactoryProphecy->reveal(), - $resourceMetadataFactory, - $propertyNameCollectionFactory, - $propertyMetadataFactory, - $schemaFactory, - $typeFactory, - $operationPathResolver, - $filterLocatorProphecy->reveal(), - $subresourceOperationFactory, - $identifiersExtractor, - ['jsonld' => ['application/ld+json']], - new Options('Test API', 'This is a test API.', '1.2.3', true, 'oauth2', 'authorizationCode', '/oauth/v2/token', '/oauth/v2/auth', '/oauth/v2/refresh', ['scope param'], [ - 'header' => [ - 'type' => 'header', - 'name' => 'Authorization', - ], - 'query' => [ - 'type' => 'query', - 'name' => 'key', - ], - ]), - new PaginationOptions(true, 'page', true, 'itemsPerPage', true, 'pagination') - ); - - $openApi = $factory(['base_url', '/app_dev.php/']); - - $paths = $openApi->getPaths(); - $pathItem = $paths->getPath('/api/questions/{id}/answer'); - - $this->assertEquals($pathItem->getGet(), new Model\Operation( - 'api_questions_answer_get_subresourceQuestionSubresource', - ['Answer', 'Question'], - [ - '200' => new Model\Response( - 'Question resource', - new \ArrayObject([ - 'application/ld+json' => new Model\MediaType(new \ArrayObject(new \ArrayObject(['$ref' => '#/components/schemas/Answer']))), - ]) - ), - ], - 'Retrieves a Question resource.', - 'Retrieves a Question resource.', - null, - [new Model\Parameter('id', 'path', 'Question identifier', true, false, false, ['type' => 'string'])] - )); - - $encoders = [new JsonEncoder()]; - $normalizers = [new ObjectNormalizer()]; - - $serializer = new Serializer($normalizers, $encoders); - $normalizers[0]->setSerializer($serializer); - - // Call the normalizer to see if everything is smooth - $normalizer = new OpenApiNormalizer($normalizers[0]); - $normalizer->normalize($openApi); - } - - public function testResetPathItem() - { - $pathItem = new Model\PathItem( - null, - '', - '', - new Model\Operation(), - new Model\Operation(), - new Model\Operation(), - new Model\Operation(), - new Model\Operation() - ); - - $this->assertNull($pathItem->withGet(null)->getGet()); - $this->assertNull($pathItem->withDelete(null)->getDelete()); - $this->assertNull($pathItem->withPost(null)->getPost()); - $this->assertNull($pathItem->withPut(null)->getPut()); - $this->assertNull($pathItem->withPatch(null)->getPatch()); - } -} diff --git a/tests/Core/Operation/DashedPathSegmentNameGeneratorTest.php b/tests/Core/Operation/DashedPathSegmentNameGeneratorTest.php deleted file mode 100644 index 40aa614f96a..00000000000 --- a/tests/Core/Operation/DashedPathSegmentNameGeneratorTest.php +++ /dev/null @@ -1,27 +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\Tests\Operation; - -use ApiPlatform\Core\Operation\DashPathSegmentNameGenerator; -use PHPUnit\Framework\TestCase; - -class DashedPathSegmentNameGeneratorTest extends TestCase -{ - public function testCreateSegmentNameGeneration() - { - $generator = new DashPathSegmentNameGenerator(); - $this->assertSame('ordering-people', $generator->getSegmentName('orderingPerson')); - $this->assertSame('some-person-names', $generator->getSegmentName('somePersonName')); - } -} diff --git a/tests/Core/Operation/Factory/CachedSubresourceOperationFactoryTest.php b/tests/Core/Operation/Factory/CachedSubresourceOperationFactoryTest.php deleted file mode 100644 index 6e0d826ad71..00000000000 --- a/tests/Core/Operation/Factory/CachedSubresourceOperationFactoryTest.php +++ /dev/null @@ -1,92 +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\Tests\Operation\Factory; - -use ApiPlatform\Core\Operation\Factory\CachedSubresourceOperationFactory; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use PHPUnit\Framework\TestCase; -use Psr\Cache\CacheException; -use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; - -/** - * @author Antoine Bluchet - */ -class CachedSubresourceOperationFactoryTest extends TestCase -{ - use ProphecyTrait; - - public function testCreateWithItemHit() - { - $cacheItem = $this->prophesize(CacheItemInterface::class); - $cacheItem->isHit()->willReturn(true)->shouldBeCalledTimes(1); - $cacheItem->get()->willReturn(['foo' => 'bar'])->shouldBeCalledTimes(1); - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem($this->generateCacheKey())->willReturn($cacheItem->reveal())->shouldBeCalledTimes(1); - - $decoratedSubresourceOperationFactory = $this->prophesize(SubresourceOperationFactoryInterface::class); - $decoratedSubresourceOperationFactory->create()->shouldNotBeCalled(); - - $cachedSubresourceOperationFactory = new CachedSubresourceOperationFactory($cacheItemPool->reveal(), $decoratedSubresourceOperationFactory->reveal()); - - $expectedResult = ['foo' => 'bar']; - $this->assertEquals($expectedResult, $cachedSubresourceOperationFactory->create(Dummy::class)); - $this->assertEquals($expectedResult, $cachedSubresourceOperationFactory->create(Dummy::class), 'Trigger the local cache'); - } - - public function testCreateWithItemNotHit() - { - $cacheItem = $this->prophesize(CacheItemInterface::class); - $cacheItem->isHit()->willReturn(false)->shouldBeCalledTimes(1); - $cacheItem->set(['foo' => 'bar'])->willReturn($cacheItem->reveal())->shouldBeCalledTimes(1); - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem($this->generateCacheKey())->willReturn($cacheItem->reveal())->shouldBeCalledTimes(1); - $cacheItemPool->save($cacheItem->reveal())->willReturn(true)->shouldBeCalledTimes(1); - - $decoratedSubresourceOperationFactory = $this->prophesize(SubresourceOperationFactoryInterface::class); - $decoratedSubresourceOperationFactory->create(Dummy::class)->shouldBeCalledTimes(1)->willReturn(['foo' => 'bar']); - - $cachedSubresourceOperationFactory = new CachedSubresourceOperationFactory($cacheItemPool->reveal(), $decoratedSubresourceOperationFactory->reveal()); - - $expectedResult = ['foo' => 'bar']; - $this->assertEquals($expectedResult, $cachedSubresourceOperationFactory->create(Dummy::class)); - $this->assertEquals($expectedResult, $cachedSubresourceOperationFactory->create(Dummy::class), 'Trigger the local cache'); - } - - public function testCreateWithGetCacheItemThrowsCacheException() - { - $cacheException = new class() extends \Exception implements CacheException {}; - - $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); - $cacheItemPool->getItem($this->generateCacheKey())->willThrow($cacheException)->shouldBeCalledTimes(1); - - $decoratedSubresourceOperationFactory = $this->prophesize(SubresourceOperationFactoryInterface::class); - $decoratedSubresourceOperationFactory->create(Dummy::class)->shouldBeCalledTimes(1)->willReturn(['foo' => 'bar']); - - $cachedSubresourceOperationFactory = new CachedSubresourceOperationFactory($cacheItemPool->reveal(), $decoratedSubresourceOperationFactory->reveal()); - - $expectedResult = ['foo' => 'bar']; - $this->assertEquals($expectedResult, $cachedSubresourceOperationFactory->create(Dummy::class)); - $this->assertEquals($expectedResult, $cachedSubresourceOperationFactory->create(Dummy::class), 'Trigger the local cache'); - } - - private function generateCacheKey(string $resourceClass = Dummy::class) - { - return CachedSubresourceOperationFactory::CACHE_KEY_PREFIX.md5($resourceClass); - } -} diff --git a/tests/Core/Operation/Factory/SubresourceOperationFactoryTest.php b/tests/Core/Operation/Factory/SubresourceOperationFactoryTest.php deleted file mode 100644 index 2974f30be05..00000000000 --- a/tests/Core/Operation/Factory/SubresourceOperationFactoryTest.php +++ /dev/null @@ -1,988 +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\Tests\Operation\Factory; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactory; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Operation\PathSegmentNameGeneratorInterface; -use ApiPlatform\Tests\Fixtures\DummyEntity; -use ApiPlatform\Tests\Fixtures\DummyValidatedEntity; -use ApiPlatform\Tests\Fixtures\RelatedDummyEntity; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; - -/** - * @author Antoine Bluchet - * @group legacy - */ -class SubresourceOperationFactoryTest extends TestCase -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - public function testCreate() - { - $this->expectDeprecation('Since api-platform/core 2.7: A subresource is declared on "ApiPlatform\Tests\Fixtures\DummyEntity::subresource". Subresources are deprecated, use another #[ApiResource] instead.'); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['foo', 'subresource', 'subcollection'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['bar', 'anotherSubresource'])); - - $subresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class)); - $subresourceMetadataCollection = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, true)); - $anotherSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'foo', Argument::type('array'))->shouldBeCalled()->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadata); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subcollection', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadataCollection); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'bar', Argument::type('array'))->shouldBeCalled()->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'anotherSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($anotherSubresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresource'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subcollection', true)->shouldBeCalled()->willReturn('subcollections'); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('anotherSubresource', false)->shouldBeCalled()->willReturn('another_subresource'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $pathSegmentNameGeneratorProphecy->reveal(), $identifiersExtractorProphecy->reveal()); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresource.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subresource_another_subresource_get_subresource' => [ - 'property' => 'anotherSubresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity', 'relatedDummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subresource' => [RelatedDummyEntity::class, 'id', false], - ], - 'route_name' => 'api_dummy_entities_subresource_another_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresource/another_subresource.{_format}', - 'operation_name' => 'subresource_another_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subresource_another_subresource_subcollections_get_subresource' => [ - 'property' => 'subcollection', - 'collection' => true, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subresource' => [RelatedDummyEntity::class, 'id', false], - 'anotherSubresource' => [DummyEntity::class, 'id', false], - ], - 'route_name' => 'api_dummy_entities_subresource_another_subresource_subcollections_get_subresource', - 'path' => '/dummy_entities/{id}/subresource/another_subresource/subcollections.{_format}', - 'operation_name' => 'subresource_another_subresource_subcollections_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subcollections_get_subresource' => [ - 'property' => 'subcollection', - 'collection' => true, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subcollections_get_subresource', - 'path' => '/dummy_entities/{id}/subcollections.{_format}', - 'operation_name' => 'subcollections_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subcollections_another_subresource_get_subresource' => [ - 'property' => 'anotherSubresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity', 'relatedDummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subcollection' => [RelatedDummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subcollections_another_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subcollections/{subcollection}/another_subresource.{_format}', - 'operation_name' => 'subcollections_another_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subcollections_another_subresource_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subcollection' => [RelatedDummyEntity::class, 'id', true], - 'anotherSubresource' => [DummyEntity::class, 'id', false], - ], - 'route_name' => 'api_dummy_entities_subcollections_another_subresource_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subcollections/{subcollection}/another_subresource/subresource.{_format}', - 'operation_name' => 'subcollections_another_subresource_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - public function testCreateByOverriding() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn((new ResourceMetadata('dummyEntity'))->withSubresourceOperations([ - 'subcollections_get_subresource' => [ - 'path' => '/dummy_entities/{id}/foobars', - ], - 'subcollections_another_subresource_get_subresource' => [ - 'path' => '/dummy_entities/{id}/foobars/{subcollection}/another_foobar.{_format}', - ], - ])); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['foo', 'subresource', 'subcollection'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['bar', 'anotherSubresource'])); - - $subresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class)); - $subresourceMetadataCollection = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, true)); - $anotherSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'foo', Argument::type('array'))->shouldBeCalled()->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadata); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subcollection', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadataCollection); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'bar', Argument::type('array'))->shouldBeCalled()->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'anotherSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($anotherSubresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresource'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subcollection', true)->shouldBeCalled()->willReturn('subcollections'); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('anotherSubresource', false)->shouldBeCalled()->willReturn('another_subresource'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $pathSegmentNameGeneratorProphecy->reveal(), $identifiersExtractorProphecy->reveal()); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresource.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subresource_another_subresource_get_subresource' => [ - 'property' => 'anotherSubresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity', 'relatedDummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subresource' => [RelatedDummyEntity::class, 'id', false], - ], - 'route_name' => 'api_dummy_entities_subresource_another_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresource/another_subresource.{_format}', - 'operation_name' => 'subresource_another_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subresource_another_subresource_subcollections_get_subresource' => [ - 'property' => 'subcollection', - 'collection' => true, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subresource' => [RelatedDummyEntity::class, 'id', false], - 'anotherSubresource' => [DummyEntity::class, 'id', false], - ], - 'route_name' => 'api_dummy_entities_subresource_another_subresource_subcollections_get_subresource', - 'path' => '/dummy_entities/{id}/subresource/another_subresource/subcollections.{_format}', - 'operation_name' => 'subresource_another_subresource_subcollections_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subcollections_get_subresource' => [ - 'property' => 'subcollection', - 'collection' => true, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subcollections_get_subresource', - 'path' => '/dummy_entities/{id}/foobars', - 'operation_name' => 'subcollections_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subcollections_another_subresource_get_subresource' => [ - 'property' => 'anotherSubresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity', 'relatedDummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subcollection' => [RelatedDummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subcollections_another_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/foobars/{subcollection}/another_foobar.{_format}', - 'operation_name' => 'subcollections_another_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subcollections_another_subresource_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subcollection' => [RelatedDummyEntity::class, 'id', true], - 'anotherSubresource' => [DummyEntity::class, 'id', false], - ], - 'route_name' => 'api_dummy_entities_subcollections_another_subresource_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/foobars/{subcollection}/another_foobar/subresource.{_format}', - 'operation_name' => 'subcollections_another_subresource_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - public function testCreateWithMaxDepth() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['bar', 'anotherSubresource'])); - - $subresourceMetadataCollectionWithMaxDepth = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false, 1)); - $anotherSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadataCollectionWithMaxDepth); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'bar', Argument::type('array'))->shouldBeCalled()->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'anotherSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($anotherSubresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresource'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresource.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - /** - * Test for issue: https://github.com/api-platform/core/issues/1711. - */ - public function testCreateWithMaxDepthMultipleSubresources() - { - /** - * DummyEntity -subresource-> RelatedDummyEntity -anotherSubresource-> DummyEntity - * DummyEntity -secondSubresource-> dummyValidatedEntity -moreSubresource-> RelatedDummyEntity. - */ - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyValidatedEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyValidatedEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource', 'secondSubresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['bar', 'anotherSubresource'])); - $propertyNameCollectionFactoryProphecy->create(DummyValidatedEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['moreSubresource'])); - - $subresourceMetadataCollectionWithMaxDepth = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false, 1)); - $anotherSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false)); - $secondSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyValidatedEntity::class, false, 2)); - $moreSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadataCollectionWithMaxDepth); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'secondSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($secondSubresourceMetadata); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'bar', Argument::type('array'))->shouldBeCalled()->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'anotherSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($anotherSubresourceMetadata); - $propertyMetadataFactoryProphecy->create(DummyValidatedEntity::class, 'moreSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($moreSubresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresources'); - $pathSegmentNameGeneratorProphecy->getSegmentName('secondSubresource', false)->shouldBeCalled()->willReturn('second_subresources'); - $pathSegmentNameGeneratorProphecy->getSegmentName('moreSubresource', false)->shouldBeCalled()->willReturn('mode_subresources'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresources.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_second_subresource_get_subresource' => [ - 'property' => 'secondSubresource', - 'collection' => false, - 'resource_class' => DummyValidatedEntity::class, - 'shortNames' => ['dummyValidatedEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_second_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/second_subresources.{_format}', - 'operation_name' => 'second_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_second_subresource_more_subresource_get_subresource' => [ - 'property' => 'moreSubresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyValidatedEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'secondSubresource' => [DummyValidatedEntity::class, 'id', false], - ], - 'route_name' => 'api_dummy_entities_second_subresource_more_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/second_subresources/mode_subresources.{_format}', - 'operation_name' => 'second_subresource_more_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - /** - * Test for issue: https://github.com/api-platform/core/issues/2103. - */ - public function testCreateWithMaxDepthMultipleSubresourcesSameMaxDepth() - { - /** - * DummyEntity -subresource (maxDepth=1)-> RelatedDummyEntity -anotherSubresource-> DummyEntity - * DummyEntity -secondSubresource (maxDepth=1)-> dummyValidatedEntity -moreSubresource-> RelatedDummyEntity. - */ - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyValidatedEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyValidatedEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource', 'secondSubresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['bar', 'anotherSubresource'])); - $propertyNameCollectionFactoryProphecy->create(DummyValidatedEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['moreSubresource'])); - - $subresourceMetadataCollectionWithMaxDepth = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false, 1)); - $secondSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyValidatedEntity::class, false, 1)); - $anotherSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false)); - $moreSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadataCollectionWithMaxDepth); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'secondSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($secondSubresourceMetadata); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'bar', Argument::type('array'))->shouldBeCalled()->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'anotherSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($anotherSubresourceMetadata); - $propertyMetadataFactoryProphecy->create(DummyValidatedEntity::class, 'moreSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($moreSubresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresources'); - $pathSegmentNameGeneratorProphecy->getSegmentName('secondSubresource', false)->shouldBeCalled()->willReturn('second_subresources'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresources.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_second_subresource_get_subresource' => [ - 'property' => 'secondSubresource', - 'collection' => false, - 'resource_class' => DummyValidatedEntity::class, - 'shortNames' => ['dummyValidatedEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_second_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/second_subresources.{_format}', - 'operation_name' => 'second_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - public function testCreateSelfReferencingSubresources() - { - /** - * DummyEntity -subresource-> DummyEntity --> DummyEntity ... - */ - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource'])); - - $subresource = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresource); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresources'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresources.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - /** - * Test for issue: https://github.com/api-platform/core/issues/2533. - */ - public function testCreateWithDifferentMaxDepthSelfReferencingSubresources() - { - /** - * subresource: maxDepth = 2 - * secondSubresource: maxDepth = 1 - * DummyEntity -subresource-> DummyEntity -secondSubresource-> DummyEntity ... - * DummyEntity -secondSubresource-> DummyEntity !!!-subresource-> DummyEntity ... - */ - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource', 'secondSubresource'])); - - $subresourceWithMaxDepthMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false, 2)); - $secondSubresourceWithMaxDepthMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false, 1)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceWithMaxDepthMetadata); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'secondSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($secondSubresourceWithMaxDepthMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresources'); - $pathSegmentNameGeneratorProphecy->getSegmentName('secondSubresource', false)->shouldBeCalled()->willReturn('second_subresources'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresources.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subresource_second_subresource_get_subresource' => [ - 'property' => 'secondSubresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subresource' => [DummyEntity::class, 'id', false], - ], - 'route_name' => 'api_dummy_entities_subresource_second_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresources/second_subresources.{_format}', - 'operation_name' => 'subresource_second_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_second_subresource_get_subresource' => [ - 'property' => 'secondSubresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_second_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/second_subresources.{_format}', - 'operation_name' => 'second_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - public function testCreateWithEnd() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['id'])); - - $subresourceMetadataCollection = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, true)); - $identifierSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false))->withIdentifier(true); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadataCollection); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'id', Argument::type('array'))->shouldBeCalled()->willReturn($identifierSubresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', true)->shouldBeCalled()->willReturn('subresource'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $result = $subresourceOperationFactory->create(DummyEntity::class); - $this->assertEquals([ - 'api_dummy_entities_subresources_get_subresource' => [ - 'property' => 'subresource', - 'collection' => true, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresources_get_subresource', - 'path' => '/dummy_entities/{id}/subresource.{_format}', - 'operation_name' => 'subresources_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_subresources_item_get_subresource' => [ - 'property' => 'id', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity', 'relatedDummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - 'subresource' => [RelatedDummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresources_item_get_subresource', - 'path' => '/dummy_entities/{id}/subresource/{subresource}.{_format}', - 'operation_name' => 'subresources_item_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $result); - } - - public function testCreateWithEndButNoCollection() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['id'])); - - $subresourceMetadataCollection = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false)); - $identifierSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false))->withIdentifier(true); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadataCollection); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'id', Argument::type('array'))->shouldBeCalled()->willReturn($identifierSubresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresource'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $result = $subresourceOperationFactory->create(DummyEntity::class); - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresource.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $result); - } - - public function testCreateWithRootResourcePrefix() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity', null, null, null, null, ['route_prefix' => 'root_resource_prefix'])); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['bar', 'anotherSubresource'])); - - $subresourceMetadataCollectionWithMaxDepth = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false, 1)); - $anotherSubresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadataCollectionWithMaxDepth); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'bar', Argument::type('array'))->shouldBeCalled()->willReturn(new PropertyMetadata()); - $propertyMetadataFactoryProphecy->create(RelatedDummyEntity::class, 'anotherSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($anotherSubresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresource'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/root_resource_prefix/dummy_entities/{id}/subresource.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - public function testCreateSelfReferencingSubresourcesWithSubresources() - { - /** - * DummyEntity -otherSubresource-> RelatedDummyEntity - * DummyEntity -subresource (maxDepth=1) -> DummyEntity -otherSubresource-> RelatedDummyEntity. - */ - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('dummyEntity')); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource', 'otherSubresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection([])); - - $subresource = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(DummyEntity::class, false, 1)); - $otherSubresourceSubresource = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresource); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'otherSubresource', Argument::type('array'))->shouldBeCalled()->willReturn($otherSubresourceSubresource); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresources'); - $pathSegmentNameGeneratorProphecy->getSegmentName('otherSubresource', false)->shouldBeCalled()->willReturn('other_subresources'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => DummyEntity::class, - 'shortNames' => ['dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresources.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - 'api_dummy_entities_other_subresource_get_subresource' => [ - 'property' => 'otherSubresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_other_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/other_subresources.{_format}', - 'operation_name' => 'other_subresource_get_subresource', - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } - - public function testCreateWithOpenapiContext() - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new ResourceMetadata('relatedDummyEntity')); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn((new ResourceMetadata('dummyEntity'))->withSubresourceOperations([ - 'subresource_get_subresource' => [ - 'openapi_context' => [ - 'summary' => 'Get related dummy entities', - 'tags' => ['Dummy'], - ], - ], - ])); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection(['subresource'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummyEntity::class)->shouldBeCalled()->willReturn(new PropertyNameCollection([])); - - $subresourceMetadata = (new PropertyMetadata())->withSubresource(new SubresourceMetadata(RelatedDummyEntity::class, false)); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyEntity::class, 'subresource', Argument::type('array'))->shouldBeCalled()->willReturn($subresourceMetadata); - - $pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class); - $pathSegmentNameGeneratorProphecy->getSegmentName('dummyEntity')->shouldBeCalled()->willReturn('dummy_entities'); - $pathSegmentNameGeneratorProphecy->getSegmentName('subresource', false)->shouldBeCalled()->willReturn('subresources'); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $pathSegmentNameGeneratorProphecy->reveal(), - $identifiersExtractorProphecy->reveal() - ); - - $this->assertEquals([ - 'api_dummy_entities_subresource_get_subresource' => [ - 'property' => 'subresource', - 'collection' => false, - 'resource_class' => RelatedDummyEntity::class, - 'shortNames' => ['relatedDummyEntity', 'dummyEntity'], - 'identifiers' => [ - 'id' => [DummyEntity::class, 'id', true], - ], - 'route_name' => 'api_dummy_entities_subresource_get_subresource', - 'path' => '/dummy_entities/{id}/subresources.{_format}', - 'operation_name' => 'subresource_get_subresource', - 'openapi_context' => [ - 'summary' => 'Get related dummy entities', - 'tags' => ['Dummy'], - ], - 'legacy_filters' => [], - 'legacy_normalization_context' => [], - 'legacy_type' => null, - ] + SubresourceOperationFactory::ROUTE_OPTIONS, - ], $subresourceOperationFactory->create(DummyEntity::class)); - } -} diff --git a/tests/Core/PathResolver/DashOperationPathResolverTest.php b/tests/Core/PathResolver/DashOperationPathResolverTest.php deleted file mode 100644 index d789228c847..00000000000 --- a/tests/Core/PathResolver/DashOperationPathResolverTest.php +++ /dev/null @@ -1,71 +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\Tests\PathResolver; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\PathResolver\DashOperationPathResolver; -use ApiPlatform\Exception\InvalidArgumentException; -use PHPUnit\Framework\TestCase; - -/** - * @author Guilhem N. - * - * @group legacy - */ -class DashOperationPathResolverTest extends TestCase -{ - /** - * @expectedDeprecation The use of ApiPlatform\Core\PathResolver\DashOperationPathResolver is deprecated since 2.1. Please use ApiPlatform\Core\Operation\DashPathSegmentNameGenerator instead. - */ - public function testResolveCollectionOperationPath() - { - $dashOperationPathResolver = new DashOperationPathResolver(); - - $this->assertSame('/short-names.{_format}', $dashOperationPathResolver->resolveOperationPath('ShortName', [], OperationType::COLLECTION, 'get')); - } - - /** - * @expectedDeprecation The use of ApiPlatform\Core\PathResolver\DashOperationPathResolver is deprecated since 2.1. Please use ApiPlatform\Core\Operation\DashPathSegmentNameGenerator instead. - */ - public function testResolveItemOperationPath() - { - $dashOperationPathResolver = new DashOperationPathResolver(); - - $this->assertSame('/short-names/{id}.{_format}', $dashOperationPathResolver->resolveOperationPath('ShortName', [], OperationType::ITEM, 'get')); - } - - /** - * @expectedDeprecation The use of ApiPlatform\Core\PathResolver\DashOperationPathResolver is deprecated since 2.1. Please use ApiPlatform\Core\Operation\DashPathSegmentNameGenerator instead. - */ - public function testResolveSubresourceOperationPath() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Subresource operations are not supported by the OperationPathResolver.'); - - $dashOperationPathResolver = new DashOperationPathResolver(); - - $dashOperationPathResolver->resolveOperationPath('ShortName', ['property' => 'bar', 'identifiers' => [['id', 'class'], ['relatedId', 'class']], 'collection' => false], OperationType::SUBRESOURCE, 'get'); - } - - /** - * @expectedDeprecation The use of ApiPlatform\Core\PathResolver\DashOperationPathResolver is deprecated since 2.1. Please use ApiPlatform\Core\Operation\DashPathSegmentNameGenerator instead. - * @expectedDeprecation Using a boolean for the Operation Type is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3 - */ - public function testLegacyResolveOperationPath() - { - $dashOperationPathResolver = new DashOperationPathResolver(); - - $this->assertSame('/short-names.{_format}', $dashOperationPathResolver->resolveOperationPath('ShortName', [], true)); - } -} diff --git a/tests/Core/PathResolver/UnderscoreOperationPathResolverTest.php b/tests/Core/PathResolver/UnderscoreOperationPathResolverTest.php deleted file mode 100644 index 0c7a5df2a84..00000000000 --- a/tests/Core/PathResolver/UnderscoreOperationPathResolverTest.php +++ /dev/null @@ -1,71 +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\Tests\PathResolver; - -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\PathResolver\UnderscoreOperationPathResolver; -use ApiPlatform\Exception\InvalidArgumentException; -use PHPUnit\Framework\TestCase; - -/** - * @author Guilhem N. - * - * @group legacy - */ -class UnderscoreOperationPathResolverTest extends TestCase -{ - /** - * @expectedDeprecation The use of ApiPlatform\Core\PathResolver\UnderscoreOperationPathResolver is deprecated since 2.1. Please use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator instead. - */ - public function testResolveCollectionOperationPath() - { - $underscoreOperationPathResolver = new UnderscoreOperationPathResolver(); - - $this->assertSame('/short_names.{_format}', $underscoreOperationPathResolver->resolveOperationPath('ShortName', [], OperationType::COLLECTION, 'get')); - } - - /** - * @expectedDeprecation The use of ApiPlatform\Core\PathResolver\UnderscoreOperationPathResolver is deprecated since 2.1. Please use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator instead. - */ - public function testResolveItemOperationPath() - { - $underscoreOperationPathResolver = new UnderscoreOperationPathResolver(); - - $this->assertSame('/short_names/{id}.{_format}', $underscoreOperationPathResolver->resolveOperationPath('ShortName', [], OperationType::ITEM, 'get')); - } - - /** - * @expectedDeprecation The use of ApiPlatform\Core\PathResolver\UnderscoreOperationPathResolver is deprecated since 2.1. Please use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator instead. - */ - public function testResolveSubresourceOperationPath() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Subresource operations are not supported by the OperationPathResolver.'); - - $dashOperationPathResolver = new UnderscoreOperationPathResolver(); - - $dashOperationPathResolver->resolveOperationPath('ShortName', ['property' => 'relatedFoo', 'identifiers' => [['id', 'class']], 'collection' => true], OperationType::SUBRESOURCE, 'get'); - } - - /** - * @expectedDeprecation The use of ApiPlatform\Core\PathResolver\UnderscoreOperationPathResolver is deprecated since 2.1. Please use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator instead. - * @expectedDeprecation Using a boolean for the Operation Type is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3 - */ - public function testLegacyResolveOperationPath() - { - $underscoreOperationPathResolver = new UnderscoreOperationPathResolver(); - - $this->assertSame('/short_names.{_format}', $underscoreOperationPathResolver->resolveOperationPath('ShortName', [], true)); - } -} diff --git a/tests/Core/ProphecyTrait.php b/tests/Core/ProphecyTrait.php deleted file mode 100644 index e9058776377..00000000000 --- a/tests/Core/ProphecyTrait.php +++ /dev/null @@ -1,135 +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\Tests; - -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\TestCase; -use Prophecy\Exception\Doubler\DoubleException; -use Prophecy\Exception\Doubler\InterfaceNotFoundException; -use Prophecy\Exception\Prediction\PredictionException; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophet; - -/** - * Copied and adapted from phpspec/prophecy-phpunit. - * To replace by the official package when we'll drop support for PHP 7.1. - * - * @see https://github.com/phpspec/prophecy-phpunit - * - * @copyright Christophe Coevoet - * @author Christophe Coevoet - * @mixin TestCase - */ -trait ProphecyTrait -{ - /** - * @var Prophet|null - * - * @internal - */ - private $prophet; - - /** - * @var bool - * - * @internal - */ - private $prophecyAssertionsCounted = false; - - /** - * @psalm-param class-string|null $classOrInterface - * - * @param mixed|null $classOrInterface - * - * @throws DoubleException - * @throws InterfaceNotFoundException - */ - protected function prophesize($classOrInterface = null): ObjectProphecy - { - if ( - \is_string($classOrInterface) && - \is_callable([$this, 'recordDoubledType']) // Support for PHPUnit 7 - ) { - \assert($this instanceof TestCase); - $this->recordDoubledType($classOrInterface); - } - - return $this->getProphet()->prophesize($classOrInterface); - } - - /** - * @postCondition - * @after - */ - protected function verifyProphecyDoubles(): void - { - if (null === $this->prophet) { - return; - } - - try { - $this->prophet->checkPredictions(); - } catch (PredictionException $e) { - throw new AssertionFailedError($e->getMessage()); - } finally { - $this->countProphecyAssertions(); - } - } - - /** - * @after - */ - protected function tearDownProphecy(): void - { - if (null !== $this->prophet && !$this->prophecyAssertionsCounted) { - // Some Prophecy assertions may have been done in tests themselves even when a failure happened before checking mock objects. - $this->countProphecyAssertions(); - } - - $this->prophet = null; - } - - /** - * @internal - */ - private function countProphecyAssertions(): void - { - \assert($this instanceof TestCase); - $this->prophecyAssertionsCounted = true; - - foreach ($this->prophet->getProphecies() as $objectProphecy) { - /** @var MethodProphecy[] $methodProphecies */ - foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { - foreach ($methodProphecies as $methodProphecy) { - \assert($methodProphecy instanceof MethodProphecy); - - $this->addToAssertionCount(\count($methodProphecy->getCheckedPredictions())); - } - } - } - } - - /** - * @internal - */ - private function getProphet(): Prophet - { - if (null === $this->prophet) { - $this->prophet = new Prophet(); - } - - return $this->prophet; - } -} diff --git a/tests/Core/Swagger/Serializer/DocumentationNormalizerV2Test.php b/tests/Core/Swagger/Serializer/DocumentationNormalizerV2Test.php deleted file mode 100644 index a552cd8286d..00000000000 --- a/tests/Core/Swagger/Serializer/DocumentationNormalizerV2Test.php +++ /dev/null @@ -1,3483 +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\Tests\Swagger\Serializer; - -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\OperationAwareFormatsProviderInterface; -use ApiPlatform\Core\Api\OperationMethodResolverInterface; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouterOperationPathResolver; -use ApiPlatform\Core\JsonSchema\SchemaFactory; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactory; -use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator; -use ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Documentation\Documentation; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\JsonSchema\TypeFactory; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\PathResolver\CustomOperationPathResolver; -use ApiPlatform\PathResolver\OperationPathResolver; -use ApiPlatform\PathResolver\OperationPathResolverInterface; -use ApiPlatform\Tests\Fixtures\DummyFilter; -use ApiPlatform\Tests\Fixtures\TestBundle\Dto\InputDto; -use ApiPlatform\Tests\Fixtures\TestBundle\Dto\OutputDto; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Answer; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPropertyWithDefaultValue; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Psr\Container\ContainerInterface; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Routing\Route; -use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\RouterInterface; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - -/** - * @author Amrouche Hamza - * @author Kévin Dunglas - * @group legacy - */ -class DocumentationNormalizerV2Test extends TestCase -{ - use ExpectDeprecationTrait; - use ProphecyTrait; - - private const OPERATION_FORMATS = [ - 'input_formats' => ['jsonld' => ['application/ld+json']], - 'output_formats' => ['jsonld' => ['application/ld+json']], - ]; - - /** - * @group legacy - */ - public function testLegacyConstruct(): void - { - $this->expectDeprecation('Passing an instance of ApiPlatform\Core\Api\UrlGeneratorInterface to ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer::__construct() is deprecated since version 2.1 and will be removed in 3.0.'); - - $normalizer = new DocumentationNormalizer( - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), - $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), - $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), - $this->prophesize(ResourceClassResolverInterface::class)->reveal(), - $this->prophesize(OperationMethodResolverInterface::class)->reveal(), - $this->prophesize(OperationPathResolverInterface::class)->reveal(), - $this->prophesize(UrlGeneratorInterface::class)->reveal() - ); - - $this->assertInstanceOf(DocumentationNormalizer::class, $normalizer); - } - - public function testNormalize(): void - { - $this->doTestNormalize(); - } - - public function testLegacyNormalize(): void - { - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->willReturn('POST'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom')->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom2')->willReturn('POST'); - - $this->doTestNormalize($operationMethodResolverProphecy->reveal()); - } - - private function doTestNormalize(OperationMethodResolverInterface $operationMethodResolver = null): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET', 'status' => '202'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'status' => '202'] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET', 'status' => '202'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST', 'status' => '202'] + self::OPERATION_FORMATS, - 'custom' => ['method' => 'GET', 'path' => '/foo', 'status' => '202'] + self::OPERATION_FORMATS, - 'custom2' => ['method' => 'POST', 'path' => '/foo'] + self::OPERATION_FORMATS, - ], - ['pagination_client_items_per_page' => true, 'normalization_context' => [AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false]] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'description', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an initializable but not writable property.', true, false, true, true, false, false, null, null, [], null, true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyDate', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTimeInterface::class), 'This is a \DateTimeInterface object.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - $operationMethodResolver, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/app_dev.php/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - [ - 'name' => 'itemsPerPage', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The number of items per page', - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy resource updated', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/foo' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'customDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - [ - 'name' => 'itemsPerPage', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The number of items per page', - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'custom2DummyCollection', - 'produces' => ['application/ld+json'], - 'consumes' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'additionalProperties' => false, - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - 'description' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is an initializable but not writable property.', - ]), - 'dummyDate' => new \ArrayObject([ - 'type' => 'string', - 'format' => 'date-time', - 'description' => 'This is a \DateTimeInterface object.', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithNameConverter(): void - { - $this->doTestNormalizeWithNameConverter(); - } - - public function testLegacyNormalizeWithNameConverter(): void - { - $this->doTestNormalizeWithNameConverter(true); - } - - private function doTestNormalizeWithNameConverter(bool $legacy = false): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'nameConverted'])); - - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, null, null, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'nameConverted', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a converted name.', true, true, null, null, false)); - - if (interface_exists(AdvancedNameConverterInterface::class)) { - $nameConverter = $this->createMock(AdvancedNameConverterInterface::class); - } else { - $nameConverter = $this->createMock(NameConverterInterface::class); - } - - $nameConverter->method('normalize') - ->with(self::logicalOr('name', 'nameConverted')) - ->willReturnCallback(static function (string $nameToNormalize): string { - return 'nameConverted' === $nameToNormalize - ? 'name_converted' - : $nameToNormalize; - }); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $typeFactory = null; - $schemaFactory = null; - - if (!$legacy) { - $typeFactory = new TypeFactory(); - $schemaFactory = new SchemaFactory($typeFactory, $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $nameConverter); - $typeFactory->setSchemaFactory($schemaFactory); - } - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $schemaFactory, - $typeFactory, - $operationPathResolver, - null, - null, - $legacy ? $nameConverter : null, - true, - 'oauth2', - 'application', - '/oauth/v2/token', - '/oauth/v2/auth', - ['scope param'], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => 'Dummy API', - 'description' => 'This is a dummy API', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - 'name_converted' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a converted name.', - ]), - ], - ]), - ]), - 'securityDefinitions' => [ - 'oauth' => [ - 'type' => 'oauth2', - 'description' => 'OAuth 2.0 application Grant', - 'flow' => 'application', - 'tokenUrl' => '/oauth/v2/token', - 'authorizationUrl' => '/oauth/v2/auth', - 'scopes' => new \ArrayObject(['scope param']), - ], - ], - 'security' => [['oauth' => []]], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithApiKeysEnabled(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, null, null, false)); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $apiKeysConfiguration = [ - 'header' => [ - 'type' => 'header', - 'name' => 'Authorization', - ], - 'query' => [ - 'type' => 'query', - 'name' => 'key', - ], - ]; - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - $apiKeysConfiguration, - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/app_dev.php/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - ]), - 'securityDefinitions' => [ - 'header' => [ - 'type' => 'apiKey', - 'in' => 'header', - 'description' => 'Value for the Authorization header', - 'name' => 'Authorization', - ], - 'query' => [ - 'type' => 'apiKey', - 'in' => 'query', - 'description' => 'Value for the key query parameter', - 'name' => 'key', - ], - ], - 'security' => [ - ['header' => []], - ['query' => []], - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithOnlyNormalizationGroups(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - $groups = ['dummy', 'foo', 'bar']; - - $ref = 'Dummy-'.implode('_', $groups); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', $groups) - ))->willReturn(new PropertyNameCollection(['gerard'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'gerard', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a gerard.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [[ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ]], - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [[ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ]], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource updated', - 'schema' => ['$ref' => '#/definitions/'.$ref], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - $ref => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'gerard' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a gerard.', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeNotAddExtraBodyParameters(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - $groups = ['dummy', 'foo', 'bar']; - - $ref = 'Dummy-'.implode('_', $groups); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', $groups) - ))->willReturn(new PropertyNameCollection(['gerard'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => [ - 'method' => 'POST', - 'swagger_context' => [ - 'parameters' => [ - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new custom Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'gerard', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a gerard.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [[ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new custom Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ]], - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [[ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ]], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource updated', - 'schema' => ['$ref' => '#/definitions/'.$ref], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - $ref => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'gerard' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a gerard.', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithSwaggerDefinitionName(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => [ - 'method' => 'GET', - 'normalization_context' => [ - DocumentationNormalizer::SWAGGER_DEFINITION_NAME => 'Read', - ], - ] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/app_dev.php/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy-Read'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy-Read' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithOnlyDenormalizationGroups(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', ['dummy']) - ))->willReturn(new PropertyNameCollection(['gerard'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy']] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'gerard', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a gerard.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [[ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ]], - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [[ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ]], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy-dummy'], - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource updated', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - 'Dummy-dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'gerard' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a gerard.', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithNormalizationAndDenormalizationGroups(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', ['dummy']) - ))->willReturn(new PropertyNameCollection(['gerard'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => [ - 'method' => 'PUT', - 'normalization_context' => [AbstractNormalizer::GROUPS => 'dummy'], 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy'], - ] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'gerard', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a gerard.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [[ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ]], - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [[ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ]], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy-dummy'], - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource updated', - 'schema' => ['$ref' => '#/definitions/Dummy-dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - 'Dummy-dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'gerard' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a gerard.', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeSkipsNotReadableAndNotWritableProperties(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'dummy', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET', 'status' => '202'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'status' => '202'] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET', 'status' => '202'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST', 'status' => '202'] + self::OPERATION_FORMATS, - ], - ['pagination_client_items_per_page' => true] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), null, false, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'dummy', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a public id.', true, false, true, true, false, true, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/app_dev.php/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - [ - 'name' => 'itemsPerPage', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The number of items per page', - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 202 => [ - 'description' => 'Dummy resource updated', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'dummy' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a public id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testFilters(): void - { - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $filters = [ - 'f1' => new DummyFilter(['name' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => true, - 'strategy' => 'exact', - 'swagger' => ['x-foo' => 'bar'], - ]]), - 'f2' => new DummyFilter(['ha' => [ - 'property' => 'foo', - 'type' => 'int', - 'required' => false, - 'strategy' => 'partial', - ]]), - 'f3' => new DummyFilter(['toto' => [ - 'property' => 'name', - 'type' => 'array', - 'is_collection' => true, - 'required' => true, - 'strategy' => 'exact', - ]]), - ]; - - foreach ($filters as $filterId => $filter) { - $filterLocatorProphecy->has($filterId)->willReturn(true); - $filterLocatorProphecy->get($filterId)->willReturn($filter); - } - - $filterLocatorProphecy->has('f4')->willReturn(false); - - $this->doTestNormalizeWithFilters($filterLocatorProphecy->reveal()); - } - - /** - * @group legacy - * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. - */ - public function testFiltersWithDeprecatedFilterCollection(): void - { - $this->doTestNormalizeWithFilters(new FilterCollection([ - 'f1' => new DummyFilter(['name' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => true, - 'strategy' => 'exact', - 'swagger' => ['x-foo' => 'bar'], - ]]), - 'f2' => new DummyFilter(['ha' => [ - 'property' => 'foo', - 'type' => 'int', - 'required' => false, - 'strategy' => 'partial', - ]]), - 'f3' => new DummyFilter(['toto' => [ - 'property' => 'name', - 'type' => 'array', - 'is_collection' => true, - 'required' => true, - 'strategy' => 'exact', - ]]), - ])); - } - - public function testConstructWithInvalidFilterLocator(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "$filterLocator" argument is expected to be an implementation of the "Psr\\Container\\ContainerInterface" interface or null.'); - - new DocumentationNormalizer( - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), - $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), - $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), - null, - null, - $this->prophesize(OperationPathResolverInterface::class)->reveal(), - null, - new \ArrayObject(), // @phpstan-ignore-line - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $this->prophesize(IdentifiersExtractorInterface::class)->reveal() - ); - } - - public function testSupports(): void - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $this->assertTrue($normalizer->supportsNormalization($documentation, 'json')); - $this->assertFalse($normalizer->supportsNormalization($documentation)); - $this->assertFalse($normalizer->supportsNormalization(new Dummy(), 'json')); - $this->assertTrue($normalizer->hasCacheableSupportsMethod()); - } - - public function testNormalizeWithNoOperations(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldNotBeCalled(); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.' - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name')->shouldNotBeCalled(); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => '', - 'version' => '0.0.0', - ], - 'paths' => new \ArrayObject([]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithCustomMethod(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - null, - [], - ['get' => ['method' => 'FOO']] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => '', - 'version' => '0.0.0', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'foo' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - ]), - ], - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithNestedNormalizationGroups(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - $groups = ['dummy', 'foo', 'bar']; - $ref = 'Dummy-'.implode('_', $groups); - $relatedDummyRef = 'RelatedDummy-'.implode('_', $groups); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', $groups) - ))->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummyWithCustomOpenApiContextType'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', $groups) - ))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ] - ); - - $relatedDummyMetadata = new ResourceMetadata( - 'RelatedDummy', - 'This is a related dummy.', - 'http://schema.example.com/RelatedDummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - ] - ); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - $resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedDummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, RelatedDummy::class), 'This is a related dummy \o/.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummyWithCustomOpenApiContextType', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, RelatedDummy::class), 'This is a related dummy with type string \o/.', true, true, true, true, false, false, null, null, ['swagger_context' => ['type' => 'string']])); - $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [[ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ]], - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [[ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ]], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource updated', - 'schema' => ['$ref' => '#/definitions/'.$ref], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - $ref => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - 'relatedDummy' => new \ArrayObject([ - 'description' => 'This is a related dummy \o/.', - '$ref' => '#/definitions/'.$relatedDummyRef, - ]), - 'relatedDummyWithCustomOpenApiContextType' => new \ArrayObject([ - 'description' => 'This is a related dummy with type string \o/.', - 'type' => 'string', - ]), - ], - ]), - $relatedDummyRef => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a related dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/RelatedDummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - private function doTestNormalizeWithFilters($filterLocator): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - null, - [], - ['get' => ['method' => 'GET', 'filters' => ['f1', 'f2', 'f3', 'f4']] + self::OPERATION_FORMATS] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - $filterLocator, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => '', - 'version' => '0.0.0', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - 'parameters' => [ - [ - 'x-foo' => 'bar', - 'name' => 'name', - 'in' => 'query', - 'required' => true, - 'type' => 'string', - ], - [ - 'name' => 'ha', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - ], - [ - 'name' => 'toto', - 'in' => 'query', - 'required' => true, - 'type' => 'array', - 'items' => [ - 'type' => 'string', - ], - 'collectionFormat' => 'csv', - ], - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'properties' => [ - 'name' => new \ArrayObject([ - 'description' => 'This is a name.', - 'type' => 'string', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithSubResource(): void - { - $this->doTestNormalizeWithSubResource(); - } - - public function testLegacyNormalizeWithSubResource(): void - { - $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); - $formatProviderProphecy->getFormatsFromOperation(Question::class, 'get', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); - $formatProviderProphecy->getFormatsFromOperation(Answer::class, 'get', OperationType::SUBRESOURCE)->willReturn(['xml' => ['text/xml']]); - - $this->doTestNormalizeWithSubResource($formatProviderProphecy->reveal()); - } - - private function doTestNormalizeWithSubResource(OperationAwareFormatsProviderInterface $formatsProvider = null): void - { - $documentation = new Documentation(new ResourceNameCollection([Question::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Question::class, Argument::cetera())->willReturn(new PropertyNameCollection(['answer'])); - $propertyNameCollectionFactoryProphecy->create(Answer::class, Argument::cetera())->willReturn(new PropertyNameCollection(['content'])); - - $questionMetadata = new ResourceMetadata( - 'Question', - 'This is a question.', - 'http://schema.example.com/Question', - ['get' => ['method' => 'GET', 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]]] - ); - $answerMetadata = new ResourceMetadata( - 'Answer', - 'This is an answer.', - 'http://schema.example.com/Answer', - [], - ['get' => ['method' => 'GET']] + self::OPERATION_FORMATS, - [], - ['get' => ['method' => 'GET', 'input_formats' => ['xml' => ['text/xml']], 'output_formats' => ['xml' => ['text/xml']]]] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Question::class)->willReturn($questionMetadata); - $resourceMetadataFactoryProphecy->create(Answer::class)->willReturn($answerMetadata); - - $subresourceMetadata = new SubresourceMetadata(Answer::class, false); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Question::class, 'answer', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, false, Question::class, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, Answer::class)), 'This is a name.', true, true, true, true, false, false, null, null, [], $subresourceMetadata)); - $propertyMetadataFactoryProphecy->create(Answer::class, 'content', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, false, Question::class, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, Answer::class)), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $routeCollection = new RouteCollection(); - $routeCollection->add('api_questions_answer_get_subresource', new Route('/api/questions/{id}/answer.{_format}')); - $routeCollection->add('api_questions_get_item', new Route('/api/questions/{id}.{_format}')); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $operationPathResolver = new RouterOperationPathResolver($routerProphecy->reveal(), new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator()))); - - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new UnderscorePathSegmentNameGenerator(), $identifiersExtractorProphecy->reveal()); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactory, - $propertyNameCollectionFactory, - $propertyMetadataFactory, - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - $subresourceOperationFactory, - true, - 'page', - false, - 'itemsPerPage', - $formatsProvider ?? ['json' => ['application/json'], 'csv' => ['text/csv']], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/api/questions/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Question'], - 'operationId' => 'getQuestionItem', - 'produces' => ['application/json', 'text/csv'], - 'summary' => 'Retrieves a Question resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Question resource response', - 'schema' => ['$ref' => '#/definitions/Question'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - ], - '/api/questions/{id}/answer' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Answer', 'Question'], - 'operationId' => 'api_questions_answer_get_subresource', - 'produces' => ['text/xml'], - 'summary' => 'Retrieves a Answer resource.', - 'responses' => [ - 200 => [ - 'description' => 'Answer resource response', - 'schema' => ['$ref' => '#/definitions/Answer'], - ], - 404 => ['description' => 'Resource not found'], - ], - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Question' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a question.', - 'externalDocs' => ['url' => 'http://schema.example.com/Question'], - 'properties' => [ - 'answer' => new \ArrayObject([ - 'type' => 'array', - 'description' => 'This is a name.', - 'items' => ['$ref' => '#/definitions/Answer'], - ]), - ], - ]), - 'Answer' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is an answer.', - 'externalDocs' => ['url' => 'http://schema.example.com/Answer'], - 'properties' => [ - 'content' => new \ArrayObject([ - 'type' => 'array', - 'description' => 'This is a name.', - 'items' => ['$ref' => '#/definitions/Answer'], - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithPropertySwaggerContext(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, ['swagger_context' => ['type' => 'string', 'enum' => ['one', 'two'], 'example' => 'one']])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/app_dev.php/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - 'enum' => ['one', 'two'], - 'example' => 'one', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithPaginationClientEnabled(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [], - ['get' => ['method' => 'GET', 'pagination_client_enabled' => true] + self::OPERATION_FORMATS] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, ['swagger_context' => ['type' => 'string', 'enum' => ['one', 'two'], 'example' => 'one']])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/app_dev.php/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'type' => 'integer', - 'description' => 'The collection page number', - ], - [ - 'name' => 'pagination', - 'in' => 'query', - 'required' => false, - 'type' => 'boolean', - 'description' => 'Enable or disable pagination', - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - 'enum' => ['one', 'two'], - 'example' => 'one', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithCustomFormatsDefinedAtOperationLevel(): void - { - $this->doTestNormalizeWithCustomFormatsDefinedAtOperationLevel(); - } - - public function testLegacyNormalizeWithCustomFormatsDefinedAtOperationLevel(): void - { - $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::ITEM)->willReturn(['jsonapi' => ['application/vnd.api+json']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'put', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::COLLECTION)->willReturn(['xml' => ['application/xml', 'text/xml']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'post', OperationType::COLLECTION)->willReturn(['xml' => ['text/xml'], 'csv' => ['text/csv']]); - - $this->doTestNormalizeWithCustomFormatsDefinedAtOperationLevel($formatProviderProphecy->reveal()); - } - - private function doTestNormalizeWithCustomFormatsDefinedAtOperationLevel(OperationAwareFormatsProviderInterface $formatProvider = null): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET', 'output_formats' => ['jsonapi' => ['application/vnd.api+json']]], - 'put' => ['method' => 'PUT', 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]], ], - [ - 'get' => ['method' => 'GET', 'output_formats' => ['xml' => ['application/xml', 'text/xml']]], - 'post' => ['method' => 'POST', 'output_formats' => ['xml' => ['text/xml'], 'csv' => ['text/csv']], 'input_formats' => ['xml' => ['text/xml'], 'csv' => ['text/csv']]], - ]); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new OperationPathResolver(new UnderscorePathSegmentNameGenerator()); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, false, - '', - '', - '', - '', - [], - [], - null, - false, - 'page', - false, - 'itemsPerPage', - $formatProvider, - false, - 'pagination', - [], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/xml', 'text/xml'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [], - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/definitions/Dummy'], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['text/xml', 'text/csv'], - 'produces' => ['text/xml', 'text/csv'], - 'summary' => 'Creates a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/vnd.api+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/json', 'text/csv'], - 'produces' => ['application/json', 'text/csv'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'type' => 'string', - 'required' => true, - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource updated', - 'schema' => ['$ref' => '#/definitions/Dummy'], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/'])); - } - - public function testLegacyNormalizeWithInputAndOutputClass(): void - { - $this->doTestNormalizeWithInputAndOutputClass(); - } - - private function doTestNormalizeWithInputAndOutputClass(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(InputDto::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo', 'bar'])); - $propertyNameCollectionFactoryProphecy->create(OutputDto::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['baz', 'bat'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT'] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ], - [ - 'input' => ['class' => InputDto::class], - 'output' => ['class' => OutputDto::class], - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - // InputDto - $propertyMetadataFactoryProphecy->create(InputDto::class, 'foo', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'foo', true, false)); - $propertyMetadataFactoryProphecy->create(InputDto::class, 'bar', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'bar', true, true, true, true, false, false, null, null, [])); - // OutputDto - $propertyMetadataFactoryProphecy->create(OutputDto::class, 'baz', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'baz', true, false)); - $propertyMetadataFactoryProphecy->create(OutputDto::class, 'bat', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'bat', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - '', - [], - [], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'swagger' => '2.0', - 'basePath' => '/app_dev.php/', - 'info' => [ - 'title' => 'Test API', - 'version' => '1.2.3', - 'description' => 'This is a test API.', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves the collection of Dummy resources.', - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'schema' => [ - 'type' => 'array', - 'items' => [ - '$ref' => '#/definitions/Dummy.OutputDto', - ], - ], - ], - ], - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'description' => 'The collection page number', - 'type' => 'integer', - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Creates a Dummy resource.', - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'schema' => [ - '$ref' => '#/definitions/Dummy.OutputDto', - ], - ], - 400 => [ - 'description' => 'Invalid input', - ], - 404 => [ - 'description' => 'Resource not found', - ], - 422 => [ - 'description' => 'Unprocessable entity', - ], - ], - 'parameters' => [ - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The new Dummy resource', - 'schema' => [ - '$ref' => '#/definitions/Dummy.InputDto', - ], - ], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'produces' => ['application/ld+json'], - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'type' => 'string', - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'schema' => [ - '$ref' => '#/definitions/Dummy.OutputDto', - ], - ], - 404 => [ - 'description' => 'Resource not found', - ], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'consumes' => ['application/ld+json'], - 'produces' => ['application/ld+json'], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'type' => 'string', - ], - [ - 'name' => 'dummy', - 'in' => 'body', - 'description' => 'The updated Dummy resource', - 'schema' => [ - '$ref' => '#/definitions/Dummy.InputDto', - ], - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource updated', - 'schema' => [ - '$ref' => '#/definitions/Dummy.OutputDto', - ], - ], - 400 => [ - 'description' => 'Invalid input', - ], - 404 => [ - 'description' => 'Resource not found', - ], - 422 => [ - 'description' => 'Unprocessable entity', - ], - ], - ]), - ], - ]), - 'definitions' => new \ArrayObject([ - 'Dummy.OutputDto' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => [ - 'url' => 'http://schema.example.com/Dummy', - ], - 'properties' => [ - 'baz' => new \ArrayObject([ - 'readOnly' => true, - 'description' => 'baz', - 'type' => 'string', - ]), - 'bat' => new \ArrayObject([ - 'description' => 'bat', - 'type' => 'integer', - ]), - ], - ]), - 'Dummy.InputDto' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => [ - 'url' => 'http://schema.example.com/Dummy', - ], - 'properties' => [ - 'foo' => new \ArrayObject([ - 'readOnly' => true, - 'description' => 'foo', - 'type' => 'string', - ]), - 'bar' => new \ArrayObject([ - 'description' => 'bar', - 'type' => 'integer', - ]), - ], - ]), - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - /** - * @dataProvider propertyWithDefaultProvider - * - * @param mixed $expectedDefault - * @param mixed $expectedExample - */ - public function testNormalizeWithDefaultProperty($expectedDefault, $expectedExample, PropertyMetadata $propertyMetadata) - { - $documentation = new Documentation(new ResourceNameCollection([DummyPropertyWithDefaultValue::class])); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyPropertyWithDefaultValue::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['foo'])); - - $dummyMetadata = new ResourceMetadata('DummyPropertyWithDefaultValue', null, null, ['get' => ['method' => 'GET']]); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyPropertyWithDefaultValue::class)->shouldBeCalled()->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyPropertyWithDefaultValue::class, 'foo', Argument::any())->shouldBeCalled()->willReturn($propertyMetadata); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - '', - [], - [], - $identifiersExtractorProphecy->reveal() - ); - - $result = $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT); - - $this->assertIsArray($result); - $this->assertEquals($expectedDefault, $result['definitions']['DummyPropertyWithDefaultValue']['properties']['foo']['default']); - $this->assertEquals($expectedExample, $result['definitions']['DummyPropertyWithDefaultValue']['properties']['foo']['example']); - } - - public function propertyWithDefaultProvider() - { - yield 'default should be use for the example if it is not defined' => [ - 'default name', - 'default name', - $this->createStringPropertyMetada('default name'), - ]; - - yield 'should use default and example if they are defined' => [ - 'default name', - 'example name', - $this->createStringPropertyMetada('default name', 'example name'), - ]; - - yield 'should use default and example from swagger context if they are defined' => [ - 'swagger default', - 'swagger example', - $this->createStringPropertyMetada('default name', 'example name', ['swagger_context' => ['default' => 'swagger default', 'example' => 'swagger example']]), - ]; - } - - protected function createStringPropertyMetada($default = null, $example = null, $attributes = []): PropertyMetadata - { - return new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), null, true, true, true, true, false, false, null, null, $attributes, null, null, $default, $example); - } -} diff --git a/tests/Core/Swagger/Serializer/DocumentationNormalizerV3Test.php b/tests/Core/Swagger/Serializer/DocumentationNormalizerV3Test.php deleted file mode 100644 index 8d9de9eb884..00000000000 --- a/tests/Core/Swagger/Serializer/DocumentationNormalizerV3Test.php +++ /dev/null @@ -1,3280 +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\Tests\Swagger\Serializer; - -use ApiPlatform\Core\Api\FilterCollection; -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Api\OperationAwareFormatsProviderInterface; -use ApiPlatform\Core\Api\OperationMethodResolverInterface; -use ApiPlatform\Core\Api\OperationType; -use ApiPlatform\Core\Api\ResourceClassResolverInterface; -use ApiPlatform\Core\Bridge\Symfony\Routing\RouterOperationPathResolver; -use ApiPlatform\Core\JsonSchema\SchemaFactory; -use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Property\PropertyMetadata; -use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Core\Metadata\Property\SubresourceMetadata; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactory; -use ApiPlatform\Core\Operation\UnderscorePathSegmentNameGenerator; -use ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Documentation\Documentation; -use ApiPlatform\Exception\InvalidArgumentException; -use ApiPlatform\JsonSchema\TypeFactory; -use ApiPlatform\JsonSchema\TypeFactoryInterface; -use ApiPlatform\Metadata\Resource\ResourceNameCollection; -use ApiPlatform\OpenApi\Model; -use ApiPlatform\OpenApi\OpenApi; -use ApiPlatform\PathResolver\CustomOperationPathResolver; -use ApiPlatform\PathResolver\OperationPathResolver; -use ApiPlatform\PathResolver\OperationPathResolverInterface; -use ApiPlatform\Tests\Fixtures\DummyFilter; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Answer; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Psr\Container\ContainerInterface; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Routing\Route; -use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\RouterInterface; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; - -/** - * @author Amrouche Hamza - * @author Kévin Dunglas - * @author Anthony GRASSIOT - * @group legacy - */ -class DocumentationNormalizerV3Test extends TestCase -{ - use ProphecyTrait; - - private const OPERATION_FORMATS = [ - 'input_formats' => ['jsonld' => ['application/ld+json']], - 'output_formats' => ['jsonld' => ['application/ld+json']], - ]; - - public function testNormalize(): void - { - $this->doTestNormalize(); - } - - public function testLegacyNormalize(): void - { - $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->willReturn('GET'); - $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'put')->willReturn('PUT'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'get')->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'post')->willReturn('POST'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom')->willReturn('GET'); - $operationMethodResolverProphecy->getCollectionOperationMethod(Dummy::class, 'custom2')->willReturn('POST'); - - $this->doTestNormalize($operationMethodResolverProphecy->reveal()); - } - - private function doTestNormalize(OperationMethodResolverInterface $operationMethodResolver = null): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT'] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - 'custom' => ['method' => 'GET', 'path' => '/foo'] + self::OPERATION_FORMATS, - 'custom2' => ['method' => 'POST', 'path' => '/foo'] + self::OPERATION_FORMATS, - ], - ['pagination_client_items_per_page' => true, 'normalization_context' => [AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false]] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::cetera())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false, null, null, null, true, null, null, null, null, null, null, null)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [], null, null, null, null, ['minLength' => 3, 'maxLength' => 20, 'pattern' => '^dummyPattern$'])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'description', Argument::cetera())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an initializable but not writable property.', true, false, true, true, false, false, null, null, [], null, true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyDate', Argument::cetera())->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTime::class), 'This is a \DateTimeInterface object.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - $operationMethodResolver, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - [ - 'name' => 'itemsPerPage', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 30, - 'minimum' => 0, - ], - 'description' => 'The number of items per page', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The new Dummy resource', - ], - 'summary' => 'Creates a Dummy resource.', - 'responses' => [ - '201' => [ - 'description' => 'Dummy resource created', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'links' => [ - 'GetDummyItem' => [ - 'operationId' => 'getDummyItem', - 'parameters' => ['id' => '$response.body#/id'], - 'description' => 'The `id` value returned in the response can be used as the `id` parameter in `GET /dummies/{id}`.', - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The updated Dummy resource', - ], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource updated', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/foo' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'customDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - [ - 'name' => 'itemsPerPage', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 30, - 'minimum' => 0, - ], - 'description' => 'The number of items per page', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'custom2DummyCollection', - 'summary' => 'Creates a Dummy resource.', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The new Dummy resource', - ], - 'responses' => [ - '201' => [ - 'description' => 'Dummy resource created', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'links' => [ - 'GetDummyItem' => [ - 'operationId' => 'getDummyItem', - 'parameters' => ['id' => '$response.body#/id'], - 'description' => 'The `id` value returned in the response can be used as the `id` parameter in `GET /dummies/{id}`.', - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'additionalProperties' => false, - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - 'minLength' => 3, - 'maxLength' => 20, - 'pattern' => '^dummyPattern$', - ]), - 'description' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is an initializable but not writable property.', - ]), - 'dummyDate' => new \ArrayObject([ - 'nullable' => true, - 'type' => 'string', - 'format' => 'date-time', - 'description' => 'This is a \DateTimeInterface object.', - ]), - ], - ]), - ]), - ], - 'servers' => [['url' => '/app_dev.php/']], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - $this->assertArrayNotHasKey('servers', (array) $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/'])); - $this->assertArrayNotHasKey('servers', (array) $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => ''])); - } - - public function testNormalizeWithNameConverter(): void - { - $this->doTestNormalizeWithNameConverter(); - } - - public function testLegacyNormalizeWithNameConverter(): void - { - $this->doTestNormalizeWithNameConverter(true); - } - - private function doTestNormalizeWithNameConverter(bool $legacy = false): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['name', 'nameConverted'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - null, - ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS] - ); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, null, null, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'nameConverted', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a converted name.', true, true, null, null, false)); - - $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); - $nameConverterProphecy->normalize('name', Dummy::class, 'jsonld', [])->willReturn('name'); - $nameConverterProphecy->normalize('nameConverted', Dummy::class, 'jsonld', [])->willReturn('name_converted'); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - /** - * @var ResourceMetadataFactoryInterface - */ - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - /** - * @var PropertyNameCollectionFactoryInterface - */ - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - /** - * @var PropertyMetadataFactoryInterface - */ - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - /** - * @var NameConverterInterface - */ - $nameConverter = $nameConverterProphecy->reveal(); - - /** - * @var TypeFactoryInterface|null - */ - $typeFactory = null; - /** - * @var SchemaFactoryInterface|null - */ - $schemaFactory = null; - - if (!$legacy) { - $typeFactory = new TypeFactory(); - $schemaFactory = new SchemaFactory($typeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $nameConverter); - $typeFactory->setSchemaFactory($schemaFactory); - } - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactory, - $propertyNameCollectionFactory, - $propertyMetadataFactory, - $schemaFactory, - $typeFactory, - $operationPathResolver, - null, - null, - $legacy ? $nameConverter : null, - true, - 'oauth2', - 'authorizationCode', - '/oauth/v2/token', - '/oauth/v2/auth', - ['scope param'], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Dummy API', - 'description' => 'This is a dummy API', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - 'name_converted' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a converted name.', - ]), - ], - ]), - ]), - 'securitySchemes' => [ - 'oauth' => [ - 'type' => 'oauth2', - 'description' => 'OAuth 2.0 authorization code Grant', - 'flows' => [ - 'authorizationCode' => [ - 'tokenUrl' => '/oauth/v2/token', - 'authorizationUrl' => '/oauth/v2/auth', - 'scopes' => new \ArrayObject(['scope param']), - ], - ], - ], - ], - ], - 'security' => [['oauth' => []]], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithApiKeysEnabled(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - null, - ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS] - ); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, null, null, false)); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $apiKeysConfiguration = [ - 'header' => [ - 'type' => 'header', - 'name' => 'Authorization', - ], - 'query' => [ - 'type' => 'query', - 'name' => 'key', - ], - ]; - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - $apiKeysConfiguration, - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'servers' => [['url' => '/app_dev.php/']], - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - ]), - 'securitySchemes' => [ - 'header' => [ - 'type' => 'apiKey', - 'in' => 'header', - 'description' => 'Value for the Authorization header', - 'name' => 'Authorization', - ], - 'query' => [ - 'type' => 'apiKey', - 'in' => 'query', - 'description' => 'Value for the key query parameter', - 'name' => 'key', - ], - ], - ], - 'security' => [ - ['header' => []], - ['query' => []], - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithOnlyNormalizationGroups(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - $groups = ['dummy', 'foo', 'bar']; - - $ref = 'Dummy-'.implode('_', $groups); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', $groups) - ))->willReturn(new PropertyNameCollection(['gerard'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'gerard', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a gerard.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'summary' => 'Creates a Dummy resource.', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The new Dummy resource', - ], - 'responses' => [ - '201' => [ - 'description' => 'Dummy resource created', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The updated Dummy resource', - ], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource updated', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/'.$ref], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - $ref => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'gerard' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a gerard.', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithOpenApiDefinitionName(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['id'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => [ - 'method' => 'GET', - 'normalization_context' => [ - DocumentationNormalizer::SWAGGER_DEFINITION_NAME => 'Read', - ], - ] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy-Read'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy-Read' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - ], - ]), - ]), - ], - 'servers' => [['url' => '/app_dev.php/']], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithOnlyDenormalizationGroups(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', ['dummy']) - ))->willReturn(new PropertyNameCollection(['gerard'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy']] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'gerard', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a gerard.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The new Dummy resource', - ], - 'summary' => 'Creates a Dummy resource.', - 'responses' => [ - '201' => [ - 'description' => 'Dummy resource created', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [[ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ]], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy-dummy'], - ], - ], - 'description' => 'The updated Dummy resource', - ], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource updated', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - 'Dummy-dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'gerard' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a gerard.', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithNormalizationAndDenormalizationGroups(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', ['dummy']) - ))->willReturn(new PropertyNameCollection(['gerard'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => [ - 'method' => 'PUT', - 'normalization_context' => [AbstractNormalizer::GROUPS => 'dummy'], 'denormalization_context' => [AbstractNormalizer::GROUPS => 'dummy'], - ] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'gerard', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a gerard.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The new Dummy resource', - ], - 'summary' => 'Creates a Dummy resource.', - 'responses' => [ - '201' => [ - 'description' => 'Dummy resource created', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [[ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ]], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy-dummy'], - ], - ], - 'description' => 'The updated Dummy resource', - ], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource updated', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy-dummy'], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - 'Dummy-dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'gerard' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a gerard.', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testFilters(): void - { - $filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $filters = [ - 'f1' => new DummyFilter(['name' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => true, - 'strategy' => 'exact', - 'openapi' => ['x-foo' => 'bar'], - ]]), - 'f2' => new DummyFilter(['ha' => [ - 'property' => 'foo', - 'type' => 'int', - 'required' => false, - 'strategy' => 'partial', - ]]), - 'f3' => new DummyFilter(['toto' => [ - 'property' => 'name', - 'type' => 'array', - 'is_collection' => true, - 'required' => true, - 'strategy' => 'exact', - ]]), - 'f4' => new DummyFilter(['order[name]' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => false, - 'schema' => [ - 'type' => 'string', - 'enum' => ['asc', 'desc'], - ], - ]]), - ]; - - foreach ($filters as $filterId => $filter) { - $filterLocatorProphecy->has($filterId)->willReturn(true); - $filterLocatorProphecy->get($filterId)->willReturn($filter); - } - - $filterLocatorProphecy->has('f5')->willReturn(false); - - $this->doTestNormalizeWithFilters($filterLocatorProphecy->reveal()); - } - - /** - * @group legacy - * @expectedDeprecation The ApiPlatform\Core\Api\FilterCollection class is deprecated since version 2.1 and will be removed in 3.0. Provide an implementation of Psr\Container\ContainerInterface instead. - */ - public function testFiltersWithDeprecatedFilterCollection(): void - { - $this->doTestNormalizeWithFilters(new FilterCollection([ - 'f1' => new DummyFilter(['name' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => true, - 'strategy' => 'exact', - 'openapi' => ['x-foo' => 'bar'], - ]]), - 'f2' => new DummyFilter(['ha' => [ - 'property' => 'foo', - 'type' => 'int', - 'required' => false, - 'strategy' => 'partial', - ]]), - 'f3' => new DummyFilter(['toto' => [ - 'property' => 'name', - 'type' => 'array', - 'is_collection' => true, - 'required' => true, - 'strategy' => 'exact', - ]]), - 'f4' => new DummyFilter(['order[name]' => [ - 'property' => 'name', - 'type' => 'string', - 'required' => false, - 'schema' => [ - 'type' => 'string', - 'enum' => ['asc', 'desc'], - ], - ]]), - ])); - } - - public function testConstructWithInvalidFilterLocator(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "$filterLocator" argument is expected to be an implementation of the "Psr\\Container\\ContainerInterface" interface or null.'); - - new DocumentationNormalizer( - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), - $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), - $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), - null, - null, - $this->prophesize(OperationPathResolverInterface::class)->reveal(), - null, - new \ArrayObject(), // @phpstan-ignore-line - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $this->prophesize(IdentifiersExtractorInterface::class)->reveal() - ); - } - - public function testSupports(): void - { - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $this->assertTrue($normalizer->supportsNormalization($documentation, 'json')); - $this->assertFalse($normalizer->supportsNormalization($documentation)); - $this->assertFalse($normalizer->supportsNormalization(new Dummy(), 'json')); - $this->assertTrue($normalizer->hasCacheableSupportsMethod()); - } - - public function testNormalizeWithNoOperations(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldNotBeCalled(); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - null, - [], - [], - ['formats' => ['jsonld' => ['application/ld+json']]] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name')->shouldNotBeCalled(); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => '', - 'version' => '0.0.0', - ], - 'paths' => new \ArrayObject([]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithCustomMethod(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - null, - [], - ['get' => ['method' => 'FOO'] + self::OPERATION_FORMATS] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => '', - 'version' => '0.0.0', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'foo' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - ]), - ], - ]), - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithNestedNormalizationGroups(): void - { - $title = 'Test API'; - $description = 'This is a test API.'; - $version = '1.2.3'; - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), $title, $description, $version); - $groups = ['dummy', 'foo', 'bar']; - $ref = 'Dummy-'.implode('_', $groups); - $relatedDummyRef = 'RelatedDummy-'.implode('_', $groups); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', $groups) - ))->willReturn(new PropertyNameCollection(['name', 'relatedDummy'])); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['name'])); - $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class, Argument::allOf( - Argument::type('array'), - Argument::withEntry('serializer_groups', $groups) - ))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'put' => ['method' => 'PUT', 'normalization_context' => [AbstractNormalizer::GROUPS => $groups]] + self::OPERATION_FORMATS, - ], - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - 'post' => ['method' => 'POST'] + self::OPERATION_FORMATS, - ] - ); - - $relatedDummyMetadata = new ResourceMetadata( - 'RelatedDummy', - 'This is a related dummy.', - 'http://schema.example.com/RelatedDummy', - [ - 'get' => ['method' => 'GET'] + self::OPERATION_FORMATS, - ] - ); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - $resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedDummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, RelatedDummy::class), 'This is a related dummy \o/.', true, true, true, true, false, false, null, null, [])); - $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => [ - 'Dummy', - ], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The new Dummy resource', - ], - 'summary' => 'Creates a Dummy resource.', - 'responses' => [ - '201' => [ - 'description' => 'Dummy resource created', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [[ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ]], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'requestBody' => [ - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The updated Dummy resource', - ], - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource updated', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/'.$ref], - ], - ], - ], - '400' => ['description' => 'Invalid input'], - '404' => ['description' => 'Resource not found'], - '422' => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - $ref => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - 'relatedDummy' => new \ArrayObject([ - 'description' => 'This is a related dummy \o/.', - 'nullable' => true, - 'anyOf' => [ - ['$ref' => '#/components/schemas/'.$relatedDummyRef], - ], - ]), - ], - ]), - $relatedDummyRef => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a related dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/RelatedDummy'], - 'properties' => [ - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - private function doTestNormalizeWithFilters($filterLocator): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), '', '', '0.0.0'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - null, - [], - ['get' => ['method' => 'GET', 'filters' => ['f1', 'f2', 'f3', 'f4', 'f5']] + self::OPERATION_FORMATS] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - $filterLocator, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => '', - 'version' => '0.0.0', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - 'parameters' => [ - [ - 'x-foo' => 'bar', - 'name' => 'name', - 'in' => 'query', - 'required' => true, - 'schema' => ['type' => 'string'], - ], - [ - 'name' => 'ha', - 'in' => 'query', - 'required' => false, - 'schema' => ['type' => 'integer'], - ], - [ - 'name' => 'toto', - 'in' => 'query', - 'required' => true, - 'schema' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'string', - ], - ], - 'style' => 'deepObject', - 'explode' => true, - ], - [ - 'name' => 'order[name]', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'string', - 'enum' => ['asc', 'desc'], - ], - ], - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'properties' => [ - 'name' => new \ArrayObject([ - 'description' => 'This is a name.', - 'type' => 'string', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithSubResource(): void - { - $this->doTestNormalizeWithSubResource(); - } - - public function testLegacytNormalizeWithSubResource(): void - { - $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); - $formatProviderProphecy->getFormatsFromOperation(Question::class, 'get', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); - $formatProviderProphecy->getFormatsFromOperation(Answer::class, 'get', OperationType::SUBRESOURCE)->willReturn(['xml' => ['text/xml']]); - - $this->doTestNormalizeWithSubResource($formatProviderProphecy->reveal()); - } - - private function doTestNormalizeWithSubResource(OperationAwareFormatsProviderInterface $formatProvider = null): void - { - $documentation = new Documentation(new ResourceNameCollection([Question::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Question::class, Argument::cetera())->willReturn(new PropertyNameCollection(['answer'])); - $propertyNameCollectionFactoryProphecy->create(Answer::class, Argument::cetera())->willReturn(new PropertyNameCollection(['content'])); - - $questionMetadata = new ResourceMetadata( - 'Question', - 'This is a question.', - 'http://schema.example.com/Question', - ['get' => ['method' => 'GET', 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]]] - ); - $answerMetadata = new ResourceMetadata( - 'Answer', - 'This is an answer.', - 'http://schema.example.com/Answer', - [], - ['get' => ['method' => 'GET']] + self::OPERATION_FORMATS, - [], - ['get' => ['method' => 'GET', 'input_formats' => ['xml' => ['text/xml']], 'output_formats' => ['xml' => ['text/xml']]]] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Question::class)->willReturn($questionMetadata); - $resourceMetadataFactoryProphecy->create(Answer::class)->willReturn($answerMetadata); - - $subresourceMetadata = new SubresourceMetadata(Answer::class, false); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Question::class, 'answer', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, false, Question::class, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, Answer::class)), 'This is a name.', true, true, true, true, false, false, null, null, [], $subresourceMetadata)); - $propertyMetadataFactoryProphecy->create(Answer::class, 'content', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, false, Question::class, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, Answer::class)), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $routeCollection = new RouteCollection(); - $routeCollection->add('api_questions_answer_get_subresource', new Route('/api/questions/{id}/answer.{_format}')); - $routeCollection->add('api_questions_get_item', new Route('/api/questions/{id}.{_format}')); - - $routerProphecy = $this->prophesize(RouterInterface::class); - $routerProphecy->getRouteCollection()->willReturn($routeCollection); - - $operationPathResolver = new RouterOperationPathResolver($routerProphecy->reveal(), new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator()))); - - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); - $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $subresourceOperationFactory = new SubresourceOperationFactory($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new UnderscorePathSegmentNameGenerator(), $identifiersExtractorProphecy->reveal()); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactory, - $propertyNameCollectionFactory, - $propertyMetadataFactory, - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - $subresourceOperationFactory, - true, - 'page', - false, - 'itemsPerPage', - $formatProvider ?? [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/api/questions/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Question'], - 'operationId' => 'getQuestionItem', - 'summary' => 'Retrieves a Question resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Question resource response', - 'content' => [ - 'application/json' => [ - 'schema' => ['$ref' => '#/components/schemas/Question'], - ], - 'text/csv' => [ - 'schema' => ['$ref' => '#/components/schemas/Question'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - ], - '/api/questions/{id}/answer' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Answer', 'Question'], - 'operationId' => 'api_questions_answer_get_subresource', - 'summary' => 'Retrieves a Answer resource.', - 'responses' => [ - '200' => [ - 'description' => 'Answer resource response', - 'content' => [ - 'text/xml' => [ - 'schema' => ['$ref' => '#/components/schemas/Answer'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Question' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a question.', - 'externalDocs' => ['url' => 'http://schema.example.com/Question'], - 'properties' => [ - 'answer' => new \ArrayObject([ - 'type' => 'array', - 'description' => 'This is a name.', - 'items' => ['$ref' => '#/components/schemas/Answer'], - ]), - ], - ]), - 'Answer' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is an answer.', - 'externalDocs' => ['url' => 'http://schema.example.com/Answer'], - 'properties' => [ - 'content' => new \ArrayObject([ - 'type' => 'array', - 'description' => 'This is a name.', - 'items' => ['$ref' => '#/components/schemas/Answer'], - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation)); - } - - public function testNormalizeWithPropertyOpenApiContext(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['id', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, ['openapi_context' => ['type' => 'string', 'enum' => ['one', 'two'], 'example' => 'one']])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'servers' => [['url' => '/app_dev.php/']], - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - '404' => ['description' => 'Resource not found'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - 'enum' => ['one', 'two'], - 'example' => 'one', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithPaginationClientEnabled(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [], - ['get' => ['method' => 'GET', 'pagination_client_enabled' => true] + self::OPERATION_FORMATS] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, ['openapi_context' => ['type' => 'string', 'enum' => ['one', 'two'], 'example' => 'one']])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'servers' => [['url' => '/app_dev.php/']], - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - [ - 'name' => 'pagination', - 'in' => 'query', - 'required' => false, - 'schema' => ['type' => 'boolean'], - 'description' => 'Enable or disable pagination', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - 'enum' => ['one', 'two'], - 'example' => 'one', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithPaginationCustomDefaultAndMaxItemsPerPage(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [], - ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS], - ['pagination_client_items_per_page' => true, 'pagination_items_per_page' => 20, 'pagination_maximum_items_per_page' => 80] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, ['openapi_context' => ['type' => 'string', 'enum' => ['one', 'two'], 'example' => 'one']])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'servers' => [['url' => '/app_dev.php/']], - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - [ - 'name' => 'itemsPerPage', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 20, - 'minimum' => 0, - 'maximum' => 80, - ], - 'description' => 'The number of items per page', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - 'enum' => ['one', 'two'], - 'example' => 'one', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - /** - * @group legacy - * @expectedDeprecation 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. - */ - public function testLegacyNormalizeWithPaginationCustomDefaultAndMaxItemsPerPage(): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['id', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [], - ['get' => ['method' => 'GET'] + self::OPERATION_FORMATS], - ['pagination_client_items_per_page' => true, 'pagination_items_per_page' => 20, 'maximum_items_per_page' => 80] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, ['openapi_context' => ['type' => 'string', 'enum' => ['one', 'two'], 'example' => 'one']])); - - $operationPathResolver = new CustomOperationPathResolver(new OperationPathResolver(new UnderscorePathSegmentNameGenerator())); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, - false, - '', - '', - '', - '', - [], - [], - null, - true, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'servers' => [['url' => '/app_dev.php/']], - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [ - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - ], - 'description' => 'The collection page number', - ], - [ - 'name' => 'itemsPerPage', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 20, - 'minimum' => 0, - 'maximum' => 80, - ], - 'description' => 'The number of items per page', - ], - ], - 'responses' => [ - '200' => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/ld+json' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - 'enum' => ['one', 'two'], - 'example' => 'one', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/app_dev.php/'])); - } - - public function testNormalizeWithCustomFormatsDefinedAtOperationLevel(): void - { - $this->doTestNormalizeWithCustomFormatsDefinedAtOperationLevel(); - } - - public function testLegacyNormalizeWithCustomFormatsDefinedAtOperationLevel(): void - { - $formatProviderProphecy = $this->prophesize(OperationAwareFormatsProviderInterface::class); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::ITEM)->willReturn(['jsonapi' => ['application/vnd.api+json']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'put', OperationType::ITEM)->willReturn(['json' => ['application/json'], 'csv' => ['text/csv']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'get', OperationType::COLLECTION)->willReturn(['xml' => ['application/xml', 'text/xml']]); - $formatProviderProphecy->getFormatsFromOperation(Dummy::class, 'post', OperationType::COLLECTION)->willReturn(['xml' => ['text/xml'], 'csv' => ['text/csv']]); - - $this->doTestNormalizeWithCustomFormatsDefinedAtOperationLevel($formatProviderProphecy->reveal()); - } - - private function doTestNormalizeWithCustomFormatsDefinedAtOperationLevel(OperationAwareFormatsProviderInterface $formatsProvider = null): void - { - $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Test API', 'This is a test API.', '1.2.3'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection(['id', 'name'])); - - $dummyMetadata = new ResourceMetadata( - 'Dummy', - 'This is a dummy.', - 'http://schema.example.com/Dummy', - [ - 'get' => ['method' => 'GET', 'output_formats' => ['jsonapi' => ['application/vnd.api+json']]], - 'put' => ['method' => 'PUT', 'output_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']], 'input_formats' => ['json' => ['application/json'], 'csv' => ['text/csv']]], ], - [ - 'get' => ['method' => 'GET', 'output_formats' => ['xml' => ['application/xml', 'text/xml']]], - 'post' => ['method' => 'POST', 'output_formats' => ['xml' => ['text/xml'], 'csv' => ['text/csv']], 'input_formats' => ['xml' => ['text/xml'], 'csv' => ['text/csv']]], - ] - ); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn($dummyMetadata); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false, null, null, null, true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::cetera())->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [])); - - $operationPathResolver = new OperationPathResolver(new UnderscorePathSegmentNameGenerator()); - - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - $identifiersExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, false, - '', - '', - '', - '', - [], - [], - null, - false, - 'page', - false, - 'itemsPerPage', - $formatsProvider ?? [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal() - ); - - $expected = [ - 'openapi' => '3.0.2', - 'info' => [ - 'title' => 'Test API', - 'description' => 'This is a test API.', - 'version' => '1.2.3', - ], - 'paths' => new \ArrayObject([ - '/dummies' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyCollection', - 'summary' => 'Retrieves the collection of Dummy resources.', - 'parameters' => [], - 'responses' => [ - 200 => [ - 'description' => 'Dummy collection response', - 'content' => [ - 'application/xml' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'text/xml' => [ - 'schema' => [ - 'type' => 'array', - 'items' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - ], - ], - ]), - 'post' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'postDummyCollection', - 'summary' => 'Creates a Dummy resource.', - 'requestBody' => [ - 'content' => [ - 'text/xml' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - 'text/csv' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The new Dummy resource', - ], - 'responses' => [ - 201 => [ - 'description' => 'Dummy resource created', - 'content' => [ - 'text/xml' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - 'text/csv' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'links' => [ - 'GetDummyItem' => [ - 'operationId' => 'getDummyItem', - 'parameters' => ['id' => '$response.body#/id'], - 'description' => 'The `id` value returned in the response can be used as the `id` parameter in `GET /dummies/{id}`.', - ], - ], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - '/dummies/{id}' => [ - 'get' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'getDummyItem', - 'summary' => 'Retrieves a Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource response', - 'content' => [ - 'application/vnd.api+json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - 404 => ['description' => 'Resource not found'], - ], - ]), - 'put' => new \ArrayObject([ - 'tags' => ['Dummy'], - 'operationId' => 'putDummyItem', - 'summary' => 'Replaces the Dummy resource.', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'schema' => ['type' => 'string'], - 'required' => true, - ], - ], - 'requestBody' => [ - 'content' => [ - 'application/json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - 'text/csv' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - 'description' => 'The updated Dummy resource', - ], - 'responses' => [ - 200 => [ - 'description' => 'Dummy resource updated', - 'content' => [ - 'application/json' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - 'text/csv' => [ - 'schema' => ['$ref' => '#/components/schemas/Dummy'], - ], - ], - ], - 400 => ['description' => 'Invalid input'], - 404 => ['description' => 'Resource not found'], - 422 => ['description' => 'Unprocessable entity'], - ], - ]), - ], - ]), - 'components' => [ - 'schemas' => new \ArrayObject([ - 'Dummy' => new \ArrayObject([ - 'type' => 'object', - 'description' => 'This is a dummy.', - 'externalDocs' => ['url' => 'http://schema.example.com/Dummy'], - 'properties' => [ - 'id' => new \ArrayObject([ - 'type' => 'integer', - 'description' => 'This is an id.', - 'readOnly' => true, - ]), - 'name' => new \ArrayObject([ - 'type' => 'string', - 'description' => 'This is a name.', - ]), - ], - ]), - ]), - ], - ]; - - $this->assertEquals($expected, $normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['base_url' => '/'])); - } - - /** - * @group legacy - * @expectedDeprecation Using the swagger DocumentationNormalizer is deprecated in favor of decorating the OpenApiFactory, use the "openapi.backward_compatibility_layer" configuration to change this behavior. - */ - public function testNormalizeOpenApi() - { - $openapi = new OpenApi(new Model\Info('api', 'v1'), [], new Model\Paths()); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $operationPathResolver = new OperationPathResolver(new UnderscorePathSegmentNameGenerator()); - $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class); - - $openApiNormalizerProphecy = $this->prophesize(NormalizerInterface::class); - $openApiNormalizerProphecy->normalize($openapi, null, [])->willReturn([])->shouldBeCalled(); - - $normalizer = new DocumentationNormalizer( - $resourceMetadataFactoryProphecy->reveal(), - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - null, - null, - $operationPathResolver, - null, - null, - null, false, - '', - '', - '', - '', - [], - [], - null, - false, - 'page', - false, - 'itemsPerPage', - [], - false, - 'pagination', - ['spec_version' => 3], - [2, 3], - $identifiersExtractorProphecy->reveal(), - $openApiNormalizerProphecy->reveal() - ); - - $this->assertTrue($normalizer->supportsNormalization($openapi, 'json')); - $this->assertEquals([], $normalizer->normalize($openapi)); - } -} diff --git a/tests/Core/Test/BookTestDeprecatedClient.php b/tests/Core/Test/BookTestDeprecatedClient.php deleted file mode 100644 index 74dc5ea72ee..00000000000 --- a/tests/Core/Test/BookTestDeprecatedClient.php +++ /dev/null @@ -1,36 +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\Tests\Test; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase; -use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client; -use ApiPlatform\Core\Bridge\Symfony\Routing\Router; - -class BookTestDeprecatedClient extends ApiTestCase -{ - /** @phpstan-ignore-next-line */ - private Client $client; - /** @phpstan-ignore-next-line */ - private Router $router; - - protected function setup(): void - { - $this->client = static::createClient(); - } - - public function testWorks(): void - { - $this->assertTrue(true); - } -} diff --git a/tests/Core/Upgrade/SubresourceTransformerTest.php b/tests/Core/Upgrade/SubresourceTransformerTest.php deleted file mode 100644 index aa70709adee..00000000000 --- a/tests/Core/Upgrade/SubresourceTransformerTest.php +++ /dev/null @@ -1,337 +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\Tests\Core\Bridge\Rector\Service; - -use ApiPlatform\Core\Upgrade\SubresourceTransformer; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Answer; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyAggregateOffer; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyOffer; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyProduct; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FourthLevel; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Greeting; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Person; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Question; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedOwnedDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedOwningDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThirdLevel; -use PHPUnit\Framework\TestCase; - -class SubresourceTransformerTest extends TestCase -{ - /** - * @dataProvider toUriVariablesProvider - */ - public function testToUriVariables(array $metadata, array $expectedMetadata): void - { - $subresourceTransformer = new SubresourceTransformer(); - self::assertEquals($expectedMetadata, $subresourceTransformer->toUriVariables($metadata)); - } - - public function toUriVariablesProvider(): \Generator - { - yield '/questions/{id}/answer' => [ - [ - 'property' => 'answer', - 'collection' => false, - 'resource_class' => Answer::class, - 'identifiers' => ['id' => [Question::class, 'id', true]], - 'path' => '/questions/{id}/answer.{_format}', - ], - [ - 'id' => [ - 'from_class' => Question::class, - 'from_property' => 'answer', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - - yield '/questions/{id}/answer/related_questions' => [ - [ - 'property' => 'relatedQuestions', - 'collection' => true, - 'resource_class' => Question::class, - 'identifiers' => [ - 'id' => [Question::class, 'id', true], - 'answer' => [Answer::class, 'id', false], - ], - 'path' => '/questions/{id}/answer/related_questions.{_format}', - ], - [ - 'id' => [ - 'from_class' => Question::class, - 'from_property' => 'answer', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - 'answer' => [ - 'from_class' => Answer::class, - 'from_property' => null, - 'to_property' => 'answer', - 'identifiers' => [], - 'composite_identifier' => false, - 'expanded_value' => 'answer', - ], - ], - ]; - - yield '/dummies/{id}/related_dummies' => [ - [ - 'property' => 'relatedDummies', - 'collection' => true, - 'resource_class' => RelatedDummy::class, - 'identifiers' => ['id' => [Dummy::class, 'id', true]], - 'path' => '/dummies/{id}/related_dummies.{_format}', - ], - [ - 'id' => [ - 'from_class' => Dummy::class, - 'from_property' => 'relatedDummies', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - - yield '/dummies/{id}/related_dummies/{relatedDummies}' => [ - [ - 'property' => 'id', - 'collection' => false, - 'resource_class' => RelatedDummy::class, - 'identifiers' => [ - 'id' => [Dummy::class, 'id', true], - 'relatedDummies' => [RelatedDummy::class, 'id', true], - ], - 'path' => '/dummies/{id}/related_dummies/{relatedDummies}.{_format}', - ], - [ - 'id' => [ - 'from_class' => Dummy::class, - 'from_property' => 'relatedDummies', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - 'relatedDummies' => [ - 'from_class' => RelatedDummy::class, - 'from_property' => null, - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - - yield '/dummies/{id}/related_dummies/{relatedDummies}/third_level' => [ - [ - 'property' => 'thirdLevel', - 'collection' => false, - 'resource_class' => ThirdLevel::class, - 'identifiers' => [ - 'id' => [Dummy::class, 'id', true], - 'relatedDummies' => [RelatedDummy::class, 'id', true], - ], - 'path' => '/dummies/{id}/related_dummies/{relatedDummies}/third_level.{_format}', - ], - [ - 'id' => [ - 'from_class' => Dummy::class, - 'from_property' => 'relatedDummies', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - 'relatedDummies' => [ - 'from_class' => RelatedDummy::class, - 'from_property' => 'thirdLevel', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - - yield '/dummies/{id}/related_dummies/{relatedDummies}/third_level/fourth_level' => [ - [ - 'property' => 'fourthLevel', - 'collection' => false, - 'resource_class' => FourthLevel::class, - 'identifiers' => [ - 'id' => [Dummy::class, 'id', true], - 'relatedDummies' => [RelatedDummy::class, 'id', true], - 'thirdLevel' => [ThirdLevel::class, 'id', false], - ], - 'path' => '/dummies/{id}/related_dummies/{relatedDummies}/third_level/fourth_level.{_format}', - ], - [ - 'id' => [ - 'from_class' => Dummy::class, - 'from_property' => 'relatedDummies', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - 'relatedDummies' => [ - 'from_class' => RelatedDummy::class, - 'from_property' => 'thirdLevel', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - 'thirdLevel' => [ - 'from_class' => ThirdLevel::class, - 'from_property' => 'fourthLevel', - 'to_property' => null, - 'identifiers' => [], - 'composite_identifier' => false, - 'expanded_value' => 'third_level', - ], - ], - ]; - - yield '/dummy_products/{id}/offers/{offers}/offers' => [ - [ - 'property' => 'offers', - 'collection' => true, - 'resource_class' => DummyOffer::class, - 'identifiers' => [ - 'id' => [DummyProduct::class, 'id', true], - 'offers' => [DummyAggregateOffer::class, 'id', true], - ], - 'path' => '/dummy_products/{id}/offers/{offers}/offers.{_format}', - ], - [ - 'id' => [ - 'from_class' => DummyProduct::class, - 'from_property' => null, - 'to_property' => 'product', - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - 'offers' => [ - 'from_class' => DummyAggregateOffer::class, - 'from_property' => null, - 'to_property' => 'aggregate', - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - - yield '/dummy_aggregate_offers/{id}/offers' => [ - [ - 'property' => 'offers', - 'collection' => true, - 'resource_class' => DummyOffer::class, - 'identifiers' => [ - 'id' => [DummyAggregateOffer::class, 'id', true], - ], - 'path' => '/dummy_aggregate_offers/{id}/offers.{_format}', - ], - [ - 'id' => [ - 'from_class' => DummyAggregateOffer::class, - 'from_property' => null, - 'to_property' => 'aggregate', - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - - yield '/people/{id}/sent_greetings' => [ - [ - 'property' => 'sentGreetings', - 'collection' => true, - 'resource_class' => Greeting::class, - 'identifiers' => [ - 'id' => [Person::class, 'id', true], - ], - 'path' => '/people/{id}/sent_greetings.{_format}', - ], - [ - 'id' => [ - 'from_class' => Person::class, - 'from_property' => null, - 'to_property' => 'sender', - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - - yield '/related_owned_dummies/{id}/owning_dummy' => [ - [ - 'property' => 'owningDummy', - 'collection' => false, - 'resource_class' => Dummy::class, - 'identifiers' => [ - 'id' => [RelatedOwnedDummy::class, 'id', true], - ], - 'path' => '/related_owned_dummies/{id}/owned_dummy.{_format}', - ], - [ - 'id' => [ - 'from_class' => RelatedOwnedDummy::class, - 'from_property' => 'owningDummy', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - - yield '/related_owning_dummies/{id}/owned_dummy' => [ - [ - 'property' => 'ownedDummy', - 'collection' => false, - 'resource_class' => Dummy::class, - 'identifiers' => [ - 'id' => [RelatedOwningDummy::class, 'id', true], - ], - 'path' => '/related_owning_dummies/{id}/owned_dummy.{_format}', - ], - [ - 'id' => [ - 'from_class' => RelatedOwningDummy::class, - 'from_property' => 'ownedDummy', - 'to_property' => null, - 'identifiers' => ['id'], - 'composite_identifier' => false, - 'expanded_value' => null, - ], - ], - ]; - } -} diff --git a/tests/Core/Validator/EventListener/ValidateListenerTest.php b/tests/Core/Validator/EventListener/ValidateListenerTest.php deleted file mode 100644 index 4600840bc8c..00000000000 --- a/tests/Core/Validator/EventListener/ValidateListenerTest.php +++ /dev/null @@ -1,189 +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\Tests\Validator\EventListener; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Symfony\EventListener\ValidateListener; -use ApiPlatform\Tests\Fixtures\DummyEntity; -use ApiPlatform\Validator\Exception\ValidationException; -use ApiPlatform\Validator\ValidatorInterface; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Event\ViewEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * @author Samuel ROZE - * @author Kévin Dunglas - * - * @group legacy - */ -class ValidateListenerTest extends TestCase -{ - use ProphecyTrait; - - public function testNotAnApiPlatformRequest() - { - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - - $request = new Request(); - $request->setMethod('POST'); - - $listener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); - - $event = new ViewEvent($this->prophesize(HttpKernelInterface::class)->reveal(), $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, []); - $listener->onKernelView($event); - } - - public function testValidatorIsCalled() - { - $data = new DummyEntity(); - $expectedValidationGroups = ['a', 'b', 'c']; - - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, ['groups' => $expectedValidationGroups])->shouldBeCalled(); - $validator = $validatorProphecy->reveal(); - - [$resourceMetadataFactory, $event] = $this->createEventObject($expectedValidationGroups, $data); - - $validationViewListener = new ValidateListener($validator, $resourceMetadataFactory); - $validationViewListener->onKernelView($event); - } - - public function testDoNotValidateWhenControllerResultIsResponse() - { - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - - $dummy = new DummyEntity(); - - $request = new Request([], [], ['data' => $dummy, '_api_resource_class' => DummyEntity::class, '_api_collection_operation_name' => 'post', '_api_receive' => false]); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - $validationViewListener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); - $validationViewListener->onKernelView($event); - } - - public function testDoNotValidateWhenReceiveFlagIsFalse() - { - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - - $dummy = new DummyEntity(); - - $request = new Request([], [], ['data' => $dummy, '_api_resource_class' => DummyEntity::class, '_api_collection_operation_name' => 'post', '_api_receive' => false]); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - $validationViewListener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); - $validationViewListener->onKernelView($event); - } - - public function testDoNotValidateWhenDisabledInOperationAttribute() - { - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate(Argument::cetera())->shouldNotBeCalled(); - - $resourceMetadata = new ResourceMetadata('DummyEntity', null, null, [], [ - 'post' => [ - 'validate' => false, - ], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->willReturn($resourceMetadata); - - $dummy = new DummyEntity(); - - $request = new Request([], [], ['data' => $dummy, '_api_resource_class' => DummyEntity::class, '_api_collection_operation_name' => 'post']); - $request->setMethod('POST'); - - $event = new ViewEvent( - $this->prophesize(HttpKernelInterface::class)->reveal(), - $request, - \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, - $dummy - ); - - $validationViewListener = new ValidateListener($validatorProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); - $validationViewListener->onKernelView($event); - } - - public function testThrowsValidationExceptionWithViolationsFound() - { - $this->expectException(ValidationException::class); - - $data = new DummyEntity(); - $expectedValidationGroups = ['a', 'b', 'c']; - - $validatorProphecy = $this->prophesize(ValidatorInterface::class); - $validatorProphecy->validate($data, ['groups' => $expectedValidationGroups])->willThrow(new ValidationException())->shouldBeCalled(); - $validator = $validatorProphecy->reveal(); - - [$resourceMetadataFactory, $event] = $this->createEventObject($expectedValidationGroups, $data); - - $validationViewListener = new ValidateListener($validator, $resourceMetadataFactory); - $validationViewListener->onKernelView($event); - } - - private function createEventObject($expectedValidationGroups, $data, bool $receive = true): array - { - $resourceMetadata = new ResourceMetadata(null, null, null, [ - 'create' => ['validation_groups' => $expectedValidationGroups], - ]); - - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); - if ($receive) { - $resourceMetadataFactoryProphecy->create(DummyEntity::class)->willReturn($resourceMetadata)->shouldBeCalled(); - } - $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); - - $kernel = $this->prophesize(HttpKernelInterface::class)->reveal(); - $request = new Request([], [], [ - '_api_resource_class' => DummyEntity::class, - '_api_item_operation_name' => 'create', - '_api_format' => 'json', - '_api_mime_type' => 'application/json', - '_api_receive' => $receive, - ]); - - $request->setMethod('POST'); - $event = new ViewEvent($kernel, $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, $data); - - return [$resourceMetadataFactory, $event]; - } -} diff --git a/tests/Doctrine/Common/State/PersistProcessorTest.php b/tests/Doctrine/Common/State/PersistProcessorTest.php index bd10fdce00d..0881f65e3e1 100644 --- a/tests/Doctrine/Common/State/PersistProcessorTest.php +++ b/tests/Doctrine/Common/State/PersistProcessorTest.php @@ -13,11 +13,11 @@ namespace ApiPlatform\Tests\Doctrine\Common\State; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Doctrine\Common\State\PersistProcessor; use ApiPlatform\Metadata\Get; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Tests\ProphecyTrait; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\Persistence\ManagerRegistry; diff --git a/tests/Doctrine/Common/State/RemoveProcessorTest.php b/tests/Doctrine/Common/State/RemoveProcessorTest.php index c63d425693d..420b96a4e9e 100644 --- a/tests/Doctrine/Common/State/RemoveProcessorTest.php +++ b/tests/Doctrine/Common/State/RemoveProcessorTest.php @@ -13,11 +13,11 @@ namespace ApiPlatform\Tests\Doctrine\Common\State; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Doctrine\Common\State\RemoveProcessor; use ApiPlatform\Metadata\Delete; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Tests\ProphecyTrait; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; use PHPUnit\Framework\TestCase; diff --git a/tests/Doctrine/EventListener/PublishMercureUpdatesListenerTest.php b/tests/Doctrine/EventListener/PublishMercureUpdatesListenerTest.php index 483c450eca2..3563a20f0cc 100644 --- a/tests/Doctrine/EventListener/PublishMercureUpdatesListenerTest.php +++ b/tests/Doctrine/EventListener/PublishMercureUpdatesListenerTest.php @@ -16,8 +16,6 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Doctrine\EventListener\PublishMercureUpdatesListener; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface as GraphQlMercureSubscriptionIriGeneratorInterface; @@ -33,6 +31,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyFriend; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyMercure; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyOffer; +use ApiPlatform\Tests\ProphecyTrait; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\UnitOfWork; @@ -47,15 +46,11 @@ /** * @author Kévin Dunglas - * @group legacy */ class PublishMercureUpdatesListenerTest extends TestCase { use ProphecyTrait; - /** - * @group legacy - */ public function testLegacyPublishUpdate(): void { if (method_exists(Update::class, 'isPrivate')) { @@ -145,9 +140,6 @@ public function testLegacyPublishUpdate(): void $this->assertSame([['enable_async_update' => true], ['enable_async_update' => true], ['enable_async_update' => true], ['foo', 'bar', 'enable_async_update' => true]], $targets); } - /** - * @group legacy - */ public function testPublishUpdateWithLegacySignature(): void { if (!method_exists(Update::class, 'isPrivate')) { @@ -309,7 +301,9 @@ public function testPublishUpdate(): void $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $resourceMetadataFactoryProphecy->create(DummyMercure::class)->willReturn(new ResourceMetadata(null, null, null, null, null, ['mercure' => []])); + $resourceMetadataFactoryProphecy->create(DummyMercure::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [(new ApiResource())->withOperations(new Operations([ + 'get' => (new Get())->withMercure([]), + ]))])); $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [(new ApiResource())->withOperations(new Operations([ 'get' => (new Get())->withMercure(['hub' => 'managed', 'enable_async_update' => false])->withNormalizationContext(['groups' => ['foo', 'bar']]), diff --git a/tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php b/tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php index 06d6128faf0..a90d31303b8 100644 --- a/tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php +++ b/tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php @@ -16,15 +16,17 @@ use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Api\UrlGeneratorInterface; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Doctrine\EventListener\PurgeHttpCacheListener; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Exception\ItemNotFoundException; use ApiPlatform\HttpCache\PurgerInterface; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Tests\Fixtures\NotAResource; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ContainNonResource; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyNoGetOperation; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; +use ApiPlatform\Tests\ProphecyTrait; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Event\PreUpdateEventArgs; @@ -36,7 +38,6 @@ /** * @author Kévin Dunglas - * @group legacy */ class PurgeHttpCacheListenerTest extends TestCase { @@ -74,6 +75,7 @@ public function testOnFlush() $iriConverterProphecy->getIriFromResource(Argument::any())->willThrow(new ItemNotFoundException()); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled(); $resourceClassResolverProphecy->getResourceClass(Argument::type(Dummy::class))->willReturn(Dummy::class)->shouldBeCalled(); $resourceClassResolverProphecy->getResourceClass(Argument::type(DummyNoGetOperation::class))->willReturn(DummyNoGetOperation::class)->shouldBeCalled(); @@ -125,6 +127,7 @@ public function testPreUpdate() $iriConverterProphecy->getIriFromResource($newRelatedDummy)->willReturn('/related_dummies/new')->shouldBeCalled(); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled(); $resourceClassResolverProphecy->getResourceClass(Argument::type(Dummy::class))->willReturn(Dummy::class)->shouldBeCalled(); $emProphecy = $this->prophesize(EntityManagerInterface::class); @@ -168,4 +171,45 @@ public function testNothingToPurge() $listener->preUpdate($eventArgs); $listener->postFlush(); } + + public function testNotAResourceClass() + { + $containNonResource = new ContainNonResource(); + $nonResource = new NotAResource('foo', 'bar'); + + $purgerProphecy = $this->prophesize(PurgerInterface::class); + $purgerProphecy->purge([])->shouldNotBeCalled(); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $iriConverterProphecy->getIriFromResource(ContainNonResource::class, UrlGeneratorInterface::ABS_PATH, Argument::any())->willReturn('/dummies/1'); + $iriConverterProphecy->getIriFromResource($nonResource)->shouldNotBeCalled(); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->getResourceClass(Argument::type(ContainNonResource::class))->willReturn(ContainNonResource::class)->shouldBeCalled(); + $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false)->shouldBeCalled(); + + $emProphecy = $this->prophesize(EntityManagerInterface::class); + + $uowProphecy = $this->prophesize(UnitOfWork::class); + $uowProphecy->getScheduledEntityInsertions()->willReturn([$containNonResource])->shouldBeCalled(); + $uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled(); + $uowProphecy->getScheduledEntityDeletions()->willReturn([])->shouldBeCalled(); + + $emProphecy = $this->prophesize(EntityManagerInterface::class); + $emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled(); + + $dummyClassMetadata = new ClassMetadata(ContainNonResource::class); + $dummyClassMetadata->associationMappings = [ + 'notAResource' => [], + ]; + $emProphecy->getClassMetadata(ContainNonResource::class)->willReturn($dummyClassMetadata); + $eventArgs = new OnFlushEventArgs($emProphecy->reveal()); + + $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $propertyAccessorProphecy->isReadable(Argument::type(ContainNonResource::class), 'notAResource')->willReturn(true); + $propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'notAResource')->shouldBeCalled()->willReturn($nonResource); + + $listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal()); + $listener->onFlush($eventArgs); + } } diff --git a/tests/Doctrine/EventListener/WriteListenerTest.php b/tests/Doctrine/EventListener/WriteListenerTest.php deleted file mode 100644 index bd1c9bcfa41..00000000000 --- a/tests/Doctrine/EventListener/WriteListenerTest.php +++ /dev/null @@ -1,146 +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\Tests\Doctrine\EventListener; - -use ApiPlatform\Core\Tests\ProphecyTrait; -use ApiPlatform\Doctrine\EventListener\WriteListener; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use Doctrine\Persistence\ManagerRegistry; -use Doctrine\Persistence\ObjectManager; -use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Event\ViewEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * @group legacy - */ -class WriteListenerTest extends TestCase -{ - use ProphecyTrait; - - /** - * @expectedDeprecation The ApiPlatform\Doctrine\EventListener\WriteListener class is deprecated since version 2.2 and will be removed in 3.0. Use the ApiPlatform\Core\EventListener\WriteListener class instead. - */ - public function testOnKernelViewWithControllerResultAndPostMethod() - { - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->persist($dummy)->shouldBeCalled(); - $objectManagerProphecy->flush()->shouldBeCalled(); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass('Dummy')->willReturn($objectManagerProphecy); - - $writeListener = new WriteListener($managerRegistryProphecy->reveal()); - $httpKernelProphecy = $this->prophesize(HttpKernelInterface::class); - $request = new Request(); - $request->setMethod('POST'); - $request->attributes->set('_api_resource_class', 'Dummy'); - $event = new ViewEvent($httpKernelProphecy->reveal(), $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, $dummy); - - $writeListener->onKernelView($event); - } - - /** - * @expectedDeprecation The ApiPlatform\Doctrine\EventListener\WriteListener class is deprecated since version 2.2 and will be removed in 3.0. Use the ApiPlatform\Core\EventListener\WriteListener class instead. - */ - public function testOnKernelViewWithControllerResultAndDeleteMethod() - { - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->remove($dummy)->shouldBeCalled(); - $objectManagerProphecy->flush()->shouldBeCalled(); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass('Dummy')->willReturn($objectManagerProphecy); - - $writeListener = new WriteListener($managerRegistryProphecy->reveal()); - $request = new Request(); - $request->setMethod('DELETE'); - $request->attributes->set('_api_resource_class', 'Dummy'); - - $event = new ViewEvent($this->prophesize(HttpKernelInterface::class)->reveal(), $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, $dummy); - $writeListener->onKernelView($event); - } - - /** - * @expectedDeprecation The ApiPlatform\Doctrine\EventListener\WriteListener class is deprecated since version 2.2 and will be removed in 3.0. Use the ApiPlatform\Core\EventListener\WriteListener class instead. - */ - public function testOnKernelViewWithSafeMethod() - { - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->flush()->shouldNotBeCalled(); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass('Dummy')->willReturn($objectManagerProphecy); - - $writeListener = new WriteListener($managerRegistryProphecy->reveal()); - $httpKernelProphecy = $this->prophesize(HttpKernelInterface::class); - $request = new Request(); - $request->setMethod('HEAD'); - $request->attributes->set('_api_resource_class', 'Dummy'); - $event = new ViewEvent($httpKernelProphecy->reveal(), $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, $dummy); - - $writeListener->onKernelView($event); - } - - /** - * @expectedDeprecation The ApiPlatform\Doctrine\EventListener\WriteListener class is deprecated since version 2.2 and will be removed in 3.0. Use the ApiPlatform\Core\EventListener\WriteListener class instead. - */ - public function testOnKernelViewWithNoResourceClass() - { - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $objectManagerProphecy = $this->prophesize(ObjectManager::class); - $objectManagerProphecy->flush()->shouldNotBeCalled(); - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass('Dummy')->willReturn($objectManagerProphecy); - - $writeListener = new WriteListener($managerRegistryProphecy->reveal()); - $httpKernelProphecy = $this->prophesize(HttpKernelInterface::class); - $request = new Request(); - $request->setMethod('POST'); - $event = new ViewEvent($httpKernelProphecy->reveal(), $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, $dummy); - - $writeListener->onKernelView($event); - } - - /** - * @expectedDeprecation The ApiPlatform\Doctrine\EventListener\WriteListener class is deprecated since version 2.2 and will be removed in 3.0. Use the ApiPlatform\Core\EventListener\WriteListener class instead. - * @doesNotPerformAssertions - */ - public function testOnKernelViewWithNoManager() - { - $dummy = new Dummy(); - $dummy->setName('Dummyrino'); - - $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); - $managerRegistryProphecy->getManagerForClass('Dummy')->willReturn(null); - - $writeListener = new WriteListener($managerRegistryProphecy->reveal()); - $httpKernelProphecy = $this->prophesize(HttpKernelInterface::class); - $request = new Request(); - $request->setMethod('DELETE'); - $request->attributes->set('_api_resource_class', 'Dummy'); - $event = new ViewEvent($httpKernelProphecy->reveal(), $request, \defined(HttpKernelInterface::class.'::MAIN_REQUEST') ? HttpKernelInterface::MAIN_REQUEST : HttpKernelInterface::MASTER_REQUEST, $dummy); - - $writeListener->onKernelView($event); - } -} diff --git a/tests/Doctrine/Odm/Filter/BooleanFilterTest.php b/tests/Doctrine/Odm/Filter/BooleanFilterTest.php index a4729a5dcd7..e59f4b47329 100644 --- a/tests/Doctrine/Odm/Filter/BooleanFilterTest.php +++ b/tests/Doctrine/Odm/Filter/BooleanFilterTest.php @@ -27,8 +27,8 @@ class BooleanFilterTest extends DoctrineMongoDbOdmFilterTestCase { use BooleanFilterTestTrait; - protected $filterClass = BooleanFilter::class; - protected $resourceClass = Dummy::class; + protected string $filterClass = BooleanFilter::class; + protected string $resourceClass = Dummy::class; public function provideApplyTestData(): array { diff --git a/tests/Doctrine/Odm/Filter/DateFilterTest.php b/tests/Doctrine/Odm/Filter/DateFilterTest.php index 4cfa6d4116a..33c7854e229 100644 --- a/tests/Doctrine/Odm/Filter/DateFilterTest.php +++ b/tests/Doctrine/Odm/Filter/DateFilterTest.php @@ -28,8 +28,8 @@ class DateFilterTest extends DoctrineMongoDbOdmFilterTestCase { use DateFilterTestTrait; - protected $filterClass = DateFilter::class; - protected $resourceClass = Dummy::class; + protected string $filterClass = DateFilter::class; + protected string $resourceClass = Dummy::class; public function provideApplyTestData(): array { diff --git a/tests/Doctrine/Odm/Filter/ExistsFilterTest.php b/tests/Doctrine/Odm/Filter/ExistsFilterTest.php index 993abaeabf7..37cb8a093af 100644 --- a/tests/Doctrine/Odm/Filter/ExistsFilterTest.php +++ b/tests/Doctrine/Odm/Filter/ExistsFilterTest.php @@ -28,8 +28,8 @@ class ExistsFilterTest extends DoctrineMongoDbOdmFilterTestCase { use ExistsFilterTestTrait; - protected $filterClass = ExistsFilter::class; - protected $resourceClass = Dummy::class; + protected string $filterClass = ExistsFilter::class; + protected string $resourceClass = Dummy::class; public function testGetDescriptionDefaultFields(): void { diff --git a/tests/Doctrine/Odm/Filter/NumericFilterTest.php b/tests/Doctrine/Odm/Filter/NumericFilterTest.php index fa6a4802162..fd5101ef317 100644 --- a/tests/Doctrine/Odm/Filter/NumericFilterTest.php +++ b/tests/Doctrine/Odm/Filter/NumericFilterTest.php @@ -27,8 +27,8 @@ class NumericFilterTest extends DoctrineMongoDbOdmFilterTestCase { use NumericFilterTestTrait; - protected $filterClass = NumericFilter::class; - protected $resourceClass = Dummy::class; + protected string $filterClass = NumericFilter::class; + protected string $resourceClass = Dummy::class; public function testGetDescriptionDefaultFields(): void { diff --git a/tests/Doctrine/Odm/Filter/OrderFilterTest.php b/tests/Doctrine/Odm/Filter/OrderFilterTest.php index c075be1411b..41267927ce1 100644 --- a/tests/Doctrine/Odm/Filter/OrderFilterTest.php +++ b/tests/Doctrine/Odm/Filter/OrderFilterTest.php @@ -30,8 +30,8 @@ class OrderFilterTest extends DoctrineMongoDbOdmFilterTestCase { use OrderFilterTestTrait; - protected $filterClass = OrderFilter::class; - protected $resourceClass = Dummy::class; + protected string $filterClass = OrderFilter::class; + protected string $resourceClass = Dummy::class; public function testGetDescriptionDefaultFields(): void { diff --git a/tests/Doctrine/Odm/Filter/RangeFilterTest.php b/tests/Doctrine/Odm/Filter/RangeFilterTest.php index aae09e675d0..bd3678d917d 100644 --- a/tests/Doctrine/Odm/Filter/RangeFilterTest.php +++ b/tests/Doctrine/Odm/Filter/RangeFilterTest.php @@ -27,8 +27,8 @@ class RangeFilterTest extends DoctrineMongoDbOdmFilterTestCase { use RangeFilterTestTrait; - protected $filterClass = RangeFilter::class; - protected $resourceClass = Dummy::class; + protected string $filterClass = RangeFilter::class; + protected string $resourceClass = Dummy::class; public function testGetDescriptionDefaultFields(): void { diff --git a/tests/Doctrine/Odm/Filter/SearchFilterTest.php b/tests/Doctrine/Odm/Filter/SearchFilterTest.php index a049031e42d..6f25c16c82c 100644 --- a/tests/Doctrine/Odm/Filter/SearchFilterTest.php +++ b/tests/Doctrine/Odm/Filter/SearchFilterTest.php @@ -36,8 +36,8 @@ class SearchFilterTest extends DoctrineMongoDbOdmFilterTestCase use ProphecyTrait; use SearchFilterTestTrait; - protected $filterClass = SearchFilter::class; - protected $resourceClass = Dummy::class; + protected string $filterClass = SearchFilter::class; + protected string $resourceClass = Dummy::class; public function testGetDescriptionDefaultFields() { @@ -756,6 +756,6 @@ protected function buildSearchFilter(ManagerRegistry $managerRegistry, ?array $p $iriConverter = $iriConverterProphecy->reveal(); $propertyAccessor = static::$kernel->getContainer()->get('test.property_accessor'); - return new SearchFilter($managerRegistry, $iriConverter, $propertyAccessor, null, $properties, new CustomConverter()); + return new SearchFilter($managerRegistry, $iriConverter, null, $propertyAccessor, null, $properties, new CustomConverter()); } } diff --git a/tests/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php b/tests/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php index 81be28b8e1f..00a1835a301 100644 --- a/tests/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php +++ b/tests/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php @@ -13,11 +13,11 @@ namespace ApiPlatform\Tests\Doctrine\Odm\Metadata\Property; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Doctrine\Odm\Metadata\Property\DoctrineMongoDbOdmPropertyMetadataFactory; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy; +use ApiPlatform\Tests\ProphecyTrait; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\Persistence\ManagerRegistry; @@ -32,7 +32,7 @@ class DoctrineMongoDbOdmPropertyMetadataFactoryTest extends TestCase { use ProphecyTrait; - public function testCreateNoManager() + public function testCreateNoManager(): void { $propertyMetadata = new ApiProperty(); $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); @@ -46,7 +46,7 @@ public function testCreateNoManager() $this->assertEquals($doctrineMongoDbOdmPropertyMetadataFactory->create(Dummy::class, 'id'), $propertyMetadata); } - public function testCreateIsIdentifier() + public function testCreateIsIdentifier(): void { $propertyMetadata = new ApiProperty(); $propertyMetadata = $propertyMetadata->withIdentifier(true); @@ -67,7 +67,7 @@ public function testCreateIsIdentifier() $this->assertEquals($doctrineMongoDbOdmPropertyMetadataFactory->create(Dummy::class, 'id'), $propertyMetadata); } - public function testCreateIsWritable() + public function testCreateIsWritable(): void { $propertyMetadata = new ApiProperty(); $propertyMetadata = $propertyMetadata->withWritable(false); @@ -92,7 +92,7 @@ public function testCreateIsWritable() $this->assertEquals($doctrinePropertyMetadata->isWritable(), false); } - public function testCreateClassMetadata() + public function testCreateClassMetadata(): void { $propertyMetadata = new ApiProperty(); diff --git a/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php b/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php index 5793373b06a..0d40a37e73f 100644 --- a/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php +++ b/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Tests\Doctrine\Odm\Metadata\Resource; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Doctrine\Common\State\PersistProcessor; use ApiPlatform\Doctrine\Common\State\RemoveProcessor; use ApiPlatform\Doctrine\Odm\Metadata\Resource\DoctrineMongoDbOdmResourceCollectionMetadataFactory; @@ -28,6 +27,7 @@ use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy; +use ApiPlatform\Tests\ProphecyTrait; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\Persistence\ManagerRegistry; use PHPUnit\Framework\TestCase; @@ -55,7 +55,7 @@ private function getResourceMetadataCollectionFactory(Operation $operation) return $resourceMetadataCollectionFactory->reveal(); } - public function testWithoutManager() + public function testWithoutManager(): void { if (!class_exists(DocumentManager::class)) { $this->markTestSkipped('ODM not installed'); @@ -75,7 +75,7 @@ public function testWithoutManager() /** * @dataProvider operationProvider */ - public function testWithProvider(Operation $operation, string $expectedProvider = null, string $expectedProcessor = null) + public function testWithProvider(Operation $operation, string $expectedProvider = null, string $expectedProcessor = null): void { if (!class_exists(DocumentManager::class)) { $this->markTestSkipped('ODM not installed'); diff --git a/tests/Doctrine/Odm/PaginatorTest.php b/tests/Doctrine/Odm/PaginatorTest.php index f12f71b9eb0..c0e0eca925b 100644 --- a/tests/Doctrine/Odm/PaginatorTest.php +++ b/tests/Doctrine/Odm/PaginatorTest.php @@ -13,11 +13,11 @@ namespace ApiPlatform\Tests\Doctrine\Odm; -use ApiPlatform\Core\Tests\ProphecyTrait; use ApiPlatform\Doctrine\Odm\Paginator; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Test\DoctrineMongoDbOdmSetup; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy; +use ApiPlatform\Tests\ProphecyTrait; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Iterator\Iterator; use PHPUnit\Framework\TestCase; @@ -31,14 +31,8 @@ class PaginatorTest extends TestCase /** * @dataProvider initializeProvider - * - * @param mixed $firstResult - * @param mixed $maxResults - * @param mixed $totalItems - * @param mixed $currentPage - * @param mixed $lastPage */ - public function testInitialize($firstResult, $maxResults, $totalItems, $currentPage, $lastPage) + public function testInitialize(int $firstResult, int $maxResults, int $totalItems, int $currentPage, int $lastPage): void { $paginator = $this->getPaginator($firstResult, $maxResults, $totalItems); @@ -47,7 +41,7 @@ public function testInitialize($firstResult, $maxResults, $totalItems, $currentP $this->assertEquals($maxResults, $paginator->getItemsPerPage()); } - public function testInitializeWithFacetStageNotApplied() + public function testInitializeWithFacetStageNotApplied(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('$facet stage was not applied to the aggregation pipeline.'); @@ -55,7 +49,7 @@ public function testInitializeWithFacetStageNotApplied() $this->getPaginatorWithMissingStage(); } - public function testInitializeWithResultsFacetNotApplied() + public function testInitializeWithResultsFacetNotApplied(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('"results" facet was not applied to the aggregation pipeline.'); @@ -63,7 +57,7 @@ public function testInitializeWithResultsFacetNotApplied() $this->getPaginatorWithMissingStage(true); } - public function testInitializeWithCountFacetNotApplied() + public function testInitializeWithCountFacetNotApplied(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('"count" facet was not applied to the aggregation pipeline.'); @@ -71,7 +65,7 @@ public function testInitializeWithCountFacetNotApplied() $this->getPaginatorWithMissingStage(true, true); } - public function testInitializeWithSkipStageNotApplied() + public function testInitializeWithSkipStageNotApplied(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('$skip stage was not applied to the facet stage of the aggregation pipeline.'); @@ -79,7 +73,7 @@ public function testInitializeWithSkipStageNotApplied() $this->getPaginatorWithMissingStage(true, true, true); } - public function testInitializeWithLimitStageNotApplied() + public function testInitializeWithLimitStageNotApplied(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('$limit stage was not applied to the facet stage of the aggregation pipeline.'); @@ -87,7 +81,7 @@ public function testInitializeWithLimitStageNotApplied() $this->getPaginatorWithMissingStage(true, true, true, true); } - public function testInitializeWithLimitZeroStageApplied() + public function testInitializeWithLimitZeroStageApplied(): void { $paginator = $this->getPaginator(0, 5, 0, true); @@ -96,7 +90,7 @@ public function testInitializeWithLimitZeroStageApplied() $this->assertEquals(0, $paginator->getItemsPerPage()); } - public function testInitializeWithNoCount() + public function testInitializeWithNoCount(): void { $paginator = $this->getPaginatorWithNoCount(); @@ -105,14 +99,14 @@ public function testInitializeWithNoCount() $this->assertEquals(15, $paginator->getItemsPerPage()); } - public function testGetIterator() + public function testGetIterator(): void { $paginator = $this->getPaginator(); $this->assertSame($paginator->getIterator(), $paginator->getIterator(), 'Iterator should be cached'); } - private function getPaginator($firstResult = 1, $maxResults = 15, $totalItems = 42, $limitZero = false) + private function getPaginator($firstResult = 1, $maxResults = 15, $totalItems = 42, $limitZero = false): Paginator { $iterator = $this->prophesize(Iterator::class); $pipeline = [ @@ -146,7 +140,7 @@ private function getPaginator($firstResult = 1, $maxResults = 15, $totalItems = return new Paginator($iterator->reveal(), $documentManager->getUnitOfWork(), Dummy::class, $pipeline); } - private function getPaginatorWithMissingStage($facet = false, $results = false, $count = false, $maxResults = false) + private function getPaginatorWithMissingStage($facet = false, $results = false, $count = false, $maxResults = false): Paginator { $pipeline = []; @@ -177,7 +171,7 @@ private function getPaginatorWithMissingStage($facet = false, $results = false, return new Paginator($iterator->reveal(), $documentManager->getUnitOfWork(), Dummy::class, $pipeline); } - private function getPaginatorWithNoCount($firstResult = 1, $maxResults = 15) + private function getPaginatorWithNoCount($firstResult = 1, $maxResults = 15): Paginator { $iterator = $this->prophesize(Iterator::class); $pipeline = [ @@ -207,7 +201,7 @@ private function getPaginatorWithNoCount($firstResult = 1, $maxResults = 15) return new Paginator($iterator->reveal(), $documentManager->getUnitOfWork(), Dummy::class, $pipeline); } - public function initializeProvider() + public function initializeProvider(): array { return [ 'First of three pages of 15 items each' => [0, 15, 42, 1, 3], diff --git a/tests/Doctrine/Odm/State/CollectionProviderTest.php b/tests/Doctrine/Odm/State/CollectionProviderTest.php index d7902084936..8498e64509f 100644 --- a/tests/Doctrine/Odm/State/CollectionProviderTest.php +++ b/tests/Doctrine/Odm/State/CollectionProviderTest.php @@ -160,7 +160,8 @@ public function testOperationNotFound(): void $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); - $operation = (new GetCollection())->withName('bar')->withClass(ProviderEntity::class); + $operation = new GetCollection(name: 'bar', class: ProviderEntity::class); + $extensionProphecy = $this->prophesize(AggregationCollectionExtensionInterface::class); $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, $operation, [])->shouldBeCalled(); diff --git a/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php b/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php index 966c0421556..266f260c7dd 100644 --- a/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php +++ b/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php @@ -251,7 +251,7 @@ public function testCreateItemWithOperation(): void $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true); - $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], (new Get())->withName('item_operation'), ['groups' => ['foo']]); + $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(name: 'item_operation'), ['groups' => ['foo']]); } public function testCreateCollectionWithOperation(): void @@ -272,7 +272,7 @@ public function testCreateCollectionWithOperation(): void $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true); - $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, (new GetCollection())->withName('collection_operation'), ['groups' => ['foo']]); + $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, new GetCollection(name: 'collection_operation'), ['groups' => ['foo']]); } public function testDenormalizeItemWithCorrectResourceClass(): void @@ -292,7 +292,7 @@ public function testDenormalizeItemWithCorrectResourceClass(): void $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true); - $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], (new Get())->withName('get')->withNormalizationContext(['groups' => ['foo']]), ['resource_class' => Dummy::class]); + $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], new Get(name: 'get', normalizationContext: ['groups' => ['foo']]), ['resource_class' => Dummy::class]); } public function testDenormalizeItemWithExistingGroups(): void @@ -312,7 +312,7 @@ public function testDenormalizeItemWithExistingGroups(): void $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true); - $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], (new Get())->withName('item_operation')->withNormalizationContext(['groups' => ['foo']]), [AbstractNormalizer::GROUPS => 'some_groups']); + $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], new Get(name: 'item_operation', normalizationContext: ['groups' => ['foo']]), [AbstractNormalizer::GROUPS => 'some_groups']); } public function testMaxJoinsReached(): void @@ -425,7 +425,7 @@ public function testMaxDepth(): void $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true, $classMetadataFactoryProphecy->reveal()); - $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, (new GetCollection())->withNormalizationContext(['enable_max_depth' => 'true', 'groups' => ['foo']])); + $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: ['enable_max_depth' => 'true', 'groups' => ['foo']])); } public function testForceEager(): void @@ -465,7 +465,7 @@ public function testForceEager(): void $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true); - $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], (new Get())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foobar'])); + $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foobar'])); } public function testExtraLazy(): void @@ -496,7 +496,7 @@ public function testExtraLazy(): void $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true); - $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], (new Get())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foobar'])); + $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foobar'])); } public function testResourceClassNotFoundException(): void @@ -517,7 +517,7 @@ public function testResourceClassNotFoundException(): void $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true); - $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], (new Get())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo'])); + $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foo'])); } public function testPropertyNotFoundException(): void @@ -538,7 +538,7 @@ public function testPropertyNotFoundException(): void $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy); $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true); - $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], (new Get())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo'])); + $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foo'])); } public function testResourceClassNotFoundExceptionPropertyNameCollection(): void @@ -565,7 +565,7 @@ public function testResourceClassNotFoundExceptionPropertyNameCollection(): void $queryBuilderProphecy->getDQLPart('join')->willReturn([]); $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true); - $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], (new Get())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo'])); + $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foo'])); } public function testAttributes(): void @@ -623,7 +623,7 @@ public function testAttributes(): void $queryBuilder = $queryBuilderProphecy->reveal(); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true); - $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, (new GetCollection())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo'])); + $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo'])); } public function testNotInAttributes(): void @@ -653,7 +653,7 @@ public function testNotInAttributes(): void $queryBuilder = $queryBuilderProphecy->reveal(); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true); - $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, (new GetCollection())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo', AbstractNormalizer::ATTRIBUTES => ['relatedDummy']])); + $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo', AbstractNormalizer::ATTRIBUTES => ['relatedDummy']])); } public function testOnlyOneRelationNotInAttributes(): void @@ -709,7 +709,7 @@ public function testOnlyOneRelationNotInAttributes(): void $queryBuilder = $queryBuilderProphecy->reveal(); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true); - $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, (new GetCollection())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo', AbstractNormalizer::ATTRIBUTES => ['relatedDummy' => ['id', 'name']]])); + $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo', AbstractNormalizer::ATTRIBUTES => ['relatedDummy' => ['id', 'name']]])); } public function testApplyToCollectionNoPartial(): void @@ -749,7 +749,7 @@ public function testApplyToCollectionNoPartial(): void $queryBuilder = $queryBuilderProphecy->reveal(); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30); - $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, (new GetCollection())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo'])); + $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo'])); } public function testApplyToCollectionWithANonReadableButFetchEagerProperty(): void @@ -794,7 +794,7 @@ public function testApplyToCollectionWithANonReadableButFetchEagerProperty(): vo $queryBuilder = $queryBuilderProphecy->reveal(); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30); - $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, (new GetCollection())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo'])); + $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo'])); } /** @@ -838,7 +838,7 @@ public function testApplyToCollectionWithExistingJoin(string $joinType): void $queryBuilder = $queryBuilderProphecy->reveal(); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false); - $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, (new GetCollection())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo']), $context); + $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']), $context); } public function provideExistingJoinCases(): iterable @@ -882,6 +882,6 @@ public function testApplyToCollectionWithAReadableButNotFetchEagerProperty(): vo $queryBuilder = $queryBuilderProphecy->reveal(); $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30); - $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, (new GetCollection())->withNormalizationContext([AbstractNormalizer::GROUPS => 'foo'])); + $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo'])); } } diff --git a/tests/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php b/tests/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php index 4ccfacf8c8b..4d86afba29b 100644 --- a/tests/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php +++ b/tests/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php @@ -50,7 +50,7 @@ public function testIsNoForceEagerCollectionAttributes(): void $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); $filterEagerLoadingExtension = new FilterEagerLoadingExtension(true); - $filterEagerLoadingExtension->applyToCollection($qb->reveal(), $queryNameGenerator->reveal(), DummyCar::class, (new Get())->withName('get')->withForceEager(false)); + $filterEagerLoadingExtension->applyToCollection($qb->reveal(), $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get', forceEager: false)); } public function testIsForceEagerConfig(): void @@ -65,7 +65,7 @@ public function testIsForceEagerConfig(): void $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); $filterEagerLoadingExtension = new FilterEagerLoadingExtension(false); - $filterEagerLoadingExtension->applyToCollection($qb->reveal(), $queryNameGenerator->reveal(), DummyCar::class, (new Get())->withName('get')); + $filterEagerLoadingExtension->applyToCollection($qb->reveal(), $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get')); } public function testHasNoWherePart(): void @@ -80,7 +80,7 @@ public function testHasNoWherePart(): void $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); $filterEagerLoadingExtension = new FilterEagerLoadingExtension(true); - $filterEagerLoadingExtension->applyToCollection($qb->reveal(), $queryNameGenerator->reveal(), DummyCar::class, (new Get())->withName('get')); + $filterEagerLoadingExtension->applyToCollection($qb->reveal(), $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get')); } public function testHasNoJoinPart(): void @@ -97,7 +97,7 @@ public function testHasNoJoinPart(): void $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); $filterEagerLoadingExtension = new FilterEagerLoadingExtension(true); - $filterEagerLoadingExtension->applyToCollection($qb->reveal(), $queryNameGenerator->reveal(), DummyCar::class, (new Get())->withName('get')); + $filterEagerLoadingExtension->applyToCollection($qb->reveal(), $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get')); } public function testApplyCollection(): void @@ -119,7 +119,7 @@ public function testApplyCollection(): void $queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2'); $filterEagerLoadingExtension = new FilterEagerLoadingExtension(true); - $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, (new Get())->withName('get')); + $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get')); $this->assertEquals('SELECT o FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar o LEFT JOIN o.colors colors WHERE o IN(SELECT o_2 FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar o_2 LEFT JOIN o_2.colors colors_2 WHERE o_2.colors = :foo)', $qb->getDQL()); } @@ -151,7 +151,7 @@ public function testApplyCollectionWithManualJoin(): void $queryNameGenerator->generateJoinAlias('t_a3')->shouldBeCalled()->willReturn('t_a3_a20'); $filterEagerLoadingExtension = new FilterEagerLoadingExtension(true, new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal())); - $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, (new Get())->withName('get')); + $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get')); $expected = <<<'SQL' SELECT o @@ -193,7 +193,7 @@ public function testApplyCollectionCorrectlyReplacesJoinCondition(): void $queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2'); $filterEagerLoadingExtension = new FilterEagerLoadingExtension(true, new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal())); - $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, (new Get())->withName('get')); + $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get')); $expected = <<<'SQL' SELECT o @@ -231,7 +231,7 @@ public function testHiddenOrderBy(): void $queryNameGenerator->generateJoinAlias('colors')->shouldBeCalled()->willReturn('colors_2'); $queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2'); $filterEagerLoadingExtension = new FilterEagerLoadingExtension(true); - $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, (new Get())->withName('get')); + $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get')); $expected = <<