diff --git a/.travis.yml b/.travis.yml index 0780c82c9f9..23871400fbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,9 +73,6 @@ script: - if [[ $lint != 1 ]]; then tests/Fixtures/app/console api:swagger:export > swagger.json && npx swagger-cli validate swagger.json && rm swagger.json; fi - - if [[ $lint != 1 ]]; then - tests/Fixtures/app/console api:swagger:export --yaml > swagger.yaml && npx swagger-cli validate --no-schema swagger.yaml && rm swagger.yaml; - fi - if [[ $lint = 1 ]]; then php php-cs-fixer.phar fix --dry-run --diff --no-ansi; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b88158cb0..e392d0a58c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,7 +75,7 @@ * Add support for the immutable date and time types introduced in Doctrine * Fix the Doctrine query generated to retrieve nested subresources * Fix several bugs in the automatic eager loading support -* Fix a bug occurring when passing neither an IRI nor an array in an embedded relation +* Fix a bug occurring when passing nor an IRI nor an array in an embedded relation * Allow to request `0` items per page in collections * Also copy the `Host` from the Symfony Router * `Paginator::getLastPage()` now always returns a `float` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6544310fd6f..b28c1c7f35d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ First of all, thank you for contributing, you're awesome! To have your code integrated in the API Platform project, there is some rules to follow, but don't panic, it's easy! -## Reporting Bugs +## Reporting bugs If you happen to find a bug, we kindly request you to report it. However, before submitting it, please: @@ -27,10 +27,11 @@ publicly**. We will disclose details of the issue and credit you after having re ### Writing a Pull Request -First of all, you must decide on what branch your changes will be based depending of the nature of the change. -See [the dedicated documentation entry](https://api-platform.com/docs/extra/releases/). +First of all, you must decide on what branch your changes will be based. If the changes your are going to make are +fully backward-compatible, you should base your changes on the latest stable branch (`2.0` at the moment). +Otherwise, you should base your changes on the `master` branch. -### Matching Coding Standards +### Matching coding standards The API Platform project follows [Symfony coding standards](https://symfony.com/doc/current/contributing/code/standards.html). But don't worry, you can fix CS issues automatically using the [PHP CS Fixer](http://cs.sensiolabs.org/) tool @@ -51,7 +52,7 @@ When you send a PR, just make sure that: * You make a PR on the related documentation in the [api-platform/docs](https://github.com/api-platform/docs) repository. * You make the PR on the same branch you based your changes on. If you see commits that you did not make in your PR, you're doing it wrong. -* Also don't forget to add a comment when you update a PR with a ping to [the maintainers](https://github.com/orgs/api-platform/people), so he/she will get a notification. +* Also don't forget to add a comment when you update a PR with a ping to the maintainer (`@dunglas`, `@sroze` or `@theofidry`), so he/she will get a notification. * Squash your commits into one commit. (see the next chapter) All Pull Requests must include [this header](.github/PULL_REQUEST_TEMPLATE.md). @@ -62,7 +63,7 @@ On `api-platform/core` there are two kinds of tests: unit (`phpunit`) and integr Both `phpunit` and `behat` are development dependencies and should be available in the `vendor` directory. -#### Phpunit and Coverage Generation +#### Phpunit and coverage generation To launch unit tests: @@ -92,7 +93,13 @@ The command to launch Behat tests is: ./vendor/bin/behat --suite=default --stop-on-failure -vvv ``` -## Squash your Commits +You may need to clear the cache manually before running behat tests because of the temporary sql database. To do so, just remove the `test` cache directory: + +``` +rm -r tests/Fixtures/app/cache/test +``` + +## Squash your commits If you have 3 commits. So start with: @@ -120,7 +127,7 @@ Now force push to update your PR: git push --force ``` -# License and Copyright Attribution +# License and copyright attribution When you open a Pull Request to the API Platform project, you agree to license your code under the [MIT license](LICENSE) and to transfer the copyright on the submitted code to Kévin Dunglas. diff --git a/LICENSE b/LICENSE index 1ca98eeb824..b1590644623 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT license -Copyright (c) 2015-present Kévin Dunglas +Copyright (c) 2015 Kévin Dunglas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/composer.json b/composer.json index 97db1f467ae..98c1f5b361d 100644 --- a/composer.json +++ b/composer.json @@ -33,9 +33,9 @@ "behat/symfony2-extension": "^2.1.1", "behatch/contexts": "^3.1", "doctrine/annotations": "^1.2", - "doctrine/doctrine-bundle": "^1.8", + "doctrine/doctrine-bundle": "^1.6.3", "doctrine/orm": "^2.5.2", - "friendsofsymfony/user-bundle": "^2.1", + "friendsofsymfony/user-bundle": "^2.0", "guzzlehttp/guzzle": "^6.0", "justinrainbow/json-schema": "^5.0", "nelmio/api-doc-bundle": "^2.13.3", @@ -44,17 +44,15 @@ "phpdocumentor/type-resolver": "^0.2.1 || ^0.3 || 0.4", "phpunit/phpunit": "^6.1", "psr/log": "^1.0", - "ramsey/uuid": "^3.7", - "ramsey/uuid-doctrine": "^1.4", "sensio/framework-extra-bundle": "^3.0.11 || ^4.0", "symfony/asset": "^3.3 || ^4.0", "symfony/cache": "^3.3 || ^4.0", - "symfony/config": "^3.4 || ^4.0", - "symfony/console": "^3.4 || ^4.0", + "symfony/config": "^3.3 || ^4.0", + "symfony/console": "^3.3 || ^4.0", "symfony/debug": "^2.8 || ^3.0 || ^4.0", - "symfony/dependency-injection": "^3.4 || ^4.0", + "symfony/dependency-injection": "^3.3 || ^4.0", "symfony/doctrine-bridge": "^2.8.12 || ^3.0 || ^4.0", - "symfony/event-dispatcher": "^3.4 || ^4.0", + "symfony/event-dispatcher": "^3.3 || ^4.0", "symfony/expression-language": "^2.8 || ^3.0 || ^4.0", "symfony/finder": "^3.3 || ^4.0", "symfony/form": "^3.3 || ^4.0", @@ -62,28 +60,25 @@ "symfony/phpunit-bridge": "^3.3 || ^4.0", "symfony/routing": "^3.3 || ^4.0", "symfony/security": "^3.0 || ^4.0", - "symfony/security-bundle": "^3.4 || ^4.0", - "symfony/twig-bundle": "^3.4 || ^4.0", + "symfony/security-bundle": "^3.0 || ^4.0", + "symfony/twig-bundle": "^3.1 || ^4.0", "symfony/validator": "^3.3 || ^4.0", - "symfony/web-profiler-bundle": "^3.3 || ^4.0", "symfony/yaml": "^3.3 || ^4.0", "webonyx/graphql-php": "^0.11.5" }, "conflict": { - "symfony/dependency-injection": "<3.4" + "symfony/dependency-injection": "<3.3" }, "suggest": { "friendsofsymfony/user-bundle": "To use the FOSUserBundle bridge.", "guzzlehttp/guzzle": "To use the HTTP cache invalidation system.", "phpdocumentor/reflection-docblock": "To support extracting metadata from PHPDoc.", "psr/cache-implementation": "To use metadata caching.", - "ramsey/uuid": "To support Ramsey's UUID identifiers.", "symfony/cache": "To have metadata caching when using Symfony integration.", "symfony/config": "To load XML configuration files.", "symfony/expression-language": "To use authorization features.", "symfony/security": "To use authorization features.", "symfony/twig-bundle": "To use the Swagger UI integration.", - "symfony/web-profiler-bundle": "To use the data collector.", "webonyx/graphql-php": "To support GraphQL." }, "autoload": { @@ -94,7 +89,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.3.x-dev" + "dev-master": "2.2.x-dev" } }, "config": { diff --git a/features/authorization/deny.feature b/features/authorization/deny.feature index 19658cdb4a2..0cf89b86a59 100644 --- a/features/authorization/deny.feature +++ b/features/authorization/deny.feature @@ -65,6 +65,7 @@ Feature: Authorization checking Then the response status code should be 403 And the response should be in JSON + @dropSchema Scenario: An user can retrieve an item he owns When I add "Accept" header equal to "application/ld+json" And I add "Authorization" header equal to "Basic ZHVuZ2xhczprZXZpbg==" diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index ec7e536639c..877967b463f 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -33,13 +33,11 @@ use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Foo; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FooDummy; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FourthLevel; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Node; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Person; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\PersonToPet; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Pet; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RamseyUuidDummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedToDummyFriend; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelationEmbedder; @@ -111,10 +109,17 @@ public function removeAcceptHeaderBeforeScenario() * @BeforeScenario @createSchema */ public function createDatabase() + { + $this->schemaTool->createSchema($this->classes); + } + + /** + * @AfterScenario @dropSchema + */ + public function dropDatabase() { $this->schemaTool->dropSchema($this->classes); $this->doctrine->getManager()->clear(); - $this->schemaTool->createSchema($this->classes); } /** @@ -733,7 +738,6 @@ public function thereIsARelatedDummyWithFriends(int $nb) $relatedDummy2->setName('RelatedDummy without friends'); $this->manager->persist($relatedDummy2); $this->manager->flush(); - $this->manager->clear(); } /** @@ -873,50 +877,4 @@ public function thereAreDummyImmutableDateObjectsWithDummyDate(int $nb) $this->manager->flush(); } - - /** - * @Given there is a ramsey identified resource with uuid :uuid - */ - public function thereIsARamseyIdentifiedResource(string $uuid) - { - $dummy = new RamseyUuidDummy(); - $dummy->setId($uuid); - - $this->manager->persist($dummy); - $this->manager->flush(); - } - - /** - * @Given there is a dummy object with a fourth level relation - */ - public function thereIsADummyObjectWithAFourthLevelRelation() - { - $fourthLevel = new FourthLevel(); - $fourthLevel->setLevel(4); - $this->manager->persist($fourthLevel); - - $thirdLevel = new ThirdLevel(); - $thirdLevel->setLevel(3); - $thirdLevel->setFourthLevel($fourthLevel); - $this->manager->persist($thirdLevel); - - $namedRelatedDummy = new RelatedDummy(); - $namedRelatedDummy->setName('Hello'); - $namedRelatedDummy->setThirdLevel($thirdLevel); - $this->manager->persist($namedRelatedDummy); - - $relatedDummy = new RelatedDummy(); - $relatedDummy = new RelatedDummy(); - $relatedDummy->setThirdLevel($thirdLevel); - $this->manager->persist($relatedDummy); - - $dummy = new Dummy(); - $dummy->setName('Dummy with relations'); - $dummy->setRelatedDummy($namedRelatedDummy); - $dummy->addRelatedDummy($namedRelatedDummy); - $dummy->addRelatedDummy($relatedDummy); - $this->manager->persist($dummy); - - $this->manager->flush(); - } } diff --git a/features/doctrine/boolean_filter.feature b/features/doctrine/boolean_filter.feature index 5ee947172b0..a9cd4fc90ee 100644 --- a/features/doctrine/boolean_filter.feature +++ b/features/doctrine/boolean_filter.feature @@ -370,6 +370,7 @@ Feature: Boolean filter on collections """ And the JSON node "hydra:totalItems" should be equal to 15 + @dropSchema Scenario: Get collection filtered by non valid properties When I send a "GET" request to "/dummies?unknown=0" Then the response status code should be 200 diff --git a/features/doctrine/date_filter.feature b/features/doctrine/date_filter.feature index 24b3d384719..d6ae3af2785 100644 --- a/features/doctrine/date_filter.feature +++ b/features/doctrine/date_filter.feature @@ -264,6 +264,7 @@ Feature: Date filter on collections } """ + @dropSchema Scenario: Get collection filtered by association date Given there are 30 dummy objects with dummyDate and relatedDummy When I send a "GET" request to "/dummies?relatedDummy.dummyDate[after]=2015-04-28" @@ -383,6 +384,7 @@ Feature: Date filter on collections } """ + @dropSchema @createSchema Scenario: Get collection filtered by association date Given there are 2 dummy objects with dummyDate and relatedDummy @@ -676,6 +678,7 @@ Feature: Date filter on collections } """ + @dropSchema @createSchema Scenario: Get collection filtered by date that is not a datetime Given there are 30 dummydate objects with dummyDate @@ -684,6 +687,7 @@ Feature: Date filter on collections And the JSON node "hydra:totalItems" should be equal to 3 And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + @dropSchema @createSchema Scenario: Get collection filtered by date that is an immutable date variant Given there are 30 dummyimmutabledate objects with dummyDate @@ -692,6 +696,7 @@ Feature: Date filter on collections And the JSON node "hydra:totalItems" should be equal to 3 And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + @dropSchema @createSchema Scenario: Get collection filtered by embedded date Given there are 2 embedded dummy objects with dummyDate and embeddedDummy diff --git a/features/doctrine/exists_filter.feature b/features/doctrine/exists_filter.feature index 6cbe3bb63f6..adb3f2e982a 100644 --- a/features/doctrine/exists_filter.feature +++ b/features/doctrine/exists_filter.feature @@ -34,6 +34,7 @@ Feature: Exists filter on collections } """ + @dropSchema Scenario: Get collection where exists does exist When I send a "GET" request to "/dummies?dummyBoolean[exists]=1" Then the response status code should be 200 diff --git a/features/doctrine/multiple_filter.feature b/features/doctrine/multiple_filter.feature index a6c25bf7425..4f8e66741ce 100644 --- a/features/doctrine/multiple_filter.feature +++ b/features/doctrine/multiple_filter.feature @@ -4,6 +4,7 @@ Feature: Multiple filters on collections I need to retrieve collections filtered by multiple parameters @createSchema + @dropSchema Scenario: Get collection filtered by multiple parameters Given there are 30 dummy objects with dummyDate and dummyBoolean true And there are 20 dummy objects with dummyDate and dummyBoolean false diff --git a/features/doctrine/numeric_filter.feature b/features/doctrine/numeric_filter.feature index 7d4a8279096..91526e934c2 100644 --- a/features/doctrine/numeric_filter.feature +++ b/features/doctrine/numeric_filter.feature @@ -47,6 +47,7 @@ Feature: Numeric filter on collections } """ + @dropSchema Scenario: Get collection by non-numeric dummyPrice=marty Given there are 10 dummy objects with dummyPrice When I send a "GET" request to "/dummies?dummyPrice=marty" diff --git a/features/doctrine/order_filter.feature b/features/doctrine/order_filter.feature index f235c65f30e..99e3eeaff28 100644 --- a/features/doctrine/order_filter.feature +++ b/features/doctrine/order_filter.feature @@ -598,6 +598,7 @@ Feature: Order filter on collections } """ + @dropSchema Scenario: Get collection ordered by a non valid properties and on which order filter has been enabled in whitelist mode When I send a "GET" request to "/dummies?order[alias]=asc" Then the response status code should be 200 @@ -832,6 +833,7 @@ Feature: Order filter on collections """ @createSchema + @dropSchema Scenario: Get collection ordered in descending order on a related property Given there are 2 dummy objects with relatedDummy When I send a "GET" request to "/dummies?order[relatedDummy.name]=desc" diff --git a/features/doctrine/range_filter.feature b/features/doctrine/range_filter.feature index dab2d60feb1..e08d9cf46c0 100644 --- a/features/doctrine/range_filter.feature +++ b/features/doctrine/range_filter.feature @@ -344,6 +344,7 @@ Feature: Range filter on collections } """ + @dropSchema Scenario: Filter for entities within an impossible range When I send a "GET" request to "/dummies?dummyPrice[gt]=19.99" Then the response status code should be 200 diff --git a/features/doctrine/search_filter.feature b/features/doctrine/search_filter.feature index 6f16a04e17a..89d994f63b8 100644 --- a/features/doctrine/search_filter.feature +++ b/features/doctrine/search_filter.feature @@ -4,13 +4,13 @@ Feature: Search filter on collections I need to search for collections properties @createSchema + @dropSchema Scenario: Test ManyToMany with filter on join table Given there is a RelatedDummy with 4 friends When I add "Accept" header equal to "application/hal+json" And I send a "GET" request to "/related_dummies?relatedToDummyFriend.dummyFriend=/dummy_friends/4" Then the response status code should be 200 And the JSON node "_embedded.item" should have 1 element - And the JSON node "_embedded.item[0].id" should be equal to the number 1 And the JSON node "_embedded.item[0]._links.relatedToDummyFriend" should have 4 elements And the JSON node "_embedded.item[0]._embedded.relatedToDummyFriend" should have 4 elements @@ -346,6 +346,7 @@ Feature: Search filter on collections } """ + @dropSchema Scenario: Search for entities within an impossible range When I send a "GET" request to "/dummies?name=MuYm" Then the response status code should be 200 @@ -375,6 +376,7 @@ Feature: Search filter on collections """ @createSchema + @dropSchema Scenario: Search related collection by name Given there are 3 dummy objects having each 3 relatedDummies When I add "Accept" header equal to "application/hal+json" @@ -465,6 +467,7 @@ Feature: Search filter on collections """ + @dropSchema Scenario: Get collection ordered by a non valid properties When I send a "GET" request to "/dummies?unknown=0" Then the response status code should be 200 diff --git a/features/filter/property_filter.feature b/features/filter/property_filter.feature index 722ced764ae..89b4aab50f4 100644 --- a/features/filter/property_filter.feature +++ b/features/filter/property_filter.feature @@ -11,6 +11,7 @@ Feature: Set properties to include And the JSON node "alias" should be equal to "Alias #0" And the JSON node "relatedDummies" should not exist + @dropSchema Scenario: Test relation embedding When I send a "GET" request to "/dummies/1?properties[]=name&properties[]=alias&properties[relatedDummy][]=name" And the JSON node "name" should be equal to "Dummy #1" diff --git a/features/graphql/authorization.feature b/features/graphql/authorization.feature index 21e9e4c570a..b3f8967f856 100644 --- a/features/graphql/authorization.feature +++ b/features/graphql/authorization.feature @@ -40,6 +40,7 @@ Feature: Authorization checking And the header "Content-Type" should be equal to "application/json" And the JSON node "errors[0].message" should be equal to "Access Denied." + @dropSchema Scenario: An anonymous user tries to create a resource he is not allowed to When I send the following GraphQL request: """ diff --git a/features/graphql/collection.feature b/features/graphql/collection.feature index 52d4b574869..f639e14dda8 100644 --- a/features/graphql/collection.feature +++ b/features/graphql/collection.feature @@ -1,5 +1,6 @@ Feature: GraphQL collection support @createSchema + @dropSchema Scenario: Retrieve a collection through a GraphQL query Given there are 4 dummy objects with relatedDummy and its thirdLevel When I send the following GraphQL request: @@ -33,6 +34,7 @@ Feature: GraphQL collection support And the JSON node "data.dummies.edges[2].node.relatedDummy.thirdLevel.level" should be equal to 3 @createSchema + @dropSchema Scenario: Retrieve an nonexistent collection through a GraphQL query When I send the following GraphQL request: """ @@ -58,6 +60,7 @@ Feature: GraphQL collection support And the JSON node "data.dummies.pageInfo.hasNextPage" should be false @createSchema + @dropSchema Scenario: Retrieve a collection with a nested collection through a GraphQL query Given there are 4 dummy objects having each 3 relatedDummies When I send the following GraphQL request: @@ -86,6 +89,7 @@ Feature: GraphQL collection support And the JSON node "data.dummies.edges[2].node.relatedDummies.edges[1].node.name" should be equal to "RelatedDummy23" @createSchema + @dropSchema Scenario: Retrieve a collection and an item through a GraphQL query Given there are 3 dummy objects with dummyDate And there are 2 dummy group objects @@ -113,6 +117,7 @@ Feature: GraphQL collection support And the JSON node "data.dummyGroup.foo" should be equal to "Foo #2" @createSchema + @dropSchema Scenario: Retrieve a specific number of items in a collection through a GraphQL query Given there are 4 dummy objects When I send the following GraphQL request: @@ -133,6 +138,7 @@ Feature: GraphQL collection support And the JSON node "data.dummies.edges" should have 2 elements @createSchema + @dropSchema Scenario: Retrieve a specific number of items in a nested collection through a GraphQL query Given there are 2 dummy objects having each 5 relatedDummies When I send the following GraphQL request: @@ -161,6 +167,7 @@ Feature: GraphQL collection support And the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 2 elements @createSchema + @dropSchema Scenario: Paginate through collections through a GraphQL query Given there are 4 dummy objects having each 4 relatedDummies When I send the following GraphQL request: @@ -314,6 +321,7 @@ Feature: GraphQL collection support And the JSON node "data.dummies.edges" should have 0 element @createSchema + @dropSchema Scenario: Retrieve an item with composite primitive identifiers through a GraphQL query Given there are composite primitive identifiers objects When I send the following GraphQL request: @@ -330,6 +338,7 @@ Feature: GraphQL collection support And the JSON node "data.compositePrimitiveItem.description" should be equal to "This is bar." @createSchema + @dropSchema Scenario: Retrieve an item with composite identifiers through a GraphQL query Given there are Composite identifier objects When I send the following GraphQL request: diff --git a/features/graphql/filters.feature b/features/graphql/filters.feature index 57468ea97e8..17607d76812 100644 --- a/features/graphql/filters.feature +++ b/features/graphql/filters.feature @@ -4,6 +4,7 @@ Feature: Collections filtering I need to be able to set filters @createSchema + @dropSchema Scenario: Retrieve a collection filtered using the boolean filter Given there is 1 dummy object with dummyBoolean true And there is 1 dummy object with dummyBoolean false @@ -24,6 +25,7 @@ Feature: Collections filtering And the JSON node "data.dummies.edges[0].node.dummyBoolean" should be false @createSchema + @dropSchema Scenario: Retrieve a collection filtered using the exists filter Given there are 3 dummy objects And there are 2 dummy objects with relatedDummy @@ -47,6 +49,7 @@ Feature: Collections filtering And the JSON node "data.dummies.edges[0].node.relatedDummy" should have 1 element @createSchema + @dropSchema Scenario: Retrieve a collection filtered using the date filter Given there are 3 dummy objects with dummyDate When I send the following GraphQL request: @@ -66,6 +69,7 @@ Feature: Collections filtering And the JSON node "data.dummies.edges[0].node.dummyDate" should be equal to "2015-04-02T00:00:00+00:00" @createSchema + @dropSchema Scenario: Retrieve a collection filtered using the search filter Given there are 10 dummy objects When I send the following GraphQL request: @@ -85,6 +89,7 @@ Feature: Collections filtering And the JSON node "data.dummies.edges[0].node.id" should be equal to "/dummies/2" @createSchema + @dropSchema Scenario: Retrieve a collection filtered using the search filter Given there are 3 dummy objects having each 3 relatedDummies When I send the following GraphQL request: @@ -113,6 +118,7 @@ Feature: Collections filtering And the JSON node "data.dummies.edges[2].node.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy13" @createSchema + @dropSchema Scenario: Retrieve a collection filtered using the related search filter Given there are 1 dummy objects having each 2 relatedDummies And there are 1 dummy objects having each 3 relatedDummies @@ -132,6 +138,7 @@ Feature: Collections filtering And the JSON node "data.dummies.edges" should have 1 element @createSchema + @dropSchema Scenario: Retrieve a collection ordered using nested properties Given there are 2 dummy objects with relatedDummy When I send the following GraphQL request: diff --git a/features/graphql/introspection.feature b/features/graphql/introspection.feature index e0551d9b1eb..a766b0dba40 100644 --- a/features/graphql/introspection.feature +++ b/features/graphql/introspection.feature @@ -187,6 +187,7 @@ Feature: GraphQL introspection support And the JSON node "data.__type.fields[9].name" should be equal to "jsonData" And the JSON node "data.__type.fields[9].type.name" should be equal to "Iterable" + @dropSchema Scenario: Retrieve an item through a GraphQL query Given there are 4 dummy objects with relatedDummy When I send the following GraphQL request: diff --git a/features/graphql/query.feature b/features/graphql/query.feature index 977335cce94..e0b94c26eb1 100644 --- a/features/graphql/query.feature +++ b/features/graphql/query.feature @@ -109,6 +109,7 @@ Feature: GraphQL query support And I send the GraphQL request with operation "DummyWithId1" And the JSON node "data.dummyItem.name" should be equal to "Dummy #1" + @dropSchema Scenario: Retrieve an nonexistent item through a GraphQL query When I send the following GraphQL request: """ diff --git a/features/hal/hal.feature b/features/hal/hal.feature index 933c844caed..4f509d60d45 100644 --- a/features/hal/hal.feature +++ b/features/hal/hal.feature @@ -137,6 +137,7 @@ Feature: HAL support """ Then the response status code should be 201 + @dropSchema Scenario: Get the object with the embedded relation When I add "Accept" header equal to "application/hal+json" And I send a "GET" request to "/relation_embedders/1" diff --git a/features/hal/problem.feature b/features/hal/problem.feature index fe92ff4ba3f..56c6e2bd2db 100644 --- a/features/hal/problem.feature +++ b/features/hal/problem.feature @@ -28,6 +28,7 @@ Feature: Error handling valid according to RFC 7807 (application/problem+json) } """ + @dropSchema Scenario: Get an error during deserialization of simple relation When I add "Content-Type" header equal to "application/json" And I add "Accept" header equal to "application/json" diff --git a/features/http_cache/headers.feature b/features/http_cache/headers.feature index 1a95d130f4d..452e051c359 100644 --- a/features/http_cache/headers.feature +++ b/features/http_cache/headers.feature @@ -4,6 +4,7 @@ Feature: Default values of HTTP cache headers I need to be able to set default cache headers values @createSchema + @dropSchema Scenario: Cache headers default value When I send a "GET" request to "/relation_embedders" Then the response status code should be 200 diff --git a/features/http_cache/tags.feature b/features/http_cache/tags.feature index 7b4d765cdb8..40cd3f10b61 100644 --- a/features/http_cache/tags.feature +++ b/features/http_cache/tags.feature @@ -93,6 +93,7 @@ Feature: Cache invalidation through HTTP Cache tags Then the response status code should be 201 And "/relation1s,/relation2s/1" IRIs should be purged + @dropSchema Scenario: Update a Relation1 When I add "Content-Type" header equal to "application/ld+json" And I send a "PUT" request to "/relation1s/1" with body: diff --git a/features/hydra/collection.feature b/features/hydra/collection.feature index 3ddd6787e71..4ffdfa851a4 100644 --- a/features/hydra/collection.feature +++ b/features/hydra/collection.feature @@ -358,6 +358,7 @@ Feature: Collections support } """ + @dropSchema Scenario: Filter with non-exact match When I send a "GET" request to "/dummies?name=Dummy%20%238" Then the response status code should be 200 @@ -395,6 +396,7 @@ Feature: Collections support } """ + @dropSchema @createSchema Scenario: Allow passing 0 to `itemsPerPage` When I send a "GET" request to "/dummies?itemsPerPage=0" diff --git a/features/hydra/error.feature b/features/hydra/error.feature index 9ec12d469fa..1c3b35eb033 100644 --- a/features/hydra/error.feature +++ b/features/hydra/error.feature @@ -85,6 +85,7 @@ Feature: Error handling And the JSON node "hydra:description" should exist And the JSON node "trace" should exist + @dropSchema Scenario: Get an error during update of an existing resource with a non-allowed update operation When I add "Content-Type" header equal to "application/ld+json" And I send a "POST" request to "/dummies" with body: @@ -118,6 +119,7 @@ Feature: Error handling And the JSON node "@id" should be equal to "/related_dummies/1" And the JSON node "symfony" should be equal to "laravel" + @dropSchema Scenario: Do not get an error during update of an existing relation with a non-allowed update operation When I add "Content-Type" header equal to "application/ld+json" And I send a "POST" request to "/relation_embedders" with body: diff --git a/features/integration/fos_user.feature b/features/integration/fos_user.feature index 82dc25441d8..8e25c361391 100644 --- a/features/integration/fos_user.feature +++ b/features/integration/fos_user.feature @@ -31,6 +31,7 @@ Feature: FOSUser integration """ And the password "azerty" for user 1 should be hashed + @dropSchema Scenario: Delete a user When I send a "DELETE" request to "/users/1" Then the response status code should be 204 diff --git a/features/jsonapi/collection_attributes.feature b/features/jsonapi/collection_attributes.feature index 5a506c56a31..256969af9ca 100644 --- a/features/jsonapi/collection_attributes.feature +++ b/features/jsonapi/collection_attributes.feature @@ -8,6 +8,7 @@ Feature: JSON API collections support And I add "Content-Type" header equal to "application/vnd.api+json" @createSchema + @dropSchema Scenario: Correctly serialize a collection Given there is a CircularReference When I send a "GET" request to "/circular_references/1" diff --git a/features/jsonapi/errors.feature b/features/jsonapi/errors.feature index b08343e74dd..a245c39c173 100644 --- a/features/jsonapi/errors.feature +++ b/features/jsonapi/errors.feature @@ -35,6 +35,7 @@ Feature: JSON API error handling } """ + @dropSchema Scenario: Get a validation error on an relationship Given there is a RelatedDummy And there is a DummyFriend diff --git a/features/jsonapi/filtering.feature b/features/jsonapi/filtering.feature index 1e21d8a399f..4cbe8acbf44 100644 --- a/features/jsonapi/filtering.feature +++ b/features/jsonapi/filtering.feature @@ -23,6 +23,7 @@ Feature: JSON API filter handling And the JSON should be valid according to the JSON API schema And the JSON node "data" should have 0 elements + @dropSchema Scenario: Apply filters based on the 'filter' query parameter with second level arguments When I send a "GET" request to "/dummies?filter[dummyDate][after]=2015-04-28" Then the response status code should be 200 diff --git a/features/jsonapi/jsonapi.feature b/features/jsonapi/jsonapi.feature index 6195854a906..fc42c9716fe 100644 --- a/features/jsonapi/jsonapi.feature +++ b/features/jsonapi/jsonapi.feature @@ -230,6 +230,7 @@ Feature: JSON API basic support And the JSON node "data.attributes.name" should be equal to "Jane Doe" And the JSON node "data.attributes.age" should be equal to the number 23 + @dropSchema Scenario: Embed a relation in a parent object When I send a "POST" request to "/relation_embedders" with body: """ diff --git a/features/jsonapi/ordering.feature b/features/jsonapi/ordering.feature index b3d4f687a3c..8d73e2ccaa5 100644 --- a/features/jsonapi/ordering.feature +++ b/features/jsonapi/ordering.feature @@ -99,6 +99,7 @@ Feature: JSON API order handling } """ + @dropSchema Scenario: Get collection ordered on two properties previously whitelisted When I send a "GET" request to "/dummies?sort=description,-id" Then the JSON should be valid according to the JSON API schema diff --git a/features/jsonapi/pagination.feature b/features/jsonapi/pagination.feature index d07aacf3b48..12d822ea6bd 100644 --- a/features/jsonapi/pagination.feature +++ b/features/jsonapi/pagination.feature @@ -24,6 +24,7 @@ Feature: JSON API pagination handling And the JSON node "data" should have 1 elements And the JSON node "meta.currentPage" should be equal to the number 4 + @dropSchema Scenario: Get a paginated collection according to custom items per page in request When I send a "GET" request to "/dummies?page[itemsPerPage]=15" Then the response status code should be 200 diff --git a/features/main/circular_reference.feature b/features/main/circular_reference.feature index 311c91a1fa9..4cfac6bdb46 100644 --- a/features/main/circular_reference.feature +++ b/features/main/circular_reference.feature @@ -33,6 +33,7 @@ Feature: Circular references handling } """ + @dropSchema Scenario: Fetch circular reference When I add "Content-Type" header equal to "application/ld+json" And I send a "POST" request to "/circular_references" with body: diff --git a/features/main/composite.feature b/features/main/composite.feature index 2e25ab50280..ccb05ead9a2 100644 --- a/features/main/composite.feature +++ b/features/main/composite.feature @@ -4,6 +4,7 @@ Feature: Retrieve data with Composite identifiers I need to retrieve all collections @createSchema + @dropSchema Scenario: Get a collection with composite identifiers Given there are Composite identifier objects When I send a "GET" request to "/composite_items" @@ -35,6 +36,7 @@ Feature: Retrieve data with Composite identifiers """ @createSchema + @dropSchema Scenario: Get collection with composite identifiers Given there are Composite identifier objects When I send a "GET" request to "/composite_relations" @@ -123,6 +125,7 @@ Feature: Retrieve data with Composite identifiers When I send a "GET" request to "/composite_relations/compositeLabel=1;" Then the response status code should be 404 + @dropSchema Scenario: Get first composite item Given there are Composite identifier objects When I send a "GET" request to "/composite_items/1" diff --git a/features/main/configurable.feature b/features/main/configurable.feature index c0e73d1ba2b..8511985835f 100644 --- a/features/main/configurable.feature +++ b/features/main/configurable.feature @@ -44,6 +44,7 @@ Feature: Configurable resource CRUD } """ + @dropSchema Scenario: Retrieve the ConfigDummy resource When I send a "GET" request to "/fileconfigdummies/1" Then the response status code should be 200 diff --git a/features/main/content_negotiation.feature b/features/main/content_negotiation.feature index ff088d8af2c..85b14b7a9d4 100644 --- a/features/main/content_negotiation.feature +++ b/features/main/content_negotiation.feature @@ -110,6 +110,7 @@ Feature: Content Negotiation support Then the response status code should be 406 And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8" + @dropSchema Scenario: If the request format is HTML, the error should be in HTML When I add "Accept" header equal to "text/html" And I send a "GET" request to "/dummies/666" diff --git a/features/main/crud.feature b/features/main/crud.feature index d38d1870f25..4986c25a77d 100644 --- a/features/main/crud.feature +++ b/features/main/crud.feature @@ -488,6 +488,7 @@ Feature: Create-Retrieve-Update-Delete } """ + @dropSchema Scenario: Delete a resource When I send a "DELETE" request to "/dummies/1" Then the response status code should be 204 diff --git a/features/main/crud_abstract.feature b/features/main/crud_abstract.feature index f0c3d66b3d9..b0a1ef98dd9 100644 --- a/features/main/crud_abstract.feature +++ b/features/main/crud_abstract.feature @@ -102,6 +102,7 @@ Feature: Create-Retrieve-Update-Delete on abstract resource } """ + @dropSchema Scenario: Delete a resource When I send a "DELETE" request to "/abstract_dummies/1" Then the response status code should be 204 diff --git a/features/main/custom_identifier.feature b/features/main/custom_identifier.feature index ba37934d76b..e0772217259 100644 --- a/features/main/custom_identifier.feature +++ b/features/main/custom_identifier.feature @@ -97,6 +97,7 @@ Feature: Using custom identifier on resource And "name" property is readable for Hydra class "CustomIdentifierDummy" And "name" property is writable for Hydra class "CustomIdentifierDummy" + @dropSchema Scenario: Delete a resource When I send a "DELETE" request to "/custom_identifier_dummies/1" Then the response status code should be 204 diff --git a/features/main/custom_normalized.feature b/features/main/custom_normalized.feature index 852fe7d2ccc..f9f636bdb4c 100644 --- a/features/main/custom_normalized.feature +++ b/features/main/custom_normalized.feature @@ -158,6 +158,7 @@ Feature: Using custom normalized entity And "alias" property is readable for Hydra class "CustomNormalizedDummy" And "alias" property is writable for Hydra class "CustomNormalizedDummy" + @dropSchema Scenario: Delete a resource When I send a "DELETE" request to "/custom_normalized_dummies/1" Then the response status code should be 204 diff --git a/features/main/custom_operation.feature b/features/main/custom_operation.feature index 3dfd9ee3a00..9b44f01662a 100644 --- a/features/main/custom_operation.feature +++ b/features/main/custom_operation.feature @@ -17,6 +17,7 @@ Feature: Custom operation } """ + @dropSchema Scenario: Custom normalization operation When I send a "GET" request to "/custom/1/normalization" Then the JSON should be equal to: @@ -26,45 +27,3 @@ Feature: Custom operation "foo": "foo" } """ - - Scenario: Custom normalization operation with shorthand configuration - When I send a "POST" request to "/short_custom/denormalization" - And I add "Content-Type" header equal to "application/ld+json" - Then the JSON should be equal to: - """ - { - "@context": "/contexts/CustomActionDummy", - "@id": "/custom_action_dummies/2", - "@type": "CustomActionDummy", - "id": 2, - "foo": "short declaration" - } - """ - - Scenario: Custom normalization operation with shorthand configuration - When I send a "GET" request to "/short_custom/2/normalization" - Then the JSON should be equal to: - """ - { - "id": 2, - "foo": "short" - } - """ - - Scenario: Custom collection name without specific route - When I send a "GET" request to "/custom_action_collection_dummies" - Then the response status code should be 200 - Then the JSON node "hydra:member" should have 2 elements - - Scenario: Custom operation name without specific route - When I send a "GET" request to "/custom_action_collection_dummies/1" - Then the JSON should be equal to: - """ - { - "@context": "/contexts/CustomActionDummy", - "@id": "/custom_action_dummies/1", - "@type": "CustomActionDummy", - "id": 1, - "foo": "custom!" - } - """ diff --git a/features/main/custom_writable_identifier.feature b/features/main/custom_writable_identifier.feature index 98e005c95ed..d76adaeb9fc 100644 --- a/features/main/custom_writable_identifier.feature +++ b/features/main/custom_writable_identifier.feature @@ -101,6 +101,7 @@ Feature: Using custom writable identifier on resource And "slug" property is readable for Hydra class "CustomWritableIdentifierDummy" And "slug" property is writable for Hydra class "CustomWritableIdentifierDummy" + @dropSchema Scenario: Delete a resource When I send a "DELETE" request to "/custom_writable_identifier_dummies/slug_modified" Then the response status code should be 204 diff --git a/features/main/default_order.feature b/features/main/default_order.feature index 6b39736eee2..0057301ee1c 100644 --- a/features/main/default_order.feature +++ b/features/main/default_order.feature @@ -61,6 +61,7 @@ Feature: Default order } """ + @dropSchema Scenario: Override custom order by association Given there are 5 fooDummy objects with fake names When I send a "GET" request to "/foo_dummies?itemsPerPage=10" diff --git a/features/main/non_resource.feature b/features/main/non_resource.feature index 0cbe67ed308..039df534636 100644 --- a/features/main/non_resource.feature +++ b/features/main/non_resource.feature @@ -11,7 +11,7 @@ Feature: Non-resources handling "@context": "/contexts/ContainNonResource", "@id": "/contain_non_resources/1", "@type": "ContainNonResource", - "id": 1, + "id": "1", "nested": { "@id": "/contain_non_resources/1-nested", "@type": "ContainNonResource", diff --git a/features/main/overridden_operation.feature b/features/main/overridden_operation.feature index a7dec71e024..79ef7c98e8a 100644 --- a/features/main/overridden_operation.feature +++ b/features/main/overridden_operation.feature @@ -126,6 +126,7 @@ Feature: Create-Retrieve-Update-Delete with a Overridden Operation context } """ + @dropSchema Scenario: Delete a resource When I send a "DELETE" request to "/overridden_operation_dummies/1" Then the response status code should be 204 diff --git a/features/main/recursive.feature b/features/main/recursive.feature index ba3ae599c80..a06d15119d3 100644 --- a/features/main/recursive.feature +++ b/features/main/recursive.feature @@ -36,6 +36,7 @@ Feature: Max depth handling } """ + @dropSchema Scenario: Make the resource recursive When I add "Content-Type" header equal to "application/ld+json" And I send a "PUT" request to "recursives/1" with body: diff --git a/features/main/subresource.feature b/features/main/subresource.feature index 81bbbe8dda4..c3034b8adb1 100644 --- a/features/main/subresource.feature +++ b/features/main/subresource.feature @@ -53,8 +53,87 @@ Feature: Subresource support } """ + Scenario: Create a fourth level + When I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/fourth_levels" with body: + """ + {"level": 4} + """ + 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: + """ + { + "@context": "/contexts/FourthLevel", + "@id": "/fourth_levels/1", + "@type": "FourthLevel", + "id": 1, + "level": 4 + } + """ + + Scenario: Create a third level + When I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/third_levels" with body: + """ + {"level": 3, "fourthLevel": "/fourth_levels/1"} + """ + 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: + """ + { + "@context": "/contexts/ThirdLevel", + "@id": "/third_levels/1", + "@type": "ThirdLevel", + "fourthLevel": "/fourth_levels/1", + "id": 1, + "level": 3, + "test": true + } + """ + + Scenario: Create a named related dummy + When I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/related_dummies" with body: + """ + {"name": "Hello", "thirdLevel": "/third_levels/1"} + """ + 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" + + Scenario: Create an unnamed related dummy + When I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/related_dummies" with body: + """ + {"thirdLevel": "/third_levels/1"} + """ + 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" + + Scenario: Create a dummy with relations + When I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/dummies" with body: + """ + { + "name": "Dummy with relations", + "relatedDummy": "http://example.com/related_dummies/1", + "relatedDummies": [ + "/related_dummies/1", + "/related_dummies/2" + ], + "name_converted": null + } + """ + 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" + Scenario: Get the subresource relation collection - Given there is a dummy object with a fourth level relation When I send a "GET" request to "/dummies/1/related_dummies" And the response status code should be 200 And the response should be in JSON @@ -220,19 +299,6 @@ Feature: Subresource support } """ - Scenario: Create a dummy with a relation that is a subresource - When I add "Content-Type" header equal to "application/ld+json" - And I send a "POST" request to "/dummies" with body: - """ - { - "name": "Dummy with relations", - "relatedDummy": "/dummies/1/related_dummies/2" - } - """ - 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" - Scenario: Get the embedded relation subresource item at the third level When I send a "GET" request to "/dummies/1/related_dummies/1/third_level" And the response status code should be 200 @@ -316,6 +382,7 @@ Feature: Subresource support } """ + @dropSchema Scenario: Recursive resource When I send a "GET" request to "/dummy_products/2" And the response status code should be 200 diff --git a/features/main/table_inheritance.feature b/features/main/table_inheritance.feature index cf0e0ea7049..84a2a611bb0 100644 --- a/features/main/table_inheritance.feature +++ b/features/main/table_inheritance.feature @@ -44,6 +44,7 @@ Feature: Table inheritance } """ + @dropSchema Scenario: Get the parent entity collection When I send a "GET" request to "/dummy_table_inheritances" Then the response status code should be 200 @@ -236,6 +237,7 @@ Feature: Table inheritance } """ + @dropSchema Scenario: Get the parent entity collection which contains multiple inherited children type When I send a "GET" request to "/dummy_table_inheritances" Then the response status code should be 200 diff --git a/features/main/uuid.feature b/features/main/uuid.feature index d444dad8900..b51b8b4509a 100644 --- a/features/main/uuid.feature +++ b/features/main/uuid.feature @@ -97,28 +97,8 @@ Feature: Using uuid identifier on resource } """ + @dropSchema Scenario: Delete a resource When I send a "DELETE" request to "/uuid_identifier_dummies/41b29566-144b-11e6-a148-3e1d05defe78" Then the response status code should be 204 And the response should be empty - - @createSchema - Scenario: Retrieve a resource identified by Ramsey\Uuid\Uuid - Given there is a ramsey identified resource with uuid "41B29566-144B-11E6-A148-3E1D05DEFE78" - When I add "Content-Type" header equal to "application/ld+json" - And I send a "GET" request to "/ramsey_uuid_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78" - 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" - - Scenario: Delete a resource identified by a Ramsey\Uuid\Uuid - When I send a "DELETE" request to "/ramsey_uuid_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78" - Then the response status code should be 204 - And the response should be empty - - Scenario: Retrieve a resource identified by a bad Ramsey\Uuid\Uuid - When I add "Content-Type" header equal to "application/ld+json" - And I send a "GET" request to "/ramsey_uuid_dummies/41B29566-144B-E1D05DEFE78" - Then the response status code should be 404 - 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/security/send_security_headers.feature b/features/security/send_security_headers.feature index d4b91f77491..f0fa1504bb3 100644 --- a/features/security/send_security_headers.feature +++ b/features/security/send_security_headers.feature @@ -21,6 +21,7 @@ Feature: Send security header And the header "X-Content-Type-Options" should be equal to "nosniff" And the header "X-Frame-Options" should be equal to "deny" + @dropSchema Scenario: Error validation responses must always contain security headers When I add "Content-Type" header equal to "application/ld+json" And I send a "POST" request to "/dummies" with body: diff --git a/features/security/strong_typing.feature b/features/security/strong_typing.feature index f401bb2f977..b412ac77f6a 100644 --- a/features/security/strong_typing.feature +++ b/features/security/strong_typing.feature @@ -122,6 +122,7 @@ Feature: Handle properly invalid data submitted to the API And the JSON node "hydra:title" should be equal to "An error occurred" And the JSON node "hydra:description" should be equal to 'The type of the "name" attribute must be "string", "integer" given.' + @dropSchema Scenario: According to the JSON spec, allow numbers without explicit floating point for JSON formats When I add "Content-Type" header equal to "application/ld+json" And I send a "POST" request to "/dummies" with body: diff --git a/features/security/unknown_attributes.feature b/features/security/unknown_attributes.feature index 3bce4587032..fb236196006 100644 --- a/features/security/unknown_attributes.feature +++ b/features/security/unknown_attributes.feature @@ -4,6 +4,7 @@ Feature: Ignore unknown attributes I can send unsupported attributes that will be ignored @createSchema + @dropSchema Scenario: Create a resource When I add "Content-Type" header equal to "application/ld+json" And I send a "POST" request to "/dummies" with body: diff --git a/features/serializer/group_filter.feature b/features/serializer/group_filter.feature index 12dce07c0f1..cf81a369721 100644 --- a/features/serializer/group_filter.feature +++ b/features/serializer/group_filter.feature @@ -952,6 +952,7 @@ Feature: Filter with serialization groups on items and collections } """ + @dropSchema Scenario: Create a resource by groups dummy, dummy_baz, with overriding and with whitelist When I add "Content-Type" header equal to "application/ld+json" And I send a "POST" request to "/dummy_groups?override_whitelisted_groups[]=dummy&override_whitelisted_groups[]=dummy_baz" with body: diff --git a/features/serializer/property_filter.feature b/features/serializer/property_filter.feature index b8b1926e991..2a94293e007 100644 --- a/features/serializer/property_filter.feature +++ b/features/serializer/property_filter.feature @@ -571,6 +571,7 @@ Feature: Filter with serialization attributes on items and collections } """ + @dropSchema Scenario: Create a resource by attributes foo, bar, group.foo, group.baz and group.qux When I add "Content-Type" header equal to "application/ld+json" And I send a "POST" request to "/dummy_properties?properties[]=foo&properties[]=bar&properties[group][]=foo&properties[group][]=baz&properties[group][]=qux" with body: diff --git a/features/swagger/docs.feature b/features/swagger/docs.feature index 2274e2074af..fa4b34edf52 100644 --- a/features/swagger/docs.feature +++ b/features/swagger/docs.feature @@ -80,6 +80,7 @@ Feature: Documentation support Then the response status code should be 200 And I should see text matching "My Dummy API" + @dropSchema 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" diff --git a/src/Annotation/ApiResource.php b/src/Annotation/ApiResource.php index ba06031e247..d70145cf0b6 100644 --- a/src/Annotation/ApiResource.php +++ b/src/Annotation/ApiResource.php @@ -13,9 +13,6 @@ namespace ApiPlatform\Core\Annotation; -use ApiPlatform\Core\Exception\InvalidArgumentException; -use Doctrine\Common\Util\Inflector; - /** * ApiResource annotation. * @@ -23,34 +20,6 @@ * * @Annotation * @Target({"CLASS"}) - * @Attributes( - * @Attribute("accessControl", type="string"), - * @Attribute("accessControlMessage", type="string"), - * @Attribute("attributes", type="array"), - * @Attribute("collectionOperations", type="array"), - * @Attribute("denormalizationContext", type="array"), - * @Attribute("description", type="string"), - * @Attribute("fetchPartial", type="bool"), - * @Attribute("forceEager", type="bool"), - * @Attribute("filters", type="string[]"), - * @Attribute("graphql", type="array"), - * @Attribute("iri", type="string"), - * @Attribute("itemOperations", type="array"), - * @Attribute("maximumItemsPerPage", type="int"), - * @Attribute("normalizationContext", type="array"), - * @Attribute("order", type="array"), - * @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("paginationPartial", type="bool"), - * @Attribute("routePrefix", type="string"), - * @Attribute("shortName", type="string"), - * @Attribute("subresourceOperations", type="array"), - * @Attribute("validationGroups", type="mixed") - * ) */ final class ApiResource { @@ -93,155 +62,4 @@ final class ApiResource * @var array */ public $attributes = []; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var string - */ - private $accessControl; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var string - */ - private $accessControlMessage; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var array - */ - private $denormalizationContext; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var bool - */ - private $fetchPartial; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var bool - */ - private $forceEager; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var string[] - */ - private $filters; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var int - */ - private $maximumItemsPerPage; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var array - */ - private $normalizationContext; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var array - */ - private $order; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var bool - */ - private $paginationClientEnabled; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var bool - */ - private $paginationClientItemsPerPage; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var bool - */ - private $paginationClientPartial; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var bool - */ - private $paginationEnabled; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var bool - */ - private $paginationFetchJoinCollection; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var int - */ - private $paginationItemsPerPage; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var int - */ - private $paginationPartial; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var string - */ - private $routePrefix; - - /** - * @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112 - * - * @var mixed - */ - private $validationGroups; - - /** - * @throws InvalidArgumentException - */ - public function __construct(array $values = []) - { - if (isset($values['attributes'])) { - $this->attributes = $values['attributes']; - unset($values['attributes']); - } - - foreach ($values as $key => $value) { - if (!property_exists($this, $key)) { - throw new InvalidArgumentException(sprintf('Unknown property "%s" on annotation "%s".', $key, self::class)); - } - - $property = new \ReflectionProperty($this, $key); - - if ($property->isPublic()) { - $this->$key = $value; - } else { - $this->attributes += [Inflector::tableize($key) => $value]; - } - } - } } diff --git a/src/Api/CachedIdentifiersExtractor.php b/src/Api/CachedIdentifiersExtractor.php index 9b21a4d0d40..6adcd0d25b0 100644 --- a/src/Api/CachedIdentifiersExtractor.php +++ b/src/Api/CachedIdentifiersExtractor.php @@ -117,18 +117,19 @@ private function getKeys($item, callable $retriever): array try { $cacheItem = $this->cacheItemPool->getItem(self::CACHE_KEY_PREFIX.md5($resourceClass)); + if ($cacheItem->isHit()) { + return $this->localCache[$resourceClass] = $cacheItem->get(); + } } catch (CacheException $e) { - return $this->localCache[$resourceClass] = array_keys($retriever($item)); - } - - if ($cacheItem->isHit()) { - return $this->localCache[$resourceClass] = $cacheItem->get(); + // do nothing } $keys = array_keys($retriever($item)); - $cacheItem->set($keys); - $this->cacheItemPool->save($cacheItem); + if (isset($cacheItem)) { + $cacheItem->set($keys); + $this->cacheItemPool->save($cacheItem); + } return $this->localCache[$resourceClass] = $keys; } diff --git a/src/Api/FilterLocatorTrait.php b/src/Api/FilterLocatorTrait.php index 740f5245414..226b129c218 100644 --- a/src/Api/FilterLocatorTrait.php +++ b/src/Api/FilterLocatorTrait.php @@ -62,7 +62,5 @@ private function getFilter(string $filterId) if ($this->filterLocator instanceof FilterCollection && $this->filterLocator->offsetExists($filterId)) { return $this->filterLocator->offsetGet($filterId); } - - return null; } } diff --git a/src/Api/IdentifiersExtractor.php b/src/Api/IdentifiersExtractor.php index a36d3ac4154..e866672fd05 100644 --- a/src/Api/IdentifiersExtractor.php +++ b/src/Api/IdentifiersExtractor.php @@ -95,11 +95,9 @@ public function getIdentifiersFromItem($item): array 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)); } diff --git a/src/Api/ResourceClassResolver.php b/src/Api/ResourceClassResolver.php index 9f360c1de42..aa37e93208b 100644 --- a/src/Api/ResourceClassResolver.php +++ b/src/Api/ResourceClassResolver.php @@ -40,28 +40,29 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName */ public function getResourceClass($value, string $resourceClass = null, bool $strict = false): string { - $type = \is_object($value) && !$value instanceof \Traversable ? $this->getObjectClass($value) : $resourceClass; - $resourceClass = $resourceClass ?? $type; - - if (null === $resourceClass) { + if (\is_object($value) && !$value instanceof \Traversable) { + $typeToFind = $type = $this->getObjectClass($value); + if (null === $resourceClass) { + $resourceClass = $typeToFind; + } + } elseif (null === $resourceClass) { throw new InvalidArgumentException(sprintf('No resource class found.')); + } else { + $typeToFind = $type = $resourceClass; } - if ( - null === $type - || ((!$strict || $resourceClass === $type) && $isResourceClass = $this->isResourceClass($type)) - ) { - return $resourceClass; - } + if (($strict && isset($type) && $resourceClass !== $type) || false === $isResourceClass = $this->isResourceClass($typeToFind)) { + if (is_subclass_of($type, $resourceClass) && $this->isResourceClass($resourceClass)) { + return $type; + } + if ($isResourceClass ?? $this->isResourceClass($typeToFind)) { + return $typeToFind; + } - if ( - ($isResourceClass ?? $this->isResourceClass($type)) - || (is_subclass_of($type, $resourceClass) && $this->isResourceClass($resourceClass)) - ) { - return $type; + throw new InvalidArgumentException(sprintf('No resource class found for object of type "%s".', $typeToFind)); } - throw new InvalidArgumentException(sprintf('No resource class found for object of type "%s".', $type)); + return $resourceClass; } /** diff --git a/src/Bridge/Doctrine/Common/DataPersister.php b/src/Bridge/Doctrine/Common/DataPersister.php index 52ddf7096fe..d66a20ec0f1 100644 --- a/src/Bridge/Doctrine/Common/DataPersister.php +++ b/src/Bridge/Doctrine/Common/DataPersister.php @@ -81,6 +81,6 @@ public function remove($data) */ private function getManager($data) { - return \is_object($data) ? $this->managerRegistry->getManagerForClass($this->getObjectClass($data)) : null; + return is_object($data) ? $this->managerRegistry->getManagerForClass($this->getObjectClass($data)) : null; } } diff --git a/src/Bridge/Doctrine/Orm/CollectionDataProvider.php b/src/Bridge/Doctrine/Orm/CollectionDataProvider.php index 0bf34715ed4..a7a94c3dd6a 100644 --- a/src/Bridge/Doctrine/Orm/CollectionDataProvider.php +++ b/src/Bridge/Doctrine/Orm/CollectionDataProvider.php @@ -27,7 +27,6 @@ * * @author Kévin Dunglas * @author Samuel ROZE - * @final */ class CollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface { @@ -37,7 +36,7 @@ class CollectionDataProvider implements ContextAwareCollectionDataProviderInterf /** * @param QueryCollectionExtensionInterface[]|ContextAwareQueryCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(ManagerRegistry $managerRegistry, /* iterable */ $collectionExtensions = []) + public function __construct(ManagerRegistry $managerRegistry, array $collectionExtensions = []) { $this->managerRegistry = $managerRegistry; $this->collectionExtensions = $collectionExtensions; diff --git a/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php b/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php index f8ea0926027..65a9651d440 100644 --- a/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php +++ b/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php @@ -224,49 +224,46 @@ private function addSelect(QueryBuilder $queryBuilder, string $entity, string $a $select = []; $entityManager = $queryBuilder->getEntityManager(); $targetClassMetadata = $entityManager->getClassMetadata($entity); - if (!empty($targetClassMetadata->subClasses)) { + if ($targetClassMetadata->subClasses) { $queryBuilder->addSelect($associationAlias); + } else { + foreach ($this->propertyNameCollectionFactory->create($entity) as $property) { + $propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions); - return; - } - - foreach ($this->propertyNameCollectionFactory->create($entity) as $property) { - $propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions); - - if (true === $propertyMetadata->isIdentifier()) { - $select[] = $property; - continue; - } - - //the field test allows to add methods to a Resource which do not reflect real database fields - if ($targetClassMetadata->hasField($property) && (true === $propertyMetadata->getAttribute('fetchable') || $propertyMetadata->isReadable())) { - $select[] = $property; - } + if (true === $propertyMetadata->isIdentifier()) { + $select[] = $property; + continue; + } - if (!array_key_exists($property, $targetClassMetadata->embeddedClasses)) { - continue; - } + //the field test allows to add methods to a Resource which do not reflect real database fields + if ($targetClassMetadata->hasField($property) && (true === $propertyMetadata->getAttribute('fetchable') || $propertyMetadata->isReadable())) { + $select[] = $property; + } - foreach ($this->propertyNameCollectionFactory->create($targetClassMetadata->embeddedClasses[$property]['class']) as $embeddedProperty) { - $propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions); - $propertyName = "$property.$embeddedProperty"; - if ($targetClassMetadata->hasField($propertyName) && (true === $propertyMetadata->getAttribute('fetchable') || $propertyMetadata->isReadable())) { - $select[] = $propertyName; + if (array_key_exists($property, $targetClassMetadata->embeddedClasses)) { + foreach ($this->propertyNameCollectionFactory->create($targetClassMetadata->embeddedClasses[$property]['class']) as $embeddedProperty) { + $propertyMetadata = $this->propertyMetadataFactory->create($entity, $property, $propertyMetadataOptions); + $propertyName = "$property.$embeddedProperty"; + if ($targetClassMetadata->hasField($propertyName) && (true === $propertyMetadata->getAttribute('fetchable') || $propertyMetadata->isReadable())) { + $select[] = $propertyName; + } + } } } - } - $queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select))); + $queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select))); + } } /** - * Gets the serializer context. + * Gets 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 { + $request = null; if (null !== $this->requestStack && null !== $this->serializerContextBuilder && null !== $request = $this->requestStack->getCurrentRequest()) { return $this->serializerContextBuilder->createFromRequest($request, 'normalization_context' === $contextType); } diff --git a/src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php b/src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php index f984c222d29..e1f159c3271 100644 --- a/src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php +++ b/src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php @@ -148,7 +148,7 @@ public function supportsResult(string $resourceClass, string $operationName = nu */ public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null, array $context = []) { - $doctrineOrmPaginator = new DoctrineOrmPaginator($queryBuilder, $this->useFetchJoinCollection($queryBuilder, $resourceClass, $operationName)); + $doctrineOrmPaginator = new DoctrineOrmPaginator($queryBuilder, $this->useFetchJoinCollection($queryBuilder)); $doctrineOrmPaginator->setUseOutputWalkers($this->useOutputWalkers($queryBuilder)); $resourceMetadata = null === $resourceClass ? null : $this->resourceMetadataFactory->create($resourceClass); @@ -197,20 +197,14 @@ private function isPaginationEnabled(Request $request, ResourceMetadata $resourc * Determines whether the Paginator should fetch join collections, if the root entity uses composite identifiers it should not. * * @see https://github.com/doctrine/doctrine2/issues/2910 + * + * @param QueryBuilder $queryBuilder + * + * @return bool */ - private function useFetchJoinCollection(QueryBuilder $queryBuilder, string $resourceClass = null, string $operationName = null): bool + private function useFetchJoinCollection(QueryBuilder $queryBuilder): bool { - if (QueryChecker::hasRootEntityWithCompositeIdentifier($queryBuilder, $this->managerRegistry)) { - return false; - } - - if (null === $resourceClass) { - return true; - } - - $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - - return $resourceMetadata->getCollectionOperationAttribute($operationName, 'pagination_fetch_join_collection', true, true); + return !QueryChecker::hasRootEntityWithCompositeIdentifier($queryBuilder, $this->managerRegistry); } /** diff --git a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php b/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php index 1e19b7b8425..76a0ca60935 100644 --- a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php @@ -223,7 +223,6 @@ protected function getNestedMetadata(string $resourceClass, array $associations) */ protected function splitPropertyParts(string $property/*, string $resourceClass*/): array { - $resourceClass = null; $parts = explode('.', $property); if (\func_num_args() > 1) { @@ -237,7 +236,7 @@ protected function splitPropertyParts(string $property/*, string $resourceClass* } } - if (null === $resourceClass) { + if (!isset($resourceClass)) { return [ 'associations' => \array_slice($parts, 0, -1), 'field' => end($parts), @@ -318,14 +317,13 @@ protected function addJoinsForNestedProperty(string $property, string $rootAlias $propertyParts = $this->splitPropertyParts($property, $resourceClass); $parentAlias = $rootAlias; - $alias = null; foreach ($propertyParts['associations'] as $association) { $alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $parentAlias, $association); $parentAlias = $alias; } - if (null === $alias) { + if (!isset($alias)) { throw new InvalidArgumentException(sprintf('Cannot add joins for property "%s" - property is not nested.', $property)); } diff --git a/src/Bridge/Doctrine/Orm/Filter/DateFilter.php b/src/Bridge/Doctrine/Orm/Filter/DateFilter.php index 7e2f8c8101c..3177e54239d 100644 --- a/src/Bridge/Doctrine/Orm/Filter/DateFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/DateFilter.php @@ -171,8 +171,8 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } 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)), - ]); + 'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)), + ]); return; } diff --git a/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php b/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php index 0adb2535305..4e0f5edddf5 100644 --- a/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php @@ -169,7 +169,7 @@ protected function isNullableField(string $property, string $resourceClass): boo */ private function isAssociationNullable(array $associationMapping): bool { - if (!empty($associationMapping['id'])) { + if (isset($associationMapping['id']) && $associationMapping['id']) { return false; } diff --git a/src/Bridge/Doctrine/Orm/ItemDataProvider.php b/src/Bridge/Doctrine/Orm/ItemDataProvider.php index 1dcdcf44d85..2833bd24682 100644 --- a/src/Bridge/Doctrine/Orm/ItemDataProvider.php +++ b/src/Bridge/Doctrine/Orm/ItemDataProvider.php @@ -20,7 +20,6 @@ use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use Doctrine\Common\Persistence\ManagerRegistry; @@ -33,7 +32,6 @@ * * @author Kévin Dunglas * @author Samuel ROZE - * @final */ class ItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface { @@ -48,7 +46,7 @@ class ItemDataProvider implements ItemDataProviderInterface, RestrictedDataProvi * @param PropertyMetadataFactoryInterface $propertyMetadataFactory * @param QueryItemExtensionInterface[] $itemExtensions */ - public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, /* iterable */ $itemExtensions = []) + public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, array $itemExtensions = []) { $this->managerRegistry = $managerRegistry; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; @@ -72,13 +70,11 @@ public function getItem(string $resourceClass, $id, string $operationName = null { $manager = $this->managerRegistry->getManagerForClass($resourceClass); - if (!($context[ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER] ?? false)) { - $id = $this->normalizeIdentifiers($id, $manager, $resourceClass); - } + $identifiers = $this->normalizeIdentifiers($id, $manager, $resourceClass); $fetchData = $context['fetch_data'] ?? true; if (!$fetchData && $manager instanceof EntityManagerInterface) { - return $manager->getReference($resourceClass, $id); + return $manager->getReference($resourceClass, $identifiers); } $repository = $manager->getRepository($resourceClass); @@ -90,10 +86,10 @@ public function getItem(string $resourceClass, $id, string $operationName = null $queryNameGenerator = new QueryNameGenerator(); $doctrineClassMetadata = $manager->getClassMetadata($resourceClass); - $this->addWhereForIdentifiers($id, $queryBuilder, $doctrineClassMetadata); + $this->addWhereForIdentifiers($identifiers, $queryBuilder, $doctrineClassMetadata); foreach ($this->itemExtensions as $extension) { - $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $id, $operationName, $context); + $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName, $context); if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName, $context)) { return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); diff --git a/src/Bridge/Doctrine/Orm/SubresourceDataProvider.php b/src/Bridge/Doctrine/Orm/SubresourceDataProvider.php index ea536f4c20c..a470828bf15 100644 --- a/src/Bridge/Doctrine/Orm/SubresourceDataProvider.php +++ b/src/Bridge/Doctrine/Orm/SubresourceDataProvider.php @@ -23,7 +23,6 @@ use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use Doctrine\Common\Persistence\ManagerRegistry; @@ -51,7 +50,7 @@ final class SubresourceDataProvider implements SubresourceDataProviderInterface * @param QueryCollectionExtensionInterface[] $collectionExtensions * @param QueryItemExtensionInterface[] $itemExtensions */ - public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, /* iterable */ $collectionExtensions = [], /* iterable */ $itemExtensions = []) + public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, array $collectionExtensions = [], array $itemExtensions = []) { $this->managerRegistry = $managerRegistry; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; @@ -69,7 +68,7 @@ public function getSubresource(string $resourceClass, array $identifiers, array { $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)); + throw new ResourceClassNotSupportedException(); } $repository = $manager->getRepository($resourceClass); @@ -77,7 +76,7 @@ public function getSubresource(string $resourceClass, array $identifiers, array throw new RuntimeException('The repository class must have a "createQueryBuilder" method.'); } - if (!isset($context['identifiers'], $context['property'])) { + if (!isset($context['identifiers']) || !isset($context['property'])) { throw new ResourceClassNotSupportedException('The given resource class is not a subresource.'); } @@ -152,16 +151,11 @@ private function buildQuery(array $identifiers, array $context, QueryNameGenerat $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[ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER] ?? false) { - $normalizedIdentifiers = $identifiers[$identifier]; - } else { - $normalizedIdentifiers = $this->normalizeIdentifiers($identifiers[$identifier], $manager, $identifierResourceClass); - } - } + $normalizedIdentifiers = isset($identifiers[$identifier]) ? $this->normalizeIdentifiers( + $identifiers[$identifier], + $manager, + $identifierResourceClass + ) : []; if ($classMetadata->hasAssociation($previousAssociationProperty)) { $relationType = $classMetadata->getAssociationMapping($previousAssociationProperty)['type']; diff --git a/src/Bridge/Doctrine/Orm/Util/IdentifierManagerTrait.php b/src/Bridge/Doctrine/Orm/Util/IdentifierManagerTrait.php index 8eb5a394c07..a9babf454a5 100644 --- a/src/Bridge/Doctrine/Orm/Util/IdentifierManagerTrait.php +++ b/src/Bridge/Doctrine/Orm/Util/IdentifierManagerTrait.php @@ -44,7 +44,6 @@ private function normalizeIdentifiers($id, ObjectManager $manager, string $resou $doctrineIdentifierFields = $doctrineClassMetadata->getIdentifier(); $isOrm = interface_exists(EntityManagerInterface::class) && $manager instanceof EntityManagerInterface; $platform = $isOrm ? $manager->getConnection()->getDatabasePlatform() : null; - $identifiersMap = null; if (\count($doctrineIdentifierFields) > 1) { $identifiersMap = []; @@ -70,9 +69,9 @@ private function normalizeIdentifiers($id, ObjectManager $manager, string $resou continue; } - $identifier = null === $identifiersMap ? $identifierValues[$i] ?? null : $identifiersMap[$propertyName] ?? null; + $identifier = !isset($identifiersMap) ? $identifierValues[$i] ?? null : $identifiersMap[$propertyName] ?? null; if (null === $identifier) { - throw new PropertyNotFoundException(sprintf('Invalid identifier "%s", "%s" was not found.', $id, $propertyName)); + throw new PropertyNotFoundException(sprintf('Invalid identifier "%s", "%s" has not been found.', $id, $propertyName)); } $doctrineTypeName = $doctrineClassMetadata->getTypeOfField($propertyName); diff --git a/src/Bridge/FosUser/EventListener.php b/src/Bridge/FosUser/EventListener.php index 92fa2858bb0..81c33899604 100644 --- a/src/Bridge/FosUser/EventListener.php +++ b/src/Bridge/FosUser/EventListener.php @@ -50,11 +50,14 @@ public function onKernelView(GetResponseForControllerResultEvent $event) return; } - if ('DELETE' === $request->getMethod()) { - $this->userManager->deleteUser($user); - $event->setControllerResult(null); - } else { - $this->userManager->updateUser($user); + switch ($request->getMethod()) { + case 'DELETE': + $this->userManager->deleteUser($user); + $event->setControllerResult(null); + break; + default: + $this->userManager->updateUser($user); + break; } } } diff --git a/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php b/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php deleted file mode 100644 index 8bac4e27f52..00000000000 --- a/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.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\RamseyUuid\Identifier\Normalizer; - -use ApiPlatform\Core\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} - */ - 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) - { - return is_a($type, UuidInterface::class, true); - } -} diff --git a/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php b/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php index 94171ff0fda..600414f5ae7 100644 --- a/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php +++ b/src/Bridge/Symfony/Bundle/ApiPlatformBundle.php @@ -14,7 +14,9 @@ namespace ApiPlatform\Core\Bridge\Symfony\Bundle; use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass; +use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataPersisterPass; use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass; +use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DoctrineQueryExtensionPass; use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\FilterPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -33,8 +35,10 @@ public function build(ContainerBuilder $container) { parent::build($container); + $container->addCompilerPass(new DataPersisterPass()); $container->addCompilerPass(new DataProviderPass()); $container->addCompilerPass(new AnnotationFilterPass()); $container->addCompilerPass(new FilterPass()); + $container->addCompilerPass(new DoctrineQueryExtensionPass()); } } diff --git a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php b/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php index c7c627f794c..9de998068d0 100644 --- a/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php +++ b/src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php @@ -17,10 +17,8 @@ use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Yaml\Yaml; /** * Console command to dump Swagger API documentations. @@ -55,8 +53,7 @@ protected function configure() { $this ->setName('api:swagger:export') - ->setDescription('Dump the Swagger 2.0 (OpenAPI) documentation') - ->addOption('yaml', 'y', InputOption::VALUE_NONE, 'Dump the documentation in YAML'); + ->setDescription('Dump the Swagger 2.0 (OpenAPI) documentation'); } /** @@ -66,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { $documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->apiTitle, $this->apiDescription, $this->apiVersion, $this->apiFormats); $data = $this->documentationNormalizer->normalize($documentation); - $content = $input->getOption('yaml') ? Yaml::dump($data) : json_encode($data, JSON_PRETTY_PRINT); + $content = json_encode($data, JSON_PRETTY_PRINT); $output->writeln($content); } } diff --git a/src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php b/src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.php deleted file mode 100644 index 1d67d4440a5..00000000000 --- a/src/Bridge/Symfony/Bundle/DataCollector/RequestDataCollector.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\Symfony\Bundle\DataCollector; - -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Util\RequestAttributesExtractor; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\DataCollector\DataCollector; - -/** - * @author Julien DENIAU - */ -final class RequestDataCollector extends DataCollector -{ - private $metadataFactory; - - public function __construct(ResourceMetadataFactoryInterface $metadataFactory) - { - $this->metadataFactory = $metadataFactory; - } - - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Exception $exception = null) - { - $resourceClass = $request->attributes->get('_api_resource_class'); - $resourceMetadata = $resourceClass ? $this->metadataFactory->create($resourceClass) : null; - - $this->data = [ - 'resource_class' => $resourceClass, - 'resource_metadata' => $resourceMetadata ? $this->cloneVar($resourceMetadata) : null, - 'acceptable_content_types' => $request->getAcceptableContentTypes(), - 'request_attributes' => RequestAttributesExtractor::extractAttributes($request), - ]; - } - - 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'] ?? []; - } - - /** - * {@inheritdoc} - */ - public function getName(): string - { - return 'api_platform.data_collector.request'; - } - - /** - * {@inheritdoc} - */ - public function reset() - { - $this->data = []; - } -} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index d962302134d..19fea15c2e0 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -26,7 +26,6 @@ use Doctrine\Common\Annotations\Annotation; use Doctrine\ORM\Version; use phpDocumentor\Reflection\DocBlockFactoryInterface; -use Ramsey\Uuid\Uuid; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Resource\DirectoryResource; @@ -123,10 +122,6 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('security.xml'); } - if (class_exists(Uuid::class)) { - $loader->load('ramsey_uuid.xml'); - } - $useDoctrine = isset($bundles['DoctrineBundle']) && class_exists(Version::class); $this->registerMetadataConfiguration($container, $config, $loader); @@ -143,7 +138,6 @@ public function load(array $configs, ContainerBuilder $container) $this->registerDoctrineExtensionConfiguration($container, $config, $useDoctrine); $this->registerHttpCache($container, $config, $loader, $useDoctrine); $this->registerValidatorConfiguration($container, $config, $loader); - $this->registerDataCollector($container, $config, $loader); } /** @@ -210,7 +204,7 @@ private function registerMetadataConfiguration(ContainerBuilder $container, arra list($xmlResources, $yamlResources) = $this->getResourcesToWatch($container, $config['mapping']['paths']); - if (!empty($config['resource_class_directories'])) { + if (isset($config['resource_class_directories']) && $config['resource_class_directories']) { $container->setParameter('api_platform.resource_class_directories', array_merge( $config['resource_class_directories'], $container->getParameter('api_platform.resource_class_directories') )); @@ -521,7 +515,7 @@ private function registerHttpCache(ContainerBuilder $container, array $config, X $definitions = []; foreach ($config['http_cache']['invalidation']['varnish_urls'] as $key => $url) { $definition = new ChildDefinition('api_platform.http_cache.purger.varnish_client'); - $definition->addArgument(['base_uri' => $url] + $config['http_cache']['invalidation']['request_options']); + $definition->addArgument(['base_uri' => $url]); $definitions[] = $definition; } @@ -564,16 +558,4 @@ private function registerValidatorConfiguration(ContainerBuilder $container, arr $container->setParameter('api_platform.validator.serialize_payload_fields', $config['validator']['serialize_payload_fields']); } - - /** - * Registers the DataCollector configuration. - */ - private function registerDataCollector(ContainerBuilder $container, array $config, XmlFileLoader $loader) - { - if (!$config['enable_profiler']) { - return; - } - - $loader->load('data_collector.xml'); - } } diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataPersisterPass.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataPersisterPass.php new file mode 100644 index 00000000000..784fe4d5957 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataPersisterPass.php @@ -0,0 +1,43 @@ + + * + * For the 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; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Registers data persisters. + * + * @internal + * + * @author Baptiste Meyer + */ +final class DataPersisterPass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $persisters = []; + $services = $container->findTaggedServiceIds('api_platform.data_persister', true); + + foreach ($services as $serviceId => $tags) { + $persisters[] = new Reference($serviceId); + } + + $container->getDefinition('api_platform.data_persister')->addArgument($persisters); + } +} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php index 5ae351d727e..4f2d65eb59e 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataProviderPass.php @@ -16,6 +16,7 @@ use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\DataProvider\SerializerAwareDataProviderInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -29,25 +30,37 @@ */ final class DataProviderPass implements CompilerPassInterface { + use PriorityTaggedServiceTrait; + /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { foreach (OperationType::TYPES as $type) { - $this->addSerializerLocator($container, $type); + $this->registerDataProviders($container, $type); } } - private function addSerializerLocator(ContainerBuilder $container, string $type) + /** + * The priority sorting algorithm has been backported from Symfony 3.2. + * + * @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php + * + * @param ContainerBuilder $container + * @param string $type + */ + private function registerDataProviders(ContainerBuilder $container, string $type) { - $services = $container->findTaggedServiceIds("api_platform.{$type}_data_provider", true); + $services = $this->findAndSortTaggedServices("api_platform.{$type}_data_provider", $container); - foreach ($services as $id => $tags) { - $definition = $container->getDefinition((string) $id); + foreach ($services as $reference) { + $definition = $container->getDefinition((string) $reference); if (is_a($definition->getClass(), SerializerAwareDataProviderInterface::class, true)) { $definition->addMethodCall('setSerializerLocator', [new Reference('api_platform.serializer_locator')]); } } + + $container->getDefinition("api_platform.{$type}_data_provider")->addArgument($services); } } diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DoctrineQueryExtensionPass.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DoctrineQueryExtensionPass.php new file mode 100644 index 00000000000..0620c330312 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DoctrineQueryExtensionPass.php @@ -0,0 +1,54 @@ + + * + * For the 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; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Injects query extensions. + * + * @internal + * + * @author Samuel ROZE + * @author Kévin Dunglas + */ +final class DoctrineQueryExtensionPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + // if doctrine not loaded + if (!$container->hasDefinition('api_platform.doctrine.metadata_factory')) { + return; + } + + $collectionDataProviderDefinition = $container->getDefinition('api_platform.doctrine.orm.collection_data_provider'); + $itemDataProviderDefinition = $container->getDefinition('api_platform.doctrine.orm.item_data_provider'); + $subresourceDataProviderDefinition = $container->getDefinition('api_platform.doctrine.orm.subresource_data_provider'); + + $collectionExtensions = $this->findAndSortTaggedServices('api_platform.doctrine.orm.query_extension.collection', $container); + $itemExtensions = $this->findAndSortTaggedServices('api_platform.doctrine.orm.query_extension.item', $container); + + $collectionDataProviderDefinition->replaceArgument(1, $collectionExtensions); + $itemDataProviderDefinition->replaceArgument(3, $itemExtensions); + $subresourceDataProviderDefinition->replaceArgument(3, $collectionExtensions); + $subresourceDataProviderDefinition->replaceArgument(4, $itemExtensions); + } +} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index e426648b13a..19d3ce64d0e 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -58,9 +58,13 @@ public function getConfigTreeBuilder() ->defaultValue('0.0.0') ->end() ->scalarNode('default_operation_path_resolver') + ->beforeNormalization()->always(function ($v) { + @trigger_error('The use of the `default_operation_path_resolver` has been deprecated in 2.1 and will be removed in 3.0. Use `path_segment_name_generator` instead.', E_USER_DEPRECATED); + + return $v; + })->end() ->defaultValue('api_platform.operation_path_resolver.underscore') - ->setDeprecated('The use of the `default_operation_path_resolver` has been deprecated in 2.1 and will be removed in 3.0. Use `path_segment_name_generator` instead.') - ->info('Specify the default operation path resolver to use for generating resources operations path.') + ->info('[Deprecated] Specify the default operation path resolver to use for generating resources operations path.') ->end() ->scalarNode('name_converter')->defaultNull()->info('Specify a name converter to use.')->end() ->scalarNode('path_segment_name_generator')->defaultValue('api_platform.path_segment_name_generator.underscore')->info('Specify a path name generator to use.')->end() @@ -83,15 +87,20 @@ public function getConfigTreeBuilder() ->end() ->booleanNode('enable_fos_user')->defaultValue(class_exists(FOSUserBundle::class))->info('Enable the FOSUserBundle integration.')->end() ->booleanNode('enable_nelmio_api_doc') + ->beforeNormalization()->always(function ($v) { + if ($v) { + @trigger_error('Enabling the NelmioApiDocBundle integration has been deprecated in 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.', E_USER_DEPRECATED); + } + + return $v; + })->end() ->defaultValue(false) - ->setDeprecated('Enabling the NelmioApiDocBundle integration has been deprecated in 2.2 and will be removed in 3.0. NelmioApiDocBundle 3 has native support for API Platform.') - ->info('Enable the NelmioApiDocBundle integration.') + ->info('[Deprecated] Enable the NelmioApiDocBundle integration.') ->end() ->booleanNode('enable_swagger')->defaultValue(true)->info('Enable the Swagger documentation and export.')->end() ->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger ui.')->end() ->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end() ->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end() - ->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end() ->arrayNode('oauth') ->canBeEnabled() @@ -203,14 +212,6 @@ public function getConfigTreeBuilder() ->prototype('scalar')->end() ->info('URLs of the Varnish servers to purge using cache tags when a resource is updated.') ->end() - ->variableNode('request_options') - ->defaultValue([]) - ->validate() - ->ifTrue(function ($v) { return false === \is_array($v); }) - ->thenInvalid('The request_options parameter must be an array.') - ->end() - ->info('To pass options to the client charged with the request.') - ->end() ->end() ->end() ->end() diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 456c1c9472a..ecda454866f 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -58,8 +58,6 @@ - - @@ -140,7 +138,6 @@ - @@ -214,7 +211,7 @@ %api_platform.exception_to_status% - + @@ -230,20 +227,6 @@ - - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml b/src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml deleted file mode 100644 index 8413c9948f1..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/data_collector.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml b/src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml index aa61dc3565a..342d6370aba 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/data_persister.xml @@ -5,9 +5,7 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml b/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml index 2ed2f84d7d4..ac0ae395c05 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml @@ -12,19 +12,13 @@ - - - + - - - + - - - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml index d8d5b661c6f..82d59e76390 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml @@ -17,22 +17,22 @@ - + - + - - + + @@ -112,7 +112,7 @@ null null %api_platform.eager_loading.fetch_partial% - + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml b/src/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml deleted file mode 100644 index a5d0626ab06..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/config/ramsey_uuid.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform.svg b/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform.svg deleted file mode 100644 index 7409bbd9b28..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/api-platform.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig b/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig deleted file mode 100644 index aa0b0eff0cb..00000000000 --- a/src/Bridge/Symfony/Bundle/Resources/views/DataCollector/request.html.twig +++ /dev/null @@ -1,148 +0,0 @@ -{% extends '@WebProfiler/Profiler/layout.html.twig' %} - -{% macro operationLine(key, operation, actualOperationName) %} - - {{ key }} - {{- profiler_dump(operation, 1) -}} - -{% endmacro %} - -{% import _self as apiPlatform %} - -{% block toolbar %} - {% set icon %} - {{ include('@ApiPlatform/DataCollector/api-platform.svg') }} - {% endset %} - - {% set text %} -
- Resource Class - {{ collector.resourceClass|default('Not an API Platform resource') }} -
- {% endset %} - - {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }} -{% endblock %} - -{% block menu %} - {# This left-hand menu appears when using the full-screen profiler. #} - - - {{ include('@ApiPlatform/DataCollector/api-platform.svg') }} - - API Platform - -{% endblock %} - -{% block panel %} -
-
- {{ collector.resourceClass|default('Not an API Platform resource') }} - Resource class -
-
- - {% if collector.resourceMetadata %} -

Metadata

-

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

- - - - - - - - - - {% for key, itemOperation in collector.resourceMetadata.itemOperations %} - {{ apiPlatform.operationLine(key, itemOperation, collector.requestAttributes.item_operation_name|default('')) }} - {% endfor %} - -
- Item operations - - Attributes -
- - - - - - - - - - - {% for key, collectionOperation in collector.resourceMetadata.collectionOperations %} - {{ apiPlatform.operationLine(key, collectionOperation, collector.requestAttributes.collection_operation_name|default('')) }} - {% endfor %} - -
- Collection operations - - Attributes -
- - - - - - - - - - {% if collector.resourceMetadata.attributes.filters is defined %} - {% for filter in collector.resourceMetadata.attributes.filters %} - - - - {% endfor %} - {% endif %} - -
- Filters -
- {{ filter }} -
- - - - - - - - - - - {% for key, value in collector.resourceMetadata.attributes if key != 'filters' %} - - - - - {% endfor %} - -
- Attributes -
- {{ key }} - - {{- profiler_dump(value, 2) -}} -
-

Acceptable Content Types

- - - - - - - - - {% for type in collector.acceptableContentTypes %} - - - - {% endfor %} - -
Content Type
{{ type }}
- {% endif %} -{% endblock %} diff --git a/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig b/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig index e3756a90239..95a61a93791 100644 --- a/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig +++ b/src/Bridge/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig @@ -13,45 +13,45 @@ - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - +
- +
-
-
+
+
diff --git a/src/Bridge/Symfony/Routing/ApiLoader.php b/src/Bridge/Symfony/Routing/ApiLoader.php index d8c66b89e79..1b6afc44d31 100644 --- a/src/Bridge/Symfony/Routing/ApiLoader.php +++ b/src/Bridge/Symfony/Routing/ApiLoader.php @@ -18,7 +18,6 @@ use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface; use ApiPlatform\Core\PathResolver\OperationPathResolverInterface; use Symfony\Component\Config\FileLocator; @@ -92,13 +91,13 @@ public function load($data, $type = null): RouteCollection if (null !== $collectionOperations = $resourceMetadata->getCollectionOperations()) { foreach ($collectionOperations as $operationName => $operation) { - $this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceMetadata, OperationType::COLLECTION); + $this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceShortName, OperationType::COLLECTION); } } if (null !== $itemOperations = $resourceMetadata->getItemOperations()) { foreach ($itemOperations as $operationName => $operation) { - $this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceMetadata, OperationType::ITEM); + $this->addRoute($routeCollection, $resourceClass, $operationName, $operation, $resourceShortName, OperationType::ITEM); } } @@ -179,12 +178,17 @@ private function loadExternalFiles(RouteCollection $routeCollection) /** * Creates and adds a route for the given operation to the route collection. * + * @param RouteCollection $routeCollection + * @param string $resourceClass + * @param string $operationName + * @param array $operation + * @param string $resourceShortName + * @param string $operationType + * * @throws RuntimeException */ - private function addRoute(RouteCollection $routeCollection, string $resourceClass, string $operationName, array $operation, ResourceMetadata $resourceMetadata, string $operationType) + private function addRoute(RouteCollection $routeCollection, string $resourceClass, string $operationName, array $operation, string $resourceShortName, string $operationType) { - $resourceShortName = $resourceMetadata->getShortName(); - if (isset($operation['route_name'])) { return; } @@ -201,11 +205,8 @@ private function addRoute(RouteCollection $routeCollection, string $resourceClas } } - $path = trim(trim($resourceMetadata->getAttribute('route_prefix', '')), '/'); - $path .= $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName); - $route = new Route( - $path, + $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName), [ '_controller' => $controller, '_format' => null, diff --git a/src/Bridge/Symfony/Routing/IriConverter.php b/src/Bridge/Symfony/Routing/IriConverter.php index e4103a3d03a..5070bba01da 100644 --- a/src/Bridge/Symfony/Routing/IriConverter.php +++ b/src/Bridge/Symfony/Routing/IriConverter.php @@ -19,16 +19,11 @@ use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\Api\UrlGeneratorInterface; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\OperationDataProviderTrait; -use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; -use ApiPlatform\Core\Exception\InvalidIdentifierException; use ApiPlatform\Core\Exception\ItemNotFoundException; use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Core\Util\AttributesExtractor; use ApiPlatform\Core\Util\ClassInfoTrait; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -43,24 +38,23 @@ final class IriConverter implements IriConverterInterface { use ClassInfoTrait; - use OperationDataProviderTrait; + private $itemDataProvider; 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, ChainIdentifierDenormalizer $identifierDenormalizer = null, SubresourceDataProviderInterface $subresourceDataProvider = null) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ItemDataProviderInterface $itemDataProvider, RouteNameResolverInterface $routeNameResolver, RouterInterface $router, PropertyAccessorInterface $propertyAccessor = null, IdentifiersExtractorInterface $identifiersExtractor = null) { $this->itemDataProvider = $itemDataProvider; $this->routeNameResolver = $routeNameResolver; $this->router = $router; - $this->identifiersExtractor = $identifiersExtractor; - $this->identifierDenormalizer = $identifierDenormalizer; - $this->subresourceDataProvider = $subresourceDataProvider; if (null === $identifiersExtractor) { - @trigger_error(sprintf('Not injecting "%s" is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3', IdentifiersExtractorInterface::class), E_USER_DEPRECATED); + @trigger_error('Not injecting ItemIdentifiersExtractor is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED); $this->identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, $propertyAccessor ?? PropertyAccess::createPropertyAccessor()); + } else { + $this->identifiersExtractor = $identifiersExtractor; } } @@ -75,31 +69,11 @@ public function getItemFromIri(string $iri, array $context = []) throw new InvalidArgumentException(sprintf('No route matches "%s".', $iri), $e->getCode(), $e); } - if (!isset($parameters['_api_resource_class'])) { + if (!isset($parameters['_api_resource_class'], $parameters['id'])) { throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri)); } - $attributes = AttributesExtractor::extractAttributes($parameters); - - try { - $identifiers = $this->extractIdentifiers($parameters, $attributes); - } catch (InvalidIdentifierException $e) { - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); - } - - if ($this->identifierDenormalizer) { - $context[ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER] = true; - } - - if (isset($attributes['subresource_operation_name'])) { - if ($item = $this->getSubresourceData($identifiers, $attributes, $context)) { - return $item; - } - - throw new ItemNotFoundException(sprintf('Item not found for "%s".', $iri)); - } - - if ($item = $this->getItemData($identifiers, $attributes, $context)) { + if ($item = $this->itemDataProvider->getItem($parameters['_api_resource_class'], $parameters['id'], null, $context)) { return $item; } diff --git a/src/Bridge/Symfony/Routing/RouteNameResolver.php b/src/Bridge/Symfony/Routing/RouteNameResolver.php index cdf8b769bd6..c71056111d0 100644 --- a/src/Bridge/Symfony/Routing/RouteNameResolver.php +++ b/src/Bridge/Symfony/Routing/RouteNameResolver.php @@ -71,6 +71,10 @@ private function isSameSubresource(array $context, array $currentContext): bool $currentSubresources[] = $identiferContext[1]; } - return $currentSubresources === $subresources; + if ($currentSubresources === $subresources) { + return true; + } + + return false; } } diff --git a/src/DataPersister/ChainDataPersister.php b/src/DataPersister/ChainDataPersister.php index fd5fbb4fec6..4f83e4ba1f2 100644 --- a/src/DataPersister/ChainDataPersister.php +++ b/src/DataPersister/ChainDataPersister.php @@ -25,7 +25,7 @@ final class ChainDataPersister implements DataPersisterInterface /** * @param DataPersisterInterface[] $persisters */ - public function __construct(/* iterable */ $persisters) + public function __construct(array $persisters) { $this->persisters = $persisters; } diff --git a/src/DataProvider/ChainCollectionDataProvider.php b/src/DataProvider/ChainCollectionDataProvider.php index c9134bd8b9d..6101920633f 100644 --- a/src/DataProvider/ChainCollectionDataProvider.php +++ b/src/DataProvider/ChainCollectionDataProvider.php @@ -27,7 +27,7 @@ final class ChainCollectionDataProvider implements ContextAwareCollectionDataPro /** * @param ContextAwareCollectionDataProviderInterface[]|CollectionDataProviderInterface[] $dataProviders */ - public function __construct(/* iterable */ $dataProviders) + public function __construct(array $dataProviders) { $this->dataProviders = $dataProviders; } diff --git a/src/DataProvider/ChainItemDataProvider.php b/src/DataProvider/ChainItemDataProvider.php index c421ebf0344..76180f57b86 100644 --- a/src/DataProvider/ChainItemDataProvider.php +++ b/src/DataProvider/ChainItemDataProvider.php @@ -27,7 +27,7 @@ final class ChainItemDataProvider implements ItemDataProviderInterface /** * @param ItemDataProviderInterface[] $dataProviders */ - public function __construct(/* iterable */ $dataProviders) + public function __construct(array $dataProviders) { $this->dataProviders = $dataProviders; } @@ -46,7 +46,7 @@ public function getItem(string $resourceClass, $id, string $operationName = null return $dataProvider->getItem($resourceClass, $id, $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); + @trigger_error(sprintf('Throwing a "%s" is deprecated in favor of implementing "%s"', get_class($e), RestrictedDataProviderInterface::class), E_USER_DEPRECATED); continue; } } diff --git a/src/DataProvider/ChainSubresourceDataProvider.php b/src/DataProvider/ChainSubresourceDataProvider.php index 91b7c4908c5..c43a43fdd70 100644 --- a/src/DataProvider/ChainSubresourceDataProvider.php +++ b/src/DataProvider/ChainSubresourceDataProvider.php @@ -27,7 +27,7 @@ final class ChainSubresourceDataProvider implements SubresourceDataProviderInter /** * @param SubresourceDataProviderInterface[] $dataProviders */ - public function __construct(/* iterable */ $dataProviders) + public function __construct(array $dataProviders) { $this->dataProviders = $dataProviders; } diff --git a/src/DataProvider/ItemDataProviderInterface.php b/src/DataProvider/ItemDataProviderInterface.php index 050cdceee1b..a11296c45f7 100644 --- a/src/DataProvider/ItemDataProviderInterface.php +++ b/src/DataProvider/ItemDataProviderInterface.php @@ -25,7 +25,7 @@ interface ItemDataProviderInterface /** * Retrieves an item. * - * @param array|int|string $id + * @param int|string $id * * @throws ResourceClassNotSupportedException * diff --git a/src/DataProvider/OperationDataProviderTrait.php b/src/DataProvider/OperationDataProviderTrait.php deleted file mode 100644 index 95bb5b78bc5..00000000000 --- a/src/DataProvider/OperationDataProviderTrait.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\DataProvider; - -use ApiPlatform\Core\Exception\InvalidIdentifierException; -use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; - -/** - * @internal - */ -trait OperationDataProviderTrait -{ - /** - * @var CollectionDataProviderInterface - */ - private $collectionDataProvider; - - /** - * @var ItemDataProviderInterface - */ - private $itemDataProvider; - - /** - * @var SubresourceDataProviderInterface - */ - private $subresourceDataProvider; - - /** - * @var ChainIdentifierDenormalizer - */ - private $identifierDenormalizer; - - /** - * Retrieves data for a collection operation. - * - * @return iterable|null - */ - private function getCollectionData(array $attributes, array $context) - { - return $this->collectionDataProvider->getCollection($attributes['resource_class'], $attributes['collection_operation_name'], $context); - } - - /** - * Gets data for an item operation. - * - * @throws NotFoundHttpException - * - * @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. - * - * @throws NotFoundHttpException - * @throws RuntimeException - * - * @return object|null - */ - private function getSubresourceData($identifiers, array $attributes, array $context) - { - if (!$this->subresourceDataProvider) { - throw new RuntimeException('Subresources not supported'); - } - - return $this->subresourceDataProvider->getSubresource($attributes['resource_class'], $identifiers, $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) - { - if (isset($attributes['item_operation_name'])) { - if (!isset($parameters['id'])) { - throw new InvalidIdentifierException('Parameter "id" not found'); - } - - $id = $parameters['id']; - - if ($this->identifierDenormalizer) { - return $this->identifierDenormalizer->denormalize((string) $id, $attributes['resource_class']); - } - - return $id; - } - - $identifiers = []; - - foreach ($attributes['subresource_context']['identifiers'] as $key => list($id, $resourceClass, $hasIdentifier)) { - if (false === $hasIdentifier) { - continue; - } - - $identifiers[$id] = $parameters[$id]; - - if ($this->identifierDenormalizer) { - $identifiers[$id] = $this->identifierDenormalizer->denormalize((string) $identifiers[$id], $resourceClass); - } - } - - return $identifiers; - } -} diff --git a/src/EventListener/DeserializeListener.php b/src/EventListener/DeserializeListener.php index 87c4257be79..180be295476 100644 --- a/src/EventListener/DeserializeListener.php +++ b/src/EventListener/DeserializeListener.php @@ -47,16 +47,12 @@ public function __construct(SerializerInterface $serializer, SerializerContextBu public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); - $method = $request->getMethod(); if ( $request->isMethodSafe(false) - || 'DELETE' === $method + || $request->isMethod('DELETE') || !($attributes = RequestAttributesExtractor::extractAttributes($request)) || !$attributes['receive'] - || ( - '' === ($requestContent = $request->getContent()) - && ('POST' === $method || 'PUT' === $method) - ) + || ('' === ($requestContent = $request->getContent()) && $request->isMethod('PUT')) ) { return; } diff --git a/src/EventListener/ReadListener.php b/src/EventListener/ReadListener.php index ec34ba2d2c0..40b41c4e671 100644 --- a/src/EventListener/ReadListener.php +++ b/src/EventListener/ReadListener.php @@ -15,11 +15,9 @@ use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; -use ApiPlatform\Core\DataProvider\OperationDataProviderTrait; use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; -use ApiPlatform\Core\Exception\InvalidIdentifierException; +use ApiPlatform\Core\Exception\PropertyNotFoundException; use ApiPlatform\Core\Exception\RuntimeException; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface; use ApiPlatform\Core\Util\RequestAttributesExtractor; use ApiPlatform\Core\Util\RequestParser; @@ -34,17 +32,17 @@ */ final class ReadListener { - use OperationDataProviderTrait; - + private $collectionDataProvider; + private $itemDataProvider; + private $subresourceDataProvider; private $serializerContextBuilder; - public function __construct(CollectionDataProviderInterface $collectionDataProvider, ItemDataProviderInterface $itemDataProvider, SubresourceDataProviderInterface $subresourceDataProvider = null, SerializerContextBuilderInterface $serializerContextBuilder = null, ChainIdentifierDenormalizer $identifierDenormalizer = null) + public function __construct(CollectionDataProviderInterface $collectionDataProvider, ItemDataProviderInterface $itemDataProvider, SubresourceDataProviderInterface $subresourceDataProvider = null, SerializerContextBuilderInterface $serializerContextBuilder = null) { $this->collectionDataProvider = $collectionDataProvider; $this->itemDataProvider = $itemDataProvider; $this->subresourceDataProvider = $subresourceDataProvider; $this->serializerContextBuilder = $serializerContextBuilder; - $this->identifierDenormalizer = $identifierDenormalizer; } /** @@ -76,39 +74,83 @@ public function onKernelRequest(GetResponseEvent $event) $request->attributes->set('_api_normalization_context', $normalizationContext); } - if (isset($attributes['collection_operation_name'])) { - $request->attributes->set('data', $request->isMethod('POST') ? null : $this->getCollectionData($attributes, $context)); - - return; + $data = []; + if (isset($attributes['item_operation_name'])) { + $data = $this->getItemData($request, $attributes, $context); + } elseif (isset($attributes['collection_operation_name'])) { + $data = $this->getCollectionData($request, $attributes, $context); + } elseif (isset($attributes['subresource_operation_name'])) { + $data = $this->getSubresourceData($request, $attributes, $context); } - $data = []; + $request->attributes->set('data', $data); + } - if ($this->identifierDenormalizer) { - $context[ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER] = true; + /** + * Retrieves data for a collection operation. + * + * @return array|\Traversable|null + */ + private function getCollectionData(Request $request, array $attributes, array $context) + { + if ($request->isMethod('POST')) { + return null; } + return $this->collectionDataProvider->getCollection($attributes['resource_class'], $attributes['collection_operation_name'], $context); + } + + /** + * Gets data for an item operation. + * + * @throws NotFoundHttpException + * + * @return object|null + */ + private function getItemData(Request $request, array $attributes, array $context) + { + $id = $request->attributes->get('id'); try { - $identifiers = $this->extractIdentifiers($request->attributes->all(), $attributes); + $data = $this->itemDataProvider->getItem($attributes['resource_class'], $id, $attributes['item_operation_name'], $context); + } catch (PropertyNotFoundException $e) { + $data = null; + } + + if (null === $data) { + throw new NotFoundHttpException('Not Found'); + } - 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.'); - } + return $data; + } - $data = $this->getSubresourceData($identifiers, $attributes, $context); + /** + * Gets data for a nested operation. + * + * @throws NotFoundHttpException + * @throws RuntimeException + * + * @return object|null + */ + private function getSubresourceData(Request $request, array $attributes, array $context) + { + if (null === $this->subresourceDataProvider) { + throw new RuntimeException('No subresource data provider.'); + } + + $attributes['subresource_context'] += $context; + $identifiers = []; + foreach ($attributes['subresource_context']['identifiers'] as $key => list($id, , $hasIdentifier)) { + if (true === $hasIdentifier) { + $identifiers[$id] = $request->attributes->get($id); } - } catch (InvalidIdentifierException $e) { - $data = null; } + $data = $this->subresourceDataProvider->getSubresource($attributes['resource_class'], $identifiers, $attributes['subresource_context'], $attributes['subresource_operation_name']); + if (null === $data) { - throw new NotFoundHttpException('Not Found'); + throw new NotFoundHttpException('Not Found.'); } - $request->attributes->set('data', $data); + return $data; } } diff --git a/src/Exception/InvalidIdentifierException.php b/src/Exception/InvalidIdentifierException.php deleted file mode 100644 index 5a6c6d8ff84..00000000000 --- a/src/Exception/InvalidIdentifierException.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\Exception; - -/** - * Indentifier is not valid exception. - * - * @author Antoine Bluchet - */ -final class InvalidIdentifierException extends \Exception implements ExceptionInterface -{ -} diff --git a/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php b/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php index 240efecec91..646c053c094 100644 --- a/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/CollectionResolverFactory.php @@ -63,10 +63,6 @@ public function __construct(CollectionDataProviderInterface $collectionDataProvi public function __invoke(string $resourceClass = null, string $rootClass = null, string $operationName = null): callable { return function ($source, $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass) { - if (null === $resourceClass) { - return null; - } - if ($this->requestStack && null !== $request = $this->requestStack->getCurrentRequest()) { $request->attributes->set( '_graphql_collections_args', @@ -79,7 +75,7 @@ public function __invoke(string $resourceClass = null, string $rootClass = null, $dataProviderContext['attributes'] = $this->fieldsToAttributes($info); $dataProviderContext['filters'] = $this->getNormalizedFilters($args); - if (isset($rootClass, $source[$rootProperty = $info->fieldName], $source[ItemNormalizer::ITEM_KEY])) { + if (isset($source[$rootProperty = $info->fieldName], $source[ItemNormalizer::ITEM_KEY])) { $rootResolvedFields = $this->identifiersExtractor->getIdentifiersFromItem(unserialize($source[ItemNormalizer::ITEM_KEY])); $subresource = $this->getSubresource($rootClass, $rootResolvedFields, array_keys($rootResolvedFields), $rootProperty, $resourceClass, true, $dataProviderContext); $collection = $subresource ?? []; diff --git a/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php b/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php index d7c609cce8f..a7d1add6c2b 100644 --- a/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php @@ -64,15 +64,11 @@ public function __construct(IriConverterInterface $iriConverter, DataPersisterIn public function __invoke(string $resourceClass = null, string $rootClass = null, string $operationName = null): callable { return function ($root, $args, $context, ResolveInfo $info) use ($resourceClass, $operationName) { - if (null === $resourceClass) { - return null; - } - $data = ['clientMutationId' => $args['input']['clientMutationId'] ?? null]; $item = null; $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); - $normalizationContext = $resourceMetadata->getGraphqlAttribute($operationName ?? '', 'normalization_context', [], true); + $normalizationContext = $resourceMetadata->getGraphqlAttribute($operationName, 'normalization_context', [], true); $normalizationContext['attributes'] = $info->getFieldSelection(PHP_INT_MAX); if (isset($args['input']['id'])) { diff --git a/src/GraphQl/Resolver/ResourceAccessCheckerTrait.php b/src/GraphQl/Resolver/ResourceAccessCheckerTrait.php index 45a3d5a3bdc..507c94eb820 100644 --- a/src/GraphQl/Resolver/ResourceAccessCheckerTrait.php +++ b/src/GraphQl/Resolver/ResourceAccessCheckerTrait.php @@ -37,7 +37,7 @@ trait ResourceAccessCheckerTrait { /** - * @param mixed $object + * @param object $object * * @throws Error */ @@ -47,7 +47,7 @@ public function canAccess(ResourceAccessCheckerInterface $resourceAccessChecker return; } - $isGranted = $resourceMetadata->getGraphqlAttribute($operationName ?? '', 'access_control', null, true); + $isGranted = $resourceMetadata->getGraphqlAttribute($operationName, 'access_control', null, true); if (null === $isGranted || $resourceAccessChecker->isGranted($resourceClass, $isGranted, ['object' => $object])) { return; } diff --git a/src/GraphQl/Type/Definition/IterableType.php b/src/GraphQl/Type/Definition/IterableType.php index c89559f64f8..d06a732a2e2 100644 --- a/src/GraphQl/Type/Definition/IterableType.php +++ b/src/GraphQl/Type/Definition/IterableType.php @@ -33,13 +33,9 @@ */ final class IterableType extends ScalarType { - public function __construct() - { - $this->name = 'Iterable'; - $this->description = 'The `Iterable` scalar type represents an array or a Traversable with any kind of data.'; + public $name = 'Iterable'; - parent::__construct(); - } + public $description = 'The `Iterable` scalar type represents an array or a Traversable with any kind of data.'; /** * {@inheritdoc} @@ -81,8 +77,6 @@ public function parseLiteral($valueNode) /** * @param StringValueNode|BooleanValueNode|IntValueNode|FloatValueNode|ObjectValueNode|ListValueNode $valueNode - * - * @return mixed */ private function parseIterableLiteral($valueNode) { diff --git a/src/Hydra/Serializer/ErrorNormalizer.php b/src/Hydra/Serializer/ErrorNormalizer.php index 2ab3495edc5..48e4b93f245 100644 --- a/src/Hydra/Serializer/ErrorNormalizer.php +++ b/src/Hydra/Serializer/ErrorNormalizer.php @@ -44,6 +44,10 @@ public function __construct(UrlGeneratorInterface $urlGenerator, bool $debug = f */ public function normalize($object, $format = null, array $context = []) { + if ($this->debug) { + $trace = $object->getTrace(); + } + $data = [ '@context' => $this->urlGenerator->generate('api_jsonld_context', ['shortName' => 'Error']), '@type' => 'hydra:Error', @@ -51,7 +55,7 @@ public function normalize($object, $format = null, array $context = []) 'hydra:description' => $this->getErrorMessage($object, $context, $this->debug), ]; - if ($this->debug && null !== $trace = $object->getTrace()) { + if (isset($trace)) { $data['trace'] = $trace; } diff --git a/src/Identifier/CompositeIdentifierParser.php b/src/Identifier/CompositeIdentifierParser.php deleted file mode 100644 index 3dab8b0095b..00000000000 --- a/src/Identifier/CompositeIdentifierParser.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\Identifier; - -/** - * Normalizes a composite identifer. - * - * @author Antoine Bluchet - */ -final class CompositeIdentifierParser -{ - private function __construct() - { - } - - /* - * Normalize takes a string and gives back an array of identifiers. - * - * For example: foo=0;bar=2 returns ['foo' => 0, 'bar' => 2]. - */ - public static function parse(string $identifier): array - { - $matches = []; - $identifiers = []; - $num = preg_match_all('/(\w+)=(?<=\w=)(.+?)(?=;\w+=)|(\w+)=([^;]+);?$/', $identifier, $matches, PREG_SET_ORDER); - - foreach ($matches as $i => $match) { - if ($i === $num - 1) { - $identifiers[$match[3]] = $match[4]; - continue; - } - $identifiers[$match[1]] = $match[2]; - } - - return $identifiers; - } -} diff --git a/src/Identifier/Normalizer/ChainIdentifierDenormalizer.php b/src/Identifier/Normalizer/ChainIdentifierDenormalizer.php deleted file mode 100644 index 3f9f85bd3e2..00000000000 --- a/src/Identifier/Normalizer/ChainIdentifierDenormalizer.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\Identifier\Normalizer; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Exception\InvalidIdentifierException; -use ApiPlatform\Core\Identifier\CompositeIdentifierParser; -use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use Symfony\Component\PropertyInfo\Type; - -/** - * Identifier normalizer. - * - * @author Antoine Bluchet - */ -class ChainIdentifierDenormalizer -{ - const HAS_IDENTIFIER_DENORMALIZER = 'has_identifier_denormalizer'; - - private $propertyMetadataFactory; - private $identifiersExtractor; - private $identifierDenormalizers; - - public function __construct(IdentifiersExtractorInterface $identifiersExtractor, PropertyMetadataFactoryInterface $propertyMetadataFactory, $identifierDenormalizers) - { - $this->propertyMetadataFactory = $propertyMetadataFactory; - $this->identifiersExtractor = $identifiersExtractor; - $this->identifierDenormalizers = $identifierDenormalizers; - } - - /** - * @throws InvalidIdentifierException - */ - public function denormalize($data, $class, $format = null, array $context = []) - { - $keys = $this->identifiersExtractor->getIdentifiersFromResourceClass($class); - - if (!$keys) { - throw new InvalidIdentifierException(sprintf('Resource "%s" has no identifiers.', $class)); - } - - if (\count($keys) > 1) { - $identifiers = CompositeIdentifierParser::parse($data); - } else { - $identifiers = [$keys[0] => $data]; - } - - // Normalize every identifier (DateTime, UUID etc.) - foreach ($keys as $key) { - if (!isset($identifiers[$key])) { - throw new InvalidIdentifierException(sprintf('Invalid identifier "%1$s", "%1$s" was not found.', $key)); - } - - $metadata = $this->getIdentifierMetadata($class, $key); - foreach ($this->identifierDenormalizers as $normalizer) { - if (!$normalizer->supportsDenormalization($identifiers[$key], $metadata)) { - continue; - } - - try { - $identifiers[$key] = $normalizer->denormalize($identifiers[$key], $metadata); - } catch (InvalidIdentifierException $e) { - throw new InvalidIdentifierException(sprintf('Identifier "%s" could not be denormalized.', $key), $e->getCode(), $e); - } - } - } - - return $identifiers; - } - - private function getIdentifierMetadata($class, $propertyName) - { - if (!$type = $this->propertyMetadataFactory->create($class, $propertyName)->getType()) { - return null; - } - - return Type::BUILTIN_TYPE_OBJECT === ($builtInType = $type->getBuiltinType()) ? $type->getClassName() : $builtInType; - } -} diff --git a/src/Identifier/Normalizer/DateTimeIdentifierDenormalizer.php b/src/Identifier/Normalizer/DateTimeIdentifierDenormalizer.php deleted file mode 100644 index 4bf8b913c66..00000000000 --- a/src/Identifier/Normalizer/DateTimeIdentifierDenormalizer.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\Identifier\Normalizer; - -use ApiPlatform\Core\Exception\InvalidIdentifierException; -use Symfony\Component\Serializer\Exception\InvalidArgumentException; -use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -final class DateTimeIdentifierDenormalizer extends DateTimeNormalizer implements DenormalizerInterface -{ - public function denormalize($data, $class, $format = null, array $context = []) - { - try { - return parent::denormalize($data, $class, $format, $context); - } catch (InvalidArgumentException $e) { - throw new InvalidIdentifierException($e->getMessage(), $e->getCode(), $e); - } - } -} diff --git a/src/Identifier/Normalizer/IntegerDenormalizer.php b/src/Identifier/Normalizer/IntegerDenormalizer.php deleted file mode 100644 index 7f1423f51cb..00000000000 --- a/src/Identifier/Normalizer/IntegerDenormalizer.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\Identifier\Normalizer; - -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - -final class IntegerDenormalizer implements DenormalizerInterface -{ - 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); - } -} diff --git a/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php b/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php index 95848ef0b7f..96ac9c92d13 100644 --- a/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php +++ b/src/JsonApi/Serializer/ConstraintViolationListNormalizer.php @@ -71,7 +71,7 @@ private function getSourcePointerFromViolation(ConstraintViolationInterface $vio $propertyMetadata = $this->propertyMetadataFactory ->create( // Im quite sure this requires some thought in case of validations over relationships - \get_class($violation->getRoot()), + get_class($violation->getRoot()), $fieldName ); diff --git a/src/JsonApi/Serializer/ErrorNormalizer.php b/src/JsonApi/Serializer/ErrorNormalizer.php index e0fcde50408..fe783de3929 100644 --- a/src/JsonApi/Serializer/ErrorNormalizer.php +++ b/src/JsonApi/Serializer/ErrorNormalizer.php @@ -37,12 +37,16 @@ public function __construct(bool $debug = false) public function normalize($object, $format = null, array $context = []) { + if ($this->debug) { + $trace = $object->getTrace(); + } + $data = [ 'title' => $context['title'] ?? 'An error occurred', 'description' => $this->getErrorMessage($object, $context, $this->debug), ]; - if ($this->debug && null !== $trace = $object->getTrace()) { + if (isset($trace)) { $data['trace'] = $trace; } diff --git a/src/JsonApi/Serializer/ItemNormalizer.php b/src/JsonApi/Serializer/ItemNormalizer.php index 2835e042513..3f39e432f8e 100644 --- a/src/JsonApi/Serializer/ItemNormalizer.php +++ b/src/JsonApi/Serializer/ItemNormalizer.php @@ -67,7 +67,7 @@ public function normalize($object, $format = null, array $context = []) // Get and populate attributes data $objectAttributesData = parent::normalize($object, $format, $context); - if (!\is_array($objectAttributesData)) { + if (!is_array($objectAttributesData)) { return $objectAttributesData; } @@ -109,7 +109,7 @@ public function supportsDenormalization($data, $type, $format = null) public function denormalize($data, $class, $format = null, array $context = []) { // Avoid issues with proxies if we populated the object - if (!isset($context[self::OBJECT_TO_POPULATE]) && isset($data['data']['id'])) { + if (isset($data['data']['id']) && !isset($context[self::OBJECT_TO_POPULATE])) { if (isset($context['api_allow_update']) && true !== $context['api_allow_update']) { throw new InvalidArgumentException('Update is not allowed for this operation.'); } @@ -147,7 +147,7 @@ protected function getAttributes($object, $format = null, array $context) */ protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = []) { - parent::setAttributeValue($object, $attribute, \is_array($value) && array_key_exists('data', $value) ? $value['data'] : $value, $format, $context); + parent::setAttributeValue($object, $attribute, is_array($value) && array_key_exists('data', $value) ? $value['data'] : $value, $format, $context); } /** @@ -164,7 +164,7 @@ protected function denormalizeRelation(string $attributeName, PropertyMetadata $ return $this->serializer->denormalize($value, $className, $format, $context); } - if (!\is_array($value) || !isset($value['id'], $value['type'])) { + if (!is_array($value) || !isset($value['id'], $value['type'])) { throw new InvalidArgumentException('Only resource linkage supported currently, see: http://jsonapi.org/format/#document-resource-object-linkage.'); } @@ -183,7 +183,7 @@ protected function denormalizeRelation(string $attributeName, PropertyMetadata $ protected function normalizeRelation(PropertyMetadata $propertyMetadata, $relatedObject, string $resourceClass, string $format = null, array $context) { if (null === $relatedObject) { - if (isset($context['operation_type'], $context['subresource_resources'][$resourceClass]) && OperationType::SUBRESOURCE === $context['operation_type']) { + if (isset($context['operation_type']) && OperationType::SUBRESOURCE === $context['operation_type'] && isset($context['subresource_resources'][$resourceClass])) { $iri = $this->iriConverter->getItemIriFromResourceClass($resourceClass, $context['subresource_resources'][$resourceClass]); } else { unset($context['resource_class']); diff --git a/src/JsonApi/Serializer/ReservedAttributeNameConverter.php b/src/JsonApi/Serializer/ReservedAttributeNameConverter.php index add93d79bf9..a6b6649bd47 100644 --- a/src/JsonApi/Serializer/ReservedAttributeNameConverter.php +++ b/src/JsonApi/Serializer/ReservedAttributeNameConverter.php @@ -57,7 +57,7 @@ public function normalize($propertyName) */ public function denormalize($propertyName) { - if (\in_array($propertyName, self::JSON_API_RESERVED_ATTRIBUTES, true)) { + if (in_array($propertyName, self::JSON_API_RESERVED_ATTRIBUTES, true)) { $propertyName = substr($propertyName, 1); } diff --git a/src/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactory.php index eae2bcd3293..3411c5d54e9 100644 --- a/src/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactory.php +++ b/src/Metadata/Property/Factory/AnnotationPropertyNameCollectionFactory.php @@ -42,8 +42,6 @@ public function __construct(Reader $reader, PropertyNameCollectionFactoryInterfa */ public function create(string $resourceClass, array $options = []): PropertyNameCollection { - $propertyNameCollection = null; - if ($this->decorated) { try { $propertyNameCollection = $this->decorated->create($resourceClass, $options); @@ -55,7 +53,7 @@ public function create(string $resourceClass, array $options = []): PropertyName try { $reflectionClass = new \ReflectionClass($resourceClass); } catch (\ReflectionException $reflectionException) { - if (null !== $propertyNameCollection) { + if (isset($propertyNameCollection)) { return $propertyNameCollection; } @@ -88,7 +86,7 @@ public function create(string $resourceClass, array $options = []): PropertyName } // Inherited from parent - if (null !== $propertyNameCollection) { + if (isset($propertyNameCollection)) { foreach ($propertyNameCollection as $propertyName) { $propertyNames[$propertyName] = $propertyName; } diff --git a/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php b/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php index 57421e8e88c..0be27543e41 100644 --- a/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php +++ b/src/Metadata/Property/Factory/ExtractorPropertyNameCollectionFactory.php @@ -43,22 +43,19 @@ public function __construct(ExtractorInterface $extractor, PropertyNameCollectio public function create(string $resourceClass, array $options = []): PropertyNameCollection { $propertyNames = []; - $propertyNameCollection = null; if ($this->decorated) { try { - $propertyNameCollection = $this->decorated->create($resourceClass, $options); + foreach ($propertyNameCollection = $this->decorated->create($resourceClass, $options) as $propertyName) { + $propertyNames[$propertyName] = $propertyName; + } } catch (ResourceClassNotFoundException $resourceClassNotFoundException) { // Ignore not found exceptions from parent } - - foreach ($propertyNameCollection as $propertyName) { - $propertyNames[$propertyName] = $propertyName; - } } if (!class_exists($resourceClass)) { - if (null !== $propertyNameCollection) { + if (isset($propertyNameCollection)) { return $propertyNameCollection; } diff --git a/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php b/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php index 50145a37c5b..4813f52c61c 100644 --- a/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php +++ b/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php @@ -107,7 +107,7 @@ private function normalize(bool $collection, ResourceMetadata $resourceMetadata, $newOperations = []; foreach ($operations as $operationName => $operation) { // e.g.: @ApiResource(itemOperations={"get"}) - if (\is_int($operationName) && \is_string($operation)) { + if (is_int($operationName) && is_string($operation)) { $operationName = $operation; $operation = []; } @@ -119,8 +119,8 @@ private function normalize(bool $collection, ResourceMetadata $resourceMetadata, $supported = isset(self::SUPPORTED_ITEM_OPERATION_METHODS[$upperOperationName]) || (isset($this->formats['jsonapi']) && 'PATCH' === $upperOperationName); } - if (!isset($operation['method']) && !isset($operation['route_name'])) { - $supported ? $operation['method'] = $upperOperationName : $operation['route_name'] = $operationName; + if ($supported && !isset($operation['method']) && !isset($operation['route_name'])) { + $operation['method'] = $upperOperationName; } $newOperations[$operationName] = $operation; @@ -132,7 +132,7 @@ private function normalize(bool $collection, ResourceMetadata $resourceMetadata, private function normalizeGraphQl(ResourceMetadata $resourceMetadata, array $operations) { foreach ($operations as $operationName => $operation) { - if (\is_int($operationName) && \is_string($operation)) { + if (is_int($operationName) && is_string($operation)) { unset($operations[$operationName]); $operations[$operation] = []; } diff --git a/src/Problem/Serializer/ErrorNormalizer.php b/src/Problem/Serializer/ErrorNormalizer.php index b2aab6591cd..9d1557498ae 100644 --- a/src/Problem/Serializer/ErrorNormalizer.php +++ b/src/Problem/Serializer/ErrorNormalizer.php @@ -41,13 +41,17 @@ public function __construct(bool $debug = false) */ public function normalize($object, $format = null, array $context = []) { + if ($this->debug) { + $trace = $object->getTrace(); + } + $data = [ 'type' => $context['type'] ?? 'https://tools.ietf.org/html/rfc2616#section-10', 'title' => $context['title'] ?? 'An error occurred', 'detail' => $this->getErrorMessage($object, $context, $this->debug), ]; - if ($this->debug && null !== $trace = $object->getTrace()) { + if (isset($trace)) { $data['trace'] = $trace; } diff --git a/src/Problem/Serializer/ErrorNormalizerTrait.php b/src/Problem/Serializer/ErrorNormalizerTrait.php index 20ecd786572..c7178628e8a 100644 --- a/src/Problem/Serializer/ErrorNormalizerTrait.php +++ b/src/Problem/Serializer/ErrorNormalizerTrait.php @@ -24,9 +24,7 @@ private function getErrorMessage($object, array $context, bool $debug = false): if ($debug) { return $message; - } - - if ($object instanceof FlattenException) { + } elseif ($object instanceof FlattenException) { $statusCode = $context['statusCode'] ?? $object->getStatusCode(); if ($statusCode >= 500 && $statusCode < 600) { $message = Response::$statusTexts[$statusCode]; diff --git a/src/Serializer/AbstractCollectionNormalizer.php b/src/Serializer/AbstractCollectionNormalizer.php index 8f5cd27e64d..870e593eef9 100644 --- a/src/Serializer/AbstractCollectionNormalizer.php +++ b/src/Serializer/AbstractCollectionNormalizer.php @@ -49,7 +49,7 @@ public function __construct(ResourceClassResolverInterface $resourceClassResolve */ public function supportsNormalization($data, $format = null) { - return static::FORMAT === $format && (\is_array($data) || $data instanceof \Traversable); + return static::FORMAT === $format && (is_array($data) || $data instanceof \Traversable); } /** @@ -97,13 +97,13 @@ protected function getPaginationConfig($object, array $context = []): array $paginated = 1. !== $lastPage = $object->getLastPage(); $totalItems = $object->getTotalItems(); } else { - $pageTotalItems = (float) \count($object); + $pageTotalItems = (float) count($object); } $currentPage = $object->getCurrentPage(); $itemsPerPage = $object->getItemsPerPage(); - } elseif (\is_array($object) || $object instanceof \Countable) { - $totalItems = \count($object); + } elseif (is_array($object) || $object instanceof \Countable) { + $totalItems = count($object); } return [$paginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems]; diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 80b31b0a3fd..dd8a6b11517 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -464,7 +464,7 @@ protected function normalizeRelation(PropertyMetadata $propertyMetadata, $relate { // On a subresource, we know the value of the identifiers. // If attributeValue is null, meaning that it hasn't been returned by the DataProvider, get the item Iri - if (null === $relatedObject && isset($context['operation_type'], $context['subresource_resources'][$resourceClass]) && OperationType::SUBRESOURCE === $context['operation_type']) { + if (null === $relatedObject && isset($context['operation_type']) && OperationType::SUBRESOURCE === $context['operation_type'] && isset($context['subresource_resources'][$resourceClass])) { return $this->iriConverter->getItemIriFromResourceClass($resourceClass, $context['subresource_resources'][$resourceClass]); } diff --git a/src/Serializer/SerializerContextBuilder.php b/src/Serializer/SerializerContextBuilder.php index f760f08635b..74962da44ca 100644 --- a/src/Serializer/SerializerContextBuilder.php +++ b/src/Serializer/SerializerContextBuilder.php @@ -69,7 +69,7 @@ public function createFromRequest(Request $request, bool $normalization, array $ $context['operation_type'] = $operationType ?: OperationType::ITEM; if (!$normalization && !isset($context['api_allow_update'])) { - $context['api_allow_update'] = \in_array($request->getMethod(), ['PUT', 'PATCH'], true); + $context['api_allow_update'] = in_array($request->getMethod(), ['PUT', 'PATCH'], true); } $context['resource_class'] = $attributes['resource_class']; diff --git a/src/Util/AnnotationFilterExtractorTrait.php b/src/Util/AnnotationFilterExtractorTrait.php index 5109a109112..2c0c4e60aa4 100644 --- a/src/Util/AnnotationFilterExtractorTrait.php +++ b/src/Util/AnnotationFilterExtractorTrait.php @@ -36,7 +36,7 @@ trait AnnotationFilterExtractorTrait private function getFilterAnnotations(array $miscAnnotations): \Iterator { foreach ($miscAnnotations as $miscAnnotation) { - if (ApiFilter::class === \get_class($miscAnnotation)) { + if (ApiFilter::class === get_class($miscAnnotation)) { yield $miscAnnotation; } } @@ -51,7 +51,7 @@ private function getFilterProperties(ApiFilter $filterAnnotation, \ReflectionCla if ($filterAnnotation->properties) { foreach ($filterAnnotation->properties as $property => $strategy) { - if (\is_int($property)) { + if (is_int($property)) { $properties[$strategy] = null; } else { $properties[$property] = $strategy; diff --git a/src/Util/AttributesExtractor.php b/src/Util/AttributesExtractor.php deleted file mode 100644 index 10f8bac19b4..00000000000 --- a/src/Util/AttributesExtractor.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the 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; - -use ApiPlatform\Core\Api\OperationType; - -/** - * Extracts data used by the library form given attributes. - * - * @author Antoine Bluchet - * - * @internal - */ -final class AttributesExtractor -{ - private function __construct() - { - } - - /** - * Extracts resource class, operation name and format request attributes. Returns an empty array if the request does - * not contain required attributes. - */ - public static function extractAttributes(array $attributes): array - { - $result = ['resource_class' => $attributes['_api_resource_class'] ?? null]; - - if ($subresourceContext = $attributes['_api_subresource_context'] ?? null) { - $result['subresource_context'] = $subresourceContext; - } - - if (null === $result['resource_class']) { - return []; - } - - $hasRequestAttributeKey = 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 (false === $hasRequestAttributeKey) { - return []; - } - - if (null === $apiRequest = $attributes['_api_receive'] ?? null) { - $result['receive'] = true; - } else { - $result['receive'] = (bool) $apiRequest; - } - - return $result; - } -} diff --git a/src/Util/ErrorFormatGuesser.php b/src/Util/ErrorFormatGuesser.php index 159c7f1b091..be17329f014 100644 --- a/src/Util/ErrorFormatGuesser.php +++ b/src/Util/ErrorFormatGuesser.php @@ -37,24 +37,14 @@ private function __construct() public static function guessErrorFormat(Request $request, array $errorFormats): array { $requestFormat = $request->getRequestFormat(''); - if ('' !== $requestFormat && isset($errorFormats[$requestFormat])) { return ['key' => $requestFormat, 'value' => $errorFormats[$requestFormat]]; } - $requestMimeTypes = Request::getMimeTypes($request->getRequestFormat()); - $defaultFormat = []; - - foreach ($errorFormats as $format => $errorMimeTypes) { - if (array_intersect($requestMimeTypes, $errorMimeTypes)) { - return ['key' => $format, 'value' => $errorMimeTypes]; - } - - if (!$defaultFormat) { - $defaultFormat = ['key' => $format, 'value' => $errorMimeTypes]; - } + foreach ($errorFormats as $key => $value) { + return ['key' => $key, 'value' => $value]; } - return $defaultFormat; + return []; } } diff --git a/src/Util/RequestAttributesExtractor.php b/src/Util/RequestAttributesExtractor.php index 13c2866b22a..77d60e91119 100644 --- a/src/Util/RequestAttributesExtractor.php +++ b/src/Util/RequestAttributesExtractor.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Util; +use ApiPlatform\Core\Api\OperationType; use Symfony\Component\HttpFoundation\Request; /** @@ -38,6 +39,36 @@ private function __construct() */ public static function extractAttributes(Request $request) { - return AttributesExtractor::extractAttributes($request->attributes->all()); + $result = ['resource_class' => $request->attributes->get('_api_resource_class')]; + + if ($subresourceContext = $request->attributes->get('_api_subresource_context')) { + $result['subresource_context'] = $subresourceContext; + } + + if (null === $result['resource_class']) { + return []; + } + + $hasRequestAttributeKey = false; + foreach (OperationType::TYPES as $operationType) { + $attribute = "_api_{$operationType}_operation_name"; + if ($request->attributes->has($attribute)) { + $result["{$operationType}_operation_name"] = $request->attributes->get($attribute); + $hasRequestAttributeKey = true; + break; + } + } + + if (false === $hasRequestAttributeKey) { + return []; + } + + if (null === $apiRequest = $request->attributes->get('_api_receive')) { + $result['receive'] = true; + } else { + $result['receive'] = (bool) $apiRequest; + } + + return $result; } } diff --git a/tests/Fixtures/AnnotatedClass.php b/tests/Annotation/AnnotatedClass.php similarity index 75% rename from tests/Fixtures/AnnotatedClass.php rename to tests/Annotation/AnnotatedClass.php index f6c573efcae..d5be5811177 100644 --- a/tests/Fixtures/AnnotatedClass.php +++ b/tests/Annotation/AnnotatedClass.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Fixtures; +namespace ApiPlatform\Core\Tests\Annotation; use ApiPlatform\Core\Annotation\ApiResource; @@ -23,10 +23,7 @@ * itemOperations={"foo"={"bar"}}, * collectionOperations={"bar"={"foo"}}, * graphql={"query"={"normalization_context"={"groups"={"foo", "bar"}}}}, - * attributes={"foo"="bar", "route_prefix"="/whatever"}, - * routePrefix="/foo", - * accessControl="has_role('ROLE_FOO')", - * accessControlMessage="You are not foo." + * attributes={"foo"="bar"} * ) * * @author Marcus Speight diff --git a/tests/Annotation/ApiResourceTest.php b/tests/Annotation/ApiResourceTest.php index 7c8b1b33ea0..f30c53cba01 100644 --- a/tests/Annotation/ApiResourceTest.php +++ b/tests/Annotation/ApiResourceTest.php @@ -14,7 +14,6 @@ namespace ApiPlatform\Core\Tests\Annotation; use ApiPlatform\Core\Annotation\ApiResource; -use ApiPlatform\Core\Tests\Fixtures\AnnotatedClass; use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; @@ -23,65 +22,23 @@ */ class ApiResourceTest extends TestCase { - public function testConstruct() + public function testAssignation() { - $resource = new ApiResource([ - 'accessControl' => 'has_role("ROLE_FOO")', - 'accessControlMessage' => 'You are not foo.', - 'attributes' => ['foo' => 'bar', 'validation_groups' => ['baz', 'qux']], - 'collectionOperations' => ['bar' => ['foo']], - 'denormalizationContext' => ['groups' => ['foo']], - 'description' => 'description', - 'fetchPartial' => true, - 'forceEager' => false, - 'filters' => ['foo', 'bar'], - 'graphql' => ['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]], - 'iri' => 'http://example.com/res', - 'itemOperations' => ['foo' => ['bar']], - 'maximumItemsPerPage' => 42, - 'normalizationContext' => ['groups' => ['bar']], - 'order' => ['foo', 'bar' => 'ASC'], - 'paginationClientEnabled' => true, - 'paginationClientItemsPerPage' => true, - 'paginationClientPartial' => true, - 'paginationEnabled' => true, - 'paginationFetchJoinCollection' => true, - 'paginationItemsPerPage' => 42, - 'paginationPartial' => true, - 'routePrefix' => '/foo', - 'shortName' => 'shortName', - 'subresourceOperations' => [], - 'validationGroups' => ['foo', 'bar'], - ]); + $resource = new ApiResource(); + $resource->shortName = 'shortName'; + $resource->description = 'description'; + $resource->iri = 'http://example.com/res'; + $resource->itemOperations = ['foo' => ['bar']]; + $resource->collectionOperations = ['bar' => ['foo']]; + $resource->graphql = ['query' => ['normalization_context' => ['groups' => ['foo', 'bar']]]]; + $resource->attributes = ['foo' => 'bar']; $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([ - 'access_control' => 'has_role("ROLE_FOO")', - 'access_control_message' => 'You are not foo.', - 'denormalization_context' => ['groups' => ['foo']], - 'fetch_partial' => true, - 'foo' => 'bar', - 'force_eager' => false, - 'filters' => ['foo', 'bar'], - 'maximum_items_per_page' => 42, - 'normalization_context' => ['groups' => ['bar']], - 'order' => ['foo', 'bar' => 'ASC'], - '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_partial' => true, - 'route_prefix' => '/foo', - 'validation_groups' => ['baz', 'qux'], - ], $resource->attributes); + $this->assertSame(['foo' => 'bar'], $resource->attributes); } public function testApiResourceAnnotation() @@ -94,24 +51,6 @@ public function testApiResourceAnnotation() $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', - 'access_control' => "has_role('ROLE_FOO')", - 'access_control_message' => 'You are not foo.', - ], $resource->attributes); - } - - /** - * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException - * @expectedExceptionMessage Unknown property "invalidAttribute" on annotation "ApiPlatform\Core\Annotation\ApiResource". - */ - public function testConstructWithInvalidAttribute() - { - new ApiResource([ - 'shortName' => 'shortName', - 'routePrefix' => '/foo', - 'invalidAttribute' => 'exception', - ]); + $this->assertSame(['foo' => 'bar'], $resource->attributes); } } diff --git a/tests/Bridge/Doctrine/Orm/Extension/PaginationExtensionTest.php b/tests/Bridge/Doctrine/Orm/Extension/PaginationExtensionTest.php index f1d02227af8..7b1cc77e41c 100644 --- a/tests/Bridge/Doctrine/Orm/Extension/PaginationExtensionTest.php +++ b/tests/Bridge/Doctrine/Orm/Extension/PaginationExtensionTest.php @@ -424,14 +424,6 @@ public function testGetResult() $this->assertInstanceOf(PaginatorInterface::class, $result); } - public function testGetResultWithoutFetchJoinCollection() - { - $result = $this->getPaginationExtensionResult(false, false, false); - - $this->assertInstanceOf(PartialPaginatorInterface::class, $result); - $this->assertInstanceOf(PaginatorInterface::class, $result); - } - public function testGetResultWithPartial() { $result = $this->getPaginationExtensionResult(true); @@ -448,7 +440,7 @@ public function testSimpleGetResult() $this->assertInstanceOf(PaginatorInterface::class, $result); } - private function getPaginationExtensionResult(bool $partial = false, bool $legacy = false, bool $fetchJoinCollection = true) + private function getPaginationExtensionResult(bool $partial = false, bool $legacy = false) { $requestStack = new RequestStack(); $requestStack->push(new Request(['partial' => $partial])); @@ -456,7 +448,7 @@ private function getPaginationExtensionResult(bool $partial = false, bool $legac $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); if (!$legacy) { - $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata(null, null, null, [], [], ['pagination_partial' => false, 'pagination_client_partial' => true, 'pagination_fetch_join_collection' => $fetchJoinCollection]))->shouldBeCalled(); + $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadata(null, null, null, [], [], ['pagination_partial' => false, 'pagination_client_partial' => true]))->shouldBeCalled(); } $configuration = new Configuration(); diff --git a/tests/Bridge/Doctrine/Orm/Filter/DateFilterTest.php b/tests/Bridge/Doctrine/Orm/Filter/DateFilterTest.php index 3f6da661672..b0269a90d50 100644 --- a/tests/Bridge/Doctrine/Orm/Filter/DateFilterTest.php +++ b/tests/Bridge/Doctrine/Orm/Filter/DateFilterTest.php @@ -33,10 +33,6 @@ class DateFilterTest extends DoctrineOrmFilterTestCase public function testApplyDate() { $this->doTestApplyDate(false); - } - - public function testApplyDateImmutable() - { $this->doTestApplyDateImmutable(false); } @@ -46,13 +42,6 @@ public function testApplyDateImmutable() public function testRequestApplyDate() { $this->doTestApplyDate(true); - } - - /** - * @group legacy - */ - public function testRequestApplyDateImmutable() - { $this->doTestApplyDateImmutable(true); } @@ -82,7 +71,7 @@ private function doTestApplyDateImmutable(bool $request) $requestStack = null; if ($request) { $requestStack = new RequestStack(); - $requestStack->push(Request::create('/api/dummy_immutable_dates', 'GET', $filters)); + $requestStack->push(Request::create('/api/dummy_immutable_date', 'GET', $filters)); } $queryBuilder = $this->repository->createQueryBuilder('o'); diff --git a/tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php b/tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php index a3aa43de48e..11154633cb1 100644 --- a/tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php +++ b/tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php @@ -17,7 +17,6 @@ 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\Normalizer\ChainIdentifierDenormalizer; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Property\PropertyMetadata; @@ -45,7 +44,7 @@ class ItemDataProviderTest extends TestCase { public function testGetItemSingleIdentifier() { - $context = ['foo' => 'bar', 'fetch_data' => true, ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true]; + $context = ['foo' => 'bar', 'fetch_data' => true]; $queryProphecy = $this->prophesize(AbstractQuery::class); $queryProphecy->getOneOrNullResult()->willReturn([])->shouldBeCalled(); @@ -78,7 +77,7 @@ public function testGetItemSingleIdentifier() $dataProvider = new ItemDataProvider($managerRegistry, $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->getItem(Dummy::class, ['id' => 1], 'foo', $context)); + $this->assertEquals([], $dataProvider->getItem(Dummy::class, 1, 'foo', $context)); } public function testGetItemDoubleIdentifier() @@ -117,18 +116,16 @@ public function testGetItemDoubleIdentifier() ], ], $queryBuilder); - $context = [ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true]; $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', $context)->shouldBeCalled(); + $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', [])->shouldBeCalled(); $dataProvider = new ItemDataProvider($managerRegistry, $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->getItem(Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', $context)); + $this->assertEquals([], $dataProvider->getItem(Dummy::class, 'ida=1;idb=2', 'foo')); } /** * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException - * @group legacy */ public function testGetItemWrongCompositeIdentifier() { @@ -174,15 +171,14 @@ public function testQueryResultExtension() ], ], $queryBuilder); - $context = [ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => 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(); + $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo', [])->shouldBeCalled(); + $extensionProphecy->supportsResult(Dummy::class, 'foo', [])->willReturn(true)->shouldBeCalled(); + $extensionProphecy->getResult($queryBuilder, Dummy::class, 'foo', [])->willReturn([])->shouldBeCalled(); $dataProvider = new ItemDataProvider($managerRegistry, $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->getItem(Dummy::class, ['id' => 1], 'foo', $context)); + $this->assertEquals([], $dataProvider->getItem(Dummy::class, 1, 'foo')); } public function testUnsupportedClass() @@ -232,7 +228,7 @@ public function testCannotCreateQueryBuilder() 'id', ]); - (new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]))->getItem(Dummy::class, 'foo', null, [ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true]); + (new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]))->getItem(Dummy::class, 'foo'); } /** diff --git a/tests/Bridge/Doctrine/Orm/SubresourceDataProviderTest.php b/tests/Bridge/Doctrine/Orm/SubresourceDataProviderTest.php index e2bf64f34e4..161ee9422e2 100644 --- a/tests/Bridge/Doctrine/Orm/SubresourceDataProviderTest.php +++ b/tests/Bridge/Doctrine/Orm/SubresourceDataProviderTest.php @@ -16,7 +16,6 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; use ApiPlatform\Core\Bridge\Doctrine\Orm\SubresourceDataProvider; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Property\PropertyMetadata; @@ -84,6 +83,10 @@ private function getManagerRegistryProphecy(QueryBuilder $queryBuilder, array $i $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->getIdentifier()->willReturn($identifiers); + foreach ($identifiers as $id) { + $classMetadataProphecy->getTypeOfField($id)->willReturn('interger'); + } + $repositoryProphecy = $this->prophesize(EntityRepository::class); $repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder); @@ -138,6 +141,8 @@ public function testGetSubresource() $this->assertIdentifierManagerMethodCalls($managerProphecy); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); + $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn($identifiers); + $classMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn('integer'); $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); @@ -165,9 +170,9 @@ public function testGetSubresource() $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - $context = ['property' => 'relatedDummies', 'identifiers' => [['id', Dummy::class]], 'collection' => true, ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true]; + $context = ['property' => 'relatedDummies', 'identifiers' => [['id', Dummy::class]], 'collection' => true]; - $this->assertEquals([], $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1]], $context)); + $this->assertEquals([], $dataProvider->getSubresource(RelatedDummy::class, ['id' => 1], $context)); } public function testGetSubSubresourceItem() @@ -196,6 +201,8 @@ public function testGetSubSubresourceItem() $qb->getDQL()->shouldBeCalled()->willReturn($dummyDQL); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); + $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn($identifiers); + $classMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn('integer'); $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); @@ -222,6 +229,8 @@ public function testGetSubSubresourceItem() $rqb->expr()->shouldBeCalled()->willReturn($relatedExpProphecy->reveal()); $rClassMetadataProphecy = $this->prophesize(ClassMetadata::class); + $rClassMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn($identifiers); + $rClassMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn('integer'); $rClassMetadataProphecy->hasAssociation('thirdLevel')->shouldBeCalled()->willReturn(true); $rClassMetadataProphecy->getAssociationMapping('thirdLevel')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_ONE]); @@ -257,9 +266,9 @@ public function testGetSubSubresourceItem() $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - $context = ['property' => 'thirdLevel', 'identifiers' => [['id', Dummy::class], ['relatedDummies', RelatedDummy::class]], 'collection' => false, ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true]; + $context = ['property' => 'thirdLevel', 'identifiers' => [['id', Dummy::class], ['relatedDummies', RelatedDummy::class]], 'collection' => false]; - $this->assertEquals($result, $dataProvider->getSubresource(ThirdLevel::class, ['id' => ['id' => 1], 'relatedDummies' => ['id' => 1]], $context)); + $this->assertEquals($result, $dataProvider->getSubresource(ThirdLevel::class, ['id' => 1, 'relatedDummies' => 1], $context)); } public function testQueryResultExtension() @@ -282,6 +291,8 @@ public function testQueryResultExtension() $this->assertIdentifierManagerMethodCalls($managerProphecy); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); + $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn($identifiers); + $classMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn('integer'); $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); @@ -316,9 +327,9 @@ public function testQueryResultExtension() $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]); - $context = ['property' => 'relatedDummies', 'identifiers' => [['id', Dummy::class]], 'collection' => true, ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true]; + $context = ['property' => 'relatedDummies', 'identifiers' => [['id', Dummy::class]], 'collection' => true]; - $this->assertEquals([], $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1]], $context)); + $this->assertEquals([], $dataProvider->getSubresource(RelatedDummy::class, ['id' => 1], $context)); } /** @@ -357,105 +368,6 @@ public function testThrowResourceClassNotSupportedException() $dataProvider->getSubresource(Dummy::class, ['id' => 1], []); } - /** - * @group legacy - */ - public function testGetSubSubresourceItemLegacy() - { - $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->getTypeOfField('id')->shouldBeCalled()->willReturn('integer'); - $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); - $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); - - $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('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)->shouldBeCalled()->willReturn($queryBuilder); - $queryBuilder->setParameter('id_p2', 1)->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()); - - list($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); @@ -482,6 +394,8 @@ public function testGetSubresourceCollectionItem() $qb->getDQL()->shouldBeCalled()->willReturn($dummyDQL); $classMetadataProphecy = $this->prophesize(ClassMetadata::class); + $classMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn($identifiers); + $classMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn('integer'); $classMetadataProphecy->hasAssociation('relatedDummies')->willReturn(true)->shouldBeCalled(); $classMetadataProphecy->getAssociationMapping('relatedDummies')->shouldBeCalled()->willReturn(['type' => ClassMetadata::MANY_TO_MANY]); @@ -508,6 +422,8 @@ public function testGetSubresourceCollectionItem() $rqb->expr()->shouldBeCalled()->willReturn($relatedExpProphecy->reveal()); $rClassMetadataProphecy = $this->prophesize(ClassMetadata::class); + $rClassMetadataProphecy->getIdentifier()->shouldBeCalled()->willReturn($identifiers); + $rClassMetadataProphecy->getTypeOfField('id')->shouldBeCalled()->willReturn('integer'); $rClassMetadataProphecy->hasAssociation('id')->shouldBeCalled()->willReturn(false); $rClassMetadataProphecy->isIdentifier('id')->shouldBeCalled()->willReturn(true); @@ -539,8 +455,8 @@ public function testGetSubresourceCollectionItem() $dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory); - $context = ['property' => 'id', 'identifiers' => [['id', Dummy::class, true], ['relatedDummies', RelatedDummy::class, true]], 'collection' => false, ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true]; + $context = ['property' => 'id', 'identifiers' => [['id', Dummy::class, true], ['relatedDummies', RelatedDummy::class, true]], 'collection' => false]; - $this->assertEquals($result, $dataProvider->getSubresource(RelatedDummy::class, ['id' => ['id' => 1], 'relatedDummies' => ['id' => 2]], $context)); + $this->assertEquals($result, $dataProvider->getSubresource(RelatedDummy::class, ['id' => 1, 'relatedDummies' => 2], $context)); } } diff --git a/tests/Bridge/Doctrine/Orm/Util/IdentifierManagerTraitTest.php b/tests/Bridge/Doctrine/Util/IdentifierManagerTraitTest.php similarity index 97% rename from tests/Bridge/Doctrine/Orm/Util/IdentifierManagerTraitTest.php rename to tests/Bridge/Doctrine/Util/IdentifierManagerTraitTest.php index 784c217879f..d7fa39276bc 100644 --- a/tests/Bridge/Doctrine/Orm/Util/IdentifierManagerTraitTest.php +++ b/tests/Bridge/Doctrine/Util/IdentifierManagerTraitTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Util; +namespace ApiPlatform\Core\Tests\Bridge\Doctrine\Util; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\IdentifierManagerTrait; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; @@ -44,9 +44,6 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName }; } - /** - * @group legacy - */ public function testSingleIdentifier() { list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [ @@ -63,9 +60,6 @@ public function testSingleIdentifier() $this->assertEquals($identifierManager->normalizeIdentifiers(1, $objectManager, Dummy::class), ['id' => 1]); } - /** - * @group legacy - */ public function testCompositeIdentifier() { list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [ @@ -88,8 +82,7 @@ public function testCompositeIdentifier() /** * @expectedException \ApiPlatform\Core\Exception\PropertyNotFoundException - * @expectedExceptionMessage Invalid identifier "idbad=1;idb=2", "ida" was not found. - * @group legacy + * @expectedExceptionMessage Invalid identifier "idbad=1;idb=2", "ida" has not been found. */ public function testInvalidIdentifier() { diff --git a/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php b/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.php deleted file mode 100644 index def11451314..00000000000 --- a/tests/Bridge/RamseyUuid/Normalizer/UuidNormalizerTest.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\Tests\Bridge\RamseyUuid\Normalizer; - -use ApiPlatform\Core\Bridge\RamseyUuid\Identifier\Normalizer\UuidNormalizer; -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, '')); - } - - /** - * @expectedException \ApiPlatform\Core\Exception\InvalidIdentifierException - */ - public function testFailDenormalizeUuid() - { - $uuid = 'notanuuid'; - $normalizer = new UuidNormalizer(); - $this->assertTrue($normalizer->supportsDenormalization($uuid, Uuid::class)); - $normalizer->denormalize($uuid, Uuid::class); - } -} diff --git a/tests/Bridge/Symfony/Bundle/ApiPlatformBundleTest.php b/tests/Bridge/Symfony/Bundle/ApiPlatformBundleTest.php index 0de5741ccd0..e3af35009cc 100644 --- a/tests/Bridge/Symfony/Bundle/ApiPlatformBundleTest.php +++ b/tests/Bridge/Symfony/Bundle/ApiPlatformBundleTest.php @@ -15,7 +15,9 @@ use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle; use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\AnnotationFilterPass; +use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataPersisterPass; use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass; +use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DoctrineQueryExtensionPass; use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\FilterPass; use PHPUnit\Framework\TestCase; use Prophecy\Argument; @@ -32,6 +34,8 @@ public function testBuild() $containerProphecy->addCompilerPass(Argument::type(DataProviderPass::class))->shouldBeCalled(); $containerProphecy->addCompilerPass(Argument::type(AnnotationFilterPass::class))->shouldBeCalled(); $containerProphecy->addCompilerPass(Argument::type(FilterPass::class))->shouldBeCalled(); + $containerProphecy->addCompilerPass(Argument::type(DoctrineQueryExtensionPass::class))->shouldBeCalled(); + $containerProphecy->addCompilerPass(Argument::type(DataPersisterPass::class))->shouldBeCalled(); $bundle = new ApiPlatformBundle(); $bundle->build($containerProphecy->reveal()); diff --git a/tests/Bridge/Symfony/Bundle/Command/SwaggerCommandTest.php b/tests/Bridge/Symfony/Bundle/Command/SwaggerCommandTest.php index 45bda81625b..f0383f24ac4 100644 --- a/tests/Bridge/Symfony/Bundle/Command/SwaggerCommandTest.php +++ b/tests/Bridge/Symfony/Bundle/Command/SwaggerCommandTest.php @@ -16,20 +16,13 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Console\Tester\ApplicationTester; -use Symfony\Component\Yaml\Exception\ParseException; -use Symfony\Component\Yaml\Yaml; /** * @author Amrouche Hamza */ class SwaggerCommandTest extends KernelTestCase { - /** - * @var ApplicationTester - */ - private $tester; - - protected function setUp() + public function testExecute() { self::bootKernel(); @@ -37,33 +30,9 @@ protected function setUp() $application->setCatchExceptions(false); $application->setAutoExit(false); - $this->tester = new ApplicationTester($application); - } + $tester = new ApplicationTester($application); + $tester->run(['command' => 'api:swagger:export']); - public function testExecute() - { - $this->tester->run(['command' => 'api:swagger:export']); - - $this->assertJson($this->tester->getDisplay()); - } - - public function testExecuteWithYaml() - { - $this->tester->run(['command' => 'api:swagger:export', '--yaml' => true]); - - $this->assertYaml($this->tester->getDisplay()); - } - - /** - * @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); + $this->assertJson($tester->getDisplay()); } } diff --git a/tests/Bridge/Symfony/Bundle/DataCollector/RequestDataCollectorTest.php b/tests/Bridge/Symfony/Bundle/DataCollector/RequestDataCollectorTest.php deleted file mode 100644 index c33cc7b55d7..00000000000 --- a/tests/Bridge/Symfony/Bundle/DataCollector/RequestDataCollectorTest.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\Tests\Bridge\Symfony\Bundle\DataCollector; - -use ApiPlatform\Core\Bridge\Symfony\Bundle\DataCollector\RequestDataCollector; -use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; -use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; -use ApiPlatform\Core\Tests\Fixtures\DummyEntity; -use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpFoundation\ParameterBag; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\VarDumper\Cloner\Data; - -/** - * @author Julien DENIAU - */ -class RequestDataCollectorTest extends TestCase -{ - private $request; - - private $response; - - private $attributes; - - private $metadataFactory; - - public function setUp() - { - $this->response = $this->createMock(Response::class); - $this->attributes = $this->prophesize(ParameterBag::class); - $this->request = $this->prophesize(Request::class); - $this->request - ->getAcceptableContentTypes() - ->shouldBeCalled() - ->willReturn(['foo', 'bar']); - - $this->metadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class); - } - - public function testNoResourceClass() - { - $this->apiResourceClassWillReturn(null); - - $dataCollector = new RequestDataCollector( - $this->metadataFactory->reveal() - ); - - $dataCollector->collect( - $this->request->reveal(), - $this->response - ); - - $this->assertEquals($dataCollector->getRequestAttributes(), []); - $this->assertEquals($dataCollector->getAcceptableContentTypes(), ['foo', 'bar']); - $this->assertNull($dataCollector->getResourceClass()); - $this->assertNull($dataCollector->getResourceMetadata()); - } - - public function testNotCallingCollect() - { - $this->request - ->getAcceptableContentTypes() - ->shouldNotBeCalled(); - $dataCollector = new RequestDataCollector( - $this->metadataFactory->reveal() - ); - - $this->assertEquals($dataCollector->getRequestAttributes(), []); - $this->assertEquals($dataCollector->getAcceptableContentTypes(), []); - $this->assertNull($dataCollector->getResourceClass()); - $this->assertNull($dataCollector->getResourceMetadata()); - } - - public function testWithRessource() - { - $this->apiResourceClassWillReturn(DummyEntity::class, ['_api_item_operation_name' => 'get', '_api_receive' => true]); - $this->request->attributes = $this->attributes->reveal(); - - $dataCollector = new RequestDataCollector( - $this->metadataFactory->reveal() - ); - - $dataCollector->collect( - $this->request->reveal(), - $this->response - ); - - $this->assertEquals(['resource_class' => DummyEntity::class, 'item_operation_name' => 'get', 'receive' => true], $dataCollector->getRequestAttributes()); - $this->assertEquals($dataCollector->getAcceptableContentTypes(), ['foo', 'bar']); - $this->assertEquals($dataCollector->getResourceClass(), DummyEntity::class); - $this->assertInstanceOf(Data::class, $dataCollector->getResourceMetadata()); - $this->assertSame(ResourceMetadata::class, $dataCollector->getResourceMetadata()->getType()); - } - - private function apiResourceClassWillReturn($data, $context = []) - { - $this->attributes->get('_api_resource_class')->shouldBeCalled()->willReturn($data); - $this->attributes->all()->shouldBeCalled()->willReturn([ - '_api_resource_class' => $data, - ] + $context); - $this->request->attributes = $this->attributes->reveal(); - - if (!$data) { - $this->metadataFactory - ->create() - ->shouldNotBeCalled(); - } else { - $this->metadataFactory - ->create($data) - ->shouldBeCalled() - ->willReturn( - new ResourceMetadata() - ); - } - } -} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 10d6e917f34..b5ad216ed93 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -73,22 +73,6 @@ class ApiPlatformExtensionTest extends TestCase 'http_cache' => ['invalidation' => [ 'enabled' => true, 'varnish_urls' => ['test'], - 'request_options' => [ - 'allow_redirects' => [ - 'max' => 5, - 'protocols' => ['http', 'https'], - 'stric' => false, - 'referer' => false, - 'track_redirects' => false, - ], - 'http_errors' => true, - 'decode_content' => false, - 'verify' => false, - 'cookies' => true, - 'headers' => [ - 'User-Agent' => 'none', - ], - ], ]], ]]; @@ -360,17 +344,6 @@ public function testNotRegisterHttpCacheWhenEnabledWithNoVarnishServer() $this->extension->load($config, $containerBuilder); } - public function testRegisterHttpCacheWhenEnabledWithNoRequestOption() - { - $containerBuilderProphecy = $this->getBaseContainerBuilderProphecy(); - $containerBuilder = $containerBuilderProphecy->reveal(); - - $config = self::DEFAULT_CONFIG; - unset($config['api_platform']['http_cache']['invalidation']['request_options']); - - $this->extension->load($config, $containerBuilder); - } - public function testDisabledDocsRemovesAddLinkHeaderService() { $containerBuilderProphecy = $this->getBaseContainerBuilderProphecy(); @@ -499,12 +472,6 @@ private function getPartialContainerBuilderProphecy($test = false) 'api_platform.filter_collection_factory', 'api_platform.filters', 'api_platform.iri_converter', - 'api_platform.identifier.denormalizer', - 'api_platform.identifier.integer', - 'api_platform.identifier.date_normalizer', - 'api_platform.identifier.uuid_normalizer', - 'api_platform.identifiers_extractor', - 'api_platform.identifiers_extractor.cached', 'api_platform.item_data_provider', 'api_platform.listener.exception', 'api_platform.listener.exception.validation', @@ -532,6 +499,8 @@ private function getPartialContainerBuilderProphecy($test = false) 'api_platform.metadata.resource.metadata_factory.xml', 'api_platform.metadata.resource.name_collection_factory.cached', 'api_platform.metadata.resource.name_collection_factory.xml', + 'api_platform.identifiers_extractor', + 'api_platform.identifiers_extractor.cached', 'api_platform.negotiator', 'api_platform.operation_method_resolver', 'api_platform.operation_path_resolver.custom', @@ -641,7 +610,6 @@ private function getBaseContainerBuilderProphecy() } $definitions = [ - 'api_platform.data_collector.request', 'api_platform.doctrine.listener.http_cache.purge', 'api_platform.doctrine.metadata_factory', 'api_platform.doctrine.orm.boolean_filter', diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPassTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPassTest.php index c4a4ad28b76..a072ac003dd 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPassTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/AnnotationFilterPassTest.php @@ -58,7 +58,7 @@ public function testProcess() $reader->getClassAnnotations(Argument::type(\ReflectionClass::class))->will(function ($args) { if (Dummy::class === $args[0]->name) { - return [new ApiFilter(['value' => SearchFilter::class, 'strategy' => 'exact', 'properties' => ['description', 'relatedDummy.name', 'name']]), new ApiResource([]), new ApiFilter(['value' => GroupFilter::class, 'arguments' => ['parameterName' => 'foobar']])]; + return [new ApiFilter(['value' => SearchFilter::class, 'strategy' => 'exact', 'properties' => ['description', 'relatedDummy.name', 'name']]), new ApiResource(), new ApiFilter(['value' => GroupFilter::class, 'arguments' => ['parameterName' => 'foobar']])]; } return []; diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataPersisterPassTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataPersisterPassTest.php new file mode 100644 index 00000000000..861b43fbfbc --- /dev/null +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DataPersisterPassTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Tests\Bridge\Symfony\Bundle\DependencyInjection\Compiler; + +use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataPersisterPass; +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Baptiste Meyer + */ +class DataPersisterPassTest extends TestCase +{ + public function testConstruct() + { + $this->assertInstanceOf(CompilerPassInterface::class, new DataPersisterPass()); + } + + public function testProcess() + { + $dataPersisterDefinitionProphecy = $this->prophesize(Definition::class); + $dataPersisterDefinitionProphecy->addArgument([new Reference('foo'), new Reference('bar')])->shouldBeCalled(); + + $containerBuilderProphecy = $this->prophesize(ContainerBuilder::class); + $containerBuilderProphecy->findTaggedServiceIds('api_platform.data_persister', true)->willReturn(['foo' => [], 'bar' => []])->shouldBeCalled(); + $containerBuilderProphecy->getDefinition('api_platform.data_persister')->willReturn($dataPersisterDefinitionProphecy->reveal())->shouldBeCalled(); + + (new DataPersisterPass())->process($containerBuilderProphecy->reveal()); + } +} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DoctrineQueryExtensionPassTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DoctrineQueryExtensionPassTest.php new file mode 100644 index 00000000000..361603ecf05 --- /dev/null +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/Compiler/DoctrineQueryExtensionPassTest.php @@ -0,0 +1,59 @@ + + * + * For the 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\DependencyInjection\Compiler; + +use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DoctrineQueryExtensionPass; +use PHPUnit\Framework\TestCase; +use Prophecy\Argument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; + +/** + * @author Kévin Dunglas + */ +class DoctrineQueryExtensionPassTest extends TestCase +{ + public function testProcess() + { + $dataProviderPass = new DoctrineQueryExtensionPass(); + + $this->assertInstanceOf(CompilerPassInterface::class, $dataProviderPass); + + $collectionDataProviderDefinitionProphecy = $this->prophesize(Definition::class); + $collectionDataProviderDefinitionProphecy->replaceArgument(1, Argument::type('array'))->shouldBeCalled(); + $collectionDataProviderDefinition = $collectionDataProviderDefinitionProphecy->reveal(); + + $itemDataProviderDefinitionProphecy = $this->prophesize(Definition::class); + $itemDataProviderDefinitionProphecy->replaceArgument(3, Argument::type('array'))->shouldBeCalled(); + $itemDataProviderDefinition = $itemDataProviderDefinitionProphecy->reveal(); + + $subresourceDataProviderDefinitionProphecy = $this->prophesize(Definition::class); + $subresourceDataProviderDefinitionProphecy->replaceArgument(3, Argument::type('array'))->shouldBeCalled(); + $subresourceDataProviderDefinitionProphecy->replaceArgument(4, Argument::type('array'))->shouldBeCalled(); + $subresourceDataProviderDefinition = $subresourceDataProviderDefinitionProphecy->reveal(); + + $containerBuilderProphecy = $this->prophesize(ContainerBuilder::class); + $containerBuilderProphecy->hasDefinition('api_platform.doctrine.metadata_factory')->willReturn(true)->shouldBeCalled(); + $containerBuilderProphecy->findTaggedServiceIds('api_platform.doctrine.orm.query_extension.collection', true)->willReturn(['foo' => [], 'bar' => ['priority' => 1]])->shouldBeCalled(); + $containerBuilderProphecy->findTaggedServiceIds('api_platform.doctrine.orm.query_extension.item', true)->willReturn(['foo' => [], 'bar' => ['priority' => 1]])->shouldBeCalled(); + + $containerBuilderProphecy->getDefinition('api_platform.doctrine.orm.collection_data_provider')->willReturn($collectionDataProviderDefinition)->shouldBeCalled(); + $containerBuilderProphecy->getDefinition('api_platform.doctrine.orm.item_data_provider')->willReturn($itemDataProviderDefinition)->shouldBeCalled(); + $containerBuilderProphecy->getDefinition('api_platform.doctrine.orm.subresource_data_provider')->willReturn($subresourceDataProviderDefinition)->shouldBeCalled(); + $containerBuilder = $containerBuilderProphecy->reveal(); + + $dataProviderPass->process($containerBuilder); + } +} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index cde0a614204..faf9a707a46 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -80,7 +80,6 @@ public function testDefaultConfig() 'enable_swagger_ui' => true, 'enable_entrypoint' => true, 'enable_docs' => true, - 'enable_profiler' => true, 'graphql' => [ 'enabled' => true, 'graphiql' => [ @@ -127,11 +126,7 @@ public function testDefaultConfig() 'paths' => [], ], 'http_cache' => [ - 'invalidation' => [ - 'enabled' => false, - 'varnish_urls' => [], - 'request_options' => [], - ], + 'invalidation' => ['enabled' => false, 'varnish_urls' => []], 'etag' => true, 'max_age' => null, 'shared_max_age' => null, diff --git a/tests/Bridge/Symfony/Routing/ApiLoaderTest.php b/tests/Bridge/Symfony/Routing/ApiLoaderTest.php index 5d2c9c8c4f9..a87bf17f5e8 100644 --- a/tests/Bridge/Symfony/Routing/ApiLoaderTest.php +++ b/tests/Bridge/Symfony/Routing/ApiLoaderTest.php @@ -97,35 +97,6 @@ public function testApiLoader() ); } - 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']], - 'put' => ['method' => 'PUT'], - 'delete' => ['method' => 'DELETE'], - ]); - $resourceMetadata = $resourceMetadata->withAttributes(['route_prefix' => '/foobar-prefix']); - - $routeCollection = $this->getApiLoaderWithResourceMetadata($resourceMetadata)->load(null); - - $this->assertEquals( - $this->getRoute('/foobar-prefix/dummies/{id}.{_format}', 'api_platform.action.get_item', DummyEntity::class, 'get', ['GET'], false, ['id' => '\d+'], ['my_default' => 'default_value']), - $routeCollection->get('api_dummies_get_item') - ); - - $this->assertEquals( - $this->getRoute('/foobar-prefix/dummies/{id}.{_format}', 'api_platform.action.delete_item', DummyEntity::class, 'delete', ['DELETE']), - $routeCollection->get('api_dummies_delete_item') - ); - - $this->assertEquals( - $this->getRoute('/foobar-prefix/dummies/{id}.{_format}', 'api_platform.action.put_item', DummyEntity::class, 'put', ['PUT']), - $routeCollection->get('api_dummies_put_item') - ); - } - /** * @expectedException \RuntimeException */ diff --git a/tests/Bridge/Symfony/Routing/IriConverterTest.php b/tests/Bridge/Symfony/Routing/IriConverterTest.php index 4c66e236520..147ca100e4e 100644 --- a/tests/Bridge/Symfony/Routing/IriConverterTest.php +++ b/tests/Bridge/Symfony/Routing/IriConverterTest.php @@ -20,9 +20,6 @@ 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\Exception\InvalidIdentifierException; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; @@ -43,9 +40,29 @@ class IriConverterTest extends TestCase */ public function testGetItemFromIriNoRouteException() { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); + + $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); + $routerProphecy = $this->prophesize(RouterInterface::class); $routerProphecy->match('/users/3')->willThrow(new RouteNotFoundException())->shouldBeCalledTimes(1); - $converter = $this->getIriConverter($routerProphecy); + + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); $converter->getItemFromIri('/users/3'); } @@ -55,61 +72,128 @@ public function testGetItemFromIriNoRouteException() */ public function testGetItemFromIriNoResourceException() { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); + + $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); + $routerProphecy = $this->prophesize(RouterInterface::class); $routerProphecy->match('/users/3')->willReturn([])->shouldBeCalledTimes(1); - $converter = $this->getIriConverter($routerProphecy); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); $converter->getItemFromIri('/users/3'); } /** - * @expectedException \ApiPlatform\Core\Exception\ItemNotFoundException + * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException * @expectedExceptionMessage Item not found for "/users/3". */ public function testGetItemFromIriItemNotFoundException() { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy - ->getItem(Dummy::class, 3, 'get', []) - ->shouldBeCalled()->willReturn(null); + $itemDataProviderProphecy->getItem('AppBundle\Entity\User', 3, null, [])->shouldBeCalledTimes(1); + + $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', + '_api_resource_class' => 'AppBundle\Entity\User', 'id' => 3, ])->shouldBeCalledTimes(1); - $converter = $this->getIriConverter($routerProphecy, null, $itemDataProviderProphecy); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); $converter->getItemFromIri('/users/3'); } public function testGetItemFromIri() { - $item = new \StdClass(); + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy->getItem(Dummy::class, 3, 'get', ['fetch_data' => true])->shouldBeCalled()->willReturn($item); + $itemDataProviderProphecy->getItem('AppBundle\Entity\User', 3, null, ['fetch_data' => true]) + ->willReturn('foo') + ->shouldBeCalledTimes(1); + + $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', + '_api_resource_class' => 'AppBundle\Entity\User', 'id' => 3, ])->shouldBeCalledTimes(1); - $converter = $this->getIriConverter($routerProphecy, null, $itemDataProviderProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3', ['fetch_data' => true]), $item); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); + $converter->getItemFromIri('/users/3', ['fetch_data' => true]); } public function testGetIriFromResourceClass() { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); + $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); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); $this->assertEquals($converter->getIriFromResourceClass(Dummy::class), '/dummies'); } @@ -119,25 +203,59 @@ public function testGetIriFromResourceClass() */ public function testNotAbleToGenerateGetIriFromResourceClass() { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); + $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); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); $converter->getIriFromResourceClass(Dummy::class); } public function testGetSubresourceIriFromResourceClass() { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); + $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); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); $this->assertEquals($converter->getSubresourceIriFromResourceClass(Dummy::class, ['subresource_identifiers' => ['id' => 1], 'subresource_resources' => [RelatedDummy::class => 1]]), '/dummies/1/related_dummies'); } @@ -147,25 +265,59 @@ public function testGetSubresourceIriFromResourceClass() */ public function testNotAbleToGenerateGetSubresourceIriFromResourceClass() { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); + $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); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); $converter->getSubresourceIriFromResourceClass(Dummy::class, ['subresource_identifiers' => ['id' => 1], 'subresource_resources' => [RelatedDummy::class => 1]]); } public function testGetItemIriFromResourceClass() { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); + $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'); - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, + $itemDataProviderProphecy->reveal(), + $routeNameResolverProphecy->reveal(), + $routerProphecy->reveal(), + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) + ); $this->assertEquals($converter->getItemIriFromResourceClass(Dummy::class, ['id' => 1]), '/dummies/1'); } @@ -175,121 +327,31 @@ public function testGetItemIriFromResourceClass() */ public function testNotAbleToGenerateGetItemIriFromResourceClass() { - $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()); + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy); - $converter->getItemIriFromResourceClass(Dummy::class, ['id' => 1]); - } + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - public function testGetItemFromIriWithIdentifierDenormalizer() - { - $item = new \StdClass(); $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy->getItem(Dummy::class, ['id' => 3], 'get', ['fetch_data' => true, ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true])->shouldBeCalled()->willReturn($item); - $identifierDenormalizerProphecy = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizerProphecy->denormalize('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', - 'id' => 3, - ])->shouldBeCalledTimes(1); - - $converter = $this->getIriConverter($routerProphecy, null, $itemDataProviderProphecy, $identifierDenormalizerProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3', ['fetch_data' => true]), $item); - } - public function testGetItemFromIriWithSubresourceDataProvider() - { - $item = new \StdClass(); - $subresourceContext = ['identifiers' => [['id', Dummy::class, true]]]; - $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); $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', - 'id' => 3, - ])->shouldBeCalledTimes(1); - $identifierDenormalizerProphecy = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizerProphecy->denormalize('3', Dummy::class)->shouldBeCalled()->willReturn(['id' => 3]); - $subresourceDataProviderProphecy = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProviderProphecy->getSubresource(Dummy::class, ['id' => ['id' => 3]], $subresourceContext + ['fetch_data' => true, ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true], 'get_subresource')->shouldBeCalled()->willReturn($item); - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, $identifierDenormalizerProphecy, $subresourceDataProviderProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3/adresses', ['fetch_data' => true]), $item); - } - - /** - * @expectedException \ApiPlatform\Core\Exception\ItemNotFoundException - * @expectedExceptionMessage Item not found for "/users/3/adresses". - */ - public function testGetItemFromIriWithSubresourceDataProviderNotFound() - { - $subresourceContext = ['identifiers' => [['id', Dummy::class, true]]]; - $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $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', - 'id' => 3, - ])->shouldBeCalledTimes(1); - $identifierDenormalizerProphecy = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizerProphecy->denormalize('3', Dummy::class)->shouldBeCalled()->willReturn(['id' => 3]); - $subresourceDataProviderProphecy = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProviderProphecy->getSubresource(Dummy::class, ['id' => ['id' => 3]], $subresourceContext + ['fetch_data' => true, ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true], 'get_subresource')->shouldBeCalled()->willReturn(null); - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, $identifierDenormalizerProphecy, $subresourceDataProviderProphecy); - $converter->getItemFromIri('/users/3/adresses', ['fetch_data' => true]); - } + $routeNameResolverProphecy->getRouteName(Dummy::class, OperationType::ITEM)->willReturn('dummies'); - /** - * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException - * @expectedExceptionMessage fail - */ - public function testGetItemFromIriBadIdentifierException() - { - $item = new \StdClass(); - $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $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', - 'id' => 3, - ])->shouldBeCalledTimes(1); - $identifierDenormalizerProphecy = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizerProphecy->denormalize('3', Dummy::class)->shouldBeCalled()->willThrow(new InvalidIdentifierException('fail')); - $converter = $this->getIriConverter($routerProphecy, $routeNameResolverProphecy, null, $identifierDenormalizerProphecy); - $this->assertEquals($converter->getItemFromIri('/users/3', ['fetch_data' => true]), $item); - } + $routerProphecy->generate('dummies', ['id' => 1], UrlGeneratorInterface::ABS_PATH)->willThrow(new RouteNotFoundException()); - /** - * @group legacy - * @expectedDeprecation Not injecting "ApiPlatform\Core\Api\IdentifiersExtractorInterface" is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3 - * @expectedDeprecation Not injecting ApiPlatform\Core\Api\ResourceClassResolverInterface in the CachedIdentifiersExtractor might introduce cache issues with object identifiers. - */ - public function testLegacyConstructor() - { - $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); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); - new IriConverter( - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), + $converter = new IriConverter( + $propertyNameCollectionFactory, + $propertyMetadataFactory, $itemDataProviderProphecy->reveal(), $routeNameResolverProphecy->reveal(), $routerProphecy->reveal(), - null + null, + new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()) ); + $converter->getItemIriFromResourceClass(Dummy::class, ['id' => 1]); } private function getResourceClassResolver() @@ -301,35 +363,4 @@ private function getResourceClassResolver() return $resourceClassResolver->reveal(); } - - private function getIriConverter($routerProphecy = null, $routeNameResolverProphecy = null, $itemDataProviderProphecy = null, $identifierDenormalizerProphecy = null, $subresourceDataProviderProphecy = 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(); - - return new IriConverter( - $propertyNameCollectionFactory, - $propertyMetadataFactory, - $itemDataProvider->reveal(), - $routeNameResolverProphecy->reveal(), - $routerProphecy->reveal(), - null, - new IdentifiersExtractor($propertyNameCollectionFactory, $propertyMetadataFactory, null, $this->getResourceClassResolver()), - $identifierDenormalizerProphecy ? $identifierDenormalizerProphecy->reveal() : null, - $subresourceDataProviderProphecy ? $subresourceDataProviderProphecy->reveal() : null - ); - } } diff --git a/tests/EventListener/DeserializeListenerTest.php b/tests/EventListener/DeserializeListenerTest.php index 44353c00639..ef8bb57dfa4 100644 --- a/tests/EventListener/DeserializeListenerTest.php +++ b/tests/EventListener/DeserializeListenerTest.php @@ -47,15 +47,12 @@ public function testDoNotCallWhenRequestMethodIsSafe() $listener->onKernelRequest($eventProphecy->reveal()); } - /** - * @dataProvider allowedEmptyRequestMethodsProvider - */ - public function testDoNotCallWhenSendingAndEmptyRequestContent($method) + public function testDoNotCallWhenPutAndEmptyRequestContent() { $eventProphecy = $this->prophesize(GetResponseEvent::class); $request = new Request([], [], ['data' => new \stdClass(), '_api_resource_class' => 'Foo', '_api_item_operation_name' => 'put'], [], [], [], ''); - $request->setMethod($method); + $request->setMethod('PUT'); $request->headers->set('Content-Type', 'application/json'); $eventProphecy->getRequest()->willReturn($request)->shouldBeCalled(); @@ -69,11 +66,6 @@ public function testDoNotCallWhenSendingAndEmptyRequestContent($method) $listener->onKernelRequest($eventProphecy->reveal()); } - public function allowedEmptyRequestMethodsProvider() - { - return [['PUT'], ['POST']]; - } - public function testDoNotCallWhenRequestNotManaged() { $eventProphecy = $this->prophesize(GetResponseEvent::class); diff --git a/tests/EventListener/ReadListenerTest.php b/tests/EventListener/ReadListenerTest.php index b6c99121f0d..b4f693fc81d 100644 --- a/tests/EventListener/ReadListenerTest.php +++ b/tests/EventListener/ReadListenerTest.php @@ -17,10 +17,7 @@ use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface; use ApiPlatform\Core\EventListener\ReadListener; -use ApiPlatform\Core\Exception\InvalidIdentifierException; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -30,29 +27,6 @@ class ReadListenerTest extends TestCase { public function testNotAnApiPlatformRequest() - { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::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(GetResponseEvent::class); - $event->getRequest()->willReturn(new Request())->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - /** - * @group legacy - */ - public function testLegacyConstructor() { $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); $collectionDataProvider->getCollection()->shouldNotBeCalled(); @@ -72,8 +46,6 @@ public function testLegacyConstructor() public function testDoNotCallWhenReceiveFlagIsFalse() { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); $collectionDataProvider->getCollection()->shouldNotBeCalled(); @@ -89,14 +61,12 @@ public function testDoNotCallWhenReceiveFlagIsFalse() $event = $this->prophesize(GetResponseEvent::class); $event->getRequest()->willReturn($request)->shouldBeCalled(); - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); $listener->onKernelRequest($event->reveal()); } public function testRetrieveCollectionPost() { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); $collectionDataProvider->getCollection()->shouldNotBeCalled(); @@ -112,7 +82,7 @@ public function testRetrieveCollectionPost() $event = $this->prophesize(GetResponseEvent::class); $event->getRequest()->willReturn($request)->shouldBeCalled(); - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); $listener->onKernelRequest($event->reveal()); $this->assertTrue($request->attributes->has('data')); @@ -121,8 +91,6 @@ public function testRetrieveCollectionPost() public function testRetrieveCollectionGet() { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); $collectionDataProvider->getCollection('Foo', 'get', ['filters' => ['foo' => 'bar']])->willReturn([])->shouldBeCalled(); @@ -138,7 +106,7 @@ public function testRetrieveCollectionGet() $event = $this->prophesize(GetResponseEvent::class); $event->getRequest()->willReturn($request)->shouldBeCalled(); - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); $listener->onKernelRequest($event->reveal()); $this->assertSame([], $request->attributes->get('data')); @@ -146,15 +114,12 @@ public function testRetrieveCollectionGet() public function testRetrieveItem() { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizer->denormalize('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', [ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true])->willReturn($data)->shouldBeCalled(); + $itemDataProvider->getItem('Foo', 1, 'get', [])->willReturn($data)->shouldBeCalled(); $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); $subresourceDataProvider->getSubresource()->shouldNotBeCalled(); @@ -165,7 +130,7 @@ public function testRetrieveItem() $event = $this->prophesize(GetResponseEvent::class); $event->getRequest()->willReturn($request)->shouldBeCalled(); - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); $listener->onKernelRequest($event->reveal()); $this->assertSame($data, $request->attributes->get('data')); @@ -173,9 +138,6 @@ public function testRetrieveItem() public function testRetrieveSubresource() { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizer->denormalize('1', 'Bar')->shouldBeCalled()->willReturn(['id' => '1']); - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); $collectionDataProvider->getCollection()->shouldNotBeCalled(); @@ -184,7 +146,7 @@ public function testRetrieveSubresource() $data = [new \stdClass()]; $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); - $subresourceDataProvider->getSubresource('Foo', ['id' => ['id' => '1']], ['identifiers' => [['id', 'Bar', true]], 'property' => 'bar', ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true], 'get')->willReturn($data)->shouldBeCalled(); + $subresourceDataProvider->getSubresource('Foo', ['id' => 1], ['identifiers' => [['id', 'Bar', true]], 'property' => 'bar'], '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]], 'property' => 'bar']]); $request->setMethod('GET'); @@ -192,7 +154,7 @@ public function testRetrieveSubresource() $event = $this->prophesize(GetResponseEvent::class); $event->getRequest()->willReturn($request)->shouldBeCalled(); - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); $listener->onKernelRequest($event->reveal()); $this->assertSame($data, $request->attributes->get('data')); @@ -203,22 +165,22 @@ public function testRetrieveSubresource() */ public function testRetrieveSubresourceNotFound() { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizer->denormalize('1', 'Bar')->willThrow(new InvalidIdentifierException())->shouldBeCalled(); - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); $collectionDataProvider->getCollection()->shouldNotBeCalled(); $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); $itemDataProvider->getItem()->shouldNotBeCalled(); + $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); + $subresourceDataProvider->getSubresource('Foo', ['id' => 1], ['identifiers' => [['id', 'Bar', true]], 'property' => 'bar'], 'get')->willReturn(null)->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]], 'property' => 'bar']]); $request->setMethod('GET'); $event = $this->prophesize(GetResponseEvent::class); $event->getRequest()->willReturn($request)->shouldBeCalled(); - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $this->prophesize(SubresourceDataProviderInterface::class)->reveal(), null, $identifierDenormalizer->reveal()); + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); $listener->onKernelRequest($event->reveal()); } @@ -227,13 +189,10 @@ public function testRetrieveSubresourceNotFound() */ public function testRetrieveItemNotFound() { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizer->denormalize('22', 'Foo')->shouldBeCalled()->willReturn(['id' => 22]); - $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem('Foo', ['id' => 22], 'get', [ChainIdentifierDenormalizer::HAS_IDENTIFIER_DENORMALIZER => true])->willReturn(null)->shouldBeCalled(); + $itemDataProvider->getItem('Foo', 22, 'get', [])->willReturn(null)->shouldBeCalled(); $subresourceDataProvider = $this->prophesize(SubresourceDataProviderInterface::class); @@ -243,57 +202,7 @@ public function testRetrieveItemNotFound() $event = $this->prophesize(GetResponseEvent::class); $event->getRequest()->willReturn($request)->shouldBeCalled(); - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - /** - * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function testRetrieveBadItemNormalizedIdentifiers() - { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizer->denormalize('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(GetResponseEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); - $listener->onKernelRequest($event->reveal()); - } - - /** - * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function testRetrieveBadSubresourceNormalizedIdentifiers() - { - $identifierDenormalizer = $this->prophesize(ChainIdentifierDenormalizer::class); - $identifierDenormalizer->denormalize(Argument::type('string'), Argument::type('string'))->shouldBeCalled()->willThrow(new InvalidIdentifierException()); - - $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()->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]], 'property' => 'bar']]); - $request->setMethod(Request::METHOD_GET); - - $event = $this->prophesize(GetResponseEvent::class); - $event->getRequest()->willReturn($request)->shouldBeCalled(); - - $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal(), null, $identifierDenormalizer->reveal()); + $listener = new ReadListener($collectionDataProvider->reveal(), $itemDataProvider->reveal(), $subresourceDataProvider->reveal()); $listener->onKernelRequest($event->reveal()); } } diff --git a/tests/Fixtures/TestBundle/Controller/CustomActionController.php b/tests/Fixtures/TestBundle/Controller/CustomActionController.php index ad0192aa72f..2f4ad7ca1de 100644 --- a/tests/Fixtures/TestBundle/Controller/CustomActionController.php +++ b/tests/Fixtures/TestBundle/Controller/CustomActionController.php @@ -62,43 +62,4 @@ public function customDenormalizationAction(Request $request) return $object; } - - /** - * @Route( - * name="short_custom_normalization", - * path="/short_custom/{id}/normalization", - * defaults={"_api_resource_class"=CustomActionDummy::class, "_api_item_operation_name"="custom_normalization"} - * ) - * @Method("GET") - */ - public function shortCustomNormalizationAction(CustomActionDummy $data) - { - $data->setFoo('short'); - - return $this->json($data); - } - - /** - * @Route( - * name="short_custom_denormalization", - * path="/short_custom/denormalization", - * defaults={ - * "_api_resource_class"=CustomActionDummy::class, - * "_api_collection_operation_name"="custom_denormalization", - * "_api_receive"=false - * } - * ) - * @Method("POST") - */ - public function shortCustomDenormalizationAction(Request $request) - { - if ($request->attributes->has('data')) { - throw new \RuntimeException('The "data" attribute must not be set.'); - } - - $object = new CustomActionDummy(); - $object->setFoo('short declaration'); - - return $object; - } } diff --git a/tests/Fixtures/TestBundle/DataProvider/ContainNonResourceItemDataProvider.php b/tests/Fixtures/TestBundle/DataProvider/ContainNonResourceItemDataProvider.php index 413fdcc20ee..9ce1a0e66a7 100644 --- a/tests/Fixtures/TestBundle/DataProvider/ContainNonResourceItemDataProvider.php +++ b/tests/Fixtures/TestBundle/DataProvider/ContainNonResourceItemDataProvider.php @@ -35,10 +35,10 @@ public function getItem(string $resourceClass, $id, string $operationName = null { // Retrieve the blog post item from somewhere $cnr = new ContainNonResource(); - $cnr->id = $id['id']; + $cnr->id = $id; $cnr->notAResource = new NotAResource('f1', 'b1'); $cnr->nested = new ContainNonResource(); - $cnr->nested->id = "{$id['id']}-nested"; + $cnr->nested->id = "$id-nested"; $cnr->nested->notAResource = new NotAResource('f2', 'b2'); return $cnr; diff --git a/tests/Fixtures/TestBundle/Entity/CustomActionDummy.php b/tests/Fixtures/TestBundle/Entity/CustomActionDummy.php index aecee9e7cc6..237deb312e2 100644 --- a/tests/Fixtures/TestBundle/Entity/CustomActionDummy.php +++ b/tests/Fixtures/TestBundle/Entity/CustomActionDummy.php @@ -20,14 +20,10 @@ * @ORM\Entity * @ApiResource(itemOperations={ * "get", - * "get_custom"={"method"="GET", "path"="custom_action_collection_dummies/{id}"}, - * "custom_normalization"={"route_name"="custom_normalization"}, - * "short_custom_normalization", + * "custom_normalization"={"route_name"="custom_normalization"} * }, collectionOperations={ * "get", - * "get_custom"={"method"="GET", "path"="custom_action_collection_dummies"}, - * "custom_denormalization"={"route_name"="custom_denormalization"}, - * "short_custom_denormalization", + * "custom_denormalization"={"route_name"="custom_denormalization"} * }) * * @author Kévin Dunglas diff --git a/tests/Fixtures/TestBundle/Entity/RamseyUuidDummy.php b/tests/Fixtures/TestBundle/Entity/RamseyUuidDummy.php deleted file mode 100644 index 0316dfa9611..00000000000 --- a/tests/Fixtures/TestBundle/Entity/RamseyUuidDummy.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\Tests\Fixtures\TestBundle\Entity; - -use ApiPlatform\Core\Annotation\ApiResource; -use Doctrine\ORM\Mapping as ORM; -use Ramsey\Uuid\Uuid; -use Ramsey\Uuid\UuidInterface; - -/** - * @ORM\Entity - * @ApiResource - */ -class RamseyUuidDummy -{ - /** - * @var \Ramsey\Uuid\UuidInterface - * - * @ORM\Id - * @ORM\Column(type="uuid", unique=true) - */ - private $id; - - public function getId(): UuidInterface - { - return $this->id; - } - - public function setId(string $uuid) - { - $this->id = Uuid::fromString($uuid); - } -} diff --git a/tests/Fixtures/app/config/config_mysql.yml b/tests/Fixtures/app/config/config_mysql.yml index c1d3b894f48..0e7effb2346 100644 --- a/tests/Fixtures/app/config/config_mysql.yml +++ b/tests/Fixtures/app/config/config_mysql.yml @@ -9,5 +9,3 @@ doctrine: user: '%user%' password: '%password%' host: '%host%' - types: - uuid: Ramsey\Uuid\Doctrine\UuidType diff --git a/tests/Fixtures/app/config/config_postgres.yml b/tests/Fixtures/app/config/config_postgres.yml index 0ce81552b72..65aff8ce5bb 100644 --- a/tests/Fixtures/app/config/config_postgres.yml +++ b/tests/Fixtures/app/config/config_postgres.yml @@ -9,5 +9,3 @@ doctrine: user: '%user%' password: '%password%' host: '%host%' - types: - uuid: Ramsey\Uuid\Doctrine\UuidType diff --git a/tests/Fixtures/app/config/config_test.yml b/tests/Fixtures/app/config/config_test.yml index 23fcef29e16..e8513018962 100644 --- a/tests/Fixtures/app/config/config_test.yml +++ b/tests/Fixtures/app/config/config_test.yml @@ -13,11 +13,9 @@ framework: doctrine: dbal: - driver: 'pdo_sqlite' - path: '%kernel.cache_dir%/db.sqlite' - charset: 'UTF8' - types: - uuid: Ramsey\Uuid\Doctrine\UuidType + driver: 'pdo_sqlite' + path: '%kernel.cache_dir%/db.sqlite' + charset: 'UTF8' orm: auto_generate_proxy_classes: '%kernel.debug%' diff --git a/tests/Identifier/CompositeIdentifierParserTest.php b/tests/Identifier/CompositeIdentifierParserTest.php deleted file mode 100644 index dd7a3dbc3ee..00000000000 --- a/tests/Identifier/CompositeIdentifierParserTest.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\Tests\Identifier; - -use ApiPlatform\Core\Identifier\CompositeIdentifierParser; -use PHPUnit\Framework\TestCase; - -class CompositeIdentifierParserTest extends TestCase -{ - /** - * @dataProvider variousIdentifiers - */ - public function testNormalizeCompositeCorrectly(array $identifiers) - { - foreach ($identifiers as $string => $expected) { - $this->assertEquals(CompositeIdentifierParser::parse($string), $expected); - } - } - - public function variousIdentifiers(): array - { - return [[[ - 'a=bd;dc=d' => ['a' => 'bd', 'dc' => 'd'], - 'a=b;c=d foo;d23i=e' => ['a' => 'b', 'c' => 'd foo', 'd23i' => 'e'], - 'a=1;c=2;d=10-30-24' => ['a' => '1', 'c' => '2', 'd' => '10-30-24'], - 'a=test;b=bar;foo;c=123' => ['a' => 'test', 'b' => 'bar;foo', 'c' => '123'], - 'a=test;b=bar ;foo;c=123;459;barz=123asgfjasdg4;' => ['a' => 'test', 'b' => 'bar ;foo', 'c' => '123;459', 'barz' => '123asgfjasdg4'], - 'foo=test=bar;;bar=bazzz;' => ['foo' => 'test=bar;', 'bar' => 'bazzz'], - ]]]; - } -} diff --git a/tests/Identifier/Normalizer/ChainIdentifierDenormalizerTest.php b/tests/Identifier/Normalizer/ChainIdentifierDenormalizerTest.php deleted file mode 100644 index fbfa7cf1905..00000000000 --- a/tests/Identifier/Normalizer/ChainIdentifierDenormalizerTest.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\Tests\Identifier\Normalizer; - -use ApiPlatform\Core\Api\IdentifiersExtractorInterface; -use ApiPlatform\Core\Identifier\Normalizer\ChainIdentifierDenormalizer; -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 PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; - -/** - * @author Antoine Bluchet - */ -class ChainIdentifierDenormalizerTest extends TestCase -{ - public function testCompositeIdentifier() - { - $identifier = '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 ChainIdentifierDenormalizer($identifiersExtractor->reveal(), $propertyMetadataFactory->reveal(), $identifierDenormalizers); - - $result = $identifierDenormalizer->denormalize($identifier, $class); - $this->assertEquals(['a' => 1, 'c' => '2', 'd' => new \DateTime('2015-04-05')], $result); - $this->assertSame(1, $result['a']); - } - - public function testSingleDateIdentifier() - { - $identifier = '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 ChainIdentifierDenormalizer($identifiersExtractor->reveal(), $propertyMetadataFactory->reveal(), $identifierDenormalizers); - - $this->assertEquals($identifierDenormalizer->denormalize($identifier, $class), ['funkyid' => new \DateTime('2015-04-05')]); - } - - public function testIntegerIdentifier() - { - $identifier = '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 ChainIdentifierDenormalizer($identifiersExtractor->reveal(), $propertyMetadataFactory->reveal(), $identifierDenormalizers); - - $this->assertSame(['id' => 42], $identifierDenormalizer->denormalize($identifier, $class)); - } -} diff --git a/tests/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php b/tests/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php index daf4e85c840..df82ce8440d 100644 --- a/tests/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php +++ b/tests/Metadata/Resource/Factory/AnnotationResourceMetadataFactoryTest.php @@ -43,22 +43,21 @@ public function testCreate(ProphecyInterface $reader, ProphecyInterface $decorat $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'], $metadata->getAttributes()); + $this->assertEquals(['a' => 1], $metadata->getAttributes()); $this->assertEquals(['foo' => 'bar'], $metadata->getGraphql()); } public function getCreateDependencies() { - $annotation = new ApiResource([ - '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'], - ]); + $annotation = new ApiResource(); + $annotation->shortName = 'shortName'; + $annotation->description = 'description'; + $annotation->iri = 'http://example.com'; + $annotation->itemOperations = ['foo' => ['bar' => true]]; + $annotation->collectionOperations = ['baz' => ['tab' => false]]; + $annotation->subresourceOperations = ['sub' => ['bus' => false]]; + $annotation->attributes = ['a' => 1]; + $annotation->graphql = ['foo' => 'bar']; $reader = $this->prophesize(Reader::class); $reader->getClassAnnotation(Argument::type(\ReflectionClass::class), ApiResource::class)->willReturn($annotation)->shouldBeCalled(); diff --git a/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php b/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php index 48f7b6ed7ec..2e7d75293ee 100644 --- a/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php +++ b/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php @@ -46,18 +46,14 @@ public function getMetadata() [new ResourceMetadata(null, null, null, ['get'], [], null, [], []), new ResourceMetadata(null, null, null, ['get' => ['method' => 'GET']], [], null, [], [])], [new ResourceMetadata(null, null, null, ['put'], [], null, [], []), new ResourceMetadata(null, null, null, ['put' => ['method' => 'PUT']], [], null, [], [])], [new ResourceMetadata(null, null, null, ['delete'], [], null, [], []), new ResourceMetadata(null, null, null, ['delete' => ['method' => 'DELETE']], [], null, [], [])], - [new ResourceMetadata(null, null, null, ['patch'], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => ['route_name' => 'patch']], [], null, [], [])], + [new ResourceMetadata(null, null, null, ['patch'], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => []], [], null, [], [])], [new ResourceMetadata(null, null, null, ['patch'], [], null, [], []), new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH']], [], null, [], []), $jsonapi], - [new ResourceMetadata(null, null, null, ['untouched' => ['method' => 'GET']], [], null, [], []), new ResourceMetadata(null, null, null, ['untouched' => ['method' => 'GET']], [], null, [], []), $jsonapi], - [new ResourceMetadata(null, null, null, ['untouched_custom' => ['route_name' => 'custom_route']], [], null, [], []), new ResourceMetadata(null, null, null, ['untouched_custom' => ['route_name' => 'custom_route']], [], null, [], []), $jsonapi], // Collection operations [new ResourceMetadata(null, null, null, [], null, null, [], []), new ResourceMetadata(null, null, null, [], ['get' => ['method' => 'GET'], 'post' => ['method' => 'POST']], null, [], [])], [new ResourceMetadata(null, null, null, [], ['get'], null, [], []), new ResourceMetadata(null, null, null, [], ['get' => ['method' => 'GET']], null, [], [])], [new ResourceMetadata(null, null, null, [], ['post'], null, [], []), new ResourceMetadata(null, null, null, [], ['post' => ['method' => 'POST']], null, [], [])], - [new ResourceMetadata(null, null, null, [], ['options'], null, [], []), new ResourceMetadata(null, null, null, [], ['options' => ['route_name' => 'options']], null, [], [])], - [new ResourceMetadata(null, null, null, [], ['untouched' => ['method' => 'GET']], null, [], []), new ResourceMetadata(null, null, null, [], ['untouched' => ['method' => 'GET']], null, [], [])], - [new ResourceMetadata(null, null, null, [], ['untouched_custom' => ['route_name' => 'custom_route']], null, [], []), new ResourceMetadata(null, null, null, [], ['untouched_custom' => ['route_name' => 'custom_route']], null, [], [])], + [new ResourceMetadata(null, null, null, [], ['options'], null, [], []), new ResourceMetadata(null, null, null, [], ['options' => []], null, [], [])], ]; } } diff --git a/tests/Util/ErrorFormatGuesserTest.php b/tests/Util/ErrorFormatGuesserTest.php index 6cb892310d4..820fc9d5c1b 100644 --- a/tests/Util/ErrorFormatGuesserTest.php +++ b/tests/Util/ErrorFormatGuesserTest.php @@ -48,14 +48,4 @@ public function testFallbackWhenNotSupported() $this->assertEquals('xml', $format['key']); $this->assertEquals('text/xml', $format['value'][0]); } - - public function testGuessCustomErrorFormat() - { - $request = new Request(); - $request->setRequestFormat('custom_json_format'); - - $format = ErrorFormatGuesser::guessErrorFormat($request, ['xml' => ['text/xml'], 'custom_json_format' => ['application/json']]); - $this->assertEquals('custom_json_format', $format['key']); - $this->assertEquals('application/json', $format['value'][0]); - } }