From db380b716aaabd7ae5459a66b7e6c60024e64b84 Mon Sep 17 00:00:00 2001
From: Dmitri Goosens <1250047+dgoosens@users.noreply.github.com>
Date: Sat, 17 Sep 2022 18:27:26 +0200
Subject: [PATCH 01/14] ignore api_platform.state.item_provider when Doctrine
is not enabled (#4954)
---
src/Symfony/Bundle/Resources/config/state.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Symfony/Bundle/Resources/config/state.xml b/src/Symfony/Bundle/Resources/config/state.xml
index f0ecb8e23af..030088b4dd0 100644
--- a/src/Symfony/Bundle/Resources/config/state.xml
+++ b/src/Symfony/Bundle/Resources/config/state.xml
@@ -42,7 +42,7 @@
-
+
From d565f954fdea5ac751875d3c6363a9892a397e0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Dunglas?=
Date: Thu, 29 Sep 2022 13:49:56 +0200
Subject: [PATCH 02/14] chore: update branch-alias (#5030)
---
composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/composer.json b/composer.json
index 84f58b654b1..b1fa38ca6ae 100644
--- a/composer.json
+++ b/composer.json
@@ -143,7 +143,7 @@
},
"extra": {
"branch-alias": {
- "dev-main": "3.0.x-dev"
+ "dev-main": "3.1.x-dev"
},
"symfony": {
"require": "^6.1"
From c493676d59c8636b4aa3c71d09ea3a435e6b43d5 Mon Sep 17 00:00:00 2001
From: davy-beauzil <38990335+davy-beauzil@users.noreply.github.com>
Date: Fri, 4 Nov 2022 16:37:58 +0100
Subject: [PATCH 03/14] chore: adding of stale bot (#5087)
---
.github/stale.yml | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 .github/stale.yml
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 00000000000..9abd4e0d252
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,17 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 60
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 7
+# Issues with these labels will never be considered stale
+exemptLabels:
+ - bug
+ - enhancement
+# Label to use when marking an issue as stale
+staleLabel: wontfix
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
From ac70771ea802a54759648cd2b9f83bb2ae001c87 Mon Sep 17 00:00:00 2001
From: Antoine Bluchet
Date: Fri, 4 Nov 2022 17:29:49 +0100
Subject: [PATCH 04/14] ci: add automatic release notes (#5119)
---
.github/workflows/tagged-release.yml | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 .github/workflows/tagged-release.yml
diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml
new file mode 100644
index 00000000000..06b836b6de5
--- /dev/null
+++ b/.github/workflows/tagged-release.yml
@@ -0,0 +1,24 @@
+---
+name: "tagged-release"
+
+on:
+ push:
+ tags:
+ - "v*"
+
+jobs:
+ gh_tagged_release:
+ runs-on: "ubuntu-latest"
+
+ steps:
+ - name: "Checkout source code"
+ uses: "actions/checkout@v2.3.4"
+ with:
+ lfs: true
+ fetch-depth: 0
+
+ - uses: "marvinpinto/action-automatic-releases@latest"
+ with:
+ repo_token: "${{ secrets.GITHUB_TOKEN }}"
+ prerelease: false
+ id: "automatic_releases"
From 6abd0fe0a69d4842eb6d5c31ef2bd6dce0e1d372 Mon Sep 17 00:00:00 2001
From: Antoine Bluchet
Date: Fri, 4 Nov 2022 20:11:53 +0100
Subject: [PATCH 05/14] Merge 3.0 into main (#5121)
* fix: update yaml extractor test file coding standard (#5068)
* fix(graphql): add clearer error message when TwigBundle is disabled but graphQL clients are enabled (#5064)
* fix(metadata): add class key in payload argument resolver (#5067)
* fix: add class key in payload argument resolver
* add null if everything else goes wrong
* fix: upgrade command remove ApiSubresource attribute (#5049)
Fixes #5038
* fix(doctrine): use abitrary index instead of value (#5079)
* fix: uri template should respect rfc 6570 (#5080)
* fix: remove @internal annotation for Operations (#5089)
See #5084
* fix(metadata): define a name on a single operation (#5090)
fixes #5082
* fix(metadata): deprecate when user decorates in legacy mode (#5091)
fixes #5078
* fix(graphql): use right nested operation (#5102)
* Revert "fix(graphql): use right nested operation (#5102)" (#5111)
This reverts commit 44337ddb3908d7b05ed75b75325b7941581f575b.
* fix(graphql): always allow to query nested resources (#5112)
* fix(graphql): always allow to query nested resources
* review
Co-authored-by: Alan Poulain
* chore: php-cs-fixer update
* fix: only alias if exists for opcache preload
Fixes https://github.com/api-platform/api-platform/issues/2284 (#5110)
Co-authored-by: Liviu Mirea
* chore: php-cs-fixer update (#5118)
* chore: php-cs-fixer update
* chore: php-cs-fixer update
* fix(metadata): upgrade script keep operation name (#5109)
origin: https://github.com/api-platform/core/pull/5105
Co-authored-by: WilliamPeralta
* chore: v2.7.3 changelog
* chore: v3.0.3 changelog
Co-authored-by: helyakin
Co-authored-by: ArnoudThibaut
Co-authored-by: davy-beauzil <38990335+davy-beauzil@users.noreply.github.com>
Co-authored-by: Baptiste Leduc
Co-authored-by: Xavier Laviron
Co-authored-by: Alan Poulain
Co-authored-by: Liviu Cristian Mirea-Ghiban
Co-authored-by: Liviu Mirea
Co-authored-by: WilliamPeralta
---
CHANGELOG.md | 32 ++++
features/graphql/collection.feature | 46 +++++
features/main/default_order.feature | 36 +++-
generate-changelog.sh | 25 +++
src/Doctrine/Odm/Extension/OrderExtension.php | 3 +-
src/Doctrine/Orm/Filter/SearchFilter.php | 6 +-
src/GraphQl/Action/EntrypointAction.php | 6 +-
...NestedOperationResourceMetadataFactory.php | 67 ++++++++
src/GraphQl/Type/FieldsBuilder.php | 71 ++++----
src/Metadata/Operations.php | 7 +-
...butesResourceMetadataCollectionFactory.php | 139 +--------------
...actorResourceMetadataCollectionFactory.php | 25 ++-
...ltersResourceMetadataCollectionFactory.php | 10 +-
.../Factory/OperationDefaultsTrait.php | 160 ++++++++++++++++++
...plateResourceMetadataCollectionFactory.php | 8 +-
src/OpenApi/Factory/OpenApiFactory.php | 3 +-
.../NormalizeOperationNameTrait.php | 3 +-
.../PayloadArgumentResolver.php | 2 +-
.../ApiPlatformExtension.php | 17 +-
.../Resources/config/doctrine_mongodb_odm.xml | 5 +
.../Bundle/Resources/config/doctrine_orm.xml | 6 +
.../Bundle/Resources/config/graphql.xml | 13 +-
src/Symfony/Routing/ApiLoader.php | 5 +
src/Symfony/Routing/SkolemIriConverter.php | 3 +-
src/Test/DoctrineMongoDbOdmTestCase.php | 6 +-
tests/Action/ExceptionActionTest.php | 41 +++--
tests/Behat/DoctrineContext.php | 17 +-
.../Common/Filter/SearchFilterTestTrait.php | 12 ++
.../Odm/Extension/OrderExtensionTest.php | 11 +-
.../Doctrine/Odm/Filter/SearchFilterTest.php | 15 ++
.../Doctrine/Orm/Filter/SearchFilterTest.php | 8 +
.../TestBundle/Document/AbsoluteUrlDummy.php | 2 +-
tests/Fixtures/TestBundle/Document/Answer.php | 4 +-
tests/Fixtures/TestBundle/Document/Book.php | 2 +-
tests/Fixtures/TestBundle/Document/Dummy.php | 4 +-
.../Document/DummyAggregateOffer.php | 4 +-
.../TestBundle/Document/DummyOffer.php | 6 +-
.../TestBundle/Document/DummyProduct.php | 2 +-
.../TestBundle/Document/DummyValidation.php | 2 +-
.../Fixtures/TestBundle/Document/FooDummy.php | 13 ++
.../TestBundle/Document/FourthLevel.php | 12 +-
.../Fixtures/TestBundle/Document/Greeting.php | 2 +-
.../TestBundle/Document/NetworkPathDummy.php | 2 +-
.../Fixtures/TestBundle/Document/Question.php | 4 +-
.../TestBundle/Document/RelatedDummy.php | 14 +-
.../Document/RelatedToDummyFriend.php | 10 +-
.../TestBundle/Document/SlugChildDummy.php | 4 +-
.../TestBundle/Document/SlugParentDummy.php | 4 +-
.../TestBundle/Document/ThirdLevel.php | 10 +-
.../TestBundle/Document/VoDummyInspection.php | 12 +-
.../TestBundle/Entity/AbsoluteUrlDummy.php | 2 +-
tests/Fixtures/TestBundle/Entity/Answer.php | 4 +-
.../Entity/AttributeOnlyOperation.php | 21 +++
.../TestBundle/Entity/AttributeResource.php | 2 +-
.../TestBundle/Entity/AttributeResources.php | 11 +-
tests/Fixtures/TestBundle/Entity/Book.php | 2 +-
tests/Fixtures/TestBundle/Entity/Dummy.php | 4 +-
.../TestBundle/Entity/DummyAggregateOffer.php | 4 +-
.../Fixtures/TestBundle/Entity/DummyOffer.php | 6 +-
.../TestBundle/Entity/DummyProduct.php | 2 +-
.../Entity/DummyToUpgradeProduct.php | 49 ++++++
.../DummyToUpgradeWithOnlyAnnotation.php | 61 +++++++
.../DummyToUpgradeWithOnlyAttribute.php | 49 ++++++
.../TestBundle/Entity/DummyValidation.php | 2 +-
tests/Fixtures/TestBundle/Entity/FooDummy.php | 13 ++
.../TestBundle/Entity/FourthLevel.php | 12 +-
tests/Fixtures/TestBundle/Entity/Greeting.php | 2 +-
.../TestBundle/Entity/NetworkPathDummy.php | 2 +-
tests/Fixtures/TestBundle/Entity/Question.php | 4 +-
.../TestBundle/Entity/RelatedDummy.php | 24 ++-
.../Entity/RelatedToDummyFriend.php | 10 +-
.../TestBundle/Entity/SlugChildDummy.php | 4 +-
.../TestBundle/Entity/SlugParentDummy.php | 4 +-
tests/Fixtures/TestBundle/Entity/SoMany.php | 3 +
.../Fixtures/TestBundle/Entity/ThirdLevel.php | 10 +-
.../TestBundle/Entity/VoDummyInspection.php | 12 +-
...edOperationResourceMetadataFactoryTest.php | 44 +++++
tests/GraphQl/Type/FieldsBuilderTest.php | 137 +++++++++------
tests/GraphQl/Type/TypeBuilderTest.php | 4 +-
tests/HttpCache/VarnishPurgerTest.php | 9 +-
tests/HttpCache/VarnishXKeyPurgerTest.php | 9 +-
.../Command/JsonSchemaGenerateCommandTest.php | 6 +-
.../PropertyMetadataCompatibilityTest.php | 3 +-
.../ResourceMetadataCompatibilityTest.php | 23 ++-
tests/Metadata/Extractor/XmlExtractorTest.php | 6 +-
.../Metadata/Extractor/YamlExtractorTest.php | 8 +-
tests/Metadata/Extractor/xml/valid.xml | 4 +-
tests/Metadata/Extractor/yaml/valid.yaml | 4 +-
...sResourceMetadataCollectionFactoryTest.php | 37 ++--
...eResourceMetadataCollectionFactoryTest.php | 8 +-
.../Pagination/TraversablePaginatorTest.php | 3 +-
.../PayloadArgumentResolverTest.php | 8 +-
.../ApiPlatformExtensionTest.php | 37 ++++
93 files changed, 1128 insertions(+), 462 deletions(-)
create mode 100755 generate-changelog.sh
create mode 100644 src/GraphQl/Metadata/Factory/GraphQlNestedOperationResourceMetadataFactory.php
create mode 100644 src/Metadata/Resource/Factory/OperationDefaultsTrait.php
create mode 100644 tests/Fixtures/TestBundle/Entity/AttributeOnlyOperation.php
create mode 100644 tests/Fixtures/TestBundle/Entity/DummyToUpgradeProduct.php
create mode 100644 tests/Fixtures/TestBundle/Entity/DummyToUpgradeWithOnlyAnnotation.php
create mode 100644 tests/Fixtures/TestBundle/Entity/DummyToUpgradeWithOnlyAttribute.php
create mode 100644 tests/GraphQl/Metadata/Factory/GraphQlNestedOperationResourceMetadataFactoryTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2be83b8dcb3..1921bdef057 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
# Changelog
+## v3.0.3
+
+### Bug fixes
+
+* [176fff2cb](https://github.com/api-platform/core/commit/176fff2cb15efa01b6c898d0442a4f540d4ddeaa) fix(metadata): upgrade script keep operation name (#5109)
+* [1b64ebf6a](https://github.com/api-platform/core/commit/1b64ebf6a438222ae091ec3690063d0fb1b61977) fix: upgrade command remove ApiSubresource attribute (#5049)
+* [27fcdc6b2](https://github.com/api-platform/core/commit/27fcdc6b270d1699e76c37ccda690b8a5ed8b4c9) fix(metadata): deprecate when user decorates in legacy mode (#5091)
+* [310363d56](https://github.com/api-platform/core/commit/310363d56129c94cf4d51977f85486729e582fbc) fix: remove @internal annotation for Operations (#5089)
+* [41bbad94e](https://github.com/api-platform/core/commit/41bbad94e93df49eb4ade0fe1307b20d9cd07102) fix: update yaml extractor test file coding standard (#5068)
+* [44337ddb3](https://github.com/api-platform/core/commit/44337ddb3908d7b05ed75b75325b7941581f575b) fix(graphql): use right nested operation (#5102)
+* [541b738e9](https://github.com/api-platform/core/commit/541b738e942156b711665952b50fbd4f060fcdea) fix(graphql): add clearer error message when TwigBundle is disabled but graphQL clients are enabled (#5064)
+* [59826bbe9](https://github.com/api-platform/core/commit/59826bbe9e246cf839bdc0c4d0d470f54e27b453) fix: only alias if exists for opcache preload
+* [7044c5a1b](https://github.com/api-platform/core/commit/7044c5a1b2895e72f0579d1e788740606f94dece) fix(doctrine): use abitrary index instead of value (#5079)
+* [8250d41a3](https://github.com/api-platform/core/commit/8250d41a38913a17364d617875bb5a90f434ec48) fix(metadata): define a name on a single operation (#5090)
+* [9c19fa171](https://github.com/api-platform/core/commit/9c19fa17110aac7dd39bff827091c00b42a80d4f) fix(metadata): add class key in payload argument resolver (#5067)
+* [a4cd12b2a](https://github.com/api-platform/core/commit/a4cd12b2a73bc0f726c5724de790f885884e6113) fix: uri template should respect rfc 6570 (#5080)
+* [bbeaf7082](https://github.com/api-platform/core/commit/bbeaf7082bba4a019206c3862425cf849d55addd) fix(graphql): always allow to query nested resources (#5112)
+* [c1cb3cd2f](https://github.com/api-platform/core/commit/c1cb3cd2ff32c8b1ee694b0989efeb133fbd8438) Revert "fix(graphql): use right nested operation (#5102)" (#5111)
+
## 3.0.2
* Metadata: generate skolem IRI by default, use `genId: false` to disable **BC**
@@ -54,6 +73,19 @@ Breaking changes:
* Serializer: `skip_null_values` now defaults to `true`
* Metadata: `Patch` is added to the automatic CRUD
+## v2.7.3
+
+### Bug fixes
+
+* [176fff2cb](https://github.com/api-platform/core/commit/176fff2cb15efa01b6c898d0442a4f540d4ddeaa) fix(metadata): upgrade script keep operation name (#5109)
+* [1b64ebf6a](https://github.com/api-platform/core/commit/1b64ebf6a438222ae091ec3690063d0fb1b61977) fix: upgrade command remove ApiSubresource attribute (#5049)
+* [27fcdc6b2](https://github.com/api-platform/core/commit/27fcdc6b270d1699e76c37ccda690b8a5ed8b4c9) fix(metadata): deprecate when user decorates in legacy mode (#5091)
+* [310363d56](https://github.com/api-platform/core/commit/310363d56129c94cf4d51977f85486729e582fbc) fix: remove @internal annotation for Operations (#5089)
+* [41bbad94e](https://github.com/api-platform/core/commit/41bbad94e93df49eb4ade0fe1307b20d9cd07102) fix: update yaml extractor test file coding standard (#5068)
+* [59826bbe9](https://github.com/api-platform/core/commit/59826bbe9e246cf839bdc0c4d0d470f54e27b453) fix: only alias if exists for opcache preload
+* [8250d41a3](https://github.com/api-platform/core/commit/8250d41a38913a17364d617875bb5a90f434ec48) fix(metadata): define a name on a single operation (#5090)
+* [9c19fa171](https://github.com/api-platform/core/commit/9c19fa17110aac7dd39bff827091c00b42a80d4f) fix(metadata): add class key in payload argument resolver (#5067)
+
## 2.7.2
* Metadata: no skolem IRI by default
diff --git a/features/graphql/collection.feature b/features/graphql/collection.feature
index 1540549f7d8..9767505171a 100644
--- a/features/graphql/collection.feature
+++ b/features/graphql/collection.feature
@@ -910,3 +910,49 @@ Feature: GraphQL collection support
Then the response status code should be 200
And the response should be in JSON
And the JSON node "data.fooDummies.collection" should have 1 element
+
+ @createSchema
+ Scenario: Retrieve paginated collections using mixed pagination
+ Given there are 5 fooDummy objects with fake names
+ When I send the following GraphQL request:
+ """
+ {
+ fooDummies(page: 1) {
+ collection {
+ id
+ name
+ soManies(first: 2) {
+ edges {
+ node {
+ content
+ }
+ cursor
+ }
+ pageInfo {
+ startCursor
+ endCursor
+ hasNextPage
+ hasPreviousPage
+ }
+ }
+ }
+ paginationInfo {
+ itemsPerPage
+ lastPage
+ totalCount
+ }
+ }
+ }
+ """
+ Then the response status code should be 200
+ And the response should be in JSON
+ And the JSON node "data.fooDummies.collection" should have 3 elements
+ And the JSON node "data.fooDummies.collection[2].id" should exist
+ And the JSON node "data.fooDummies.collection[2].name" should exist
+ And the JSON node "data.fooDummies.collection[2].soManies" should exist
+ And the JSON node "data.fooDummies.collection[2].soManies.edges" should have 2 elements
+ And the JSON node "data.fooDummies.collection[2].soManies.edges[1].node.content" should be equal to "So many 1"
+ And the JSON node "data.fooDummies.collection[2].soManies.pageInfo.startCursor" should be equal to "MA=="
+ And the JSON node "data.fooDummies.paginationInfo.itemsPerPage" should be equal to the number 3
+ And the JSON node "data.fooDummies.paginationInfo.lastPage" should be equal to the number 2
+ And the JSON node "data.fooDummies.paginationInfo.totalCount" should be equal to the number 5
diff --git a/features/main/default_order.feature b/features/main/default_order.feature
index 91211014315..7458c2456e9 100644
--- a/features/main/default_order.feature
+++ b/features/main/default_order.feature
@@ -79,35 +79,61 @@ Feature: Default order
"@type": "FooDummy",
"id": 5,
"name": "Balbo",
- "dummy": "/dummies/5"
+ "dummy": "/dummies/5",
+ "soManies": [
+ "/so_manies/13",
+ "/so_manies/14",
+ "/so_manies/15"
+ ]
+
},
{
"@id": "/foo_dummies/3",
"@type": "FooDummy",
"id": 3,
"name": "Sthenelus",
- "dummy": "/dummies/3"
+ "dummy": "/dummies/3",
+ "soManies": [
+ "/so_manies/7",
+ "/so_manies/8",
+ "/so_manies/9"
+ ]
},
{
"@id": "/foo_dummies/2",
"@type": "FooDummy",
"id": 2,
"name": "Ephesian",
- "dummy": "/dummies/2"
+ "dummy": "/dummies/2",
+ "soManies": [
+ "/so_manies/4",
+ "/so_manies/5",
+ "/so_manies/6"
+ ]
},
{
"@id": "/foo_dummies/1",
"@type": "FooDummy",
"id": 1,
"name": "Hawsepipe",
- "dummy": "/dummies/1"
+ "dummy": "/dummies/1",
+ "soManies": [
+ "/so_manies/1",
+ "/so_manies/2",
+ "/so_manies/3"
+ ]
},
{
"@id": "/foo_dummies/4",
"@type": "FooDummy",
"id": 4,
"name": "Separativeness",
- "dummy": "/dummies/4"
+ "dummy": "/dummies/4",
+ "soManies": [
+ "/so_manies/10",
+ "/so_manies/11",
+ "/so_manies/12"
+ ]
}
],
"hydra:totalItems": 5,
diff --git a/generate-changelog.sh b/generate-changelog.sh
new file mode 100755
index 00000000000..f2903beb1b3
--- /dev/null
+++ b/generate-changelog.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# usage: generate-changelog.sh previous_tag next_tag
+# example: generate-changelog.sh v2.7.2 v2.7.3 > CHANGELOG.new.md
+log=$(git log "$1..HEAD" --pretty='format:* [%h](https://github.com/api-platform/core/commit/%H) %s' --no-merges)
+
+diff=$(
+printf "# Changelog\n\n"
+printf "## %s\n\n" "$2"
+
+if [[ 0 != $(echo "$log" | grep fix | grep -v chore | wc -l) ]];
+then
+ printf "### Bug fixes\n\n"
+ printf "$log" | grep fix | grep -v chore | sort
+ printf "\n\n"
+fi
+
+if [[ 0 != $(echo "$log" | grep feat | grep -v chore | wc -l) ]];
+then
+ printf "### Features\n\n"
+ printf "$log" | grep feat | grep -v chore | sort
+fi
+)
+
+changelog=$(tail -n+2 CHANGELOG.md)
+printf "%s\n%s" "$diff" "$changelog"
diff --git a/src/Doctrine/Odm/Extension/OrderExtension.php b/src/Doctrine/Odm/Extension/OrderExtension.php
index f3cf5eb3725..77cd255d69b 100644
--- a/src/Doctrine/Odm/Extension/OrderExtension.php
+++ b/src/Doctrine/Odm/Extension/OrderExtension.php
@@ -19,7 +19,6 @@
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Aggregation\Stage\Sort;
use Doctrine\Persistence\ManagerRegistry;
-use OutOfRangeException;
/**
* Applies selected ordering while querying resource collection.
@@ -100,7 +99,7 @@ private function hasSortStage(Builder $aggregationBuilder): bool
// If at least one stage is sort, then it has sorting
return true;
}
- } catch (OutOfRangeException) {
+ } catch (\OutOfRangeException $outOfRangeException) {
// There is no more stages on the aggregation builder
$shouldStop = true;
}
diff --git a/src/Doctrine/Orm/Filter/SearchFilter.php b/src/Doctrine/Orm/Filter/SearchFilter.php
index 3f35706b3b5..8401ebb842a 100644
--- a/src/Doctrine/Orm/Filter/SearchFilter.php
+++ b/src/Doctrine/Orm/Filter/SearchFilter.php
@@ -179,7 +179,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild
$parameters = [];
foreach ($values as $key => $value) {
$keyValueParameter = sprintf('%s_%s', $valueParameter, $key);
- $parameters[$caseSensitive ? $value : strtolower($value)] = $keyValueParameter;
+ $parameters[] = [$caseSensitive ? $value : strtolower($value), $keyValueParameter];
$ors[] = match ($strategy) {
self::STRATEGY_PARTIAL => $queryBuilder->expr()->like(
@@ -209,7 +209,9 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild
}
$queryBuilder->andWhere($queryBuilder->expr()->orX(...$ors));
- array_walk($parameters, $queryBuilder->setParameter(...));
+ foreach ($parameters as $parameter) {
+ $queryBuilder->setParameter($parameter[1], $parameter[0]);
+ }
}
/**
diff --git a/src/GraphQl/Action/EntrypointAction.php b/src/GraphQl/Action/EntrypointAction.php
index f4c9af93e68..dcdd4a1dfd8 100644
--- a/src/GraphQl/Action/EntrypointAction.php
+++ b/src/GraphQl/Action/EntrypointAction.php
@@ -34,7 +34,7 @@ final class EntrypointAction
{
private int $debug;
- public function __construct(private readonly SchemaBuilderInterface $schemaBuilder, private readonly ExecutorInterface $executor, private readonly GraphiQlAction $graphiQlAction, private readonly GraphQlPlaygroundAction $graphQlPlaygroundAction, private readonly NormalizerInterface $normalizer, private readonly ErrorHandlerInterface $errorHandler, bool $debug = false, private readonly bool $graphiqlEnabled = false, private readonly bool $graphQlPlaygroundEnabled = false, private readonly ?string $defaultIde = null)
+ public function __construct(private readonly SchemaBuilderInterface $schemaBuilder, private readonly ExecutorInterface $executor, private readonly ?GraphiQlAction $graphiQlAction, private readonly ?GraphQlPlaygroundAction $graphQlPlaygroundAction, private readonly NormalizerInterface $normalizer, private readonly ErrorHandlerInterface $errorHandler, bool $debug = false, private readonly bool $graphiqlEnabled = false, private readonly bool $graphQlPlaygroundEnabled = false, private readonly ?string $defaultIde = null)
{
$this->debug = $debug ? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE : DebugFlag::NONE;
}
@@ -43,11 +43,11 @@ public function __invoke(Request $request): Response
{
try {
if ($request->isMethod('GET') && 'html' === $request->getRequestFormat()) {
- if ('graphiql' === $this->defaultIde && $this->graphiqlEnabled) {
+ if ('graphiql' === $this->defaultIde && $this->graphiqlEnabled && $this->graphiQlAction) {
return ($this->graphiQlAction)($request);
}
- if ('graphql-playground' === $this->defaultIde && $this->graphQlPlaygroundEnabled) {
+ if ('graphql-playground' === $this->defaultIde && $this->graphQlPlaygroundEnabled && $this->graphQlPlaygroundAction) {
return ($this->graphQlPlaygroundAction)($request);
}
}
diff --git a/src/GraphQl/Metadata/Factory/GraphQlNestedOperationResourceMetadataFactory.php b/src/GraphQl/Metadata/Factory/GraphQlNestedOperationResourceMetadataFactory.php
new file mode 100644
index 00000000000..05163b0c2b7
--- /dev/null
+++ b/src/GraphQl/Metadata/Factory/GraphQlNestedOperationResourceMetadataFactory.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\GraphQl\Metadata\Factory;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Resource\Factory\OperationDefaultsTrait;
+use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
+use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
+
+final class GraphQlNestedOperationResourceMetadataFactory implements ResourceMetadataCollectionFactoryInterface
+{
+ use OperationDefaultsTrait;
+
+ public function __construct(array $defaults, private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null, ?LoggerInterface $logger = null)
+ {
+ $this->defaults = $defaults;
+ $this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter();
+ $this->logger = $logger ?? new NullLogger();
+ }
+
+ public function create(string $resourceClass): ResourceMetadataCollection
+ {
+ $resourceMetadataCollection = new ResourceMetadataCollection($resourceClass);
+
+ if ($this->decorated) {
+ $resourceMetadataCollection = $this->decorated->create($resourceClass);
+ }
+
+ if (0 < \count($resourceMetadataCollection)) {
+ return $resourceMetadataCollection;
+ }
+
+ $shortName = (false !== $pos = strrpos($resourceClass, '\\')) ? substr($resourceClass, $pos + 1) : $resourceClass;
+
+ $apiResource = new ApiResource(
+ class: $resourceClass,
+ shortName: $shortName
+ );
+
+ if (class_exists($resourceClass)) {
+ $refl = new \ReflectionClass($resourceClass);
+ $attribute = $refl->getAttributes(ApiResource::class)[0] ?? null;
+ $attributeInstance = $attribute?->newInstance();
+ if ($filters = $attributeInstance?->getFilters()) {
+ $apiResource = $apiResource->withFilters($filters);
+ }
+ }
+
+ $resourceMetadataCollection[0] = $this->addDefaultGraphQlOperations($apiResource);
+
+ return $resourceMetadataCollection;
+ }
+}
diff --git a/src/GraphQl/Type/FieldsBuilder.php b/src/GraphQl/Type/FieldsBuilder.php
index 8817899c8ef..d64f1cb800b 100644
--- a/src/GraphQl/Type/FieldsBuilder.php
+++ b/src/GraphQl/Type/FieldsBuilder.php
@@ -20,9 +20,7 @@
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Operation;
use ApiPlatform\Metadata\GraphQl\Query;
-use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Subscription;
-use ApiPlatform\Metadata\Operation as AbstractOperation;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
@@ -47,7 +45,7 @@
*/
final class FieldsBuilder implements FieldsBuilderInterface
{
- public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly TypesContainerInterface $typesContainer, private readonly TypeBuilderInterface $typeBuilder, private readonly TypeConverterInterface $typeConverter, private readonly ResolverFactoryInterface $itemResolverFactory, private readonly ResolverFactoryInterface $collectionResolverFactory, private readonly ResolverFactoryInterface $itemMutationResolverFactory, private readonly ResolverFactoryInterface $itemSubscriptionResolverFactory, private readonly ContainerInterface $filterLocator, private readonly Pagination $pagination, private readonly ?NameConverterInterface $nameConverter, private readonly string $nestingSeparator)
+ public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly TypesContainerInterface $typesContainer, private readonly TypeBuilderInterface $typeBuilder, private readonly TypeConverterInterface $typeConverter, private readonly ResolverFactoryInterface $itemResolverFactory, private readonly ResolverFactoryInterface $collectionResolverFactory, private readonly ResolverFactoryInterface $itemMutationResolverFactory, private readonly ResolverFactoryInterface $itemSubscriptionResolverFactory, private readonly ContainerInterface $filterLocator, private readonly Pagination $pagination, private readonly ?NameConverterInterface $nameConverter, private readonly string $nestingSeparator, private readonly ?ResourceMetadataCollectionFactoryInterface $graphQlNestedOperationResourceMetadataFactory = null)
{
}
@@ -256,7 +254,23 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
$resourceClass = $type->getClassName();
}
- $graphqlType = $this->convertType($type, $input, $rootOperation, $resourceClass ?? '', $rootResource, $property, $depth, $forceNullable);
+ $resourceOperation = $rootOperation;
+ if ($resourceClass && $rootOperation->getClass() && $this->resourceClassResolver->isResourceClass($resourceClass) && $rootOperation->getClass() !== $resourceClass) {
+ $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
+ try {
+ $resourceOperation = $resourceMetadataCollection->getOperation($isCollectionType ? 'collection_query' : 'item_query');
+ } catch (OperationNotFoundException) {
+ // If there is no query operation for a nested resource we force one to exist
+ $nestedResourceMetadataCollection = $this->graphQlNestedOperationResourceMetadataFactory->create($resourceClass);
+ $resourceOperation = $nestedResourceMetadataCollection->getOperation($isCollectionType ? 'collection_query' : 'item_query');
+ }
+ }
+
+ if (!$resourceOperation instanceof Operation) {
+ throw new \LogicException('The resource operation should be a GraphQL operation.');
+ }
+
+ $graphqlType = $this->convertType($type, $input, $resourceOperation, $rootOperation, $resourceClass ?? '', $rootResource, $property, $depth, $forceNullable);
$graphqlWrappedType = $graphqlType instanceof WrappingType ? $graphqlType->getWrappedType(true) : $graphqlType;
$isStandardGraphqlType = \in_array($graphqlWrappedType, GraphQLType::getStandardTypes(), true);
@@ -271,43 +285,22 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
$args = [];
- $resolverOperation = $rootOperation;
-
- if ($resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass) && $rootOperation->getClass() !== $resourceClass) {
- $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
- $resolverOperation = $resourceMetadataCollection->getOperation(null, $isCollectionType);
-
- if (!$resolverOperation instanceof Operation) {
- $resolverOperation = ($isCollectionType ? new QueryCollection() : new Query())->withOperation($resolverOperation);
- }
- }
-
if (!$input && !$rootOperation instanceof Mutation && !$rootOperation instanceof Subscription && !$isStandardGraphqlType && $isCollectionType) {
- if ($this->pagination->isGraphQlEnabled($rootOperation)) {
- $args = $this->getGraphQlPaginationArgs($rootOperation);
- }
-
- // Find the collection operation to get filters, there might be a smarter way to do this
- $operation = null;
- if (!empty($resourceClass)) {
- $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
- try {
- $operation = $resourceMetadataCollection->getOperation(null, true);
- } catch (OperationNotFoundException) {
- }
+ if ($this->pagination->isGraphQlEnabled($resourceOperation)) {
+ $args = $this->getGraphQlPaginationArgs($resourceOperation);
}
- $args = $this->getFilterArgs($args, $resourceClass, $rootResource, $rootOperation, $property, $depth, $operation);
+ $args = $this->getFilterArgs($args, $resourceClass, $rootResource, $resourceOperation, $rootOperation, $property, $depth);
}
if ($isStandardGraphqlType || $input) {
$resolve = null;
} elseif (($rootOperation instanceof Mutation || $rootOperation instanceof Subscription) && $depth <= 0) {
- $resolve = $rootOperation instanceof Mutation ? ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $resolverOperation) : ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $resolverOperation);
+ $resolve = $rootOperation instanceof Mutation ? ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $resourceOperation) : ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $resourceOperation);
} elseif ($this->typeBuilder->isCollection($type)) {
- $resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $resolverOperation);
+ $resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $resourceOperation);
} else {
- $resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resolverOperation);
+ $resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resourceOperation);
}
return [
@@ -368,13 +361,13 @@ private function getGraphQlPaginationArgs(Operation $queryOperation): array
return $args;
}
- private function getFilterArgs(array $args, ?string $resourceClass, string $rootResource, Operation $rootOperation, ?string $property, int $depth, ?AbstractOperation $operation = null): array
+ private function getFilterArgs(array $args, ?string $resourceClass, string $rootResource, Operation $resourceOperation, Operation $rootOperation, ?string $property, int $depth): array
{
- if (null === $operation || null === $resourceClass) {
+ if (null === $resourceClass) {
return $args;
}
- foreach ($operation->getFilters() ?? [] as $filterId) {
+ foreach ($resourceOperation->getFilters() ?? [] as $filterId) {
if (!$this->filterLocator->has($filterId)) {
continue;
}
@@ -382,7 +375,7 @@ private function getFilterArgs(array $args, ?string $resourceClass, string $root
foreach ($this->filterLocator->get($filterId)->getDescription($resourceClass) as $key => $value) {
$nullable = isset($value['required']) ? !$value['required'] : true;
$filterType = \in_array($value['type'], Type::$builtinTypes, true) ? new Type($value['type'], $nullable) : new Type('object', $nullable, $value['type']);
- $graphqlFilterType = $this->convertType($filterType, false, $rootOperation, $resourceClass, $rootResource, $property, $depth);
+ $graphqlFilterType = $this->convertType($filterType, false, $resourceOperation, $rootOperation, $resourceClass, $rootResource, $property, $depth);
if (str_ends_with($key, '[]')) {
$graphqlFilterType = GraphQLType::listOf($graphqlFilterType);
@@ -399,14 +392,14 @@ private function getFilterArgs(array $args, ?string $resourceClass, string $root
array_walk_recursive($parsed, static function (&$value) use ($graphqlFilterType): void {
$value = $graphqlFilterType;
});
- $args = $this->mergeFilterArgs($args, $parsed, $operation, $key);
+ $args = $this->mergeFilterArgs($args, $parsed, $resourceOperation, $key);
}
}
return $this->convertFilterArgsToTypes($args);
}
- private function mergeFilterArgs(array $args, array $parsed, ?AbstractOperation $operation = null, string $original = ''): array
+ private function mergeFilterArgs(array $args, array $parsed, ?Operation $operation = null, string $original = ''): array
{
foreach ($parsed as $key => $value) {
// Never override keys that cannot be merged
@@ -470,7 +463,7 @@ private function convertFilterArgsToTypes(array $args): array
*
* @throws InvalidTypeException
*/
- private function convertType(Type $type, bool $input, Operation $rootOperation, string $resourceClass, string $rootResource, ?string $property, int $depth, bool $forceNullable = false): GraphQLType|ListOfType|NonNull
+ private function convertType(Type $type, bool $input, Operation $resourceOperation, Operation $rootOperation, string $resourceClass, string $rootResource, ?string $property, int $depth, bool $forceNullable = false): GraphQLType|ListOfType|NonNull
{
$graphqlType = $this->typeConverter->convertType($type, $input, $rootOperation, $resourceClass, $rootResource, $property, $depth);
@@ -487,7 +480,7 @@ private function convertType(Type $type, bool $input, Operation $rootOperation,
}
if ($this->typeBuilder->isCollection($type)) {
- return $this->pagination->isGraphQlEnabled($rootOperation) && !$input ? $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $rootOperation) : GraphQLType::listOf($graphqlType);
+ return $this->pagination->isGraphQlEnabled($resourceOperation) && !$input ? $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $resourceOperation) : GraphQLType::listOf($graphqlType);
}
return $forceNullable || !$graphqlType instanceof NullableType || $type->isNullable() || ($rootOperation instanceof Mutation && 'update' === $rootOperation->getName())
diff --git a/src/Metadata/Operations.php b/src/Metadata/Operations.php
index cdbd4fe6bf3..5565bc99bd0 100644
--- a/src/Metadata/Operations.php
+++ b/src/Metadata/Operations.php
@@ -13,11 +13,6 @@
namespace ApiPlatform\Metadata;
-use RuntimeException;
-
-/**
- * @internal
- */
final class Operations implements \IteratorAggregate, \Countable
{
private array $operations = [];
@@ -73,7 +68,7 @@ public function remove(string $key): self
}
}
- throw new RuntimeException(sprintf('Could not remove operation "%s".', $key));
+ throw new \RuntimeException(sprintf('Could not remove operation "%s".', $key));
}
public function has(string $key): bool
diff --git a/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php
index 9255c236fa8..9b125aed5c3 100644
--- a/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php
+++ b/src/Metadata/Resource/Factory/AttributesResourceMetadataCollectionFactory.php
@@ -14,20 +14,12 @@
namespace ApiPlatform\Metadata\Resource\Factory;
use ApiPlatform\Exception\ResourceClassNotFoundException;
-use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
-use ApiPlatform\Metadata\GraphQl\DeleteMutation;
-use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
-use ApiPlatform\Metadata\GraphQl\Query;
-use ApiPlatform\Metadata\GraphQl\QueryCollection;
-use ApiPlatform\Metadata\GraphQl\Subscription;
use ApiPlatform\Metadata\HttpOperation;
-use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
@@ -45,12 +37,12 @@
*/
final class AttributesResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
- private readonly LoggerInterface $logger;
- private readonly CamelCaseToSnakeCaseNameConverter $camelCaseToSnakeCaseNameConverter;
+ use OperationDefaultsTrait;
- public function __construct(private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null, LoggerInterface $logger = null, private readonly array $defaults = [], private readonly bool $graphQlEnabled = false)
+ public function __construct(private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null, LoggerInterface $logger = null, array $defaults = [], private readonly bool $graphQlEnabled = false)
{
$this->logger = $logger ?? new NullLogger();
+ $this->defaults = $defaults;
$this->camelCaseToSnakeCaseNameConverter = new CamelCaseToSnakeCaseNameConverter();
}
@@ -164,115 +156,6 @@ private function buildResourceOperations(array $attributes, string $resourceClas
return $resources;
}
- private function getOperationWithDefaults(ApiResource $resource, Operation $operation, bool $generated = false): array
- {
- // Inherit from resource defaults
- foreach (get_class_methods($resource) as $methodName) {
- if (!str_starts_with($methodName, 'get')) {
- continue;
- }
-
- if (!method_exists($operation, $methodName) || null !== $operation->{$methodName}()) {
- continue;
- }
-
- if (null === ($value = $resource->{$methodName}())) {
- continue;
- }
-
- $operation = $operation->{'with'.substr($methodName, 3)}($value);
- }
-
- $operation = $operation->withExtraProperties(array_merge(
- $resource->getExtraProperties(),
- $operation->getExtraProperties(),
- $generated ? ['generated_operation' => true] : []
- ));
-
- // Add global defaults attributes to the operation
- $operation = $this->addGlobalDefaults($operation);
-
- if ($operation instanceof GraphQlOperation) {
- if (!$operation->getName()) {
- throw new RuntimeException('No GraphQL operation name.');
- }
-
- if ($operation instanceof Mutation) {
- $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}."));
- }
-
- return [$operation->getName(), $operation];
- }
-
- if (!$operation instanceof HttpOperation) {
- throw new RuntimeException(sprintf('Operation should be an instance of "%s"', HttpOperation::class));
- }
-
- if ($operation->getRouteName()) {
- /** @var HttpOperation $operation */
- $operation = $operation->withName($operation->getRouteName());
- }
-
- // Check for name conflict
- if ($operation->getName()) {
- if (null !== $resource->getOperations() && !$resource->getOperations()->has($operation->getName())) {
- return [$operation->getName(), $operation];
- }
-
- $this->logger->warning(sprintf('The operation "%s" already exists on the resource "%s", pick a different name or leave it empty. In the meantime we will generate a unique name.', $operation->getName(), $resource->getClass()));
- /** @var HttpOperation $operation */
- $operation = $operation->withName('');
- }
-
- return [
- sprintf(
- '_api_%s_%s%s',
- $operation->getUriTemplate() ?: $operation->getShortName(),
- strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET),
- $operation instanceof CollectionOperationInterface ? '_collection' : '',
- ),
- $operation,
- ];
- }
-
- private function addGlobalDefaults(ApiResource|HttpOperation|GraphQlOperation $operation): ApiResource|HttpOperation|GraphQlOperation
- {
- $extraProperties = [];
- foreach ($this->defaults as $key => $value) {
- $upperKey = ucfirst($this->camelCaseToSnakeCaseNameConverter->denormalize($key));
- $getter = 'get'.$upperKey;
-
- if (!method_exists($operation, $getter)) {
- if (!isset($extraProperties[$key])) {
- $extraProperties[$key] = $value;
- }
- } else {
- $currentValue = $operation->{$getter}();
-
- if (\is_array($currentValue) && $currentValue) {
- $operation = $operation->{'with'.$upperKey}(array_merge($value, $currentValue));
- }
-
- if (null !== $currentValue) {
- continue;
- }
-
- $operation = $operation->{'with'.$upperKey}($value);
- }
- }
-
- return $operation->withExtraProperties(array_merge($extraProperties, $operation->getExtraProperties()));
- }
-
- private function getResourceWithDefaults(string $resourceClass, string $shortName, ApiResource $resource): ApiResource
- {
- $resource = $resource
- ->withShortName($resource->getShortName() ?? $shortName)
- ->withClass($resourceClass);
-
- return $this->addGlobalDefaults($resource);
- }
-
private function hasResourceAttributes(\ReflectionClass $reflectionClass): bool
{
foreach ($reflectionClass->getAttributes() as $attribute) {
@@ -303,22 +186,6 @@ private function hasSameOperation(ApiResource $resource, string $operationClass,
return false;
}
- private function addDefaultGraphQlOperations(ApiResource $resource): ApiResource
- {
- $graphQlOperations = [];
- foreach ([new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')] as $i => $operation) {
- [$key, $operation] = $this->getOperationWithDefaults($resource, $operation);
- $graphQlOperations[$key] = $operation;
- }
-
- if ($resource->getMercure()) {
- [$key, $operation] = $this->getOperationWithDefaults($resource, (new Subscription())->withDescription("Subscribes to the update event of a {$operation->getShortName()}."));
- $graphQlOperations[$key] = $operation;
- }
-
- return $resource->withGraphQlOperations($graphQlOperations);
- }
-
private function getDefaultHttpOperations($resource): iterable
{
$post = new Post();
diff --git a/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php
index c44358897ca..86c154f7bc4 100644
--- a/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php
+++ b/src/Metadata/Resource/Factory/ExtractorResourceMetadataCollectionFactory.php
@@ -19,7 +19,12 @@
use ApiPlatform\Metadata\Extractor\ResourceExtractorInterface;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\GraphQl\DeleteMutation;
+use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\HttpOperation;
+use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
@@ -88,9 +93,7 @@ private function buildResources(array $nodes, string $resourceClass): array
}
}
- if (isset($node['graphQlOperations'])) {
- $resource = $resource->withGraphQlOperations($this->buildGraphQlOperations($node['graphQlOperations'], $resource));
- }
+ $resource = $resource->withGraphQlOperations($this->buildGraphQlOperations($node['graphQlOperations'] ?? null, $resource));
$resources[] = $resource->withOperations(new Operations($this->buildOperations($node['operations'] ?? null, $resource)));
}
@@ -148,6 +151,20 @@ private function buildGraphQlOperations(?array $data, ApiResource $resource): ar
{
$operations = [];
+ if (null === $data) {
+ foreach ([new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')] as $operation) {
+ $operation = $this->getOperationWithDefaults($resource, $operation);
+
+ if ($operation instanceof Mutation) {
+ $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}."));
+ }
+
+ $operations[$operation->getName()] = $operation;
+ }
+
+ return $operations;
+ }
+
foreach ($data as $attributes) {
/** @var HttpOperation $operation */
$operation = (new $attributes['graphql_operation_class']())->withShortName($resource->getShortName());
@@ -175,7 +192,7 @@ private function buildGraphQlOperations(?array $data, ApiResource $resource): ar
return $operations;
}
- private function getOperationWithDefaults(ApiResource $resource, HttpOperation $operation): HttpOperation
+ private function getOperationWithDefaults(ApiResource $resource, Operation $operation): Operation
{
foreach (($this->defaults['attributes'] ?? []) as $key => $value) {
$key = $this->camelCaseToSnakeCaseNameConverter->denormalize($key);
diff --git a/src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php
index bb68448c4c2..10a711fe71a 100644
--- a/src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php
+++ b/src/Metadata/Resource/Factory/FiltersResourceMetadataCollectionFactory.php
@@ -50,17 +50,21 @@ public function create(string $resourceClass): ResourceMetadataCollection
$filters = array_keys($this->readFilterAttributes($reflectionClass));
foreach ($resourceMetadataCollection as $i => $resource) {
- foreach ($operations = $resource->getOperations() as $operationName => $operation) {
+ foreach ($operations = $resource->getOperations() ?? [] as $operationName => $operation) {
$operations->add($operationName, $operation->withFilters(array_unique(array_merge($resource->getFilters() ?? [], $operation->getFilters() ?? [], $filters))));
}
- $resourceMetadataCollection[$i] = $resource->withOperations($operations);
+ if ($operations) {
+ $resourceMetadataCollection[$i] = $resource->withOperations($operations);
+ }
foreach ($graphQlOperations = $resource->getGraphQlOperations() ?? [] as $operationName => $operation) {
$graphQlOperations[$operationName] = $operation->withFilters(array_unique(array_merge($resource->getFilters() ?? [], $operation->getFilters() ?? [], $filters)));
}
- $resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
+ if ($graphQlOperations) {
+ $resourceMetadataCollection[$i] = $resource->withGraphQlOperations($graphQlOperations);
+ }
}
return $resourceMetadataCollection;
diff --git a/src/Metadata/Resource/Factory/OperationDefaultsTrait.php b/src/Metadata/Resource/Factory/OperationDefaultsTrait.php
new file mode 100644
index 00000000000..cdb91e879a7
--- /dev/null
+++ b/src/Metadata/Resource/Factory/OperationDefaultsTrait.php
@@ -0,0 +1,160 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Metadata\Resource\Factory;
+
+use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\CollectionOperationInterface;
+use ApiPlatform\Metadata\GraphQl\DeleteMutation;
+use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
+use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\GraphQl\QueryCollection;
+use ApiPlatform\Metadata\GraphQl\Subscription;
+use ApiPlatform\Metadata\HttpOperation;
+use ApiPlatform\Metadata\Operation;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
+
+trait OperationDefaultsTrait
+{
+ private CamelCaseToSnakeCaseNameConverter $camelCaseToSnakeCaseNameConverter;
+ private array $defaults = [];
+ private LoggerInterface $logger;
+
+ private function addGlobalDefaults(ApiResource|Operation $operation): ApiResource|Operation
+ {
+ $extraProperties = [];
+ foreach ($this->defaults as $key => $value) {
+ $upperKey = ucfirst($this->camelCaseToSnakeCaseNameConverter->denormalize($key));
+ $getter = 'get'.$upperKey;
+
+ if (!method_exists($operation, $getter)) {
+ if (!isset($extraProperties[$key])) {
+ $extraProperties[$key] = $value;
+ }
+ } else {
+ $currentValue = $operation->{$getter}();
+
+ if (\is_array($currentValue) && $currentValue) {
+ $operation = $operation->{'with'.$upperKey}(array_merge($value, $currentValue));
+ }
+
+ if (null !== $currentValue) {
+ continue;
+ }
+
+ $operation = $operation->{'with'.$upperKey}($value);
+ }
+ }
+
+ return $operation->withExtraProperties(array_merge($extraProperties, $operation->getExtraProperties()));
+ }
+
+ private function getResourceWithDefaults(string $resourceClass, string $shortName, ApiResource $resource): ApiResource
+ {
+ $resource = $resource
+ ->withShortName($resource->getShortName() ?? $shortName)
+ ->withClass($resourceClass);
+
+ return $this->addGlobalDefaults($resource);
+ }
+
+ private function addDefaultGraphQlOperations(ApiResource $resource): ApiResource
+ {
+ $graphQlOperations = [];
+ foreach ([new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')] as $i => $operation) {
+ [$key, $operation] = $this->getOperationWithDefaults($resource, $operation);
+ $graphQlOperations[$key] = $operation;
+ }
+
+ if ($resource->getMercure()) {
+ [$key, $operation] = $this->getOperationWithDefaults($resource, (new Subscription())->withDescription("Subscribes to the update event of a {$operation->getShortName()}."));
+ $graphQlOperations[$key] = $operation;
+ }
+
+ return $resource->withGraphQlOperations($graphQlOperations);
+ }
+
+ private function getOperationWithDefaults(ApiResource $resource, Operation $operation, bool $generated = false): array
+ {
+ // Inherit from resource defaults
+ foreach (get_class_methods($resource) as $methodName) {
+ if (!str_starts_with($methodName, 'get')) {
+ continue;
+ }
+
+ if (!method_exists($operation, $methodName) || null !== $operation->{$methodName}()) {
+ continue;
+ }
+
+ if (null === ($value = $resource->{$methodName}())) {
+ continue;
+ }
+
+ $operation = $operation->{'with'.substr($methodName, 3)}($value);
+ }
+
+ $operation = $operation->withExtraProperties(array_merge(
+ $resource->getExtraProperties(),
+ $operation->getExtraProperties(),
+ $generated ? ['generated_operation' => true] : []
+ ));
+
+ // Add global defaults attributes to the operation
+ $operation = $this->addGlobalDefaults($operation);
+
+ if ($operation instanceof GraphQlOperation) {
+ if (!$operation->getName()) {
+ throw new RuntimeException('No GraphQL operation name.');
+ }
+
+ if ($operation instanceof Mutation) {
+ $operation = $operation->withDescription(ucfirst("{$operation->getName()}s a {$resource->getShortName()}."));
+ }
+
+ return [$operation->getName(), $operation];
+ }
+
+ if (!$operation instanceof HttpOperation) {
+ throw new RuntimeException(sprintf('Operation should be an instance of "%s"', HttpOperation::class));
+ }
+
+ if ($operation->getRouteName()) {
+ /** @var HttpOperation $operation */
+ $operation = $operation->withName($operation->getRouteName());
+ }
+
+ // Check for name conflict
+ if ($operation->getName() && null !== ($operations = $resource->getOperations())) {
+ if (!$operations->has($operation->getName())) {
+ return [$operation->getName(), $operation];
+ }
+
+ $this->logger->warning(sprintf('The operation "%s" already exists on the resource "%s", pick a different name or leave it empty. In the meantime we will generate a unique name.', $operation->getName(), $resource->getClass()));
+ /** @var HttpOperation $operation */
+ $operation = $operation->withName('');
+ }
+
+ return [
+ sprintf(
+ '_api_%s_%s%s',
+ $operation->getUriTemplate() ?: $operation->getShortName(),
+ strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET),
+ $operation instanceof CollectionOperationInterface ? '_collection' : '',
+ ),
+ $operation,
+ ];
+ }
+}
diff --git a/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php
index 1ceacfc5636..7b4a5ca7c70 100644
--- a/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php
+++ b/src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php
@@ -105,7 +105,7 @@ private function generateUriTemplate(HttpOperation $operation): string
}
}
- return sprintf('%s.{_format}', $uriTemplate);
+ return sprintf('%s{._format}', $uriTemplate);
}
private function configureUriVariables(ApiResource|HttpOperation $operation): ApiResource|HttpOperation
@@ -144,8 +144,12 @@ private function configureUriVariables(ApiResource|HttpOperation $operation): Ap
}
$operation = $operation->withUriVariables($uriVariables);
+ if (str_ends_with($uriTemplate, '{._format}')) {
+ $uriTemplate = substr($uriTemplate, 0, -10);
+ }
+
$route = (new Route($uriTemplate))->compile();
- $variables = array_filter($route->getPathVariables(), fn ($v): bool => '_format' !== $v);
+ $variables = $route->getPathVariables();
if (\count($variables) !== \count($uriVariables)) {
$newUriVariables = [];
diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php
index 34dc68f2cfe..75f1d2ee8bb 100644
--- a/src/OpenApi/Factory/OpenApiFactory.php
+++ b/src/OpenApi/Factory/OpenApiFactory.php
@@ -333,7 +333,8 @@ private function flattenMimeTypes(array $responseFormats): array
*/
private function getPath(string $path): string
{
- if (str_ends_with($path, '.{_format}')) {
+ // Handle either API Platform's URI Template (rfc6570) or Symfony's route
+ if (str_ends_with($path, '{._format}') || str_ends_with($path, '.{_format}')) {
$path = substr($path, 0, -10);
}
diff --git a/src/OpenApi/Serializer/NormalizeOperationNameTrait.php b/src/OpenApi/Serializer/NormalizeOperationNameTrait.php
index 53bb0b22a10..2d37d93b8f1 100644
--- a/src/OpenApi/Serializer/NormalizeOperationNameTrait.php
+++ b/src/OpenApi/Serializer/NormalizeOperationNameTrait.php
@@ -22,7 +22,6 @@ trait NormalizeOperationNameTrait
{
private function normalizeOperationName(string $operationName): string
{
- // .{_format} is related to the symfony router
- return preg_replace('/^_/', '', str_replace(['/', '.{_format}', '{', '}'], ['', '', '_', ''], $operationName));
+ return preg_replace('/^_/', '', str_replace(['/', '{._format}', '{', '}'], ['', '', '_', ''], $operationName));
}
}
diff --git a/src/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php b/src/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php
index 97cd93678b3..bf11ad4d37b 100644
--- a/src/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php
+++ b/src/Symfony/Bundle/ArgumentResolver/PayloadArgumentResolver.php
@@ -71,6 +71,6 @@ private function getExpectedInputClass(Request $request): ?string
$context = $this->serializationContextBuilder->createFromRequest($request, false, RequestAttributesExtractor::extractAttributes($request));
- return $context['input'] ?? $context['resource_class'];
+ return $context['input']['class'] ?? $context['resource_class'] ?? null;
}
}
diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
index fcc023abb68..3b1c8faeb5a 100644
--- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
+++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
@@ -53,6 +53,7 @@
use Symfony\Component\Uid\AbstractUid;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Yaml\Yaml;
+use Twig\Environment;
/**
* The extension of this bundle.
@@ -462,9 +463,12 @@ private function registerGraphQlConfiguration(ContainerBuilder $container, array
{
$enabled = $this->isConfigEnabled($container, $config['graphql']);
+ $graphiqlEnabled = $enabled && $this->isConfigEnabled($container, $config['graphql']['graphiql']);
+ $graphqlPlayGroundEnabled = $enabled && $this->isConfigEnabled($container, $config['graphql']['graphql_playground']);
+
$container->setParameter('api_platform.graphql.enabled', $enabled);
- $container->setParameter('api_platform.graphql.graphiql.enabled', $enabled && $this->isConfigEnabled($container, $config['graphql']['graphiql']));
- $container->setParameter('api_platform.graphql.graphql_playground.enabled', $enabled && $this->isConfigEnabled($container, $config['graphql']['graphql_playground']));
+ $container->setParameter('api_platform.graphql.graphiql.enabled', $graphiqlEnabled);
+ $container->setParameter('api_platform.graphql.graphql_playground.enabled', $graphqlPlayGroundEnabled);
$container->setParameter('api_platform.graphql.collection.pagination', $config['graphql']['collection']['pagination']);
if (!$enabled) {
@@ -476,6 +480,15 @@ private function registerGraphQlConfiguration(ContainerBuilder $container, array
$loader->load('graphql.xml');
+ // @phpstan-ignore-next-line because PHPStan uses the container of the test env cache and in test the parameter kernel.bundles always contains the key TwigBundle
+ if (!class_exists(Environment::class) || !isset($container->getParameter('kernel.bundles')['TwigBundle'])) {
+ if ($graphiqlEnabled || $graphqlPlayGroundEnabled) {
+ throw new RuntimeException(sprintf('GraphiQL and GraphQL Playground interfaces depend on Twig. Please activate TwigBundle for the %s environnement or disable GraphiQL and GraphQL Playground.', $container->getParameter('kernel.environment')));
+ }
+ $container->removeDefinition('api_platform.graphql.action.graphiql');
+ $container->removeDefinition('api_platform.graphql.action.graphql_playground');
+ }
+
$container->registerForAutoconfiguration(QueryItemResolverInterface::class)
->addTag('api_platform.graphql.query_resolver');
$container->registerForAutoconfiguration(QueryCollectionResolverInterface::class)
diff --git a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml
index 4cecc9d339a..29f9a7a761f 100644
--- a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml
+++ b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml
@@ -156,6 +156,11 @@
+
+
+
+
+
diff --git a/src/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Symfony/Bundle/Resources/config/doctrine_orm.xml
index a46540ab0a9..0b6949bb9b8 100644
--- a/src/Symfony/Bundle/Resources/config/doctrine_orm.xml
+++ b/src/Symfony/Bundle/Resources/config/doctrine_orm.xml
@@ -166,6 +166,12 @@
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/Resources/config/graphql.xml b/src/Symfony/Bundle/Resources/config/graphql.xml
index debaefb508c..0f713b273b9 100644
--- a/src/Symfony/Bundle/Resources/config/graphql.xml
+++ b/src/Symfony/Bundle/Resources/config/graphql.xml
@@ -49,8 +49,8 @@
-
-
+
+
%kernel.debug%
@@ -133,6 +133,7 @@
%api_platform.graphql.nesting_separator%
+
@@ -274,6 +275,14 @@
+
+
+ %api_platform.defaults%
+
+
+
+
+
diff --git a/src/Symfony/Routing/ApiLoader.php b/src/Symfony/Routing/ApiLoader.php
index 923a9b50110..a9b636e063c 100644
--- a/src/Symfony/Routing/ApiLoader.php
+++ b/src/Symfony/Routing/ApiLoader.php
@@ -75,6 +75,11 @@ public function load(mixed $data, string $type = null): RouteCollection
$path = str_replace(sprintf('{%s}', $parameterName), $expandedValue, $path);
}
+ // Within Symfony .{_format} is a special parameter but the rfc6570 specifies label expansion with a dot operator
+ if (str_ends_with($path, '{._format}')) {
+ $path = str_replace('{._format}', '.{_format}', $path);
+ }
+
if (($controller = $operation->getController()) && !$this->container->has($controller)) {
throw new RuntimeException(sprintf('There is no builtin action for the "%s" operation. You need to define the controller yourself.', $operationName));
}
diff --git a/src/Symfony/Routing/SkolemIriConverter.php b/src/Symfony/Routing/SkolemIriConverter.php
index 051efeccb4e..c11facaa008 100644
--- a/src/Symfony/Routing/SkolemIriConverter.php
+++ b/src/Symfony/Routing/SkolemIriConverter.php
@@ -17,7 +17,6 @@
use ApiPlatform\Api\UrlGeneratorInterface;
use ApiPlatform\Exception\ItemNotFoundException;
use ApiPlatform\Metadata\Operation;
-use SplObjectStorage;
use Symfony\Component\Routing\RouterInterface;
/**
@@ -36,7 +35,7 @@ final class SkolemIriConverter implements IriConverterInterface
public function __construct(RouterInterface $router)
{
$this->router = $router;
- $this->objectHashMap = new SplObjectStorage();
+ $this->objectHashMap = new \SplObjectStorage();
}
/**
diff --git a/src/Test/DoctrineMongoDbOdmTestCase.php b/src/Test/DoctrineMongoDbOdmTestCase.php
index 5ee6c03ee15..6d63d70b87f 100644
--- a/src/Test/DoctrineMongoDbOdmTestCase.php
+++ b/src/Test/DoctrineMongoDbOdmTestCase.php
@@ -20,8 +20,6 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
-use function sys_get_temp_dir;
-
/**
* Source: https://github.com/doctrine/DoctrineMongoDBBundle/blob/0174003844bc566bb4cb3b7d10c5528d1924d719/Tests/TestCase.php
* Test got excluded from vendor in 4.x.
@@ -32,8 +30,8 @@ public static function createTestDocumentManager($paths = []): DocumentManager
{
$config = new Configuration();
$config->setAutoGenerateProxyClasses(Configuration::AUTOGENERATE_FILE_NOT_EXISTS);
- $config->setProxyDir(sys_get_temp_dir());
- $config->setHydratorDir(sys_get_temp_dir());
+ $config->setProxyDir(\sys_get_temp_dir());
+ $config->setHydratorDir(\sys_get_temp_dir());
$config->setProxyNamespace('SymfonyTests\Doctrine');
$config->setHydratorNamespace('SymfonyTests\Doctrine');
$config->setMetadataDriverImpl(new AttributeDriver($paths, new AttributeReader())); // @phpstan-ignore-line
diff --git a/tests/Action/ExceptionActionTest.php b/tests/Action/ExceptionActionTest.php
index 2088fdb7e50..27914032b87 100644
--- a/tests/Action/ExceptionActionTest.php
+++ b/tests/Action/ExceptionActionTest.php
@@ -21,7 +21,6 @@
use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
-use DomainException;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
@@ -74,7 +73,7 @@ public function testActionWithOperationExceptionToStatus(
?array $operationExceptionToStatus,
int $expectedStatusCode
): void {
- $exception = new DomainException();
+ $exception = new \DomainException();
$flattenException = FlattenException::create($exception);
$serializer = $this->prophesize(SerializerInterface::class);
@@ -135,86 +134,86 @@ public function provideOperationExceptionToStatusCases(): \Generator
];
yield 'on global attributes' => [
- [DomainException::class => 100],
+ [\DomainException::class => 100],
null,
null,
100,
];
yield 'on global attributes with empty resource and operation attributes' => [
- [DomainException::class => 100],
+ [\DomainException::class => 100],
[],
[],
100,
];
yield 'on global attributes and resource attributes' => [
- [DomainException::class => 100],
- [DomainException::class => 200],
+ [\DomainException::class => 100],
+ [\DomainException::class => 200],
null,
200,
];
yield 'on global attributes and resource attributes with empty operation attributes' => [
- [DomainException::class => 100],
- [DomainException::class => 200],
+ [\DomainException::class => 100],
+ [\DomainException::class => 200],
[],
200,
];
yield 'on global attributes and operation attributes' => [
- [DomainException::class => 100],
+ [\DomainException::class => 100],
null,
- [DomainException::class => 300],
+ [\DomainException::class => 300],
300,
];
yield 'on global attributes and operation attributes with empty resource attributes' => [
- [DomainException::class => 100],
+ [\DomainException::class => 100],
[],
- [DomainException::class => 300],
+ [\DomainException::class => 300],
300,
];
yield 'on global, resource and operation attributes' => [
- [DomainException::class => 100],
- [DomainException::class => 200],
- [DomainException::class => 300],
+ [\DomainException::class => 100],
+ [\DomainException::class => 200],
+ [\DomainException::class => 300],
300,
];
yield 'on resource attributes' => [
[],
- [DomainException::class => 200],
+ [\DomainException::class => 200],
null,
200,
];
yield 'on resource attributes with empty operation attributes' => [
[],
- [DomainException::class => 200],
+ [\DomainException::class => 200],
[],
200,
];
yield 'on resource and operation attributes' => [
[],
- [DomainException::class => 200],
- [DomainException::class => 300],
+ [\DomainException::class => 200],
+ [\DomainException::class => 300],
300,
];
yield 'on operation attributes' => [
[],
null,
- [DomainException::class => 300],
+ [\DomainException::class => 300],
300,
];
yield 'on operation attributes with empty resource attributes' => [
[],
[],
- [DomainException::class => 300],
+ [\DomainException::class => 300],
300,
];
}
diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php
index 5e02353fcfb..37aaa67205b 100644
--- a/tests/Behat/DoctrineContext.php
+++ b/tests/Behat/DoctrineContext.php
@@ -286,10 +286,10 @@ public function thereArePaginationEntities(int $nb): void
public function thereAreOfTheseSoManyObjects(int $nb): void
{
for ($i = 1; $i <= $nb; ++$i) {
- $dummy = $this->isOrm() ? new SoMany() : new SoManyDocument();
- $dummy->content = 'Many #'.$i;
+ $soMany = $this->buildSoMany();
+ $soMany->content = 'Many #'.$i;
- $this->manager->persist($dummy);
+ $this->manager->persist($soMany);
}
$this->manager->flush();
@@ -340,6 +340,12 @@ public function thereAreFooDummyObjectsWithFakeNames($nb): void
$foo = $this->buildFooDummy();
$foo->setName($names[$i]);
$foo->setDummy($dummy);
+ for ($j = 0; $j < 3; ++$j) {
+ $soMany = $this->buildSoMany();
+ $soMany->content = "So many $j";
+ $soMany->fooDummy = $foo;
+ $foo->soManies->add($soMany);
+ }
$this->manager->persist($foo);
}
@@ -2200,6 +2206,11 @@ private function buildRelatedSecureDummy(): RelatedSecuredDummy|RelatedSecuredDu
return $this->isOrm() ? new RelatedSecuredDummy() : new RelatedSecuredDummyDocument();
}
+ private function buildSoMany(): SoMany|SoManyDocument
+ {
+ return $this->isOrm() ? new SoMany() : new SoManyDocument();
+ }
+
private function buildThirdLevel(): ThirdLevel|ThirdLevelDocument
{
return $this->isOrm() ? new ThirdLevel() : new ThirdLevelDocument();
diff --git a/tests/Doctrine/Common/Filter/SearchFilterTestTrait.php b/tests/Doctrine/Common/Filter/SearchFilterTestTrait.php
index a7fc3780999..51b8811918d 100644
--- a/tests/Doctrine/Common/Filter/SearchFilterTestTrait.php
+++ b/tests/Doctrine/Common/Filter/SearchFilterTestTrait.php
@@ -297,6 +297,18 @@ private function provideApplyTestArguments(): array
],
],
],
+ 'partial (multiple almost same values; case insensitive)' => [
+ [
+ 'id' => null,
+ 'name' => 'ipartial',
+ ],
+ [
+ 'name' => [
+ 'blue car',
+ 'Blue Car',
+ ],
+ ],
+ ],
'start' => [
[
'id' => null,
diff --git a/tests/Doctrine/Odm/Extension/OrderExtensionTest.php b/tests/Doctrine/Odm/Extension/OrderExtensionTest.php
index 5c3f97ba515..ce722cdc0c4 100644
--- a/tests/Doctrine/Odm/Extension/OrderExtensionTest.php
+++ b/tests/Doctrine/Odm/Extension/OrderExtensionTest.php
@@ -22,7 +22,6 @@
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\Persistence\ManagerRegistry;
-use OutOfRangeException;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
@@ -39,7 +38,7 @@ public function testApplyToCollectionWithValidOrder(): void
{
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
- $aggregationBuilderProphecy->getStage(0)->willThrow(new OutOfRangeException('message'));
+ $aggregationBuilderProphecy->getStage(0)->willThrow(new \OutOfRangeException('message'));
$aggregationBuilderProphecy->sort(['name' => 'asc'])->shouldBeCalled();
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
@@ -60,7 +59,7 @@ public function testApplyToCollectionWithWrongOrder(): void
{
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
- $aggregationBuilderProphecy->getStage(0)->willThrow(new OutOfRangeException('message'));
+ $aggregationBuilderProphecy->getStage(0)->willThrow(new \OutOfRangeException('message'));
$aggregationBuilderProphecy->sort(['name' => 'asc'])->shouldNotBeCalled();
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
@@ -81,7 +80,7 @@ public function testApplyToCollectionWithOrderOverridden(): void
{
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
- $aggregationBuilderProphecy->getStage(0)->willThrow(new OutOfRangeException('message'));
+ $aggregationBuilderProphecy->getStage(0)->willThrow(new \OutOfRangeException('message'));
$aggregationBuilderProphecy->sort(['foo' => 'DESC'])->shouldBeCalled();
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
@@ -102,7 +101,7 @@ public function testApplyToCollectionWithOrderOverriddenWithNoDirection(): void
{
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
- $aggregationBuilderProphecy->getStage(0)->willThrow(new OutOfRangeException('message'));
+ $aggregationBuilderProphecy->getStage(0)->willThrow(new \OutOfRangeException('message'));
$aggregationBuilderProphecy->sort(['foo' => 'ASC'])->shouldBeCalled();
$aggregationBuilderProphecy->sort(['foo' => 'ASC', 'bar' => 'DESC'])->shouldBeCalled();
@@ -130,7 +129,7 @@ public function testApplyToCollectionWithOrderOverriddenWithAssociation(): void
$lookupProphecy->alias('author_lkup')->shouldBeCalled();
$aggregationBuilderProphecy->lookup(Dummy::class)->shouldBeCalled()->willReturn($lookupProphecy->reveal());
$aggregationBuilderProphecy->unwind('$author_lkup')->shouldBeCalled();
- $aggregationBuilderProphecy->getStage(0)->willThrow(new OutOfRangeException('message'));
+ $aggregationBuilderProphecy->getStage(0)->willThrow(new \OutOfRangeException('message'));
$aggregationBuilderProphecy->sort(['author_lkup.name' => 'ASC'])->shouldBeCalled();
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
diff --git a/tests/Doctrine/Odm/Filter/SearchFilterTest.php b/tests/Doctrine/Odm/Filter/SearchFilterTest.php
index 28b721f3475..5dd394ce965 100644
--- a/tests/Doctrine/Odm/Filter/SearchFilterTest.php
+++ b/tests/Doctrine/Odm/Filter/SearchFilterTest.php
@@ -426,6 +426,21 @@ public function provideApplyTestData(): array
],
$filterFactory,
],
+ 'partial (multiple almost same values; case insensitive)' => [
+ [
+ [
+ '$match' => [
+ 'name' => [
+ '$in' => [
+ new Regex('blue car', 'i'),
+ new Regex('Blue Car', 'i'),
+ ],
+ ],
+ ],
+ ],
+ ],
+ $filterFactory,
+ ],
'start' => [
[
[
diff --git a/tests/Doctrine/Orm/Filter/SearchFilterTest.php b/tests/Doctrine/Orm/Filter/SearchFilterTest.php
index 2d2f2a79e1f..7ad972534a7 100644
--- a/tests/Doctrine/Orm/Filter/SearchFilterTest.php
+++ b/tests/Doctrine/Orm/Filter/SearchFilterTest.php
@@ -352,6 +352,14 @@ public function provideApplyTestData(): array
],
$filterFactory,
],
+ 'partial (multiple almost same values; case insensitive)' => [
+ sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', $this->alias, Dummy::class),
+ [
+ 'name_p1_0' => 'blue car',
+ 'name_p1_1' => 'blue car',
+ ],
+ $filterFactory,
+ ],
'start' => [
sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\')', $this->alias, Dummy::class),
['name_p1_0' => 'partial'],
diff --git a/tests/Fixtures/TestBundle/Document/AbsoluteUrlDummy.php b/tests/Fixtures/TestBundle/Document/AbsoluteUrlDummy.php
index 1209f780a9a..23b8bea0b1d 100644
--- a/tests/Fixtures/TestBundle/Document/AbsoluteUrlDummy.php
+++ b/tests/Fixtures/TestBundle/Document/AbsoluteUrlDummy.php
@@ -20,7 +20,7 @@
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
#[ApiResource(urlGenerationStrategy: UrlGeneratorInterface::ABS_URL)]
-#[ApiResource(uriTemplate: '/absolute_url_relation_dummies/{id}/absolute_url_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: AbsoluteUrlRelationDummy::class, identifiers: ['id'], toProperty: 'absoluteUrlRelationDummy')], status: 200, urlGenerationStrategy: UrlGeneratorInterface::ABS_URL, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/absolute_url_relation_dummies/{id}/absolute_url_dummies{._format}', uriVariables: ['id' => new Link(fromClass: AbsoluteUrlRelationDummy::class, identifiers: ['id'], toProperty: 'absoluteUrlRelationDummy')], status: 200, urlGenerationStrategy: UrlGeneratorInterface::ABS_URL, operations: [new GetCollection()])]
#[ODM\Document]
class AbsoluteUrlDummy
{
diff --git a/tests/Fixtures/TestBundle/Document/Answer.php b/tests/Fixtures/TestBundle/Document/Answer.php
index 03023a4c892..2b45253e55d 100644
--- a/tests/Fixtures/TestBundle/Document/Answer.php
+++ b/tests/Fixtures/TestBundle/Document/Answer.php
@@ -29,8 +29,8 @@
* Answer.
*/
#[ApiResource(operations: [new Get(), new Put(), new Patch(), new Delete(), new GetCollection(normalizationContext: ['groups' => ['foobar']])])]
-#[ApiResource(uriTemplate: '/answers/{id}/related_questions/{relatedQuestions}/answer.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'], toProperty: 'answer'), 'relatedQuestions' => new Link(fromClass: Question::class, identifiers: ['id'], fromProperty: 'answer')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/questions/{id}/answer.{_format}', uriVariables: ['id' => new Link(fromClass: Question::class, identifiers: ['id'], fromProperty: 'answer')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/answers/{id}/related_questions/{relatedQuestions}/answer{._format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'], toProperty: 'answer'), 'relatedQuestions' => new Link(fromClass: Question::class, identifiers: ['id'], fromProperty: 'answer')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/questions/{id}/answer{._format}', uriVariables: ['id' => new Link(fromClass: Question::class, identifiers: ['id'], fromProperty: 'answer')], status: 200, operations: [new Get()])]
#[ODM\Document]
class Answer
{
diff --git a/tests/Fixtures/TestBundle/Document/Book.php b/tests/Fixtures/TestBundle/Document/Book.php
index e95179ce536..c74a8a30328 100644
--- a/tests/Fixtures/TestBundle/Document/Book.php
+++ b/tests/Fixtures/TestBundle/Document/Book.php
@@ -22,7 +22,7 @@
*
* @author Antoine Bluchet
*/
-#[ApiResource(operations: [new Get(), new Get(uriTemplate: '/books/by_isbn/{isbn}.{_format}', requirements: ['isbn' => '.+'], uriVariables: 'isbn')])]
+#[ApiResource(operations: [new Get(), new Get(uriTemplate: '/books/by_isbn/{isbn}{._format}', requirements: ['isbn' => '.+'], uriVariables: 'isbn')])]
#[ODM\Document]
class Book
{
diff --git a/tests/Fixtures/TestBundle/Document/Dummy.php b/tests/Fixtures/TestBundle/Document/Dummy.php
index 4a8b56c991c..cfa585544e0 100644
--- a/tests/Fixtures/TestBundle/Document/Dummy.php
+++ b/tests/Fixtures/TestBundle/Document/Dummy.php
@@ -29,8 +29,8 @@
* @author Alexandre Delplace
*/
#[ApiResource(extraProperties: ['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]], filters: ['my_dummy.mongodb.boolean', 'my_dummy.mongodb.date', 'my_dummy.mongodb.exists', 'my_dummy.mongodb.numeric', 'my_dummy.mongodb.order', 'my_dummy.mongodb.range', 'my_dummy.mongodb.search', 'my_dummy.property'])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy')], status: 200, filters: ['my_dummy.mongodb.boolean', 'my_dummy.mongodb.date', 'my_dummy.mongodb.exists', 'my_dummy.mongodb.numeric', 'my_dummy.mongodb.order', 'my_dummy.mongodb.range', 'my_dummy.mongodb.search', 'my_dummy.property'], operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy')], status: 200, filters: ['my_dummy.mongodb.boolean', 'my_dummy.mongodb.date', 'my_dummy.mongodb.exists', 'my_dummy.mongodb.numeric', 'my_dummy.mongodb.order', 'my_dummy.mongodb.range', 'my_dummy.mongodb.search', 'my_dummy.property'], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy')], status: 200, filters: ['my_dummy.mongodb.boolean', 'my_dummy.mongodb.date', 'my_dummy.mongodb.exists', 'my_dummy.mongodb.numeric', 'my_dummy.mongodb.order', 'my_dummy.mongodb.range', 'my_dummy.mongodb.search', 'my_dummy.property'], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy')], status: 200, filters: ['my_dummy.mongodb.boolean', 'my_dummy.mongodb.date', 'my_dummy.mongodb.exists', 'my_dummy.mongodb.numeric', 'my_dummy.mongodb.order', 'my_dummy.mongodb.range', 'my_dummy.mongodb.search', 'my_dummy.property'], operations: [new Get()])]
#[ODM\Document]
class Dummy
{
diff --git a/tests/Fixtures/TestBundle/Document/DummyAggregateOffer.php b/tests/Fixtures/TestBundle/Document/DummyAggregateOffer.php
index ebec8ed9c59..8ccf3657399 100644
--- a/tests/Fixtures/TestBundle/Document/DummyAggregateOffer.php
+++ b/tests/Fixtures/TestBundle/Document/DummyAggregateOffer.php
@@ -28,8 +28,8 @@
* @author Antoine Bluchet
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products/{relatedProducts}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id']), 'relatedProducts' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products/{relatedProducts}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id']), 'relatedProducts' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product')], status: 200, operations: [new GetCollection()])]
#[ODM\Document]
class DummyAggregateOffer
{
diff --git a/tests/Fixtures/TestBundle/Document/DummyOffer.php b/tests/Fixtures/TestBundle/Document/DummyOffer.php
index 80bb1aecaa8..518829e632b 100644
--- a/tests/Fixtures/TestBundle/Document/DummyOffer.php
+++ b/tests/Fixtures/TestBundle/Document/DummyOffer.php
@@ -26,9 +26,9 @@
* @author Antoine Bluchet
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummy_aggregate_offers/{id}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/offers/{offers}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product'), 'offers' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products/{relatedProducts}/offers/{offers}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id']), 'relatedProducts' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product'), 'offers' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_aggregate_offers/{id}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/offers/{offers}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product'), 'offers' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products/{relatedProducts}/offers/{offers}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id']), 'relatedProducts' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product'), 'offers' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
#[ODM\Document]
class DummyOffer
{
diff --git a/tests/Fixtures/TestBundle/Document/DummyProduct.php b/tests/Fixtures/TestBundle/Document/DummyProduct.php
index 7ea04a0447a..065956e5ee7 100644
--- a/tests/Fixtures/TestBundle/Document/DummyProduct.php
+++ b/tests/Fixtures/TestBundle/Document/DummyProduct.php
@@ -28,7 +28,7 @@
* @author Antoine Bluchet
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products{._format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, operations: [new GetCollection()])]
#[ODM\Document]
class DummyProduct
{
diff --git a/tests/Fixtures/TestBundle/Document/DummyValidation.php b/tests/Fixtures/TestBundle/Document/DummyValidation.php
index 587fd26b7be..a0fa6b10033 100644
--- a/tests/Fixtures/TestBundle/Document/DummyValidation.php
+++ b/tests/Fixtures/TestBundle/Document/DummyValidation.php
@@ -21,7 +21,7 @@
#[ApiResource(operations: [
new GetCollection(),
- new Post(uriTemplate: 'dummy_validation.{_format}'),
+ new Post(uriTemplate: 'dummy_validation{._format}'),
new Post(routeName: 'post_validation_groups', validationContext: ['groups' => ['a']]),
new Post(routeName: 'post_validation_sequence', validationContext: ['groups' => 'app.dummy_validation.group_generator']),
]
diff --git a/tests/Fixtures/TestBundle/Document/FooDummy.php b/tests/Fixtures/TestBundle/Document/FooDummy.php
index cc9fe959f25..8736509e855 100644
--- a/tests/Fixtures/TestBundle/Document/FooDummy.php
+++ b/tests/Fixtures/TestBundle/Document/FooDummy.php
@@ -15,6 +15,8 @@
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
/**
@@ -42,6 +44,17 @@ class FooDummy
#[ODM\ReferenceOne(targetDocument: Dummy::class, cascade: ['persist'], storeAs: 'id')]
private ?Dummy $dummy = null;
+ /**
+ * @var Collection
+ */
+ #[ODM\ReferenceMany(targetDocument: SoMany::class, cascade: ['persist'], storeAs: 'id')]
+ public Collection $soManies;
+
+ public function __construct()
+ {
+ $this->soManies = new ArrayCollection();
+ }
+
public function getId(): ?int
{
return $this->id;
diff --git a/tests/Fixtures/TestBundle/Document/FourthLevel.php b/tests/Fixtures/TestBundle/Document/FourthLevel.php
index 46a9cd1d615..bf653bfbc37 100644
--- a/tests/Fixtures/TestBundle/Document/FourthLevel.php
+++ b/tests/Fixtures/TestBundle/Document/FourthLevel.php
@@ -26,12 +26,12 @@
* @author Alan Poulain
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/id/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/third_levels/{id}/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: ThirdLevel::class, identifiers: ['id'], fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/id/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/third_levels/{id}/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: ThirdLevel::class, identifiers: ['id'], fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
#[ODM\Document]
class FourthLevel
{
diff --git a/tests/Fixtures/TestBundle/Document/Greeting.php b/tests/Fixtures/TestBundle/Document/Greeting.php
index 086b22e84b1..bb6ec15928a 100644
--- a/tests/Fixtures/TestBundle/Document/Greeting.php
+++ b/tests/Fixtures/TestBundle/Document/Greeting.php
@@ -19,7 +19,7 @@
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
#[ApiResource]
-#[ApiResource(uriTemplate: '/people/{id}/sent_greetings.{_format}', uriVariables: ['id' => new Link(fromClass: Person::class, identifiers: ['id'], toProperty: 'sender')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/people/{id}/sent_greetings{._format}', uriVariables: ['id' => new Link(fromClass: Person::class, identifiers: ['id'], toProperty: 'sender')], status: 200, operations: [new GetCollection()])]
#[ODM\Document]
class Greeting
{
diff --git a/tests/Fixtures/TestBundle/Document/NetworkPathDummy.php b/tests/Fixtures/TestBundle/Document/NetworkPathDummy.php
index 41b63270bf8..bd2794e67a5 100644
--- a/tests/Fixtures/TestBundle/Document/NetworkPathDummy.php
+++ b/tests/Fixtures/TestBundle/Document/NetworkPathDummy.php
@@ -20,7 +20,7 @@
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
#[ApiResource(urlGenerationStrategy: UrlGeneratorInterface::NET_PATH)]
-#[ApiResource(uriTemplate: '/network_path_relation_dummies/{id}/network_path_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: NetworkPathRelationDummy::class, identifiers: ['id'], toProperty: 'networkPathRelationDummy')], status: 200, urlGenerationStrategy: UrlGeneratorInterface::NET_PATH, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/network_path_relation_dummies/{id}/network_path_dummies{._format}', uriVariables: ['id' => new Link(fromClass: NetworkPathRelationDummy::class, identifiers: ['id'], toProperty: 'networkPathRelationDummy')], status: 200, urlGenerationStrategy: UrlGeneratorInterface::NET_PATH, operations: [new GetCollection()])]
#[ODM\Document]
class NetworkPathDummy
{
diff --git a/tests/Fixtures/TestBundle/Document/Question.php b/tests/Fixtures/TestBundle/Document/Question.php
index 6375515e66c..417f78864b9 100644
--- a/tests/Fixtures/TestBundle/Document/Question.php
+++ b/tests/Fixtures/TestBundle/Document/Question.php
@@ -19,8 +19,8 @@
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
#[ApiResource]
-#[ApiResource(uriTemplate: '/answers/{id}/related_questions.{_format}', uriVariables: ['id' => new Link(fromClass: Answer::class, identifiers: ['id'], toProperty: 'answer')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/questions/{id}/answer/related_questions.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'], fromProperty: 'answer'), 'answer' => new Link(fromClass: Answer::class, identifiers: [], expandedValue: 'answer', toProperty: 'answer')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/answers/{id}/related_questions{._format}', uriVariables: ['id' => new Link(fromClass: Answer::class, identifiers: ['id'], toProperty: 'answer')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/questions/{id}/answer/related_questions{._format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'], fromProperty: 'answer'), 'answer' => new Link(fromClass: Answer::class, identifiers: [], expandedValue: 'answer', toProperty: 'answer')], status: 200, operations: [new GetCollection()])]
#[ODM\Document]
class Question
{
diff --git a/tests/Fixtures/TestBundle/Document/RelatedDummy.php b/tests/Fixtures/TestBundle/Document/RelatedDummy.php
index 0ca2c57dc04..a400aa06450 100644
--- a/tests/Fixtures/TestBundle/Document/RelatedDummy.php
+++ b/tests/Fixtures/TestBundle/Document/RelatedDummy.php
@@ -37,13 +37,13 @@
* @author Alexandre Delplace
*/
#[ApiResource(graphQlOperations: [new Query(name: 'item_query'), new Mutation(name: 'update', normalizationContext: ['groups' => ['chicago', 'fakemanytomany']], denormalizationContext: ['groups' => ['friends']])], types: ['https://schema.org/Product'], normalizationContext: ['groups' => ['friends']], filters: ['related_dummy.mongodb.friends'])]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/id.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/id{._format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.mongodb.friends'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
#[ApiFilter(filterClass: SearchFilter::class, properties: ['id'])]
#[ODM\Document]
class RelatedDummy extends ParentDummy implements \Stringable
diff --git a/tests/Fixtures/TestBundle/Document/RelatedToDummyFriend.php b/tests/Fixtures/TestBundle/Document/RelatedToDummyFriend.php
index 254fd5da549..8955436b628 100644
--- a/tests/Fixtures/TestBundle/Document/RelatedToDummyFriend.php
+++ b/tests/Fixtures/TestBundle/Document/RelatedToDummyFriend.php
@@ -25,11 +25,11 @@
* Related To Dummy Friend represent an association table for a manytomany relation.
*/
#[ApiResource(normalizationContext: ['groups' => ['fakemanytomany']], filters: ['related_to_dummy_friend.mongodb.name'])]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/id/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/id/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.mongodb.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
#[ODM\Document]
class RelatedToDummyFriend
{
diff --git a/tests/Fixtures/TestBundle/Document/SlugChildDummy.php b/tests/Fixtures/TestBundle/Document/SlugChildDummy.php
index 4d210239e04..890cf391652 100644
--- a/tests/Fixtures/TestBundle/Document/SlugChildDummy.php
+++ b/tests/Fixtures/TestBundle/Document/SlugChildDummy.php
@@ -20,8 +20,8 @@
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
#[ApiResource]
-#[ApiResource(uriTemplate: '/slug_parent_dummies/{slug}/child_dummies.{_format}', uriVariables: ['slug' => new Link(fromClass: SlugParentDummy::class, identifiers: ['slug'], toProperty: 'parentDummy')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/slug_child_dummies/{slug}/parent_dummy/child_dummies.{_format}', uriVariables: ['slug' => new Link(fromClass: self::class, identifiers: ['slug'], fromProperty: 'parentDummy'), 'parentDummy' => new Link(fromClass: SlugParentDummy::class, identifiers: [], expandedValue: 'parent_dummy', toProperty: 'parentDummy')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/slug_parent_dummies/{slug}/child_dummies{._format}', uriVariables: ['slug' => new Link(fromClass: SlugParentDummy::class, identifiers: ['slug'], toProperty: 'parentDummy')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/slug_child_dummies/{slug}/parent_dummy/child_dummies{._format}', uriVariables: ['slug' => new Link(fromClass: self::class, identifiers: ['slug'], fromProperty: 'parentDummy'), 'parentDummy' => new Link(fromClass: SlugParentDummy::class, identifiers: [], expandedValue: 'parent_dummy', toProperty: 'parentDummy')], status: 200, operations: [new GetCollection()])]
#[ODM\Document]
class SlugChildDummy
{
diff --git a/tests/Fixtures/TestBundle/Document/SlugParentDummy.php b/tests/Fixtures/TestBundle/Document/SlugParentDummy.php
index 4ff555b8764..ed34ea94bd6 100644
--- a/tests/Fixtures/TestBundle/Document/SlugParentDummy.php
+++ b/tests/Fixtures/TestBundle/Document/SlugParentDummy.php
@@ -25,8 +25,8 @@
* Custom Identifier Dummy With Subresource.
*/
#[ApiResource(uriVariables: 'slug')]
-#[ApiResource(uriTemplate: '/slug_parent_dummies/{slug}/child_dummies/{childDummies}/parent_dummy.{_format}', uriVariables: ['slug' => new Link(fromClass: self::class, identifiers: ['slug'], toProperty: 'parentDummy'), 'childDummies' => new Link(fromClass: SlugChildDummy::class, identifiers: ['slug'], fromProperty: 'parentDummy')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/slug_child_dummies/{slug}/parent_dummy.{_format}', uriVariables: ['slug' => new Link(fromClass: SlugChildDummy::class, identifiers: ['slug'], fromProperty: 'parentDummy')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/slug_parent_dummies/{slug}/child_dummies/{childDummies}/parent_dummy{._format}', uriVariables: ['slug' => new Link(fromClass: self::class, identifiers: ['slug'], toProperty: 'parentDummy'), 'childDummies' => new Link(fromClass: SlugChildDummy::class, identifiers: ['slug'], fromProperty: 'parentDummy')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/slug_child_dummies/{slug}/parent_dummy{._format}', uriVariables: ['slug' => new Link(fromClass: SlugChildDummy::class, identifiers: ['slug'], fromProperty: 'parentDummy')], status: 200, operations: [new Get()])]
#[ODM\Document]
class SlugParentDummy
{
diff --git a/tests/Fixtures/TestBundle/Document/ThirdLevel.php b/tests/Fixtures/TestBundle/Document/ThirdLevel.php
index e4b6fe19cfa..046b89afb16 100644
--- a/tests/Fixtures/TestBundle/Document/ThirdLevel.php
+++ b/tests/Fixtures/TestBundle/Document/ThirdLevel.php
@@ -26,11 +26,11 @@
* @author Alexandre Delplace
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/id/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/third_level{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/id/third_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/third_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/third_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/third_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
#[ODM\Document]
class ThirdLevel
{
diff --git a/tests/Fixtures/TestBundle/Document/VoDummyInspection.php b/tests/Fixtures/TestBundle/Document/VoDummyInspection.php
index 4501a935a59..703abcff6a3 100644
--- a/tests/Fixtures/TestBundle/Document/VoDummyInspection.php
+++ b/tests/Fixtures/TestBundle/Document/VoDummyInspection.php
@@ -14,7 +14,6 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
use ApiPlatform\Metadata\ApiResource;
-use DateTime;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Serializer\Annotation\Groups;
@@ -27,9 +26,12 @@ class VoDummyInspection
#[ODM\Field(type: 'date')]
private \DateTime $performed;
- public function __construct(#[Groups(['car_read', 'car_write', 'inspection_read', 'inspection_write'])] #[ODM\Field(type: 'bool')] private bool $accepted, #[Groups(['inspection_read', 'inspection_write'])] #[ODM\ReferenceOne(targetDocument: VoDummyCar::class, inversedBy: 'inspections')] private VoDummyCar $car, DateTime $performed = null, private string $attributeWithoutConstructorEquivalent = '')
+ private $attributeWithoutConstructorEquivalent;
+
+ public function __construct(#[Groups(['car_read', 'car_write', 'inspection_read', 'inspection_write'])] #[ODM\Field(type: 'bool')] private bool $accepted, #[Groups(['inspection_read', 'inspection_write'])] #[ODM\ReferenceOne(targetDocument: VoDummyCar::class, inversedBy: 'inspections')] private VoDummyCar $car, \DateTime $performed = null, string $parameterWhichIsNotClassAttribute = '')
{
- $this->performed = $performed ?: new DateTime();
+ $this->performed = $performed ?: new \DateTime();
+ $this->attributeWithoutConstructorEquivalent = $parameterWhichIsNotClassAttribute;
}
public function isAccepted(): bool
@@ -42,12 +44,12 @@ public function getCar(): VoDummyCar
return $this->car;
}
- public function getPerformed(): DateTime
+ public function getPerformed(): \DateTime
{
return $this->performed;
}
- public function setPerformed(DateTime $performed)
+ public function setPerformed(\DateTime $performed)
{
$this->performed = $performed;
diff --git a/tests/Fixtures/TestBundle/Entity/AbsoluteUrlDummy.php b/tests/Fixtures/TestBundle/Entity/AbsoluteUrlDummy.php
index ff2fb260aca..dfb1f919c21 100644
--- a/tests/Fixtures/TestBundle/Entity/AbsoluteUrlDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/AbsoluteUrlDummy.php
@@ -20,7 +20,7 @@
use Doctrine\ORM\Mapping as ORM;
#[ApiResource(urlGenerationStrategy: UrlGeneratorInterface::ABS_URL)]
-#[ApiResource(uriTemplate: '/absolute_url_relation_dummies/{id}/absolute_url_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: AbsoluteUrlRelationDummy::class, identifiers: ['id'], toProperty: 'absoluteUrlRelationDummy')], status: 200, urlGenerationStrategy: UrlGeneratorInterface::ABS_URL, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/absolute_url_relation_dummies/{id}/absolute_url_dummies{._format}', uriVariables: ['id' => new Link(fromClass: AbsoluteUrlRelationDummy::class, identifiers: ['id'], toProperty: 'absoluteUrlRelationDummy')], status: 200, urlGenerationStrategy: UrlGeneratorInterface::ABS_URL, operations: [new GetCollection()])]
#[ORM\Entity]
class AbsoluteUrlDummy
{
diff --git a/tests/Fixtures/TestBundle/Entity/Answer.php b/tests/Fixtures/TestBundle/Entity/Answer.php
index 7a6fb033669..945fd5e3b6d 100644
--- a/tests/Fixtures/TestBundle/Entity/Answer.php
+++ b/tests/Fixtures/TestBundle/Entity/Answer.php
@@ -29,8 +29,8 @@
* Answer.
*/
#[ApiResource(operations: [new Get(), new Put(), new Patch(), new Delete(), new GetCollection(normalizationContext: ['groups' => ['foobar']])])]
-#[ApiResource(uriTemplate: '/answers/{id}/related_questions/{relatedQuestions}/answer.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'], toProperty: 'answer'), 'relatedQuestions' => new Link(fromClass: Question::class, identifiers: ['id'], fromProperty: 'answer')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/questions/{id}/answer.{_format}', uriVariables: ['id' => new Link(fromClass: Question::class, identifiers: ['id'], fromProperty: 'answer')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/answers/{id}/related_questions/{relatedQuestions}/answer{._format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'], toProperty: 'answer'), 'relatedQuestions' => new Link(fromClass: Question::class, identifiers: ['id'], fromProperty: 'answer')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/questions/{id}/answer{._format}', uriVariables: ['id' => new Link(fromClass: Question::class, identifiers: ['id'], fromProperty: 'answer')], status: 200, operations: [new Get()])]
#[ORM\Entity]
class Answer
{
diff --git a/tests/Fixtures/TestBundle/Entity/AttributeOnlyOperation.php b/tests/Fixtures/TestBundle/Entity/AttributeOnlyOperation.php
new file mode 100644
index 00000000000..e13550bb603
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/AttributeOnlyOperation.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Metadata\Get;
+
+#[Get(name: 'my own name')]
+final class AttributeOnlyOperation
+{
+}
diff --git a/tests/Fixtures/TestBundle/Entity/AttributeResource.php b/tests/Fixtures/TestBundle/Entity/AttributeResource.php
index 560251f7c69..0c4649e27e7 100644
--- a/tests/Fixtures/TestBundle/Entity/AttributeResource.php
+++ b/tests/Fixtures/TestBundle/Entity/AttributeResource.php
@@ -31,7 +31,7 @@
#[Put]
#[Delete]
#[ApiResource(
- '/dummy/{dummyId}/attribute_resources/{identifier}.{_format}',
+ '/dummy/{dummyId}/attribute_resources/{identifier}{._format}',
inputFormats: ['json' => ['application/merge-patch+json']],
status: 301,
provider: AttributeResourceProvider::class,
diff --git a/tests/Fixtures/TestBundle/Entity/AttributeResources.php b/tests/Fixtures/TestBundle/Entity/AttributeResources.php
index 6685647c810..e2397c8d938 100644
--- a/tests/Fixtures/TestBundle/Entity/AttributeResources.php
+++ b/tests/Fixtures/TestBundle/Entity/AttributeResources.php
@@ -17,18 +17,15 @@
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Tests\Fixtures\TestBundle\State\AttributeResourceProvider;
-use ArrayIterator;
-use IteratorAggregate;
-use Traversable;
#[ApiResource(
- '/attribute_resources.{_format}',
+ '/attribute_resources{._format}',
normalizationContext: ['skip_null_values' => true],
provider: AttributeResourceProvider::class
)]
#[GetCollection]
#[Post]
-final class AttributeResources implements IteratorAggregate
+final class AttributeResources implements \IteratorAggregate
{
/**
* @var AttributeResource[]
@@ -40,8 +37,8 @@ public function __construct(AttributeResource ...$collection)
$this->collection = $collection;
}
- public function getIterator(): Traversable
+ public function getIterator(): \Traversable
{
- return new ArrayIterator($this->collection);
+ return new \ArrayIterator($this->collection);
}
}
diff --git a/tests/Fixtures/TestBundle/Entity/Book.php b/tests/Fixtures/TestBundle/Entity/Book.php
index 45a30eb6807..f892b81cb3f 100644
--- a/tests/Fixtures/TestBundle/Entity/Book.php
+++ b/tests/Fixtures/TestBundle/Entity/Book.php
@@ -22,7 +22,7 @@
*
* @author Antoine Bluchet
*/
-#[ApiResource(operations: [new Get(), new Get(uriTemplate: '/books/by_isbn/{isbn}.{_format}', requirements: ['isbn' => '.+'], uriVariables: 'isbn')])]
+#[ApiResource(operations: [new Get(), new Get(uriTemplate: '/books/by_isbn/{isbn}{._format}', requirements: ['isbn' => '.+'], uriVariables: 'isbn')])]
#[ORM\Entity]
class Book
{
diff --git a/tests/Fixtures/TestBundle/Entity/Dummy.php b/tests/Fixtures/TestBundle/Entity/Dummy.php
index 33fc651dbd7..69e9384db63 100644
--- a/tests/Fixtures/TestBundle/Entity/Dummy.php
+++ b/tests/Fixtures/TestBundle/Entity/Dummy.php
@@ -28,8 +28,8 @@
* @author Kévin Dunglas
*/
#[ApiResource(filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])]
#[ORM\Entity]
class Dummy
{
diff --git a/tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php b/tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php
index 33d5def4a21..7089cdf2a70 100644
--- a/tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php
+++ b/tests/Fixtures/TestBundle/Entity/DummyAggregateOffer.php
@@ -28,8 +28,8 @@
* @author Antoine Bluchet
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products/{relatedProducts}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id']), 'relatedProducts' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products/{relatedProducts}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id']), 'relatedProducts' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product')], status: 200, operations: [new GetCollection()])]
#[ORM\Entity]
class DummyAggregateOffer
{
diff --git a/tests/Fixtures/TestBundle/Entity/DummyOffer.php b/tests/Fixtures/TestBundle/Entity/DummyOffer.php
index 8448b8bf21c..5e9d52dad81 100644
--- a/tests/Fixtures/TestBundle/Entity/DummyOffer.php
+++ b/tests/Fixtures/TestBundle/Entity/DummyOffer.php
@@ -26,9 +26,9 @@
* @author Antoine Bluchet
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummy_aggregate_offers/{id}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/offers/{offers}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product'), 'offers' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products/{relatedProducts}/offers/{offers}/offers.{_format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id']), 'relatedProducts' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product'), 'offers' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_aggregate_offers/{id}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/offers/{offers}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product'), 'offers' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products/{relatedProducts}/offers/{offers}/offers{._format}', uriVariables: ['id' => new Link(fromClass: DummyProduct::class, identifiers: ['id']), 'relatedProducts' => new Link(fromClass: DummyProduct::class, identifiers: ['id'], toProperty: 'product'), 'offers' => new Link(fromClass: DummyAggregateOffer::class, identifiers: ['id'], toProperty: 'aggregate')], status: 200, operations: [new GetCollection()])]
#[ORM\Entity]
class DummyOffer
{
diff --git a/tests/Fixtures/TestBundle/Entity/DummyProduct.php b/tests/Fixtures/TestBundle/Entity/DummyProduct.php
index 6901e719705..f2a5475d46a 100644
--- a/tests/Fixtures/TestBundle/Entity/DummyProduct.php
+++ b/tests/Fixtures/TestBundle/Entity/DummyProduct.php
@@ -28,7 +28,7 @@
* @author Antoine Bluchet
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummy_products/{id}/related_products{._format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, operations: [new GetCollection()])]
#[ORM\Entity]
class DummyProduct
{
diff --git a/tests/Fixtures/TestBundle/Entity/DummyToUpgradeProduct.php b/tests/Fixtures/TestBundle/Entity/DummyToUpgradeProduct.php
new file mode 100644
index 00000000000..cabc7a4352c
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/DummyToUpgradeProduct.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Core\Annotation\ApiResource;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ORM\Entity
+ *
+ * @ApiResource
+ */
+class DummyToUpgradeProduct
+{
+ /**
+ * @var int
+ *
+ * @ORM\Id
+ * @ORM\GeneratedValue
+ * @ORM\Column(type="integer")
+ */
+ private $id;
+
+ /**
+ * @var Collection
+ *
+ * @ORM\OneToMany(mappedBy="dummyToUpgradeProduct", targetEntity=DummyToUpgradeWithOnlyAnnotation::class)
+ */
+ private $dummysToUpgradeWithOnlyAnnotation;
+
+ /**
+ * @var Collection
+ *
+ * @ORM\OneToMany(mappedBy="dummyToUpgradeProduct", targetEntity=DummyToUpgradeWithOnlyAttribute::class)
+ */
+ private $dummysToUpgradeWithOnlyAttribute;
+}
diff --git a/tests/Fixtures/TestBundle/Entity/DummyToUpgradeWithOnlyAnnotation.php b/tests/Fixtures/TestBundle/Entity/DummyToUpgradeWithOnlyAnnotation.php
new file mode 100644
index 00000000000..576ca2a55f0
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/DummyToUpgradeWithOnlyAnnotation.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiProperty;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Annotation\ApiSubresource;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\ExistsFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * @ORM\Entity
+ *
+ * @ApiResource
+ *
+ * @ApiFilter(SearchFilter::class, properties={"id"})
+ */
+class DummyToUpgradeWithOnlyAnnotation
+{
+ /**
+ * @var int
+ *
+ * @ORM\Id
+ * @ORM\GeneratedValue
+ * @ORM\Column(type="integer")
+ * @Groups({"chicago", "friends"})
+ * @ApiProperty(writable=false)
+ * @ApiFilter(DateFilter::class)
+ */
+ private $id;
+
+ /**
+ * @var DummyToUpgradeProduct
+ *
+ * @ORM\ManyToOne(targetEntity="DummyToUpgradeProduct", cascade={"persist"}, inversedBy="dummysToUpgradeWithOnlyAnnotation")
+ * @ORM\JoinColumn(nullable=false)
+ * @Groups({"barcelona", "chicago", "friends"})
+ *
+ * @ApiSubresource
+ *
+ * @ApiProperty(iri="DummyToUpgradeWithOnlyAnnotation.dummyToUpgradeProduct")
+ * @ApiFilter(SearchFilter::class)
+ * @ApiFilter(ExistsFilter::class)
+ */
+ private $dummyToUpgradeProduct;
+}
diff --git a/tests/Fixtures/TestBundle/Entity/DummyToUpgradeWithOnlyAttribute.php b/tests/Fixtures/TestBundle/Entity/DummyToUpgradeWithOnlyAttribute.php
new file mode 100644
index 00000000000..f9a1fd1f592
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/DummyToUpgradeWithOnlyAttribute.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Core\Annotation\ApiProperty;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Annotation\ApiSubresource;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * @ORM\Entity
+ */
+#[ApiResource()]
+class DummyToUpgradeWithOnlyAttribute
+{
+ /**
+ * @var int
+ *
+ * @ORM\Id
+ * @ORM\GeneratedValue
+ * @ORM\Column(type="integer")
+ */
+ #[Groups(['chicago', 'friends'])]
+ #[ApiProperty(writable: false)]
+ private $id;
+
+ /**
+ * @var DummyToUpgradeProduct
+ *
+ * @ORM\ManyToOne(targetEntity="DummyToUpgradeProduct", inversedBy="dummysToUpgradeWithOnlyAttribute")
+ * @ORM\JoinColumn(nullable=false)
+ */
+ #[Groups(['barcelona', 'chicago', 'friends'])]
+ #[ApiSubresource]
+ #[ApiProperty(iri: 'DummyToUpgradeWithOnlyAttribute.dummyToUpgradeProduct')]
+ private $dummyToUpgradeProduct;
+}
diff --git a/tests/Fixtures/TestBundle/Entity/DummyValidation.php b/tests/Fixtures/TestBundle/Entity/DummyValidation.php
index e06a4f02d29..8500aa1556b 100644
--- a/tests/Fixtures/TestBundle/Entity/DummyValidation.php
+++ b/tests/Fixtures/TestBundle/Entity/DummyValidation.php
@@ -21,7 +21,7 @@
#[ApiResource(operations: [
new GetCollection(),
- new Post(uriTemplate: 'dummy_validation.{_format}'),
+ new Post(uriTemplate: 'dummy_validation{._format}'),
new Post(routeName: 'post_validation_groups', validationContext: ['groups' => ['a']]),
new Post(routeName: 'post_validation_sequence', validationContext: ['groups' => 'app.dummy_validation.group_generator']),
]
diff --git a/tests/Fixtures/TestBundle/Entity/FooDummy.php b/tests/Fixtures/TestBundle/Entity/FooDummy.php
index b744c0c2a55..92596dad682 100644
--- a/tests/Fixtures/TestBundle/Entity/FooDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/FooDummy.php
@@ -15,6 +15,8 @@
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
@@ -44,6 +46,17 @@ class FooDummy
#[ORM\ManyToOne(targetEntity: Dummy::class, cascade: ['persist'])]
private ?Dummy $dummy = null;
+ /**
+ * @var Collection
+ */
+ #[ORM\OneToMany(targetEntity: SoMany::class, mappedBy: 'fooDummy', cascade: ['persist'])]
+ public Collection $soManies;
+
+ public function __construct()
+ {
+ $this->soManies = new ArrayCollection();
+ }
+
public function getId(): ?int
{
return $this->id;
diff --git a/tests/Fixtures/TestBundle/Entity/FourthLevel.php b/tests/Fixtures/TestBundle/Entity/FourthLevel.php
index 85161c78705..b62eaed1b27 100644
--- a/tests/Fixtures/TestBundle/Entity/FourthLevel.php
+++ b/tests/Fixtures/TestBundle/Entity/FourthLevel.php
@@ -26,12 +26,12 @@
* @author Alan Poulain
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/id/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/third_level/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/third_levels/{id}/fourth_level.{_format}', uriVariables: ['id' => new Link(fromClass: ThirdLevel::class, identifiers: ['id'], fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/id/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/third_level/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel'), 'thirdLevel' => new Link(fromClass: ThirdLevel::class, identifiers: [], expandedValue: 'third_level', fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/third_levels/{id}/fourth_level{._format}', uriVariables: ['id' => new Link(fromClass: ThirdLevel::class, identifiers: ['id'], fromProperty: 'fourthLevel')], status: 200, operations: [new Get()])]
#[ORM\Entity]
class FourthLevel
{
diff --git a/tests/Fixtures/TestBundle/Entity/Greeting.php b/tests/Fixtures/TestBundle/Entity/Greeting.php
index ee0bcd5602d..d74e8217b55 100644
--- a/tests/Fixtures/TestBundle/Entity/Greeting.php
+++ b/tests/Fixtures/TestBundle/Entity/Greeting.php
@@ -19,7 +19,7 @@
use Doctrine\ORM\Mapping as ORM;
#[ApiResource]
-#[ApiResource(uriTemplate: '/people/{id}/sent_greetings.{_format}', uriVariables: ['id' => new Link(fromClass: Person::class, identifiers: ['id'], toProperty: 'sender')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/people/{id}/sent_greetings{._format}', uriVariables: ['id' => new Link(fromClass: Person::class, identifiers: ['id'], toProperty: 'sender')], status: 200, operations: [new GetCollection()])]
#[ORM\Entity]
class Greeting
{
diff --git a/tests/Fixtures/TestBundle/Entity/NetworkPathDummy.php b/tests/Fixtures/TestBundle/Entity/NetworkPathDummy.php
index 30070090fda..46530b96c5e 100644
--- a/tests/Fixtures/TestBundle/Entity/NetworkPathDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/NetworkPathDummy.php
@@ -20,7 +20,7 @@
use Doctrine\ORM\Mapping as ORM;
#[ApiResource(urlGenerationStrategy: UrlGeneratorInterface::NET_PATH)]
-#[ApiResource(uriTemplate: '/network_path_relation_dummies/{id}/network_path_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: NetworkPathRelationDummy::class, identifiers: ['id'], toProperty: 'networkPathRelationDummy')], status: 200, urlGenerationStrategy: UrlGeneratorInterface::NET_PATH, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/network_path_relation_dummies/{id}/network_path_dummies{._format}', uriVariables: ['id' => new Link(fromClass: NetworkPathRelationDummy::class, identifiers: ['id'], toProperty: 'networkPathRelationDummy')], status: 200, urlGenerationStrategy: UrlGeneratorInterface::NET_PATH, operations: [new GetCollection()])]
#[ORM\Entity]
class NetworkPathDummy
{
diff --git a/tests/Fixtures/TestBundle/Entity/Question.php b/tests/Fixtures/TestBundle/Entity/Question.php
index 0ffc8bfd2cb..78b1afd5026 100644
--- a/tests/Fixtures/TestBundle/Entity/Question.php
+++ b/tests/Fixtures/TestBundle/Entity/Question.php
@@ -19,8 +19,8 @@
use Doctrine\ORM\Mapping as ORM;
#[ApiResource]
-#[ApiResource(uriTemplate: '/answers/{id}/related_questions.{_format}', uriVariables: ['id' => new Link(fromClass: Answer::class, identifiers: ['id'], toProperty: 'answer')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/questions/{id}/answer/related_questions.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'], fromProperty: 'answer'), 'answer' => new Link(fromClass: Answer::class, identifiers: [], expandedValue: 'answer', toProperty: 'answer')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/answers/{id}/related_questions{._format}', uriVariables: ['id' => new Link(fromClass: Answer::class, identifiers: ['id'], toProperty: 'answer')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/questions/{id}/answer/related_questions{._format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'], fromProperty: 'answer'), 'answer' => new Link(fromClass: Answer::class, identifiers: [], expandedValue: 'answer', toProperty: 'answer')], status: 200, operations: [new GetCollection()])]
#[ORM\Entity]
class Question
{
diff --git a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php
index bbb36c24aca..a6e60f79169 100644
--- a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php
@@ -35,14 +35,22 @@
*
* @author Kévin Dunglas
*/
-#[ApiResource(graphQlOperations: [new Query(name: 'item_query'), new Mutation(name: 'update', normalizationContext: ['groups' => ['chicago', 'fakemanytomany']], denormalizationContext: ['groups' => ['friends']])], types: ['https://schema.org/Product'], normalizationContext: ['groups' => ['friends']], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'])]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/id.{_format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
+#[ApiResource(
+ graphQlOperations: [
+ new Query(name: 'item_query'),
+ new Mutation(name: 'update', normalizationContext: ['groups' => ['chicago', 'fakemanytomany']], denormalizationContext: ['groups' => ['friends']]),
+ ],
+ types: ['https://schema.org/Product'],
+ normalizationContext: ['groups' => ['friends']],
+ filters: ['related_dummy.friends', 'related_dummy.complex_sub_query']
+)]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/id{._format}', uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies')], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: self::class, identifiers: ['id'])], status: 200, types: ['https://schema.org/Product'], filters: ['related_dummy.friends', 'related_dummy.complex_sub_query'], normalizationContext: ['groups' => ['friends']], operations: [new Get()])]
#[ApiFilter(filterClass: SearchFilter::class, properties: ['id'])]
#[ORM\Entity]
class RelatedDummy extends ParentDummy implements \Stringable
diff --git a/tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php b/tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php
index b7823a78d86..c584c30596b 100644
--- a/tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php
+++ b/tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php
@@ -25,11 +25,11 @@
* Related To Dummy Friend represent an association table for a manytomany relation.
*/
#[ApiResource(normalizationContext: ['groups' => ['fakemanytomany']], filters: ['related_to_dummy_friend.name'])]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/id/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/related_to_dummy_friends.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/id/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])]
#[ORM\Entity]
class RelatedToDummyFriend
{
diff --git a/tests/Fixtures/TestBundle/Entity/SlugChildDummy.php b/tests/Fixtures/TestBundle/Entity/SlugChildDummy.php
index bab3c01e521..c1d8c5b21e4 100644
--- a/tests/Fixtures/TestBundle/Entity/SlugChildDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/SlugChildDummy.php
@@ -20,8 +20,8 @@
use Doctrine\ORM\Mapping as ORM;
#[ApiResource]
-#[ApiResource(uriTemplate: '/slug_parent_dummies/{slug}/child_dummies.{_format}', uriVariables: ['slug' => new Link(fromClass: SlugParentDummy::class, identifiers: ['slug'], toProperty: 'parentDummy')], status: 200, operations: [new GetCollection()])]
-#[ApiResource(uriTemplate: '/slug_child_dummies/{slug}/parent_dummy/child_dummies.{_format}', uriVariables: ['slug' => new Link(fromClass: self::class, identifiers: ['slug'], fromProperty: 'parentDummy'), 'parentDummy' => new Link(fromClass: SlugParentDummy::class, identifiers: [], expandedValue: 'parent_dummy', toProperty: 'parentDummy')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/slug_parent_dummies/{slug}/child_dummies{._format}', uriVariables: ['slug' => new Link(fromClass: SlugParentDummy::class, identifiers: ['slug'], toProperty: 'parentDummy')], status: 200, operations: [new GetCollection()])]
+#[ApiResource(uriTemplate: '/slug_child_dummies/{slug}/parent_dummy/child_dummies{._format}', uriVariables: ['slug' => new Link(fromClass: self::class, identifiers: ['slug'], fromProperty: 'parentDummy'), 'parentDummy' => new Link(fromClass: SlugParentDummy::class, identifiers: [], expandedValue: 'parent_dummy', toProperty: 'parentDummy')], status: 200, operations: [new GetCollection()])]
#[ORM\Entity]
class SlugChildDummy
{
diff --git a/tests/Fixtures/TestBundle/Entity/SlugParentDummy.php b/tests/Fixtures/TestBundle/Entity/SlugParentDummy.php
index c6412d78229..104ca2cc97e 100644
--- a/tests/Fixtures/TestBundle/Entity/SlugParentDummy.php
+++ b/tests/Fixtures/TestBundle/Entity/SlugParentDummy.php
@@ -25,8 +25,8 @@
* Custom Identifier Dummy With Subresource.
*/
#[ApiResource(uriVariables: 'slug')]
-#[ApiResource(uriTemplate: '/slug_parent_dummies/{slug}/child_dummies/{childDummies}/parent_dummy.{_format}', uriVariables: ['slug' => new Link(fromClass: self::class, identifiers: ['slug'], toProperty: 'parentDummy'), 'childDummies' => new Link(fromClass: SlugChildDummy::class, identifiers: ['slug'], fromProperty: 'parentDummy')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/slug_child_dummies/{slug}/parent_dummy.{_format}', uriVariables: ['slug' => new Link(fromClass: SlugChildDummy::class, identifiers: ['slug'], fromProperty: 'parentDummy')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/slug_parent_dummies/{slug}/child_dummies/{childDummies}/parent_dummy{._format}', uriVariables: ['slug' => new Link(fromClass: self::class, identifiers: ['slug'], toProperty: 'parentDummy'), 'childDummies' => new Link(fromClass: SlugChildDummy::class, identifiers: ['slug'], fromProperty: 'parentDummy')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/slug_child_dummies/{slug}/parent_dummy{._format}', uriVariables: ['slug' => new Link(fromClass: SlugChildDummy::class, identifiers: ['slug'], fromProperty: 'parentDummy')], status: 200, operations: [new Get()])]
#[ORM\Entity]
class SlugParentDummy
{
diff --git a/tests/Fixtures/TestBundle/Entity/SoMany.php b/tests/Fixtures/TestBundle/Entity/SoMany.php
index 65a2ad93790..e3770b8007d 100644
--- a/tests/Fixtures/TestBundle/Entity/SoMany.php
+++ b/tests/Fixtures/TestBundle/Entity/SoMany.php
@@ -31,4 +31,7 @@ class SoMany
public $id;
#[ORM\Column(nullable: true)]
public $content;
+
+ #[ORM\ManyToOne]
+ public ?FooDummy $fooDummy;
}
diff --git a/tests/Fixtures/TestBundle/Entity/ThirdLevel.php b/tests/Fixtures/TestBundle/Entity/ThirdLevel.php
index 1d18d362d73..1ca2e4c6f43 100644
--- a/tests/Fixtures/TestBundle/Entity/ThirdLevel.php
+++ b/tests/Fixtures/TestBundle/Entity/ThirdLevel.php
@@ -25,11 +25,11 @@
* @author Kévin Dunglas
*/
#[ApiResource]
-#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/id/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_dummies/{id}/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
-#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/third_level.{_format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/third_level{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/id/third_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_dummies/{id}/third_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy/related_dummies/{relatedDummies}/third_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy'), 'owningDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owning_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
+#[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy/related_dummies/{relatedDummies}/third_level{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy'), 'ownedDummy' => new Link(fromClass: Dummy::class, identifiers: [], expandedValue: 'owned_dummy', fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], fromProperty: 'thirdLevel')], status: 200, operations: [new Get()])]
#[ORM\Entity]
class ThirdLevel
{
diff --git a/tests/Fixtures/TestBundle/Entity/VoDummyInspection.php b/tests/Fixtures/TestBundle/Entity/VoDummyInspection.php
index fc3eb0561f8..7eebd395415 100644
--- a/tests/Fixtures/TestBundle/Entity/VoDummyInspection.php
+++ b/tests/Fixtures/TestBundle/Entity/VoDummyInspection.php
@@ -14,7 +14,6 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
use ApiPlatform\Metadata\ApiResource;
-use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
@@ -28,9 +27,12 @@ class VoDummyInspection
#[Groups(['car_read', 'car_write', 'inspection_read', 'inspection_write'])]
private \DateTime $performed;
- public function __construct(#[ORM\Column(type: 'boolean')] #[Groups(['car_read', 'car_write', 'inspection_read', 'inspection_write'])] private bool $accepted, #[ORM\ManyToOne(targetEntity: VoDummyCar::class, inversedBy: 'inspections')] #[Groups(['inspection_read', 'inspection_write'])] private ?VoDummyCar $car, DateTime $performed = null, private string $attributeWithoutConstructorEquivalent = '')
+ private $attributeWithoutConstructorEquivalent;
+
+ public function __construct(#[ORM\Column(type: 'boolean')] #[Groups(['car_read', 'car_write', 'inspection_read', 'inspection_write'])] private bool $accepted, #[ORM\ManyToOne(targetEntity: VoDummyCar::class, inversedBy: 'inspections')] #[Groups(['inspection_read', 'inspection_write'])] private ?VoDummyCar $car, \DateTime $performed = null, string $parameterWhichIsNotClassAttribute = '')
{
- $this->performed = $performed ?: new DateTime();
+ $this->performed = $performed ?: new \DateTime();
+ $this->attributeWithoutConstructorEquivalent = $parameterWhichIsNotClassAttribute;
}
public function isAccepted(): bool
@@ -43,12 +45,12 @@ public function getCar(): ?VoDummyCar
return $this->car;
}
- public function getPerformed(): DateTime
+ public function getPerformed(): \DateTime
{
return $this->performed;
}
- public function setPerformed(DateTime $performed)
+ public function setPerformed(\DateTime $performed)
{
$this->performed = $performed;
diff --git a/tests/GraphQl/Metadata/Factory/GraphQlNestedOperationResourceMetadataFactoryTest.php b/tests/GraphQl/Metadata/Factory/GraphQlNestedOperationResourceMetadataFactoryTest.php
new file mode 100644
index 00000000000..65c4b65c51f
--- /dev/null
+++ b/tests/GraphQl/Metadata/Factory/GraphQlNestedOperationResourceMetadataFactoryTest.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\Tests\GraphQl\Metadata\Factory;
+
+use ApiPlatform\GraphQl\Metadata\Factory\GraphQlNestedOperationResourceMetadataFactory;
+use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
+use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
+use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
+use PHPUnit\Framework\TestCase;
+use Prophecy\PhpUnit\ProphecyTrait;
+
+final class GraphQlNestedOperationResourceMetadataFactoryTest extends TestCase
+{
+ use ProphecyTrait;
+
+ public function testCreate(): void
+ {
+ $decorated = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
+ $decorated->create('someClass')->shouldBeCalled()->willReturn(new ResourceMetadataCollection('someClass'));
+
+ $metadataFactory = new GraphQlNestedOperationResourceMetadataFactory(['status' => 500], $decorated->reveal());
+ $apiResource = $metadataFactory->create('someClass')[0];
+ $this->assertCount(5, $apiResource->getGraphQlOperations());
+ }
+
+ public function testCreateWithResource(): void
+ {
+ $metadataFactory = new GraphQlNestedOperationResourceMetadataFactory(['status' => 500]);
+ $apiResource = $metadataFactory->create(RelatedDummy::class)[0];
+ $this->assertNotEmpty($apiResource->getFilters());
+ $this->assertEquals('RelatedDummy', $apiResource->getShortName());
+ }
+}
diff --git a/tests/GraphQl/Type/FieldsBuilderTest.php b/tests/GraphQl/Type/FieldsBuilderTest.php
index c7bb3ba897a..c5b59ad50b7 100644
--- a/tests/GraphQl/Type/FieldsBuilderTest.php
+++ b/tests/GraphQl/Type/FieldsBuilderTest.php
@@ -56,29 +56,18 @@ class FieldsBuilderTest extends TestCase
use ProphecyTrait;
private ObjectProphecy $propertyNameCollectionFactoryProphecy;
-
private ObjectProphecy $propertyMetadataFactoryProphecy;
-
private ObjectProphecy $resourceMetadataCollectionFactoryProphecy;
-
+ private ObjectProphecy $graphQlNestedOperationResourceMetadataFactoryProphecy;
private ObjectProphecy $typesContainerProphecy;
-
private ObjectProphecy $typeBuilderProphecy;
-
private ObjectProphecy $typeConverterProphecy;
-
private ObjectProphecy $itemResolverFactoryProphecy;
-
private ObjectProphecy $collectionResolverFactoryProphecy;
-
private ObjectProphecy $itemMutationResolverFactoryProphecy;
-
private ObjectProphecy $itemSubscriptionResolverFactoryProphecy;
-
private ObjectProphecy $filterLocatorProphecy;
-
private ObjectProphecy $resourceClassResolverProphecy;
-
private FieldsBuilder $fieldsBuilder;
/**
@@ -98,12 +87,13 @@ protected function setUp(): void
$this->itemSubscriptionResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
$this->filterLocatorProphecy = $this->prophesize(ContainerInterface::class);
$this->resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
+ $this->graphQlNestedOperationResourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
$this->fieldsBuilder = $this->buildFieldsBuilder();
}
private function buildFieldsBuilder(?AdvancedNameConverterInterface $advancedNameConverter = null): FieldsBuilder
{
- return new FieldsBuilder($this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceClassResolverProphecy->reveal(), $this->typesContainerProphecy->reveal(), $this->typeBuilderProphecy->reveal(), $this->typeConverterProphecy->reveal(), $this->itemResolverFactoryProphecy->reveal(), $this->collectionResolverFactoryProphecy->reveal(), $this->itemMutationResolverFactoryProphecy->reveal(), $this->itemSubscriptionResolverFactoryProphecy->reveal(), $this->filterLocatorProphecy->reveal(), new Pagination(), $advancedNameConverter ?? new CustomConverter(), '__');
+ return new FieldsBuilder($this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceClassResolverProphecy->reveal(), $this->typesContainerProphecy->reveal(), $this->typeBuilderProphecy->reveal(), $this->typeConverterProphecy->reveal(), $this->itemResolverFactoryProphecy->reveal(), $this->collectionResolverFactoryProphecy->reveal(), $this->itemMutationResolverFactoryProphecy->reveal(), $this->itemSubscriptionResolverFactoryProphecy->reveal(), $this->filterLocatorProphecy->reveal(), new Pagination(), $advancedNameConverter ?? new CustomConverter(), '__', $this->graphQlNestedOperationResourceMetadataFactoryProphecy->reveal());
}
public function testGetNodeQueryFields(): void
@@ -139,7 +129,6 @@ public function testGetItemQueryFields(string $resourceClass, Operation $operati
$this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
$this->typeConverterProphecy->resolveType(Argument::type('string'))->willReturn(GraphQLType::string());
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
- $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])]));
$this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($resolver);
$queryFields = $this->fieldsBuilder->getItemQueryFields($resourceClass, $operation, $configuration);
@@ -150,8 +139,8 @@ public function testGetItemQueryFields(string $resourceClass, Operation $operati
public function itemQueryFieldsProvider(): array
{
return [
- 'no resource field configuration' => ['resourceClass', (new Query())->withName('action'), [], null, null, []],
- 'nominal standard type case with deprecation reason and description' => ['resourceClass', (new Query())->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful')->withDescription('Custom description.'), [], GraphQLType::string(), null,
+ 'no resource field configuration' => ['resourceClass', (new Query())->withClass('resourceClass')->withName('action'), [], null, null, []],
+ 'nominal standard type case with deprecation reason and description' => ['resourceClass', (new Query())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful')->withDescription('Custom description.'), [], GraphQLType::string(), null,
[
'actionShortName' => [
'type' => GraphQLType::string(),
@@ -164,7 +153,7 @@ public function itemQueryFieldsProvider(): array
],
],
],
- 'nominal item case' => ['resourceClass', (new Query())->withName('action')->withShortName('ShortName'), [], $graphqlType = new ObjectType(['name' => 'item']), $resolver = function (): void {
+ 'nominal item case' => ['resourceClass', (new Query())->withClass('resourceClass')->withName('action')->withShortName('ShortName'), [], $graphqlType = new ObjectType(['name' => 'item']), $resolver = function (): void {
},
[
'actionShortName' => [
@@ -179,7 +168,7 @@ public function itemQueryFieldsProvider(): array
],
],
'empty overridden args and add fields' => [
- 'resourceClass', (new Query())->withShortName('ShortName'), ['args' => [], 'name' => 'customActionName'], GraphQLType::string(), null,
+ 'resourceClass', (new Query())->withClass('resourceClass')->withShortName('ShortName'), ['args' => [], 'name' => 'customActionName'], GraphQLType::string(), null,
[
'shortName' => [
'type' => GraphQLType::string(),
@@ -192,7 +181,7 @@ public function itemQueryFieldsProvider(): array
],
],
'override args with custom ones' => [
- 'resourceClass', (new Query())->withShortName('ShortName'), ['args' => ['customArg' => ['type' => 'a type']]], GraphQLType::string(), null,
+ 'resourceClass', (new Query())->withClass('resourceClass')->withShortName('ShortName'), ['args' => ['customArg' => ['type' => 'a type']]], GraphQLType::string(), null,
[
'shortName' => [
'type' => GraphQLType::string(),
@@ -220,7 +209,6 @@ public function testGetCollectionQueryFields(string $resourceClass, Operation $o
$this->typeConverterProphecy->resolveType(Argument::type('string'))->willReturn(GraphQLType::string());
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(true);
$this->typeBuilderProphecy->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $operation)->willReturn($graphqlType);
- $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])]));
$this->collectionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($resolver);
$this->filterLocatorProphecy->has('my_filter')->willReturn(true);
$filterProphecy = $this->prophesize(FilterInterface::class);
@@ -244,8 +232,8 @@ public function testGetCollectionQueryFields(string $resourceClass, Operation $o
public function collectionQueryFieldsProvider(): array
{
return [
- 'no resource field configuration' => ['resourceClass', (new QueryCollection())->withName('action'), [], null, null, []],
- 'nominal collection case with deprecation reason and description' => ['resourceClass', (new QueryCollection())->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful')->withDescription('Custom description.'), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
+ 'no resource field configuration' => ['resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action'), [], null, null, []],
+ 'nominal collection case with deprecation reason and description' => ['resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful')->withDescription('Custom description.'), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
},
[
'actionShortNames' => [
@@ -274,7 +262,7 @@ public function collectionQueryFieldsProvider(): array
],
],
],
- 'collection with filters' => ['resourceClass', (new QueryCollection())->withName('action')->withShortName('ShortName')->withFilters(['my_filter']), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
+ 'collection with filters' => ['resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withFilters(['my_filter']), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
},
[
'actionShortNames' => [
@@ -308,7 +296,7 @@ public function collectionQueryFieldsProvider(): array
],
],
'collection empty overridden args and add fields' => [
- 'resourceClass', (new QueryCollection())->withArgs([])->withName('action')->withShortName('ShortName'), ['args' => [], 'name' => 'customActionName'], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
+ 'resourceClass', (new QueryCollection())->withArgs([])->withClass('resourceClass')->withName('action')->withShortName('ShortName'), ['args' => [], 'name' => 'customActionName'], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
},
[
'actionShortNames' => [
@@ -322,7 +310,7 @@ public function collectionQueryFieldsProvider(): array
],
],
'collection override args with custom ones' => [
- 'resourceClass', (new QueryCollection())->withName('action')->withShortName('ShortName'), ['args' => ['customArg' => ['type' => 'a type']]], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
+ 'resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action')->withShortName('ShortName'), ['args' => ['customArg' => ['type' => 'a type']]], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
},
[
'actionShortNames' => [
@@ -338,7 +326,7 @@ public function collectionQueryFieldsProvider(): array
],
],
],
- 'collection with page-based pagination enabled' => ['resourceClass', (new QueryCollection())->withName('action')->withShortName('ShortName')->withPaginationType('page')->withFilters(['my_filter']), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
+ 'collection with page-based pagination enabled' => ['resourceClass', (new QueryCollection())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withPaginationType('page')->withFilters(['my_filter']), [], $graphqlType = GraphQLType::listOf(new ObjectType(['name' => 'collection'])), $resolver = function (): void {
},
[
'actionShortNames' => [
@@ -371,8 +359,6 @@ public function testGetMutationFields(string $resourceClass, Operation $operatio
$this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
$this->typeConverterProphecy->convertType(Argument::type(Type::class), true, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($inputGraphqlType);
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
- $this->typeBuilderProphecy->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $operation->getName())->willReturn($graphqlType);
- $this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])]));
$this->itemMutationResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($mutationResolver);
$mutationFields = $this->fieldsBuilder->getMutationFields($resourceClass, $operation);
@@ -383,7 +369,7 @@ public function testGetMutationFields(string $resourceClass, Operation $operatio
public function mutationFieldsProvider(): array
{
return [
- 'nominal case with deprecation reason' => ['resourceClass', (new Mutation())->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful'), $graphqlType = new ObjectType(['name' => 'mutation']), $inputGraphqlType = new ObjectType(['name' => 'input']), $mutationResolver = function (): void {
+ 'nominal case with deprecation reason' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withDeprecationReason('not useful'), $graphqlType = new ObjectType(['name' => 'mutation']), $inputGraphqlType = new ObjectType(['name' => 'input']), $mutationResolver = function (): void {
},
[
'actionShortName' => [
@@ -403,7 +389,7 @@ public function mutationFieldsProvider(): array
],
],
],
- 'custom description' => ['resourceClass', (new Mutation())->withName('action')->withShortName('ShortName')->withDescription('Custom description.'), $graphqlType = new ObjectType(['name' => 'mutation']), $inputGraphqlType = new ObjectType(['name' => 'input']), $mutationResolver = function (): void {
+ 'custom description' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withDescription('Custom description.'), $graphqlType = new ObjectType(['name' => 'mutation']), $inputGraphqlType = new ObjectType(['name' => 'input']), $mutationResolver = function (): void {
},
[
'actionShortName' => [
@@ -435,7 +421,6 @@ public function testGetSubscriptionFields(string $resourceClass, Operation $oper
$this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
$this->typeConverterProphecy->convertType(Argument::type(Type::class), true, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($inputGraphqlType);
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
- $this->typeBuilderProphecy->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $operation->getName())->willReturn($graphqlType);
$this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])]));
$this->itemSubscriptionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($subscriptionResolver);
@@ -447,9 +432,9 @@ public function testGetSubscriptionFields(string $resourceClass, Operation $oper
public function subscriptionFieldsProvider(): array
{
return [
- 'mercure not enabled' => ['resourceClass', (new Subscription())->withName('action')->withShortName('ShortName'), new ObjectType(['name' => 'subscription']), new ObjectType(['name' => 'input']), null, [],
+ 'mercure not enabled' => ['resourceClass', (new Subscription())->withClass('resourceClass')->withName('action')->withShortName('ShortName'), new ObjectType(['name' => 'subscription']), new ObjectType(['name' => 'input']), null, [],
],
- 'nominal case with deprecation reason' => ['resourceClass', (new Subscription())->withName('action')->withShortName('ShortName')->withMercure(true)->withDeprecationReason('not useful'), $graphqlType = new ObjectType(['name' => 'subscription']), $inputGraphqlType = new ObjectType(['name' => 'input']), $subscriptionResolver = function (): void {
+ 'nominal case with deprecation reason' => ['resourceClass', (new Subscription())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withMercure(true)->withDeprecationReason('not useful'), $graphqlType = new ObjectType(['name' => 'subscription']), $inputGraphqlType = new ObjectType(['name' => 'input']), $subscriptionResolver = function (): void {
},
[
'actionShortNameSubscribe' => [
@@ -469,7 +454,7 @@ public function subscriptionFieldsProvider(): array
],
],
],
- 'custom description' => ['resourceClass', (new Subscription())->withName('action')->withShortName('ShortName')->withMercure(true)->withDescription('Custom description.'), $graphqlType = new ObjectType(['name' => 'subscription']), $inputGraphqlType = new ObjectType(['name' => 'input']), $subscriptionResolver = function (): void {
+ 'custom description' => ['resourceClass', (new Subscription())->withClass('resourceClass')->withName('action')->withShortName('ShortName')->withMercure(true)->withDescription('Custom description.'), $graphqlType = new ObjectType(['name' => 'subscription']), $inputGraphqlType = new ObjectType(['name' => 'input']), $subscriptionResolver = function (): void {
},
[
'actionShortNameSubscribe' => [
@@ -498,6 +483,8 @@ public function subscriptionFieldsProvider(): array
public function testGetResourceObjectTypeFields(string $resourceClass, Operation $operation, array $properties, bool $input, int $depth, ?array $ioMetadata, array $expectedResourceObjectTypeFields, ?AdvancedNameConverterInterface $advancedNameConverter = null): void
{
$this->resourceClassResolverProphecy->isResourceClass($resourceClass)->willReturn(true);
+ $this->resourceClassResolverProphecy->isResourceClass('nestedResourceClass')->willReturn(true);
+ $this->resourceClassResolverProphecy->isResourceClass('nestedResourceNoQueryClass')->willReturn(true);
$this->resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(false);
$this->propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection(array_keys($properties)));
foreach ($properties as $propertyName => $propertyMetadata) {
@@ -505,20 +492,32 @@ public function testGetResourceObjectTypeFields(string $resourceClass, Operation
$this->typeConverterProphecy->convertType(new Type(Type::BUILTIN_TYPE_NULL), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn(null);
$this->typeConverterProphecy->convertType(new Type(Type::BUILTIN_TYPE_CALLABLE), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn('NotRegisteredType');
$this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn(GraphQLType::string());
+ $this->typeConverterProphecy->convertType(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn(GraphQLType::nonNull(GraphQLType::listOf(GraphQLType::nonNull(GraphQLType::string()))));
+
if ('propertyObject' === $propertyName) {
$this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), 'objectClass', $resourceClass, $propertyName, $depth + 1)->willReturn(new ObjectType(['name' => 'objectType']));
- $this->resourceMetadataCollectionFactoryProphecy->create('objectClass')->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations(['item_query' => new Query()])]));
$this->itemResolverFactoryProphecy->__invoke('objectClass', $resourceClass, $operation)->willReturn(static function (): void {
});
}
- $this->typeConverterProphecy->convertType(Argument::type(Type::class), true, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), 'anotherResourceClass', $propertyName, $depth + 1)->willReturn(GraphQLType::string());
- $this->typeConverterProphecy->convertType(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), '', $resourceClass, $propertyName, $depth + 1)->willReturn(GraphQLType::nonNull(GraphQLType::listOf(GraphQLType::nonNull(GraphQLType::string()))));
+ if ('propertyNestedResource' === $propertyName) {
+ $nestedResourceQueryOperation = new Query();
+ $this->resourceMetadataCollectionFactoryProphecy->create('nestedResourceClass')->willReturn(new ResourceMetadataCollection('nestedResourceClass', [(new ApiResource())->withGraphQlOperations(['item_query' => $nestedResourceQueryOperation])]));
+ $this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), 'nestedResourceClass', $resourceClass, $propertyName, $depth + 1)->willReturn(new ObjectType(['name' => 'objectType']));
+ $this->itemResolverFactoryProphecy->__invoke('nestedResourceClass', $resourceClass, $nestedResourceQueryOperation)->willReturn(static function (): void {
+ });
+ }
+ if ('propertyNestedResourceNoQuery' === $propertyName) {
+ $nestedResourceQueryOperation = new Query();
+ $this->resourceMetadataCollectionFactoryProphecy->create('nestedResourceNoQueryClass')->willReturn(new ResourceMetadataCollection('nestedResourceNoQueryClass', [(new ApiResource())->withDescription('A description.')->withGraphQlOperations([])]));
+ $this->graphQlNestedOperationResourceMetadataFactoryProphecy->create('nestedResourceNoQueryClass')->shouldBeCalled()->willReturn(new ResourceMetadataCollection('nestedResourceNoQueryClass', [(new ApiResource())->withGraphQlOperations(['item_query' => $nestedResourceQueryOperation])]));
+ $this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), 'nestedResourceNoQueryClass', $resourceClass, $propertyName, $depth + 1)->willReturn(new ObjectType(['name' => 'objectType']));
+ $this->itemResolverFactoryProphecy->__invoke('nestedResourceNoQueryClass', $resourceClass, $nestedResourceQueryOperation)->willReturn(static function (): void {
+ });
+ }
}
$this->typesContainerProphecy->has('NotRegisteredType')->willReturn(false);
$this->typesContainerProphecy->all()->willReturn([]);
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
- $this->resourceMetadataCollectionFactoryProphecy->create('resourceClass')->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])]));
- $this->resourceMetadataCollectionFactoryProphecy->create('anotherResourceClass')->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations(['item_query' => new Query()])]));
$fieldsBuilder = $this->fieldsBuilder;
if ($advancedNameConverter) {
@@ -535,7 +534,7 @@ public function resourceObjectTypeFieldsProvider(): array
$advancedNameConverter->normalize('field', 'resourceClass')->willReturn('normalizedField');
return [
- 'query' => ['resourceClass', new Query(),
+ 'query' => ['resourceClass', (new Query())->withClass('resourceClass'),
[
'property' => new ApiProperty(),
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(true)->withWritable(false),
@@ -563,7 +562,7 @@ public function resourceObjectTypeFieldsProvider(): array
],
],
],
- 'query with advanced name converter' => ['resourceClass', new Query(),
+ 'query with advanced name converter' => ['resourceClass', (new Query())->withClass('resourceClass'),
[
'field' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withReadable(true)->withWritable(false),
],
@@ -582,7 +581,7 @@ public function resourceObjectTypeFieldsProvider(): array
],
$advancedNameConverter->reveal(),
],
- 'query input' => ['resourceClass', new Query(),
+ 'query input' => ['resourceClass', (new Query())->withClass('resourceClass'),
[
'property' => new ApiProperty(),
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(false),
@@ -601,7 +600,7 @@ public function resourceObjectTypeFieldsProvider(): array
],
],
],
- 'query with simple non-null string array property' => ['resourceClass', new Query(),
+ 'query with simple non-null string array property' => ['resourceClass', (new Query())->withClass('resourceClass'),
[
'property' => (new ApiProperty())->withBuiltinTypes([
new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)),
@@ -621,12 +620,40 @@ public function resourceObjectTypeFieldsProvider(): array
],
],
],
- 'mutation non input' => ['resourceClass', (new Mutation())->withName('mutation'),
+ 'query with nested resources' => ['resourceClass', (new Query())->withClass('resourceClass'),
+ [
+ 'propertyNestedResource' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'nestedResourceClass')])->withReadable(true)->withWritable(true),
+ 'propertyNestedResourceNoQuery' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'nestedResourceNoQueryClass')])->withReadable(true)->withWritable(true),
+ ],
+ false, 0, null,
+ [
+ 'id' => [
+ 'type' => GraphQLType::nonNull(GraphQLType::id()),
+ ],
+ 'propertyNestedResource' => [
+ 'type' => GraphQLType::nonNull(new ObjectType(['name' => 'objectType'])),
+ 'description' => null,
+ 'args' => [],
+ 'resolve' => static function (): void {
+ },
+ 'deprecationReason' => null,
+ ],
+ 'propertyNestedResourceNoQuery' => [
+ 'type' => GraphQLType::nonNull(new ObjectType(['name' => 'objectType'])),
+ 'description' => null,
+ 'args' => [],
+ 'resolve' => static function (): void {
+ },
+ 'deprecationReason' => null,
+ ],
+ ],
+ ],
+ 'mutation non input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('mutation'),
[
'property' => new ApiProperty(),
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
'propertyReadable' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(true)->withWritable(true),
- 'propertyObject' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL, false, 'objectClass')])->withReadable(true)->withWritable(true),
+ 'propertyObject' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, 'objectClass')])->withReadable(true)->withWritable(true),
],
false, 0, null,
[
@@ -650,7 +677,7 @@ public function resourceObjectTypeFieldsProvider(): array
],
],
],
- 'mutation input' => ['resourceClass', (new Mutation())->withName('mutation'),
+ 'mutation input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('mutation'),
[
'property' => new ApiProperty(),
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('propertyBool description')->withReadable(false)->withWritable(true)->withDeprecationReason('not useful'),
@@ -686,7 +713,7 @@ public function resourceObjectTypeFieldsProvider(): array
'clientMutationId' => GraphQLType::string(),
],
],
- 'mutation nested input' => ['resourceClass', (new Mutation())->withName('mutation'),
+ 'mutation nested input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('mutation'),
[
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
],
@@ -705,7 +732,7 @@ public function resourceObjectTypeFieldsProvider(): array
'clientMutationId' => GraphQLType::string(),
],
],
- 'delete mutation input' => ['resourceClass', (new Mutation())->withName('delete'),
+ 'delete mutation input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('delete'),
[
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
],
@@ -717,7 +744,7 @@ public function resourceObjectTypeFieldsProvider(): array
'clientMutationId' => GraphQLType::string(),
],
],
- 'create mutation input' => ['resourceClass', (new Mutation())->withName('create'),
+ 'create mutation input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('create'),
[
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
],
@@ -733,7 +760,7 @@ public function resourceObjectTypeFieldsProvider(): array
'clientMutationId' => GraphQLType::string(),
],
],
- 'update mutation input' => ['resourceClass', (new Mutation())->withName('update'),
+ 'update mutation input' => ['resourceClass', (new Mutation())->withClass('resourceClass')->withName('update'),
[
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
],
@@ -752,7 +779,7 @@ public function resourceObjectTypeFieldsProvider(): array
'clientMutationId' => GraphQLType::string(),
],
],
- 'subscription non input' => ['resourceClass', new Subscription(),
+ 'subscription non input' => ['resourceClass', (new Subscription())->withClass('resourceClass'),
[
'property' => new ApiProperty(),
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
@@ -772,7 +799,7 @@ public function resourceObjectTypeFieldsProvider(): array
],
],
],
- 'subscription input' => ['resourceClass', new Subscription(),
+ 'subscription input' => ['resourceClass', (new Subscription())->withClass('resourceClass'),
[
'property' => new ApiProperty(),
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('propertyBool description')->withReadable(false)->withWritable(true)->withDeprecationReason('not useful'),
@@ -787,13 +814,13 @@ public function resourceObjectTypeFieldsProvider(): array
'clientSubscriptionId' => GraphQLType::string(),
],
],
- 'null io metadata non input' => ['resourceClass', new Query(),
+ 'null io metadata non input' => ['resourceClass', (new Query())->withClass('resourceClass'),
[
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
],
false, 0, ['class' => null], [],
],
- 'null io metadata input' => ['resourceClass', new Query(),
+ 'null io metadata input' => ['resourceClass', (new Query())->withClass('resourceClass'),
[
'propertyBool' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withReadable(false)->withWritable(true),
],
@@ -802,7 +829,7 @@ public function resourceObjectTypeFieldsProvider(): array
'clientMutationId' => GraphQLType::string(),
],
],
- 'invalid types' => ['resourceClass', new Query(),
+ 'invalid types' => ['resourceClass', (new Query())->withClass('resourceClass'),
[
'propertyInvalidType' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_NULL)])->withReadable(true)->withWritable(false),
'propertyNotRegisteredType' => (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_CALLABLE)])->withReadable(true)->withWritable(false),
diff --git a/tests/GraphQl/Type/TypeBuilderTest.php b/tests/GraphQl/Type/TypeBuilderTest.php
index 66b0e7cd855..77ba4f7e842 100644
--- a/tests/GraphQl/Type/TypeBuilderTest.php
+++ b/tests/GraphQl/Type/TypeBuilderTest.php
@@ -483,7 +483,7 @@ public function testCursorBasedGetResourcePaginatedCollectionType(): void
$this->typesContainerProphecy->set('StringPageInfo', Argument::type(ObjectType::class))->shouldBeCalled();
/** @var ObjectType $resourcePaginatedCollectionType */
- $resourcePaginatedCollectionType = $this->typeBuilder->getResourcePaginatedCollectionType(GraphQLType::string(), 'StringResourceClass', $operation);
+ $resourcePaginatedCollectionType = $this->typeBuilder->getResourcePaginatedCollectionType(GraphQLType::string(), 'test', $operation);
$this->assertSame('StringCursorConnection', $resourcePaginatedCollectionType->name);
$this->assertSame('Cursor connection for String.', $resourcePaginatedCollectionType->description);
@@ -538,7 +538,7 @@ public function testPageBasedGetResourcePaginatedCollectionType(): void
$this->typesContainerProphecy->set('StringPaginationInfo', Argument::type(ObjectType::class))->shouldBeCalled();
/** @var ObjectType $resourcePaginatedCollectionType */
- $resourcePaginatedCollectionType = $this->typeBuilder->getResourcePaginatedCollectionType(GraphQLType::string(), 'StringResourceClass', $operation);
+ $resourcePaginatedCollectionType = $this->typeBuilder->getResourcePaginatedCollectionType(GraphQLType::string(), 'test', $operation);
$this->assertSame('StringPageConnection', $resourcePaginatedCollectionType->name);
$this->assertSame('Page connection for String.', $resourcePaginatedCollectionType->description);
diff --git a/tests/HttpCache/VarnishPurgerTest.php b/tests/HttpCache/VarnishPurgerTest.php
index ffc2a5b53f4..faca342dc91 100644
--- a/tests/HttpCache/VarnishPurgerTest.php
+++ b/tests/HttpCache/VarnishPurgerTest.php
@@ -17,7 +17,6 @@
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response;
-use LogicException;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Http\Message\RequestInterface;
@@ -79,12 +78,12 @@ public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLengt
public function send(RequestInterface $request, array $options = []): ResponseInterface
{
- throw new LogicException('Not implemented');
+ throw new \LogicException('Not implemented');
}
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
{
- throw new LogicException('Not implemented');
+ throw new \LogicException('Not implemented');
}
public function request($method, $uri, array $options = []): ResponseInterface
@@ -96,12 +95,12 @@ public function request($method, $uri, array $options = []): ResponseInterface
public function requestAsync($method, $uri, array $options = []): PromiseInterface
{
- throw new LogicException('Not implemented');
+ throw new \LogicException('Not implemented');
}
public function getConfig($option = null): void
{
- throw new LogicException('Not implemented');
+ throw new \LogicException('Not implemented');
}
};
diff --git a/tests/HttpCache/VarnishXKeyPurgerTest.php b/tests/HttpCache/VarnishXKeyPurgerTest.php
index 33c64da8678..96e380752b9 100644
--- a/tests/HttpCache/VarnishXKeyPurgerTest.php
+++ b/tests/HttpCache/VarnishXKeyPurgerTest.php
@@ -17,7 +17,6 @@
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response;
-use LogicException;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Http\Message\RequestInterface;
@@ -109,12 +108,12 @@ public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLengt
public function send(RequestInterface $request, array $options = []): ResponseInterface
{
- throw new LogicException('Not implemented');
+ throw new \LogicException('Not implemented');
}
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
{
- throw new LogicException('Not implemented');
+ throw new \LogicException('Not implemented');
}
public function request($method, $uri, array $options = []): ResponseInterface
@@ -126,12 +125,12 @@ public function request($method, $uri, array $options = []): ResponseInterface
public function requestAsync($method, $uri, array $options = []): PromiseInterface
{
- throw new LogicException('Not implemented');
+ throw new \LogicException('Not implemented');
}
public function getConfig($option = null): void
{
- throw new LogicException('Not implemented');
+ throw new \LogicException('Not implemented');
}
};
diff --git a/tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php b/tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php
index fb6040a5de1..ae69822be21 100644
--- a/tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php
+++ b/tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php
@@ -49,21 +49,21 @@ public function testExecuteWithoutOption(): void
public function testExecuteWithItemOperationGet(): void
{
- $this->tester->run(['command' => 'api:json-schema:generate', 'resource' => $this->entityClass, '--operation' => '_api_/dummies/{id}.{_format}_get', '--type' => 'output']);
+ $this->tester->run(['command' => 'api:json-schema:generate', 'resource' => $this->entityClass, '--operation' => '_api_/dummies/{id}{._format}_get', '--type' => 'output']);
$this->assertJson($this->tester->getDisplay());
}
public function testExecuteWithCollectionOperationGet(): void
{
- $this->tester->run(['command' => 'api:json-schema:generate', 'resource' => $this->entityClass, '--operation' => '_api_/dummies.{_format}_get_collection', '--type' => 'output']);
+ $this->tester->run(['command' => 'api:json-schema:generate', 'resource' => $this->entityClass, '--operation' => '_api_/dummies{._format}_get_collection', '--type' => 'output']);
$this->assertJson($this->tester->getDisplay());
}
public function testExecuteWithJsonldFormatOption(): void
{
- $this->tester->run(['command' => 'api:json-schema:generate', 'resource' => $this->entityClass, '--operation' => '_api_/dummies.{_format}_post', '--format' => 'jsonld']);
+ $this->tester->run(['command' => 'api:json-schema:generate', 'resource' => $this->entityClass, '--operation' => '_api_/dummies{._format}_post', '--format' => 'jsonld']);
$result = $this->tester->getDisplay();
$this->assertStringContainsString('@id', $result);
diff --git a/tests/Metadata/Extractor/PropertyMetadataCompatibilityTest.php b/tests/Metadata/Extractor/PropertyMetadataCompatibilityTest.php
index 04279076970..4c34da9000f 100644
--- a/tests/Metadata/Extractor/PropertyMetadataCompatibilityTest.php
+++ b/tests/Metadata/Extractor/PropertyMetadataCompatibilityTest.php
@@ -21,7 +21,6 @@
use ApiPlatform\Tests\Metadata\Extractor\Adapter\PropertyAdapterInterface;
use ApiPlatform\Tests\Metadata\Extractor\Adapter\XmlPropertyAdapter;
use ApiPlatform\Tests\Metadata\Extractor\Adapter\YamlPropertyAdapter;
-use Exception;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Type;
@@ -87,7 +86,7 @@ public function testValidMetadata(string $extractorClass, PropertyAdapterInterfa
$extractor = new $extractorClass($adapter(self::RESOURCE_CLASS, self::PROPERTY, $parameters, self::FIXTURES));
$factory = new ExtractorPropertyMetadataFactory($extractor);
$property = $factory->create(self::RESOURCE_CLASS, self::PROPERTY);
- } catch (Exception $exception) {
+ } catch (\Exception $exception) {
throw new AssertionFailedError('Failed asserting that the schema is valid according to '.ApiProperty::class, 0, $exception);
}
diff --git a/tests/Metadata/Extractor/ResourceMetadataCompatibilityTest.php b/tests/Metadata/Extractor/ResourceMetadataCompatibilityTest.php
index 20342ad36e6..e6ce7a3727d 100644
--- a/tests/Metadata/Extractor/ResourceMetadataCompatibilityTest.php
+++ b/tests/Metadata/Extractor/ResourceMetadataCompatibilityTest.php
@@ -20,10 +20,13 @@
use ApiPlatform\Metadata\Extractor\YamlResourceExtractor;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\GraphQl\DeleteMutation;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Subscription;
use ApiPlatform\Metadata\HttpOperation;
+use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
@@ -34,7 +37,6 @@
use ApiPlatform\Tests\Metadata\Extractor\Adapter\ResourceAdapterInterface;
use ApiPlatform\Tests\Metadata\Extractor\Adapter\XmlResourceAdapter;
use ApiPlatform\Tests\Metadata\Extractor\Adapter\YamlResourceAdapter;
-use Exception;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestCase;
@@ -224,7 +226,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase
[
'name' => 'custom_operation_name',
'method' => 'GET',
- 'uriTemplate' => '/users/{userId}/comments.{_format}',
+ 'uriTemplate' => '/users/{userId}/comments{._format}',
'shortName' => self::SHORT_NAME,
'description' => 'A list of Comments',
'types' => ['Comment'],
@@ -330,7 +332,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase
],
],
[
- 'uriTemplate' => '/users/{userId}/comments/{commentId}.{_format}',
+ 'uriTemplate' => '/users/{userId}/comments/{commentId}{._format}',
'class' => Get::class,
'uriVariables' => [
'userId' => [
@@ -420,7 +422,7 @@ public function testValidMetadata(string $extractorClass, ResourceAdapterInterfa
$extractor = new $extractorClass($adapter(self::RESOURCE_CLASS, $parameters, self::FIXTURES));
$factory = new ExtractorResourceMetadataCollectionFactory($extractor);
$collection = $factory->create(self::RESOURCE_CLASS);
- } catch (Exception $exception) {
+ } catch (\Exception $exception) {
throw new AssertionFailedError('Failed asserting that the schema is valid according to '.ApiResource::class, 0, $exception);
}
@@ -453,7 +455,16 @@ private function buildApiResources(): array
$operations[$operationName] = $this->getOperationWithDefaults($resource, $operation)->withName($operationName);
}
- $resources[] = $resource->withOperations(new Operations($operations));
+ $resource = $resource->withOperations(new Operations($operations));
+
+ // Build default GraphQL operations
+ $graphQlOperations = [];
+ foreach ([new QueryCollection(), new Query(), (new Mutation())->withName('update'), (new DeleteMutation())->withName('delete'), (new Mutation())->withName('create')] as $graphQlOperation) {
+ $description = $graphQlOperation instanceof Mutation ? ucfirst("{$graphQlOperation->getName()}s a {$resource->getShortName()}.") : null;
+ $graphQlOperations[$graphQlOperation->getName()] = $this->getOperationWithDefaults($resource, $graphQlOperation)->withName($graphQlOperation->getName())->withDescription($description);
+ }
+
+ $resources[] = $resource->withGraphQlOperations($graphQlOperations);
continue;
}
@@ -591,7 +602,7 @@ private function withGraphQlOperations(array $values, ?array $fixtures): array
return $operations;
}
- private function getOperationWithDefaults(ApiResource $resource, HttpOperation $operation): HttpOperation
+ private function getOperationWithDefaults(ApiResource $resource, Operation $operation): Operation
{
foreach (get_class_methods($resource) as $methodName) {
if (!str_starts_with($methodName, 'get')) {
diff --git a/tests/Metadata/Extractor/XmlExtractorTest.php b/tests/Metadata/Extractor/XmlExtractorTest.php
index dc1f9b45e8f..b229f52f1b6 100644
--- a/tests/Metadata/Extractor/XmlExtractorTest.php
+++ b/tests/Metadata/Extractor/XmlExtractorTest.php
@@ -99,7 +99,7 @@ public function testValidXML(): void
'write' => null,
],
[
- 'uriTemplate' => '/users/{author}/comments.{_format}',
+ 'uriTemplate' => '/users/{author}/comments{._format}',
'shortName' => null,
'description' => 'User comments',
'routePrefix' => null,
@@ -177,7 +177,7 @@ public function testValidXML(): void
[
'name' => 'custom_operation_name',
'class' => GetCollection::class,
- 'uriTemplate' => '/users/{author}/comments.{_format}',
+ 'uriTemplate' => '/users/{author}/comments{._format}',
'shortName' => null,
'description' => 'User comments',
'routePrefix' => null,
@@ -268,7 +268,7 @@ public function testValidXML(): void
[
'name' => null,
'class' => Get::class,
- 'uriTemplate' => '/users/{userId}/comments/{id}.{_format}',
+ 'uriTemplate' => '/users/{userId}/comments/{id}{._format}',
'shortName' => null,
'description' => 'User comments',
'routePrefix' => null,
diff --git a/tests/Metadata/Extractor/YamlExtractorTest.php b/tests/Metadata/Extractor/YamlExtractorTest.php
index 843df41f723..cede4108ebc 100644
--- a/tests/Metadata/Extractor/YamlExtractorTest.php
+++ b/tests/Metadata/Extractor/YamlExtractorTest.php
@@ -166,7 +166,7 @@ public function testValidYaml(): void
'write' => null,
],
[
- 'uriTemplate' => '/users/{author}/programs.{_format}',
+ 'uriTemplate' => '/users/{author}/programs{._format}',
'shortName' => null,
'description' => 'User programs',
'routePrefix' => null,
@@ -230,7 +230,7 @@ public function testValidYaml(): void
[
'name' => null,
'class' => GetCollection::class,
- 'uriTemplate' => '/users/{author}/programs.{_format}',
+ 'uriTemplate' => '/users/{author}/programs{._format}',
'shortName' => null,
'description' => 'User programs',
'routePrefix' => null,
@@ -305,7 +305,7 @@ public function testValidYaml(): void
[
'name' => null,
'class' => Get::class,
- 'uriTemplate' => '/users/{userId}/programs/{id}.{_format}',
+ 'uriTemplate' => '/users/{userId}/programs/{id}{._format}',
'shortName' => null,
'description' => 'User programs',
'routePrefix' => null,
@@ -471,7 +471,7 @@ public function testInvalidYaml(string $path, string $error): void
public function getInvalidPaths(): array
{
return [
- [__DIR__.'/yaml/invalid/invalid_resources.yaml', '"resources" setting is expected to be null or an array, string given in "'.__DIR__.'/yaml/invalid/invalid_resources.yaml'.'".'],
+ [__DIR__.'/yaml/invalid/invalid_resources.yaml', '"resources" setting is expected to be null or an array, string given in "'.__DIR__.'/yaml/invalid/invalid_resources.yaml".'],
];
}
}
diff --git a/tests/Metadata/Extractor/xml/valid.xml b/tests/Metadata/Extractor/xml/valid.xml
index 3269a7ba104..cfcccc202d3 100644
--- a/tests/Metadata/Extractor/xml/valid.xml
+++ b/tests/Metadata/Extractor/xml/valid.xml
@@ -7,7 +7,7 @@