diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4bc65d0a1b6..6b33c58ceea 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -52,10 +52,6 @@ jobs:
ini-values: memory_limit=-1
tools: pecl, composer
coverage: none
- - name: Download PHPArkitect PHAR
- run: |
- wget -q https://github.com/phparkitect/arkitect/releases/latest/download/phparkitect.phar
- chmod +x phparkitect.phar
- name: Get composer cache directory
id: composercache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
@@ -68,8 +64,7 @@ jobs:
- name: Update project dependencies
run: |
composer update --no-interaction --no-progress --ansi
- - name: Run PHPArkitect
- run: ./phparkitect.phar check
+ - run: php components.php
php-cs-fixer:
name: PHP CS Fixer (PHP ${{ matrix.php }})
@@ -145,8 +140,6 @@ jobs:
rm -Rf tests/Fixtures/app/var/cache/*
tests/Fixtures/app/console cache:warmup
- name: Run PHPStan analysis
- env:
- SYMFONY_PHPUNIT_VERSION: '9.5'
run: |
./vendor/bin/phpstan --version
./vendor/bin/phpstan analyse --no-interaction --no-progress --ansi
@@ -244,6 +237,9 @@ jobs:
- GraphQl
- Serializer
- Symfony
+ - Doctrine/Common
+ - Doctrine/Orm
+ - Doctrine/Odm
include:
- php: '8.1'
coverage: true
@@ -768,8 +764,6 @@ jobs:
restore-keys: ${{ runner.os }}-composer-
- name: Update project dependencies
run: composer update --prefer-lowest --no-interaction --no-progress --ansi
- - name: Install PHPUnit
- run: vendor/bin/simple-phpunit --version
- name: Clear test app cache
run: tests/Fixtures/app/console cache:clear --ansi
- name: Run Behat tests
@@ -1033,12 +1027,10 @@ jobs:
run: rm -Rf tests/Fixtures/app/var/cache/*
- name: Update project dependencies
run: composer update --prefer-lowest --no-interaction --no-progress --ansi
- - name: Install PHPUnit
- run: vendor/bin/simple-phpunit --version
- name: Clear test app cache
run: tests/Fixtures/app/console cache:clear --ansi
- name: Run PHPUnit tests
- run: vendor/bin/simple-phpunit
+ run: vendor/bin/phpunit
env:
SYMFONY_DEPRECATIONS_HELPER: max[self]=0&ignoreFile=./tests/.ignored-deprecations
@@ -1077,8 +1069,6 @@ jobs:
run: rm -Rf tests/Fixtures/app/var/cache/*
- name: Update project dependencies
run: composer update --prefer-lowest --no-interaction --no-progress --ansi
- - name: Install PHPUnit
- run: vendor/bin/simple-phpunit --version
- name: Clear test app cache
run: tests/Fixtures/app/console cache:clear --ansi
- name: Run Behat tests
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 89495254045..f790b3b5e99 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -18,7 +18,8 @@
'tests/Fixtures/app/var',
'docs/guides',
'docs/var',
- '**vendor**'
+ 'src/Doctrine/Orm/Tests/var',
+ 'src/Doctrine/Odm/Tests/var'
])
->notPath('src/Symfony/Bundle/DependencyInjection/Configuration.php')
->notPath('src/Annotation/ApiFilter.php') // temporary
diff --git a/components.php b/components.php
new file mode 100644
index 00000000000..b2530fbb686
--- /dev/null
+++ b/components.php
@@ -0,0 +1,168 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+/*
+ * This script checks for dependencies between our components
+ * and fails if a component has a wrong dependency.
+ */
+use Symfony\Component\Filesystem\Path;
+use Symfony\Component\Finder\Finder;
+
+$loader = require './vendor/autoload.php';
+$namespace = 'ApiPlatform';
+$prefix = 'api-platform';
+$ignoreList = ['ApiPlatform\\Api', 'ApiPlatform\\Exception', 'ApiPlatform\\Util'];
+$stopOnFailure = in_array('--stop-on-failure', $_SERVER['argv'], true);
+$skipPaths = [__DIR__.'/src/GraphQl/Resolver/Stage'];
+
+/**
+ * Reads the beginning of a PHP class and returns "use" instructions matching our namespace.
+ */
+$class_uses_namespaces = function (ReflectionClass $r) use ($namespace): \Generator {
+ $fp = fopen($r->getFileName(), 'r');
+ $u = 'use';
+ $c = false;
+ while (($buffer = fgets($fp, 4096)) !== false) {
+ if ($c && \PHP_EOL === $buffer) {
+ break;
+ }
+
+ if (!str_starts_with($buffer, $u)) {
+ continue;
+ }
+
+ $c = true;
+ $buffer = substr($buffer, 4, -2);
+ if (str_starts_with($buffer, $namespace)) {
+ yield substr($buffer, 0, strpos($buffer, ' ') ?: null);
+ }
+ }
+
+ fclose($fp);
+};
+
+// Creates and require the map of dependencies
+$directories = [];
+$map = [];
+foreach (Finder::create()->in('src')->notPath('vendor')->name('composer.json') as $f) {
+ if (null === ($component = json_decode($f->getContents(), true))) {
+ continue;
+ }
+
+ $filter = fn ($v) => str_starts_with($v, $prefix);
+ $dependencies =
+ array_merge(
+ array_filter(array_keys((array) $component['require']), $filter),
+ array_filter(array_keys((array) $component['require-dev'] ?? []), $filter)
+ );
+
+ $map[$component['name']] = ['namespace' => substr(key($component['autoload']['psr-4']), 0, -1), 'dependencies' => $dependencies];
+ $directories[] = substr($f->getRealPath(), 0, -14);
+
+ foreach (Finder::create()->in($f->getPath())->notPath('vendor')->notPath('var')->name('*.php')->notName('*.tpl.php')->notName('bootstrap.php') as $f) {
+ require_once $f->getRealPath();
+ }
+}
+
+// create a PSR map of dependencies
+$psrMap = [];
+foreach ($map as $component) {
+ $depsPsr = [];
+ foreach ($component['dependencies'] as $d) {
+ $depsPsr[] = $map[$d]['namespace'];
+ }
+
+ $psrMap[$component['namespace']] = $depsPsr;
+}
+
+$warned = [];
+$getComponentNamespace = function (ReflectionClass $r, ReflectionClass $inside = null) use ($psrMap, $warned, $ignoreList, $namespace) {
+ $ns = $r->getNamespaceName();
+ // Find this components namespace
+ $nsParts = explode('\\', $ns);
+ $n = count($nsParts);
+ $componentNs = $nsParts[0].'\\'.$nsParts[1];
+ $i = 2;
+
+ while (!isset($psrMap[$componentNs]) && $i < $n) {
+ if ($part = ($nsParts[$i++] ?? null)) {
+ $componentNs .= '\\'.$part;
+ }
+ }
+
+ if (!isset($psrMap[$componentNs])) {
+ if (in_array($componentNs, $ignoreList, true)) {
+ return null;
+ }
+
+ $guess = $nsParts[0].'\\'.$nsParts[1];
+ if ($warned[$guess] ?? true) {
+ echo sprintf('"%s" is not an %s component at "%s" %s', $guess, $namespace, ($inside ?? $r)->getFileName(), \PHP_EOL);
+ $warned[$guess] = false;
+ }
+
+ return null;
+ }
+
+ return $componentNs;
+};
+
+$exitCode = 0;
+$lnamespace = strlen($namespace);
+foreach (array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()) as $className) {
+ $r = new ReflectionClass($className);
+ $ns = $r->getNamespaceName();
+
+ foreach ($skipPaths as $base) {
+ if (!($fileName = $r->getFileName())) {
+ continue;
+ }
+
+ if (Path::isBasePath($skipPaths[0], $fileName)) {
+ continue 2;
+ }
+ }
+
+ if (!str_starts_with($ns, $namespace)) {
+ continue;
+ }
+
+ $componentNs = $getComponentNamespace($r);
+
+ if (!$componentNs) {
+ continue;
+ }
+
+ foreach ($class_uses_namespaces($r) as $u) {
+ if (str_starts_with($u, $componentNs)) {
+ continue;
+ }
+
+ $useNs = $getComponentNamespace(new ReflectionClass($u), $r);
+
+ if (!$useNs || $useNs === $componentNs) {
+ continue;
+ }
+
+ if (!in_array($useNs, $psrMap[$componentNs], true)) {
+ echo sprintf('"%s" uses "%s" although "%s" is not one of its dependencies %s', $className, $u, $useNs, \PHP_EOL);
+ $exitCode = 1;
+
+ if ($stopOnFailure) {
+ exit($exitCode);
+ }
+ }
+ }
+}
+
+exit($exitCode);
diff --git a/composer.json b/composer.json
index f7b965bb550..a7efa293cc6 100644
--- a/composer.json
+++ b/composer.json
@@ -28,13 +28,13 @@
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"psr/container": "^1.0 || ^2.0",
"symfony/deprecation-contracts": "^3.1",
- "symfony/http-foundation": "^6.1 || ^7.0",
- "symfony/http-kernel": "^6.1 || ^7.0",
- "symfony/property-access": "^6.1 || ^7.0",
- "symfony/property-info": "^6.1 || ^7.0",
- "symfony/serializer": "^6.1 || ^7.0",
+ "symfony/http-foundation": "^6.4 || ^7.0",
+ "symfony/http-kernel": "^6.4 || ^7.0",
+ "symfony/property-access": "^6.4 || ^7.0",
+ "symfony/property-info": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
"symfony/translation-contracts": "^3.3",
- "symfony/web-link": "^6.1 || ^7.0",
+ "symfony/web-link": "^6.4 || ^7.0",
"willdurand/negotiation": "^3.0"
},
"require-dev": {
@@ -57,47 +57,47 @@
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpdoc-parser": "^1.13",
- "phpstan/phpstan": "^1.1",
+ "phpstan/phpstan": "^1.10",
"phpstan/phpstan-doctrine": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-symfony": "^1.0",
- "phpunit/phpunit": "^9.5",
+ "phpunit/phpunit": "^9.6",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"ramsey/uuid": "^3.9.7 || ^4.0",
"ramsey/uuid-doctrine": "^1.4 || ^2.0",
"sebastian/comparator": "<5.0",
"soyuka/contexts": "v3.3.9",
"soyuka/stubs-mongodb": "^1.0",
- "symfony/asset": "^6.1 || ^7.0",
- "symfony/browser-kit": "^6.1 || ^7.0",
- "symfony/cache": "^6.1 || ^7.0",
- "symfony/config": "^6.1 || ^7.0",
- "symfony/console": "^6.1 || ^7.0",
- "symfony/css-selector": "^6.1 || ^7.0",
- "symfony/dependency-injection": "^6.1 || ^7.0.12",
- "symfony/doctrine-bridge": "^6.1 || ^7.0",
- "symfony/dom-crawler": "^6.1 || ^7.0",
- "symfony/error-handler": "^6.1 || ^7.0",
- "symfony/event-dispatcher": "^6.1 || ^7.0",
- "symfony/expression-language": "^6.1 || ^7.0",
- "symfony/finder": "^6.1 || ^7.0",
- "symfony/form": "^6.1 || ^7.0",
- "symfony/framework-bundle": "^6.1 || ^7.0",
- "symfony/http-client": "^6.1 || ^7.0",
- "symfony/intl": "^6.1 || ^7.0",
+ "symfony/asset": "^6.4 || ^7.0",
+ "symfony/browser-kit": "^6.4 || ^7.0",
+ "symfony/cache": "^6.4 || ^7.0",
+ "symfony/config": "^6.4 || ^7.0",
+ "symfony/console": "^6.4 || ^7.0",
+ "symfony/css-selector": "^6.4 || ^7.0",
+ "symfony/dependency-injection": "^6.4 || ^7.0.12",
+ "symfony/doctrine-bridge": "^6.4 || ^7.0",
+ "symfony/dom-crawler": "^6.4 || ^7.0",
+ "symfony/error-handler": "^6.4 || ^7.0",
+ "symfony/event-dispatcher": "^6.4 || ^7.0",
+ "symfony/expression-language": "^6.4 || ^7.0",
+ "symfony/finder": "^6.4 || ^7.0",
+ "symfony/form": "^6.4 || ^7.0",
+ "symfony/framework-bundle": "^6.4 || ^7.0",
+ "symfony/http-client": "^6.4 || ^7.0",
+ "symfony/intl": "^6.4 || ^7.0",
"symfony/maker-bundle": "^1.24",
"symfony/mercure-bundle": "*",
- "symfony/messenger": "^6.1 || ^7.0",
- "symfony/phpunit-bridge": "^6.1 || ^7.0",
- "symfony/routing": "^6.1 || ^7.0",
- "symfony/security-bundle": "^6.1 || ^7.0",
- "symfony/security-core": "^6.1 || ^7.0",
- "symfony/stopwatch": "^6.1 || ^7.0",
- "symfony/twig-bundle": "^6.1 || ^7.0",
- "symfony/uid": "^6.1 || ^7.0",
- "symfony/validator": "^6.1 || ^7.0",
- "symfony/web-profiler-bundle": "^6.1 || ^7.0",
- "symfony/yaml": "^6.1 || ^7.0",
+ "symfony/messenger": "^6.4 || ^7.0",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/routing": "^6.4 || ^7.0",
+ "symfony/security-bundle": "^6.4 || ^7.0",
+ "symfony/security-core": "^6.4 || ^7.0",
+ "symfony/stopwatch": "^6.4 || ^7.0",
+ "symfony/twig-bundle": "^6.4 || ^7.0",
+ "symfony/uid": "^6.4 || ^7.0",
+ "symfony/validator": "^6.4 || ^7.0",
+ "symfony/web-profiler-bundle": "^6.4 || ^7.0",
+ "symfony/yaml": "^6.4 || ^7.0",
"twig/twig": "^1.42.3 || ^2.12 || ^3.0",
"webonyx/graphql-php": "^14.0 || ^15.0"
},
@@ -157,7 +157,7 @@
"dev-main": "3.3.x-dev"
},
"symfony": {
- "require": "^6.1 || ^7.0"
+ "require": "^6.4 || ^7.0"
}
}
}
diff --git a/phparkitect-baseline.json b/phparkitect-baseline.json
deleted file mode 100644
index 54ab60e8266..00000000000
--- a/phparkitect-baseline.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "violations": [
- ],
- "stopOnFailure": false
-}
diff --git a/phparkitect.php b/phparkitect.php
deleted file mode 100644
index 91e71043c58..00000000000
--- a/phparkitect.php
+++ /dev/null
@@ -1,70 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-declare(strict_types=1);
-
-use Arkitect\ClassSet;
-use Arkitect\CLI\Config;
-use Arkitect\RuleBuilders\Architecture\Architecture;
-
-return static function (Config $config): void {
- $classSet = ClassSet::fromDir(__DIR__.'/src')
- ->excludePath('*/vendor/*')
- ->excludePath('*/Tests/*');
-
- $config->add($classSet, ...Architecture::withComponents()
- ->component('DoctrineCommon')->definedBy('ApiPlatform\Doctrine\Common\*')
- ->component('Documentation')->definedBy('ApiPlatform\Documentation\*')
- ->component('Elasticsearch')->definedBy('ApiPlatform\Elasticsearch\*')
- ->component('GraphQl')->definedBy('ApiPlatform\GraphQl\*')
- ->component('HttpCache')->definedBy('ApiPlatform\HttpCache\*')
- ->component('Hydra')->definedBy('ApiPlatform\Hydra\*')
- ->component('JsonLd')->definedBy('ApiPlatform\JsonLd\*')
- ->component('JsonSchema')->definedBy('ApiPlatform\JsonSchema\*')
- ->component('Metadata')->definedBy('ApiPlatform\Metadata\*')
- ->component('OpenApi')->definedBy('ApiPlatform\OpenApi\*')
- ->component('ParameterValidator')->definedBy('ApiPlatform\ParameterValidator\*')
- ->component('RamseyUuid')->definedBy('ApiPlatform\RamseyUuid\*')
- ->component('Serializer')->definedBy('ApiPlatform\Serializer\*')
- ->component('State')->definedBy('ApiPlatform\State\*')
- ->component('Symfony')->definedBy('ApiPlatform\Symfony\*')
- ->component('Validator')->definedBy('ApiPlatform\Validator\*')
-
- ->where('DoctrineCommon')->mayDependOnComponents('Metadata', 'State')
- ->where('Documentation')->mayDependOnComponents('Metadata', 'OpenApi', 'State')
- ->where('Elasticsearch')->mayDependOnComponents('Metadata', 'Serializer', 'State')
- ->where('GraphQl')->mayDependOnComponents('Metadata', 'Serializer', 'State', 'Validator')
- ->where('HttpCache')->mayDependOnComponents('Metadata', 'State')
- ->where('Hydra')->mayDependOnComponents('Metadata', 'State', 'JsonLd', 'Serializer', 'JsonSchema')
- ->where('JsonLd')->mayDependOnComponents('Metadata', 'State', 'Serializer')
- ->where('JsonSchema')->mayDependOnComponents('Metadata')
- ->where('OpenApi')->mayDependOnComponents('JsonSchema', 'Metadata', 'State')
- ->where('RamseyUuid')->mayDependOnComponents('Metadata')
- ->where('Serializer')->mayDependOnComponents('Metadata', 'State')
- ->where('Symfony')->mayDependOnComponents(
- 'Documentation',
- 'GraphQl',
- 'Metadata',
- 'State',
- 'Validator',
- 'Serializer',
- 'JsonSchema',
- 'JsonLd',
- 'OpenApi',
- 'ParameterValidator',
- 'HttpCache',
- 'Elasticsearch'
- )
- ->where('Validator')->mayDependOnComponents('Metadata')
-
- ->rules()
- );
-};
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index baf9ea0b4bd..03e6f9f4d42 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -27,7 +27,10 @@ parameters:
# Templates for Maker
- src/Symfony/Maker/Resources/skeleton
# subtree split
- - **vendor**
+ - src/Doctrine/Orm/Tests/var/*
+ - src/Doctrine/Odm/Tests/var/*
+ - src/Doctrine/*/vendor/*
+ - src/*/vendor/*
# Symfony 6 support
- src/OpenApi/Serializer/CacheableSupportsMethodInterface.php
- src/Serializer/CacheableSupportsMethodInterface.php
@@ -90,9 +93,6 @@ parameters:
-
message: '#^Service "[^"]+" is private.$#'
path: src
- -
- message: '#^Property .+ is unused.$#'
- path: tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineDummy.php
-
message: '#^Class .+ not found.$#'
path: src/Elasticsearch/Tests
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index c4c10132275..2afecb3bf9a 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -30,6 +30,7 @@
tests
vendor
src/**/Tests/
+ src/Doctrine/**/Tests/
.php-cs-fixer.dist.php
diff --git a/src/Action/ExceptionAction.php b/src/Action/ExceptionAction.php
index fc662490531..b6425fdc693 100644
--- a/src/Action/ExceptionAction.php
+++ b/src/Action/ExceptionAction.php
@@ -20,6 +20,7 @@
use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
use ApiPlatform\Util\ErrorFormatGuesser;
+use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface as ApiPlatformConstraintViolationListAwareExceptionInterface;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -77,7 +78,7 @@ public function __invoke(FlattenException $exception, Request $request): Respons
$context = ['statusCode' => $statusCode, 'rfc_7807_compliant_errors' => $operation?->getExtraProperties()['rfc_7807_compliant_errors'] ?? false];
$error = $request->attributes->get('exception') ?? $exception;
- if ($error instanceof ConstraintViolationListAwareExceptionInterface) {
+ if ($error instanceof ConstraintViolationListAwareExceptionInterface || $error instanceof ApiPlatformConstraintViolationListAwareExceptionInterface) {
$error = $error->getConstraintViolationList();
} elseif (method_exists($error, 'getViolations') && $error->getViolations() instanceof ConstraintViolationListInterface) {
$error = $error->getViolations();
diff --git a/src/Api/composer.json b/src/Api/composer.json
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/src/Doctrine/Common/.gitattributes b/src/Doctrine/Common/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Doctrine/Common/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Doctrine/Common/Filter/BooleanFilterTrait.php b/src/Doctrine/Common/Filter/BooleanFilterTrait.php
index 95525ef7816..f906836b755 100644
--- a/src/Doctrine/Common/Filter/BooleanFilterTrait.php
+++ b/src/Doctrine/Common/Filter/BooleanFilterTrait.php
@@ -14,7 +14,7 @@
namespace ApiPlatform\Doctrine\Common\Filter;
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use Psr\Log\LoggerInterface;
/**
diff --git a/src/Doctrine/Common/Filter/DateFilterTrait.php b/src/Doctrine/Common/Filter/DateFilterTrait.php
index 9b55b2d00ab..f5ceb6d5874 100644
--- a/src/Doctrine/Common/Filter/DateFilterTrait.php
+++ b/src/Doctrine/Common/Filter/DateFilterTrait.php
@@ -14,7 +14,7 @@
namespace ApiPlatform\Doctrine\Common\Filter;
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
/**
* Trait for filtering the collection by date intervals.
diff --git a/src/Doctrine/Common/Filter/ExistsFilterTrait.php b/src/Doctrine/Common/Filter/ExistsFilterTrait.php
index ce5648fca64..88a3da57746 100644
--- a/src/Doctrine/Common/Filter/ExistsFilterTrait.php
+++ b/src/Doctrine/Common/Filter/ExistsFilterTrait.php
@@ -14,7 +14,7 @@
namespace ApiPlatform\Doctrine\Common\Filter;
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use Psr\Log\LoggerInterface;
/**
diff --git a/src/Doctrine/Common/Filter/NumericFilterTrait.php b/src/Doctrine/Common/Filter/NumericFilterTrait.php
index 153a4deb09d..8136fb01b5b 100644
--- a/src/Doctrine/Common/Filter/NumericFilterTrait.php
+++ b/src/Doctrine/Common/Filter/NumericFilterTrait.php
@@ -14,7 +14,7 @@
namespace ApiPlatform\Doctrine\Common\Filter;
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use Psr\Log\LoggerInterface;
/**
diff --git a/src/Doctrine/Common/Filter/RangeFilterTrait.php b/src/Doctrine/Common/Filter/RangeFilterTrait.php
index 6dbadab2e80..895488d1cb3 100644
--- a/src/Doctrine/Common/Filter/RangeFilterTrait.php
+++ b/src/Doctrine/Common/Filter/RangeFilterTrait.php
@@ -14,7 +14,7 @@
namespace ApiPlatform\Doctrine\Common\Filter;
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use Psr\Log\LoggerInterface;
/**
diff --git a/src/Doctrine/Common/Filter/SearchFilterTrait.php b/src/Doctrine/Common/Filter/SearchFilterTrait.php
index e85bbf8a056..6a54b1e211b 100644
--- a/src/Doctrine/Common/Filter/SearchFilterTrait.php
+++ b/src/Doctrine/Common/Filter/SearchFilterTrait.php
@@ -16,7 +16,7 @@
use ApiPlatform\Api\IdentifiersExtractorInterface as LegacyIdentifiersExtractorInterface;
use ApiPlatform\Api\IriConverterInterface as LegacyIriConverterInterface;
use ApiPlatform\Doctrine\Common\PropertyHelperTrait;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\IdentifiersExtractorInterface;
use ApiPlatform\Metadata\IriConverterInterface;
use Psr\Log\LoggerInterface;
diff --git a/src/Doctrine/Common/State/LinksHandlerLocatorTrait.php b/src/Doctrine/Common/State/LinksHandlerLocatorTrait.php
index 84f8960d993..8c09fae6e52 100644
--- a/src/Doctrine/Common/State/LinksHandlerLocatorTrait.php
+++ b/src/Doctrine/Common/State/LinksHandlerLocatorTrait.php
@@ -13,7 +13,6 @@
namespace ApiPlatform\Doctrine\Common\State;
-use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Operation;
use Psr\Container\ContainerInterface;
diff --git a/src/Doctrine/Common/State/Options.php b/src/Doctrine/Common/State/Options.php
new file mode 100644
index 00000000000..df42fc6cb84
--- /dev/null
+++ b/src/Doctrine/Common/State/Options.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Common\State;
+
+use ApiPlatform\State\OptionsInterface;
+
+class Options implements OptionsInterface
+{
+ /**
+ * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future
+ */
+ public function __construct(
+ protected mixed $handleLinks = null,
+ ) {
+ }
+
+ public function getHandleLinks(): mixed
+ {
+ return $this->handleLinks;
+ }
+
+ public function withHandleLinks(mixed $handleLinks): self
+ {
+ $self = clone $this;
+ $self->handleLinks = $handleLinks;
+
+ return $self;
+ }
+}
diff --git a/tests/Doctrine/Common/Filter/BooleanFilterTestTrait.php b/src/Doctrine/Common/Tests/Filter/BooleanFilterTestTrait.php
similarity index 98%
rename from tests/Doctrine/Common/Filter/BooleanFilterTestTrait.php
rename to src/Doctrine/Common/Tests/Filter/BooleanFilterTestTrait.php
index 3b7c6442bc1..090ef4b0667 100644
--- a/tests/Doctrine/Common/Filter/BooleanFilterTestTrait.php
+++ b/src/Doctrine/Common/Tests/Filter/BooleanFilterTestTrait.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Common\Filter;
+namespace ApiPlatform\Doctrine\Common\Tests\Filter;
/**
* @author Amrouche Hamza
diff --git a/tests/Doctrine/Common/Filter/DateFilterTestTrait.php b/src/Doctrine/Common/Tests/Filter/DateFilterTestTrait.php
similarity index 99%
rename from tests/Doctrine/Common/Filter/DateFilterTestTrait.php
rename to src/Doctrine/Common/Tests/Filter/DateFilterTestTrait.php
index 95fe94d71f1..e47b22517f2 100644
--- a/tests/Doctrine/Common/Filter/DateFilterTestTrait.php
+++ b/src/Doctrine/Common/Tests/Filter/DateFilterTestTrait.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Common\Filter;
+namespace ApiPlatform\Doctrine\Common\Tests\Filter;
/**
* @author Théo FIDRY
diff --git a/tests/Doctrine/Common/Filter/ExistsFilterTestTrait.php b/src/Doctrine/Common/Tests/Filter/ExistsFilterTestTrait.php
similarity index 99%
rename from tests/Doctrine/Common/Filter/ExistsFilterTestTrait.php
rename to src/Doctrine/Common/Tests/Filter/ExistsFilterTestTrait.php
index c74262fbd74..f7e294b6e3f 100644
--- a/tests/Doctrine/Common/Filter/ExistsFilterTestTrait.php
+++ b/src/Doctrine/Common/Tests/Filter/ExistsFilterTestTrait.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Common\Filter;
+namespace ApiPlatform\Doctrine\Common\Tests\Filter;
/**
* @author Antoine Bluchet
diff --git a/tests/Doctrine/Common/Filter/NumericFilterTestTrait.php b/src/Doctrine/Common/Tests/Filter/NumericFilterTestTrait.php
similarity index 99%
rename from tests/Doctrine/Common/Filter/NumericFilterTestTrait.php
rename to src/Doctrine/Common/Tests/Filter/NumericFilterTestTrait.php
index d019b08afea..91b63e4b2ff 100644
--- a/tests/Doctrine/Common/Filter/NumericFilterTestTrait.php
+++ b/src/Doctrine/Common/Tests/Filter/NumericFilterTestTrait.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Common\Filter;
+namespace ApiPlatform\Doctrine\Common\Tests\Filter;
/**
* @author Amrouche Hamza
diff --git a/tests/Doctrine/Common/Filter/OrderFilterTestTrait.php b/src/Doctrine/Common/Tests/Filter/OrderFilterTestTrait.php
similarity index 99%
rename from tests/Doctrine/Common/Filter/OrderFilterTestTrait.php
rename to src/Doctrine/Common/Tests/Filter/OrderFilterTestTrait.php
index f6d1ae833a2..374e0ba239d 100644
--- a/tests/Doctrine/Common/Filter/OrderFilterTestTrait.php
+++ b/src/Doctrine/Common/Tests/Filter/OrderFilterTestTrait.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Common\Filter;
+namespace ApiPlatform\Doctrine\Common\Tests\Filter;
/**
* @author Théo FIDRY
diff --git a/tests/Doctrine/Common/Filter/RangeFilterTestTrait.php b/src/Doctrine/Common/Tests/Filter/RangeFilterTestTrait.php
similarity index 98%
rename from tests/Doctrine/Common/Filter/RangeFilterTestTrait.php
rename to src/Doctrine/Common/Tests/Filter/RangeFilterTestTrait.php
index 5b0520597c5..029b938caa3 100644
--- a/tests/Doctrine/Common/Filter/RangeFilterTestTrait.php
+++ b/src/Doctrine/Common/Tests/Filter/RangeFilterTestTrait.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Common\Filter;
+namespace ApiPlatform\Doctrine\Common\Tests\Filter;
/**
* @author Lee Siong Chan
diff --git a/tests/Doctrine/Common/Filter/SearchFilterTestTrait.php b/src/Doctrine/Common/Tests/Filter/SearchFilterTestTrait.php
similarity index 99%
rename from tests/Doctrine/Common/Filter/SearchFilterTestTrait.php
rename to src/Doctrine/Common/Tests/Filter/SearchFilterTestTrait.php
index bcfad26191e..5cf9cd54f26 100644
--- a/tests/Doctrine/Common/Filter/SearchFilterTestTrait.php
+++ b/src/Doctrine/Common/Tests/Filter/SearchFilterTestTrait.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Common\Filter;
+namespace ApiPlatform\Doctrine\Common\Tests\Filter;
/**
* @author Julien Deniau
diff --git a/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/RelatedDummy.php b/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/RelatedDummy.php
index 718c00e5a06..fff67d6e77e 100644
--- a/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/RelatedDummy.php
+++ b/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/RelatedDummy.php
@@ -13,10 +13,6 @@
namespace ApiPlatform\Doctrine\Common\Tests\Fixtures\TestBundle\Entity;
-use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
-use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter;
-use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
-use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
@@ -51,7 +47,6 @@
#[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
{
@@ -73,8 +68,6 @@ class RelatedDummy extends ParentDummy implements \Stringable
#[ApiProperty(deprecationReason: 'This property is deprecated for upgrade test')]
#[ORM\Column]
#[Groups(['barcelona', 'chicago', 'friends'])]
- #[ApiFilter(filterClass: SearchFilter::class)]
- #[ApiFilter(filterClass: ExistsFilter::class)]
protected $symfony = 'symfony';
/**
@@ -83,7 +76,6 @@ class RelatedDummy extends ParentDummy implements \Stringable
#[ORM\Column(type: 'datetime', nullable: true)]
#[Assert\DateTime]
#[Groups(['friends'])]
- #[ApiFilter(filterClass: DateFilter::class)]
public $dummyDate;
#[ORM\ManyToOne(targetEntity: ThirdLevel::class, cascade: ['persist'])]
diff --git a/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php b/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php
index 45fca2fa8b5..ecaf3785583 100644
--- a/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php
+++ b/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/RelatedToDummyFriend.php
@@ -17,7 +17,6 @@
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyFriend;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
diff --git a/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/ThirdLevel.php b/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/ThirdLevel.php
index 948101bac72..d2c4e6ea31a 100644
--- a/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/ThirdLevel.php
+++ b/src/Doctrine/Common/Tests/Fixtures/TestBundle/Entity/ThirdLevel.php
@@ -16,7 +16,6 @@
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Link;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FourthLevel;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
diff --git a/src/Doctrine/Common/composer.json b/src/Doctrine/Common/composer.json
index 52272764052..9f5353a1a27 100644
--- a/src/Doctrine/Common/composer.json
+++ b/src/Doctrine/Common/composer.json
@@ -23,20 +23,18 @@
],
"require": {
"php": ">=8.1",
+ "api-platform/metadata": "*@dev || ^3.1",
"api-platform/state": "*@dev || ^3.1",
- "api-platform/metadata": "*@dev || ^3.1"
+ "doctrine/collections": "^2.1",
+ "doctrine/common": "^3.2.2",
+ "doctrine/persistence": "^3.2"
},
"require-dev": {
- "symfony/phpunit-bridge": "^6.1",
- "phpstan/phpdoc-parser": "^1.16",
- "symfony/routing": "^6.1",
- "symfony/yaml": "^6.1",
- "symfony/config": "^6.1",
- "doctrine/orm": "^2.14",
- "doctrine/mongodb-odm": "^2.2",
- "doctrine/mongodb-odm-bundle": "^4.0",
- "doctrine/common": "^3.2.2",
- "phpspec/prophecy-phpunit": "^2.0"
+ "doctrine/mongodb-odm": "^2.6",
+ "doctrine/orm": "^2.17",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.0",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0"
},
"conflict": {
"doctrine/persistence": "<1.3"
@@ -62,7 +60,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
diff --git a/src/Doctrine/Common/phpunit.xml.dist b/src/Doctrine/Common/phpunit.xml.dist
index 326841db8f3..968fa633da6 100644
--- a/src/Doctrine/Common/phpunit.xml.dist
+++ b/src/Doctrine/Common/phpunit.xml.dist
@@ -1,31 +1,20 @@
-
-
-
-
-
-
-
-
- ./Tests/
-
-
-
-
-
- ./
-
-
- ./Tests
- ./vendor
-
-
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
-
diff --git a/src/Doctrine/Odm/.gitattributes b/src/Doctrine/Odm/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Doctrine/Odm/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Doctrine/Odm/.gitignore b/src/Doctrine/Odm/.gitignore
new file mode 100644
index 00000000000..2db77de0df4
--- /dev/null
+++ b/src/Doctrine/Odm/.gitignore
@@ -0,0 +1,5 @@
+/composer.lock
+/vendor
+/.phpunit.result.cache
+/.phpunit.cache
+/Tests/var
diff --git a/src/Doctrine/Odm/Extension/PaginationExtension.php b/src/Doctrine/Odm/Extension/PaginationExtension.php
index 1456207a327..97da9973159 100644
--- a/src/Doctrine/Odm/Extension/PaginationExtension.php
+++ b/src/Doctrine/Odm/Extension/PaginationExtension.php
@@ -14,7 +14,7 @@
namespace ApiPlatform\Doctrine\Odm\Extension;
use ApiPlatform\Doctrine\Odm\Paginator;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\Pagination\Pagination;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
diff --git a/src/Doctrine/Odm/Filter/DateFilter.php b/src/Doctrine/Odm/Filter/DateFilter.php
index 0b848b2a49b..bd2426932e5 100644
--- a/src/Doctrine/Odm/Filter/DateFilter.php
+++ b/src/Doctrine/Odm/Filter/DateFilter.php
@@ -15,7 +15,7 @@
use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface;
use ApiPlatform\Doctrine\Common\Filter\DateFilterTrait;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\Operation;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Types\Type as MongoDbType;
diff --git a/src/Doctrine/Odm/Filter/SearchFilter.php b/src/Doctrine/Odm/Filter/SearchFilter.php
index 230fff70fe1..63870618639 100644
--- a/src/Doctrine/Odm/Filter/SearchFilter.php
+++ b/src/Doctrine/Odm/Filter/SearchFilter.php
@@ -17,7 +17,7 @@
use ApiPlatform\Api\IriConverterInterface as LegacyIriConverterInterface;
use ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface;
use ApiPlatform\Doctrine\Common\Filter\SearchFilterTrait;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\IdentifiersExtractorInterface;
use ApiPlatform\Metadata\IriConverterInterface;
use ApiPlatform\Metadata\Operation;
diff --git a/src/Doctrine/Odm/LICENSE b/src/Doctrine/Odm/LICENSE
new file mode 100644
index 00000000000..1ca98eeb824
--- /dev/null
+++ b/src/Doctrine/Odm/LICENSE
@@ -0,0 +1,21 @@
+The MIT license
+
+Copyright (c) 2015-present Kévin Dunglas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/Doctrine/Odm/Paginator.php b/src/Doctrine/Odm/Paginator.php
index 8639094175f..4bd028ec0e0 100644
--- a/src/Doctrine/Odm/Paginator.php
+++ b/src/Doctrine/Odm/Paginator.php
@@ -13,7 +13,7 @@
namespace ApiPlatform\Doctrine\Odm;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\State\Pagination\PaginatorInterface;
use Doctrine\ODM\MongoDB\Iterator\Iterator;
use Doctrine\ODM\MongoDB\UnitOfWork;
diff --git a/src/Doctrine/Odm/PropertyHelperTrait.php b/src/Doctrine/Odm/PropertyHelperTrait.php
index 449168b281b..c368df150c7 100644
--- a/src/Doctrine/Odm/PropertyHelperTrait.php
+++ b/src/Doctrine/Odm/PropertyHelperTrait.php
@@ -13,7 +13,7 @@
namespace ApiPlatform\Doctrine\Odm;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata as MongoDbOdmClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
diff --git a/src/Doctrine/Odm/State/CollectionProvider.php b/src/Doctrine/Odm/State/CollectionProvider.php
index 84d9cc7fd27..5825788e2e3 100644
--- a/src/Doctrine/Odm/State/CollectionProvider.php
+++ b/src/Doctrine/Odm/State/CollectionProvider.php
@@ -16,7 +16,7 @@
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface;
use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\ProviderInterface;
diff --git a/src/Doctrine/Odm/State/ItemProvider.php b/src/Doctrine/Odm/State/ItemProvider.php
index 04f728e0b0c..6c4c76ccec6 100644
--- a/src/Doctrine/Odm/State/ItemProvider.php
+++ b/src/Doctrine/Odm/State/ItemProvider.php
@@ -16,7 +16,7 @@
use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait;
use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface;
use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\ProviderInterface;
diff --git a/src/Doctrine/Odm/State/LinksHandler.php b/src/Doctrine/Odm/State/LinksHandler.php
new file mode 100644
index 00000000000..15520025e32
--- /dev/null
+++ b/src/Doctrine/Odm/State/LinksHandler.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\State;
+
+use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
+use Doctrine\ODM\MongoDB\Aggregation\Builder;
+use Doctrine\Persistence\ManagerRegistry;
+
+final class LinksHandler implements LinksHandlerInterface
+{
+ use LinksHandlerTrait {
+ handleLinks as private handle;
+ }
+
+ public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry)
+ {
+ $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
+ $this->managerRegistry = $managerRegistry;
+ }
+
+ public function handleLinks(Builder $aggregationBuilder, array $uriVariables, array $context): void
+ {
+ $this->handle($aggregationBuilder, $uriVariables, $context, $context['documentClass'], $context['operation']);
+ }
+}
diff --git a/src/Doctrine/Odm/State/LinksHandlerInterface.php b/src/Doctrine/Odm/State/LinksHandlerInterface.php
index a60421fe1f9..99e46168324 100644
--- a/src/Doctrine/Odm/State/LinksHandlerInterface.php
+++ b/src/Doctrine/Odm/State/LinksHandlerInterface.php
@@ -26,8 +26,8 @@ interface LinksHandlerInterface
*
* @see LinksHandlerTrait
*
- * @param array $uriVariables
- * @param array{entityClass: string, operation: Operation}&array $context
+ * @param array $uriVariables
+ * @param array{documentClass: string, operation: Operation}&array $context
*/
public function handleLinks(Builder $aggregationBuilder, array $uriVariables, array $context): void;
}
diff --git a/src/Doctrine/Odm/State/Options.php b/src/Doctrine/Odm/State/Options.php
index 919c10f8045..459d6bc49ec 100644
--- a/src/Doctrine/Odm/State/Options.php
+++ b/src/Doctrine/Odm/State/Options.php
@@ -13,9 +13,10 @@
namespace ApiPlatform\Doctrine\Odm\State;
+use ApiPlatform\Doctrine\Common\State\Options as CommonOptions;
use ApiPlatform\State\OptionsInterface;
-class Options implements OptionsInterface
+class Options extends CommonOptions implements OptionsInterface
{
/**
* @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future
@@ -24,8 +25,9 @@ class Options implements OptionsInterface
*/
public function __construct(
protected ?string $documentClass = null,
- protected mixed $handleLinks = null,
+ mixed $handleLinks = null,
) {
+ parent::__construct(handleLinks: $handleLinks);
}
public function getDocumentClass(): ?string
@@ -40,17 +42,4 @@ public function withDocumentClass(?string $documentClass): self
return $self;
}
-
- public function getHandleLinks(): mixed
- {
- return $this->handleLinks;
- }
-
- public function withHandleLinks(mixed $handleLinks): self
- {
- $self = clone $this;
- $self->handleLinks = $handleLinks;
-
- return $self;
- }
}
diff --git a/src/Doctrine/Odm/Tests/AppKernel.php b/src/Doctrine/Odm/Tests/AppKernel.php
new file mode 100644
index 00000000000..6a07f359d97
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/AppKernel.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests;
+
+use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
+use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle;
+use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
+use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
+use Symfony\Component\Config\Loader\LoaderInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Kernel;
+
+/**
+ * AppKernel for tests.
+ *
+ * @author Kévin Dunglas
+ */
+class AppKernel extends Kernel
+{
+ use MicroKernelTrait;
+
+ public function __construct(string $environment, bool $debug)
+ {
+ parent::__construct($environment, $debug);
+
+ // patch for behat/symfony2-extension not supporting %env(APP_ENV)%
+ $this->environment = $_SERVER['APP_ENV'] ?? $environment;
+ }
+
+ public function registerBundles(): array
+ {
+ return [
+ new ApiPlatformBundle(),
+ new FrameworkBundle(),
+ new DoctrineMongoDBBundle(),
+ ];
+ }
+
+ public function getProjectDir(): string
+ {
+ return __DIR__;
+ }
+
+ protected function configureRoutes($routes): void
+ {
+ }
+
+ protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader): void
+ {
+ $c->setParameter('kernel.project_dir', __DIR__);
+
+ $cookie = ['cookie_secure' => true, 'cookie_samesite' => 'lax', 'handler_id' => 'session.handler.native_file'];
+ $config = [
+ 'secret' => 'dunglas.fr',
+ 'validation' => ['enable_attributes' => true, 'email_validation_mode' => 'html5'],
+ 'serializer' => ['enable_attributes' => true],
+ 'test' => null,
+ 'session' => ['storage_factory_id' => 'session.storage.factory.mock_file'] + $cookie,
+ 'profiler' => ['enabled' => false],
+ 'property_access' => ['enabled' => true],
+ 'php_errors' => ['log' => true],
+ 'router' => ['utf8' => true],
+ 'http_method_override' => false,
+ 'annotations' => false,
+ 'handle_all_throwables' => true,
+ 'uid' => ['default_uuid_version' => 7, 'time_based_uuid_version' => 7],
+ ];
+
+ $c->prependExtensionConfig('framework', $config);
+
+ $loader->load(__DIR__.'/config.yml');
+ }
+
+ protected function build(ContainerBuilder $container): void
+ {
+ }
+}
diff --git a/src/Test/DoctrineMongoDbOdmFilterTestCase.php b/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmFilterTestCase.php
similarity index 98%
rename from src/Test/DoctrineMongoDbOdmFilterTestCase.php
rename to src/Doctrine/Odm/Tests/DoctrineMongoDbOdmFilterTestCase.php
index a87cf891c4f..3837a048fa6 100644
--- a/src/Test/DoctrineMongoDbOdmFilterTestCase.php
+++ b/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmFilterTestCase.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Test;
+namespace ApiPlatform\Doctrine\Odm\Tests;
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
use Doctrine\ODM\MongoDB\DocumentManager;
diff --git a/src/Test/DoctrineMongoDbOdmSetup.php b/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmSetup.php
similarity index 98%
rename from src/Test/DoctrineMongoDbOdmSetup.php
rename to src/Doctrine/Odm/Tests/DoctrineMongoDbOdmSetup.php
index d514ae85568..f0348a64453 100644
--- a/src/Test/DoctrineMongoDbOdmSetup.php
+++ b/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmSetup.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Test;
+namespace ApiPlatform\Doctrine\Odm\Tests;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\CacheProvider;
diff --git a/src/Test/DoctrineMongoDbOdmTestCase.php b/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmTestCase.php
similarity index 97%
rename from src/Test/DoctrineMongoDbOdmTestCase.php
rename to src/Doctrine/Odm/Tests/DoctrineMongoDbOdmTestCase.php
index 307342b4da0..48eae27ed6a 100644
--- a/src/Test/DoctrineMongoDbOdmTestCase.php
+++ b/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmTestCase.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Test;
+namespace ApiPlatform\Doctrine\Odm\Tests;
use Doctrine\ODM\MongoDB\Configuration;
use Doctrine\ODM\MongoDB\DocumentManager;
diff --git a/tests/Doctrine/Odm/Extension/FilterExtensionTest.php b/src/Doctrine/Odm/Tests/Extension/FilterExtensionTest.php
similarity index 93%
rename from tests/Doctrine/Odm/Extension/FilterExtensionTest.php
rename to src/Doctrine/Odm/Tests/Extension/FilterExtensionTest.php
index 0f80c6a7a08..d90104ca55d 100644
--- a/tests/Doctrine/Odm/Extension/FilterExtensionTest.php
+++ b/src/Doctrine/Odm/Tests/Extension/FilterExtensionTest.php
@@ -11,13 +11,13 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Extension;
+namespace ApiPlatform\Doctrine\Odm\Tests\Extension;
-use ApiPlatform\Api\FilterInterface as ApiFilterInterface;
use ApiPlatform\Doctrine\Odm\Extension\FilterExtension;
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
+use ApiPlatform\Metadata\FilterInterface as ApiFilterInterface;
use ApiPlatform\Metadata\GetCollection;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
diff --git a/tests/Doctrine/Odm/Extension/OrderExtensionTest.php b/src/Doctrine/Odm/Tests/Extension/OrderExtensionTest.php
similarity index 98%
rename from tests/Doctrine/Odm/Extension/OrderExtensionTest.php
rename to src/Doctrine/Odm/Tests/Extension/OrderExtensionTest.php
index 654955acc40..db157f582d8 100644
--- a/tests/Doctrine/Odm/Extension/OrderExtensionTest.php
+++ b/src/Doctrine/Odm/Tests/Extension/OrderExtensionTest.php
@@ -11,11 +11,11 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Extension;
+namespace ApiPlatform\Doctrine\Odm\Tests\Extension;
use ApiPlatform\Doctrine\Odm\Extension\OrderExtension;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
use ApiPlatform\Metadata\GetCollection;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Aggregation\Stage\Lookup;
use Doctrine\ODM\MongoDB\Aggregation\Stage\Sort;
diff --git a/tests/Doctrine/Odm/Extension/PaginationExtensionTest.php b/src/Doctrine/Odm/Tests/Extension/PaginationExtensionTest.php
similarity index 98%
rename from tests/Doctrine/Odm/Extension/PaginationExtensionTest.php
rename to src/Doctrine/Odm/Tests/Extension/PaginationExtensionTest.php
index 4f28ef88f9c..ed880c214df 100644
--- a/tests/Doctrine/Odm/Extension/PaginationExtensionTest.php
+++ b/src/Doctrine/Odm/Tests/Extension/PaginationExtensionTest.php
@@ -11,17 +11,17 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Extension;
+namespace ApiPlatform\Doctrine\Odm\Tests\Extension;
use ApiPlatform\Doctrine\Odm\Extension\PaginationExtension;
use ApiPlatform\Doctrine\Odm\Paginator;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmSetup;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\State\Pagination\Pagination;
use ApiPlatform\State\Pagination\PaginatorInterface;
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
-use ApiPlatform\Test\DoctrineMongoDbOdmSetup;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Aggregation\Stage\Count;
use Doctrine\ODM\MongoDB\Aggregation\Stage\Facet;
diff --git a/tests/Doctrine/Odm/Filter/BooleanFilterTest.php b/src/Doctrine/Odm/Tests/Filter/BooleanFilterTest.php
similarity index 92%
rename from tests/Doctrine/Odm/Filter/BooleanFilterTest.php
rename to src/Doctrine/Odm/Tests/Filter/BooleanFilterTest.php
index 86c0c0aa876..0b52592ea85 100644
--- a/tests/Doctrine/Odm/Filter/BooleanFilterTest.php
+++ b/src/Doctrine/Odm/Tests/Filter/BooleanFilterTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Filter;
+namespace ApiPlatform\Doctrine\Odm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\BooleanFilterTestTrait;
use ApiPlatform\Doctrine\Odm\Filter\BooleanFilter;
-use ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\BooleanFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmFilterTestCase;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
/**
* @group mongodb
diff --git a/tests/Doctrine/Odm/Filter/DateFilterTest.php b/src/Doctrine/Odm/Tests/Filter/DateFilterTest.php
similarity index 98%
rename from tests/Doctrine/Odm/Filter/DateFilterTest.php
rename to src/Doctrine/Odm/Tests/Filter/DateFilterTest.php
index 0f05385c746..f36b87ce128 100644
--- a/tests/Doctrine/Odm/Filter/DateFilterTest.php
+++ b/src/Doctrine/Odm/Tests/Filter/DateFilterTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Filter;
+namespace ApiPlatform\Doctrine\Odm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\DateFilterTestTrait;
use ApiPlatform\Doctrine\Odm\Filter\DateFilter;
-use ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\DateFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmFilterTestCase;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
use MongoDB\BSON\UTCDateTime;
/**
diff --git a/tests/Doctrine/Odm/Filter/ExistsFilterTest.php b/src/Doctrine/Odm/Tests/Filter/ExistsFilterTest.php
similarity index 98%
rename from tests/Doctrine/Odm/Filter/ExistsFilterTest.php
rename to src/Doctrine/Odm/Tests/Filter/ExistsFilterTest.php
index 4348617e5e2..230c384103c 100644
--- a/tests/Doctrine/Odm/Filter/ExistsFilterTest.php
+++ b/src/Doctrine/Odm/Tests/Filter/ExistsFilterTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Filter;
+namespace ApiPlatform\Doctrine\Odm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\ExistsFilterTestTrait;
use ApiPlatform\Doctrine\Odm\Filter\ExistsFilter;
-use ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\ExistsFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmFilterTestCase;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
use Doctrine\Persistence\ManagerRegistry;
/**
diff --git a/tests/Doctrine/Odm/Filter/NumericFilterTest.php b/src/Doctrine/Odm/Tests/Filter/NumericFilterTest.php
similarity index 95%
rename from tests/Doctrine/Odm/Filter/NumericFilterTest.php
rename to src/Doctrine/Odm/Tests/Filter/NumericFilterTest.php
index 32216a47799..e14732b1691 100644
--- a/tests/Doctrine/Odm/Filter/NumericFilterTest.php
+++ b/src/Doctrine/Odm/Tests/Filter/NumericFilterTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Filter;
+namespace ApiPlatform\Doctrine\Odm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\NumericFilterTestTrait;
use ApiPlatform\Doctrine\Odm\Filter\NumericFilter;
-use ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\NumericFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmFilterTestCase;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
/**
* @group mongodb
diff --git a/tests/Doctrine/Odm/Filter/OrderFilterTest.php b/src/Doctrine/Odm/Tests/Filter/OrderFilterTest.php
similarity index 98%
rename from tests/Doctrine/Odm/Filter/OrderFilterTest.php
rename to src/Doctrine/Odm/Tests/Filter/OrderFilterTest.php
index 43ceb21de3e..479f23621b4 100644
--- a/tests/Doctrine/Odm/Filter/OrderFilterTest.php
+++ b/src/Doctrine/Odm/Tests/Filter/OrderFilterTest.php
@@ -11,14 +11,14 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Filter;
+namespace ApiPlatform\Doctrine\Odm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\OrderFilterTestTrait;
use ApiPlatform\Doctrine\Odm\Filter\OrderFilter;
-use ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\OrderFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\EmbeddedDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmFilterTestCase;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\CustomConverter;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\EmbeddedDummy;
use Doctrine\Persistence\ManagerRegistry;
/**
diff --git a/tests/Doctrine/Odm/Filter/RangeFilterTest.php b/src/Doctrine/Odm/Tests/Filter/RangeFilterTest.php
similarity index 98%
rename from tests/Doctrine/Odm/Filter/RangeFilterTest.php
rename to src/Doctrine/Odm/Tests/Filter/RangeFilterTest.php
index ef58e7ca771..177ae9f6c59 100644
--- a/tests/Doctrine/Odm/Filter/RangeFilterTest.php
+++ b/src/Doctrine/Odm/Tests/Filter/RangeFilterTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Filter;
+namespace ApiPlatform\Doctrine\Odm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\RangeFilterTestTrait;
use ApiPlatform\Doctrine\Odm\Filter\RangeFilter;
-use ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\RangeFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmFilterTestCase;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
/**
* @group mongodb
diff --git a/tests/Doctrine/Odm/Filter/SearchFilterTest.php b/src/Doctrine/Odm/Tests/Filter/SearchFilterTest.php
similarity index 98%
rename from tests/Doctrine/Odm/Filter/SearchFilterTest.php
rename to src/Doctrine/Odm/Tests/Filter/SearchFilterTest.php
index 845e3a3d11c..284a1e1b207 100644
--- a/tests/Doctrine/Odm/Filter/SearchFilterTest.php
+++ b/src/Doctrine/Odm/Tests/Filter/SearchFilterTest.php
@@ -11,16 +11,16 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Filter;
+namespace ApiPlatform\Doctrine\Odm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\SearchFilterTestTrait;
use ApiPlatform\Doctrine\Odm\Filter\SearchFilter;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmFilterTestCase;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\CustomConverter;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\RelatedDummy;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\IriConverterInterface;
-use ApiPlatform\Test\DoctrineMongoDbOdmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\SearchFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\RelatedDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
use Doctrine\Persistence\ManagerRegistry;
use MongoDB\BSON\Regex;
use Prophecy\Argument;
diff --git a/src/Doctrine/Odm/Tests/Fixtures/CustomConverter.php b/src/Doctrine/Odm/Tests/Fixtures/CustomConverter.php
new file mode 100644
index 00000000000..4b5a85d9e8d
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/CustomConverter.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures;
+
+use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
+
+/**
+ * Custom converter that will only convert a property named "nameConverted"
+ * with the same logic as Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter.
+ */
+class CustomConverter extends CamelCaseToSnakeCaseNameConverter
+{
+ public function normalize(string $propertyName): string
+ {
+ return 'nameConverted' === $propertyName ? parent::normalize($propertyName) : $propertyName;
+ }
+
+ public function denormalize(string $propertyName): string
+ {
+ return 'name_converted' === $propertyName ? parent::denormalize($propertyName) : $propertyName;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/Dummy.php b/src/Doctrine/Odm/Tests/Fixtures/Document/Dummy.php
new file mode 100644
index 00000000000..12f8acbca75
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/Dummy.php
@@ -0,0 +1,259 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Dummy.
+ *
+ * @author Kévin Dunglas
+ * @author Alexandre Delplace
+ */
+#[ODM\Document]
+class Dummy
+{
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int', nullable: true)]
+ private ?int $id = null;
+ /**
+ * @var string|null The dummy name
+ */
+ #[Assert\NotBlank]
+ #[ODM\Field(type: 'string')]
+ private $name;
+ /**
+ * @var string|null The dummy name alias
+ */
+ #[ODM\Field(nullable: true)]
+ private $alias;
+ /**
+ * @var array|null foo
+ */
+ private ?array $foo = null;
+ /**
+ * @var string|null A short description of the item
+ */
+ #[ODM\Field(type: 'string', nullable: true)]
+ public $description;
+ /**
+ * @var string|null A dummy
+ */
+ #[ODM\Field(nullable: true)]
+ public $dummy;
+ /**
+ * @var bool|null A dummy boolean
+ */
+ #[ODM\Field(type: 'bool', nullable: true)]
+ public ?bool $dummyBoolean = null;
+ /**
+ * @var \DateTime|null A dummy date
+ */
+ #[ODM\Field(type: 'date', nullable: true)]
+ public $dummyDate;
+ /**
+ * @var float|null A dummy float
+ */
+ #[ODM\Field(type: 'float', nullable: true)]
+ public $dummyFloat;
+ /**
+ * @var float|null A dummy price
+ */
+ #[ODM\Field(type: 'float', nullable: true)]
+ public $dummyPrice;
+ #[ODM\ReferenceOne(targetDocument: RelatedDummy::class, storeAs: 'id', nullable: true)]
+ public ?RelatedDummy $relatedDummy = null;
+ #[ODM\ReferenceMany(targetDocument: RelatedDummy::class, storeAs: 'id', nullable: true)]
+ public Collection|iterable $relatedDummies;
+ #[ODM\Field(type: 'hash', nullable: true)]
+ public array $jsonData = [];
+ #[ODM\Field(type: 'collection', nullable: true)]
+ public array $arrayData = [];
+ /**
+ * @var string|null
+ */
+ #[ODM\Field(nullable: true)]
+ public $nameConverted;
+ /**
+ * @var RelatedOwnedDummy|null
+ */
+ #[ODM\ReferenceOne(targetDocument: RelatedOwnedDummy::class, cascade: ['persist'], mappedBy: 'owningDummy', nullable: true)]
+ public $relatedOwnedDummy;
+ /**
+ * @var RelatedOwningDummy|null
+ */
+ #[ODM\ReferenceOne(targetDocument: RelatedOwningDummy::class, cascade: ['persist'], inversedBy: 'ownedDummy', nullable: true, storeAs: 'id')]
+ public $relatedOwningDummy;
+
+ public static function staticMethod(): void
+ {
+ }
+
+ public function __construct()
+ {
+ $this->relatedDummies = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setAlias($alias): void
+ {
+ $this->alias = $alias;
+ }
+
+ public function getAlias()
+ {
+ return $this->alias;
+ }
+
+ public function setDescription($description): void
+ {
+ $this->description = $description;
+ }
+
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ public function getFoo(): ?array
+ {
+ return $this->foo;
+ }
+
+ public function setFoo(array $foo = null): void
+ {
+ $this->foo = $foo;
+ }
+
+ public function setDummyDate(\DateTime $dummyDate = null): void
+ {
+ $this->dummyDate = $dummyDate;
+ }
+
+ public function getDummyDate()
+ {
+ return $this->dummyDate;
+ }
+
+ public function setDummyPrice($dummyPrice)
+ {
+ $this->dummyPrice = $dummyPrice;
+
+ return $this;
+ }
+
+ public function getDummyPrice()
+ {
+ return $this->dummyPrice;
+ }
+
+ public function setJsonData(array $jsonData): void
+ {
+ $this->jsonData = $jsonData;
+ }
+
+ public function getJsonData(): array
+ {
+ return $this->jsonData;
+ }
+
+ public function setArrayData(array $arrayData): void
+ {
+ $this->arrayData = $arrayData;
+ }
+
+ public function getArrayData(): array
+ {
+ return $this->arrayData;
+ }
+
+ public function getRelatedDummy(): ?RelatedDummy
+ {
+ return $this->relatedDummy;
+ }
+
+ public function setRelatedDummy(RelatedDummy $relatedDummy): void
+ {
+ $this->relatedDummy = $relatedDummy;
+ }
+
+ public function addRelatedDummy(RelatedDummy $relatedDummy): void
+ {
+ $this->relatedDummies->add($relatedDummy);
+ }
+
+ public function getRelatedOwnedDummy()
+ {
+ return $this->relatedOwnedDummy;
+ }
+
+ public function setRelatedOwnedDummy(RelatedOwnedDummy $relatedOwnedDummy): void
+ {
+ $this->relatedOwnedDummy = $relatedOwnedDummy;
+ if ($this !== $this->relatedOwnedDummy->getOwningDummy()) {
+ $this->relatedOwnedDummy->setOwningDummy($this);
+ }
+ }
+
+ public function getRelatedOwningDummy()
+ {
+ return $this->relatedOwningDummy;
+ }
+
+ public function setRelatedOwningDummy(RelatedOwningDummy $relatedOwningDummy): void
+ {
+ $this->relatedOwningDummy = $relatedOwningDummy;
+ }
+
+ public function isDummyBoolean(): ?bool
+ {
+ return $this->dummyBoolean;
+ }
+
+ public function setDummyBoolean(?bool $dummyBoolean): void
+ {
+ $this->dummyBoolean = $dummyBoolean;
+ }
+
+ public function setDummy($dummy = null): void
+ {
+ $this->dummy = $dummy;
+ }
+
+ public function getDummy()
+ {
+ return $this->dummy;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/DummyFriend.php b/src/Doctrine/Odm/Tests/Fixtures/Document/DummyFriend.php
new file mode 100644
index 00000000000..d9b73088005
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/DummyFriend.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Dummy Friend.
+ *
+ * @author Kévin Dunglas
+ * @author Alan Poulain
+ */
+#[ODM\Document]
+class DummyFriend implements \Stringable
+{
+ /**
+ * @var int|null The id
+ */
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private ?int $id = null;
+ /**
+ * @var string|null The dummy name
+ */
+ #[ODM\Field(type: 'string')]
+ private ?string $name = null;
+
+ /**
+ * Get id.
+ */
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set id.
+ *
+ * @param int $id the value to set
+ */
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * Get name.
+ */
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set name.
+ *
+ * @param string $name the value to set
+ */
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->getId();
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/EmbeddableDummy.php b/src/Doctrine/Odm/Tests/Fixtures/Document/EmbeddableDummy.php
new file mode 100644
index 00000000000..8bd82bdbe33
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/EmbeddableDummy.php
@@ -0,0 +1,122 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Embeddable Dummy.
+ *
+ * @author Jordan Samouh
+ * @author Alexandre Delplace
+ */
+#[ODM\EmbeddedDocument]
+class EmbeddableDummy
+{
+ /**
+ * @var string|null The dummy name
+ */
+ #[ODM\Field(type: 'string')]
+ private ?string $dummyName = null;
+ /**
+ * @var bool|null A dummy boolean
+ */
+ #[ODM\Field(type: 'bool')]
+ public ?bool $dummyBoolean = null;
+ /**
+ * @var \DateTime|null A dummy date
+ */
+ #[ODM\Field(type: 'date')]
+ public ?\DateTime $dummyDate = null;
+ /**
+ * @var float|null A dummy float
+ */
+ #[ODM\Field(type: 'float')]
+ public ?float $dummyFloat = null;
+ /**
+ * @var float|null A dummy price
+ */
+ #[ODM\Field(type: 'float')]
+ public ?float $dummyPrice = null;
+ #[ODM\Field(type: 'string')]
+ protected ?string $symfony = null;
+
+ public static function staticMethod(): void
+ {
+ }
+
+ public function __construct()
+ {
+ }
+
+ public function getDummyName(): ?string
+ {
+ return $this->dummyName;
+ }
+
+ public function setDummyName(string $dummyName): void
+ {
+ $this->dummyName = $dummyName;
+ }
+
+ public function isDummyBoolean(): ?bool
+ {
+ return $this->dummyBoolean;
+ }
+
+ public function setDummyBoolean(bool $dummyBoolean): void
+ {
+ $this->dummyBoolean = $dummyBoolean;
+ }
+
+ public function getDummyDate(): ?\DateTime
+ {
+ return $this->dummyDate;
+ }
+
+ public function setDummyDate(\DateTime $dummyDate): void
+ {
+ $this->dummyDate = $dummyDate;
+ }
+
+ public function getDummyFloat(): ?float
+ {
+ return $this->dummyFloat;
+ }
+
+ public function setDummyFloat(float $dummyFloat): void
+ {
+ $this->dummyFloat = $dummyFloat;
+ }
+
+ public function getDummyPrice(): ?float
+ {
+ return $this->dummyPrice;
+ }
+
+ public function setDummyPrice(float $dummyPrice): void
+ {
+ $this->dummyPrice = $dummyPrice;
+ }
+
+ public function getSymfony(): ?string
+ {
+ return $this->symfony;
+ }
+
+ public function setSymfony(string $symfony): void
+ {
+ $this->symfony = $symfony;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/EmbeddedDummy.php b/src/Doctrine/Odm/Tests/Fixtures/Document/EmbeddedDummy.php
new file mode 100644
index 00000000000..c2b45ac471c
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/EmbeddedDummy.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Embedded Dummy.
+ *
+ * @author Jordan Samouh
+ * @author Alexandre Delplace
+ */
+#[ODM\Document]
+class EmbeddedDummy
+{
+ /**
+ * @var int|null The id
+ */
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private ?int $id = null;
+ /**
+ * @var string|null The dummy name
+ */
+ #[ODM\Field(type: 'string')]
+ private ?string $name = null;
+ /**
+ * @var \DateTime|null A dummy date
+ */
+ #[ODM\Field(type: 'date')]
+ public ?\DateTime $dummyDate = null;
+ #[ODM\EmbedOne(targetDocument: EmbeddableDummy::class)]
+ public ?EmbeddableDummy $embeddedDummy = null;
+ /**
+ * @var RelatedDummy|null A related dummy
+ */
+ #[ODM\ReferenceOne(targetDocument: RelatedDummy::class, storeAs: 'id')]
+ public ?RelatedDummy $relatedDummy = null;
+
+ public static function staticMethod(): void
+ {
+ }
+
+ public function __construct()
+ {
+ $this->embeddedDummy = new EmbeddableDummy();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getEmbeddedDummy(): EmbeddableDummy
+ {
+ return $this->embeddedDummy;
+ }
+
+ public function setEmbeddedDummy(EmbeddableDummy $embeddedDummy): void
+ {
+ $this->embeddedDummy = $embeddedDummy;
+ }
+
+ public function getDummyDate(): ?\DateTime
+ {
+ return $this->dummyDate;
+ }
+
+ public function setDummyDate(\DateTime $dummyDate): void
+ {
+ $this->dummyDate = $dummyDate;
+ }
+
+ public function getRelatedDummy(): ?RelatedDummy
+ {
+ return $this->relatedDummy;
+ }
+
+ public function setRelatedDummy(RelatedDummy $relatedDummy): void
+ {
+ $this->relatedDummy = $relatedDummy;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/FourthLevel.php b/src/Doctrine/Odm/Tests/Fixtures/Document/FourthLevel.php
new file mode 100644
index 00000000000..38dfc0488b1
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/FourthLevel.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Fourth Level.
+ *
+ * @author Alan Poulain
+ */
+#[ODM\Document]
+class FourthLevel
+{
+ /**
+ * @var int|null The id
+ */
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private ?int $id = null;
+ #[ODM\Field(type: 'int')]
+ private ?int $level = 4;
+ #[ODM\ReferenceMany(targetDocument: ThirdLevel::class, cascade: ['persist'], mappedBy: 'badFourthLevel', storeAs: 'id')]
+ public Collection|iterable|null $badThirdLevel = null;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getLevel(): ?int
+ {
+ return $this->level;
+ }
+
+ public function setLevel(int $level): void
+ {
+ $this->level = $level;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/ParentDummy.php b/src/Doctrine/Odm/Tests/Fixtures/Document/ParentDummy.php
new file mode 100644
index 00000000000..5fbc24e3b08
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/ParentDummy.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Parent Dummy.
+ *
+ * @author Kévin Dunglas
+ * @author Alexandre Delplace
+ */
+#[ODM\MappedSuperclass]
+class ParentDummy
+{
+ /**
+ * @var int The age
+ */
+ #[ODM\Field(type: 'int', nullable: true)]
+ private $age;
+
+ public function getAge()
+ {
+ return $this->age;
+ }
+
+ public function setAge($age)
+ {
+ return $this->age = $age;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/ProviderDocument.php b/src/Doctrine/Odm/Tests/Fixtures/Document/ProviderDocument.php
new file mode 100644
index 00000000000..df9fd7b905a
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/ProviderDocument.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\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * ProviderEntity.
+ */
+#[ODM\Document]
+class ProviderDocument
+{
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private ?int $id = null;
+
+ #[ODM\Field]
+ private ?string $foo = null;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getFoo(): string
+ {
+ return $this->foo;
+ }
+
+ public function setFoo(string $foo): void
+ {
+ $this->foo = $foo;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedDummy.php b/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedDummy.php
new file mode 100644
index 00000000000..e186a9d76bb
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedDummy.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Related Dummy.
+ *
+ * @author Kévin Dunglas
+ * @author Alexandre Delplace
+ */
+#[ODM\Document]
+class RelatedDummy extends ParentDummy implements \Stringable
+{
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private $id;
+ /**
+ * @var string A name
+ */
+ #[ODM\Field(type: 'string', nullable: true)]
+ public $name;
+ #[ODM\Field(type: 'string')]
+ protected $symfony = 'symfony';
+ /**
+ * @var \DateTime A dummy date
+ */
+ #[ODM\Field(type: 'date', nullable: true)]
+ public $dummyDate;
+ #[ODM\ReferenceOne(targetDocument: ThirdLevel::class, cascade: ['persist'], nullable: true, storeAs: 'id', inversedBy: 'relatedDummies')]
+ public ?ThirdLevel $thirdLevel = null;
+ #[ODM\ReferenceMany(targetDocument: RelatedToDummyFriend::class, cascade: ['persist'], mappedBy: 'relatedDummy', storeAs: 'id')]
+ public Collection|iterable $relatedToDummyFriend;
+ #[ODM\Field(type: 'bool')]
+ public ?bool $dummyBoolean = null;
+ #[ODM\EmbedOne(targetDocument: EmbeddableDummy::class)]
+ public ?EmbeddableDummy $embeddedDummy = null;
+
+ public function __construct()
+ {
+ $this->relatedToDummyFriend = new ArrayCollection();
+ $this->embeddedDummy = new EmbeddableDummy();
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getSymfony()
+ {
+ return $this->symfony;
+ }
+
+ public function setSymfony($symfony): void
+ {
+ $this->symfony = $symfony;
+ }
+
+ public function setDummyDate(\DateTime $dummyDate): void
+ {
+ $this->dummyDate = $dummyDate;
+ }
+
+ public function getDummyDate()
+ {
+ return $this->dummyDate;
+ }
+
+ public function isDummyBoolean(): ?bool
+ {
+ return $this->dummyBoolean;
+ }
+
+ /**
+ * @param bool $dummyBoolean
+ */
+ public function setDummyBoolean($dummyBoolean): void
+ {
+ $this->dummyBoolean = $dummyBoolean;
+ }
+
+ public function getThirdLevel(): ?ThirdLevel
+ {
+ return $this->thirdLevel;
+ }
+
+ public function setThirdLevel(ThirdLevel $thirdLevel = null): void
+ {
+ $this->thirdLevel = $thirdLevel;
+ }
+
+ /**
+ * Get relatedToDummyFriend.
+ */
+ public function getRelatedToDummyFriend(): Collection|iterable
+ {
+ return $this->relatedToDummyFriend;
+ }
+
+ /**
+ * Set relatedToDummyFriend.
+ *
+ * @param RelatedToDummyFriend $relatedToDummyFriend the value to set
+ */
+ public function addRelatedToDummyFriend(RelatedToDummyFriend $relatedToDummyFriend): void
+ {
+ $this->relatedToDummyFriend->add($relatedToDummyFriend);
+ }
+
+ public function getEmbeddedDummy(): EmbeddableDummy
+ {
+ return $this->embeddedDummy;
+ }
+
+ public function setEmbeddedDummy(EmbeddableDummy $embeddedDummy): void
+ {
+ $this->embeddedDummy = $embeddedDummy;
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->getId();
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedOwnedDummy.php b/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedOwnedDummy.php
new file mode 100644
index 00000000000..c3232611776
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedOwnedDummy.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Related Owned Dummy.
+ *
+ * @author Sergey V. Ryabov
+ * @author Alan Poulain
+ */
+#[ODM\Document]
+class RelatedOwnedDummy
+{
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private $id;
+ /**
+ * @var string|null A name
+ */
+ #[ODM\Field(type: 'string')]
+ public $name;
+ #[ODM\ReferenceOne(targetDocument: Dummy::class, cascade: ['persist'], inversedBy: 'relatedOwnedDummy', storeAs: 'id')]
+ public ?Dummy $owningDummy = null;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get owning dummy.
+ */
+ public function getOwningDummy(): ?Dummy
+ {
+ return $this->owningDummy;
+ }
+
+ /**
+ * Set owning dummy.
+ *
+ * @param Dummy $owningDummy the value to set
+ */
+ public function setOwningDummy(Dummy $owningDummy): void
+ {
+ $this->owningDummy = $owningDummy;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedOwningDummy.php b/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedOwningDummy.php
new file mode 100644
index 00000000000..7a0df1cb8a3
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedOwningDummy.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Related Owning Dummy.
+ *
+ * @author Sergey V. Ryabov
+ * @author Alan Poulain
+ */
+#[ODM\Document]
+class RelatedOwningDummy
+{
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private $id;
+ /**
+ * @var string A name
+ */
+ #[ODM\Field(type: 'string')]
+ public $name;
+ #[ODM\ReferenceOne(targetDocument: Dummy::class, cascade: ['persist'], mappedBy: 'relatedOwningDummy')]
+ public ?Dummy $ownedDummy = null;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get owned dummy.
+ */
+ public function getOwnedDummy(): Dummy
+ {
+ return $this->ownedDummy;
+ }
+
+ /**
+ * Set owned dummy.
+ *
+ * @param Dummy $ownedDummy the value to set
+ */
+ public function setOwnedDummy(Dummy $ownedDummy): void
+ {
+ $this->ownedDummy = $ownedDummy;
+ if ($this !== $this->ownedDummy->getRelatedOwningDummy()) {
+ $this->ownedDummy->setRelatedOwningDummy($this);
+ }
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedToDummyFriend.php b/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedToDummyFriend.php
new file mode 100644
index 00000000000..81f5647d89c
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/RelatedToDummyFriend.php
@@ -0,0 +1,109 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Related To Dummy Friend represent an association table for a manytomany relation.
+ */
+#[ODM\Document]
+class RelatedToDummyFriend
+{
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private $id;
+ /**
+ * @var string The dummy name
+ */
+ #[ODM\Field(type: 'string')]
+ private $name;
+ /**
+ * @var string|null The dummy description
+ */
+ #[ODM\Field(type: 'string')]
+ private ?string $description = null;
+ #[ODM\ReferenceOne(targetDocument: DummyFriend::class, storeAs: 'id')]
+ private DummyFriend $dummyFriend;
+ #[ODM\ReferenceOne(targetDocument: RelatedDummy::class, inversedBy: 'relatedToDummyFriend', storeAs: 'id')]
+ private RelatedDummy $relatedDummy;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getDescription(): ?string
+ {
+ return $this->description;
+ }
+
+ /**
+ * @param string|null $description
+ */
+ public function setDescription($description): void
+ {
+ $this->description = $description;
+ }
+
+ /**
+ * Gets dummyFriend.
+ */
+ public function getDummyFriend(): DummyFriend
+ {
+ return $this->dummyFriend;
+ }
+
+ /**
+ * Sets dummyFriend.
+ *
+ * @param DummyFriend $dummyFriend the value to set
+ */
+ public function setDummyFriend(DummyFriend $dummyFriend): void
+ {
+ $this->dummyFriend = $dummyFriend;
+ }
+
+ /**
+ * Gets relatedDummy.
+ */
+ public function getRelatedDummy(): RelatedDummy
+ {
+ return $this->relatedDummy;
+ }
+
+ /**
+ * Sets relatedDummy.
+ *
+ * @param RelatedDummy $relatedDummy the value to set
+ */
+ public function setRelatedDummy(RelatedDummy $relatedDummy): void
+ {
+ $this->relatedDummy = $relatedDummy;
+ }
+}
diff --git a/src/Doctrine/Odm/Tests/Fixtures/Document/ThirdLevel.php b/src/Doctrine/Odm/Tests/Fixtures/Document/ThirdLevel.php
new file mode 100644
index 00000000000..0601aab9950
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/Fixtures/Document/ThirdLevel.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document;
+
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * Third Level.
+ *
+ * @author Kévin Dunglas
+ * @author Alexandre Delplace
+ */
+#[ODM\Document]
+class ThirdLevel
+{
+ /**
+ * @var int|null The id
+ */
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private ?int $id = null;
+ #[ODM\Field(type: 'int')]
+ private int $level = 3;
+ #[ODM\Field(type: 'bool')]
+ private bool $test = true;
+ #[ODM\ReferenceOne(targetDocument: FourthLevel::class, cascade: ['persist'], storeAs: 'id')]
+ public ?FourthLevel $fourthLevel = null;
+ #[ODM\ReferenceOne(targetDocument: FourthLevel::class, cascade: ['persist'])]
+ public $badFourthLevel;
+ #[ODM\ReferenceMany(mappedBy: 'thirdLevel', targetDocument: RelatedDummy::class)]
+ public Collection|iterable $relatedDummies;
+
+ public function __construct()
+ {
+ $this->relatedDummies = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getLevel(): int
+ {
+ return $this->level;
+ }
+
+ public function setLevel(int $level): void
+ {
+ $this->level = $level;
+ }
+
+ public function isTest(): bool
+ {
+ return $this->test;
+ }
+
+ public function setTest(bool $test): void
+ {
+ $this->test = $test;
+ }
+
+ public function getFourthLevel(): ?FourthLevel
+ {
+ return $this->fourthLevel;
+ }
+
+ public function setFourthLevel(FourthLevel $fourthLevel = null): void
+ {
+ $this->fourthLevel = $fourthLevel;
+ }
+}
diff --git a/tests/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php b/src/Doctrine/Odm/Tests/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php
similarity index 97%
rename from tests/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php
rename to src/Doctrine/Odm/Tests/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php
index f40642e777f..6b98b56a2d4 100644
--- a/tests/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php
+++ b/src/Doctrine/Odm/Tests/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactoryTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Metadata\Property;
+namespace ApiPlatform\Doctrine\Odm\Tests\Metadata\Property;
use ApiPlatform\Doctrine\Odm\Metadata\Property\DoctrineMongoDbOdmPropertyMetadataFactory;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\Persistence\ManagerRegistry;
diff --git a/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php b/src/Doctrine/Odm/Tests/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php
similarity index 97%
rename from tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php
rename to src/Doctrine/Odm/Tests/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php
index deb100f26ce..a09c2798d38 100644
--- a/tests/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php
+++ b/src/Doctrine/Odm/Tests/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactoryTest.php
@@ -11,11 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\Metadata\Resource;
+namespace ApiPlatform\Doctrine\Odm\Tests\Metadata\Resource;
use ApiPlatform\Doctrine\Odm\Metadata\Resource\DoctrineMongoDbOdmResourceCollectionMetadataFactory;
use ApiPlatform\Doctrine\Odm\State\CollectionProvider;
use ApiPlatform\Doctrine\Odm\State\ItemProvider;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
@@ -24,7 +25,6 @@
use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase;
diff --git a/tests/Doctrine/Odm/PaginatorTest.php b/src/Doctrine/Odm/Tests/PaginatorTest.php
similarity index 97%
rename from tests/Doctrine/Odm/PaginatorTest.php
rename to src/Doctrine/Odm/Tests/PaginatorTest.php
index bd0bc3325f0..1bae284239e 100644
--- a/tests/Doctrine/Odm/PaginatorTest.php
+++ b/src/Doctrine/Odm/Tests/PaginatorTest.php
@@ -11,12 +11,11 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm;
+namespace ApiPlatform\Doctrine\Odm\Tests;
use ApiPlatform\Doctrine\Odm\Paginator;
-use ApiPlatform\Exception\InvalidArgumentException;
-use ApiPlatform\Test\DoctrineMongoDbOdmSetup;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\Dummy;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Iterator\Iterator;
use PHPUnit\Framework\TestCase;
diff --git a/tests/Doctrine/Odm/PropertyInfo/DoctrineExtractorTest.php b/src/Doctrine/Odm/Tests/PropertyInfo/DoctrineExtractorTest.php
similarity index 91%
rename from tests/Doctrine/Odm/PropertyInfo/DoctrineExtractorTest.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/DoctrineExtractorTest.php
index e6eb44768f1..6dfaf129f17 100644
--- a/tests/Doctrine/Odm/PropertyInfo/DoctrineExtractorTest.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/DoctrineExtractorTest.php
@@ -11,19 +11,19 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo;
use ApiPlatform\Doctrine\Odm\PropertyInfo\DoctrineExtractor;
-use ApiPlatform\Test\DoctrineMongoDbOdmSetup;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\DoctrineDummy;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\DoctrineEmbeddable;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\DoctrineEnum;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\DoctrineFooType;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\DoctrineGeneratedValue;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\DoctrineRelation;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\DoctrineWithEmbedded;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\EnumInt;
-use ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures\EnumString;
+use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmSetup;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\DoctrineDummy;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\DoctrineEnum;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\DoctrineFooType;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\DoctrineGeneratedValue;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\DoctrineRelation;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\EnumInt;
+use ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures\EnumString;
use Doctrine\Common\Collections\Collection;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Types\Type as MongoDbType;
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineDummy.php
similarity index 73%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineDummy.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineDummy.php
index 63d29dfa109..6dfc701bb07 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineDummy.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineDummy.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Field;
@@ -59,43 +59,43 @@ class DoctrineDummy
protected $binUuidRfc4122;
#[Field(type: 'timestamp')]
- private $timestamp;
+ private $timestamp; // @phpstan-ignore-line
#[Field(type: 'date')]
- private $date;
+ private $date; // @phpstan-ignore-line
#[Field(type: 'date_immutable')]
- private $dateImmutable;
+ private $dateImmutable; // @phpstan-ignore-line
#[Field(type: 'float')]
- private $float;
+ private $float; // @phpstan-ignore-line
#[Field(type: 'bool')]
- private $bool;
+ private $bool; // @phpstan-ignore-line
#[Field(type: 'custom_foo')]
- private $customFoo;
+ private $customFoo; // @phpstan-ignore-line
#[Field(type: 'int')]
- private $int;
+ private $int; // @phpstan-ignore-line
#[Field(type: 'string')]
- private $string;
+ private $string; // @phpstan-ignore-line
#[Field(type: 'key')]
- private $key;
+ private $key; // @phpstan-ignore-line
#[Field(type: 'hash')]
- private $hash;
+ private $hash; // @phpstan-ignore-line
#[Field(type: 'collection')]
- private $collection;
+ private $collection; // @phpstan-ignore-line
#[Field(type: 'object_id')]
- private $objectId;
+ private $objectId; // @phpstan-ignore-line
#[Field(type: 'raw')]
- private $raw;
+ private $raw; // @phpstan-ignore-line
public $notMapped;
}
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineEmbeddable.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php
similarity index 89%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineEmbeddable.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php
index 7ff0e97ca22..1114eb6aee0 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineEmbeddable.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
use Doctrine\ODM\MongoDB\Mapping\Annotations\EmbeddedDocument;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Field;
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineEnum.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineEnum.php
similarity index 92%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineEnum.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineEnum.php
index 57efa0ec47e..7fe69bfc0a4 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineEnum.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineEnum.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Field;
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineFooType.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineFooType.php
similarity index 94%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineFooType.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineFooType.php
index a27758f3288..713421f643b 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineFooType.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineFooType.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
use Doctrine\ODM\MongoDB\Types\Type;
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineGeneratedValue.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php
similarity index 91%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineGeneratedValue.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php
index 7b72407a291..1d1046de1d3 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineGeneratedValue.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Field;
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineRelation.php
similarity index 91%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineRelation.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineRelation.php
index b391726b910..130a2b8985a 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineRelation.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineRelation.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Id;
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineWithEmbedded.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php
similarity index 92%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineWithEmbedded.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php
index fd47ec1ffad..0a8747f544b 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineWithEmbedded.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
use Doctrine\ODM\MongoDB\Mapping\Annotations\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations\EmbedMany;
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/EnumInt.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/EnumInt.php
similarity index 83%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/EnumInt.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/EnumInt.php
index 0fc31cff2a5..c96c7f82a0e 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/EnumInt.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/EnumInt.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
enum EnumInt: int
{
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/EnumString.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/EnumString.php
similarity index 83%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/EnumString.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/EnumString.php
index f96c6e29bd3..34d90f73dd4 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/EnumString.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/EnumString.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
enum EnumString: string
{
diff --git a/tests/Doctrine/Odm/PropertyInfo/Fixtures/Foo.php b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/Foo.php
similarity index 85%
rename from tests/Doctrine/Odm/PropertyInfo/Fixtures/Foo.php
rename to src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/Foo.php
index 1a5b7d87fda..2bd54d4ebc9 100644
--- a/tests/Doctrine/Odm/PropertyInfo/Fixtures/Foo.php
+++ b/src/Doctrine/Odm/Tests/PropertyInfo/Fixtures/Foo.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\PropertyInfo\Fixtures;
+namespace ApiPlatform\Doctrine\Odm\Tests\PropertyInfo\Fixtures;
/**
* @author Teoh Han Hui
diff --git a/tests/Doctrine/Odm/State/CollectionProviderTest.php b/src/Doctrine/Odm/Tests/State/CollectionProviderTest.php
similarity index 64%
rename from tests/Doctrine/Odm/State/CollectionProviderTest.php
rename to src/Doctrine/Odm/Tests/State/CollectionProviderTest.php
index 784d4c0e39a..5d81c5d90cf 100644
--- a/tests/Doctrine/Odm/State/CollectionProviderTest.php
+++ b/src/Doctrine/Odm/Tests/State/CollectionProviderTest.php
@@ -11,16 +11,15 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\State;
+namespace ApiPlatform\Doctrine\Odm\Tests\State;
use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface;
use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface;
use ApiPlatform\Doctrine\Odm\State\CollectionProvider;
-use ApiPlatform\Doctrine\Odm\State\Options;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\ProviderDocument;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\ProviderEntity;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Iterator\Iterator;
@@ -57,7 +56,7 @@ public function testGetCollection(): void
$iterator = $this->prophesize(Iterator::class)->reveal();
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
- $aggregationBuilderProphecy->hydrate(ProviderEntity::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
+ $aggregationBuilderProphecy->hydrate(ProviderDocument::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
$aggregationBuilderProphecy->execute([])->willReturn($iterator)->shouldBeCalled();
$aggregationBuilder = $aggregationBuilderProphecy->reveal();
@@ -65,14 +64,14 @@ public function testGetCollection(): void
$repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder)->shouldBeCalled();
$managerProphecy = $this->prophesize(DocumentManager::class);
- $managerProphecy->getRepository(ProviderEntity::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
+ $managerProphecy->getRepository(ProviderDocument::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
- $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
+ $this->managerRegistryProphecy->getManagerForClass(ProviderDocument::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
- $operation = (new GetCollection())->withName('foo')->withClass(ProviderEntity::class);
+ $operation = (new GetCollection())->withName('foo')->withClass(ProviderDocument::class);
$extensionProphecy = $this->prophesize(AggregationCollectionExtensionInterface::class);
- $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, $operation, [])->shouldBeCalled();
+ $extensionProphecy->applyToCollection($aggregationBuilder, ProviderDocument::class, $operation, [])->shouldBeCalled();
$dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]);
$this->assertSame($iterator, $dataProvider->provide($operation, []));
@@ -83,7 +82,7 @@ public function testGetCollectionWithExecuteOptions(): void
$iterator = $this->prophesize(Iterator::class)->reveal();
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
- $aggregationBuilderProphecy->hydrate(ProviderEntity::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
+ $aggregationBuilderProphecy->hydrate(ProviderDocument::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
$aggregationBuilderProphecy->execute(['allowDiskUse' => true])->willReturn($iterator)->shouldBeCalled();
$aggregationBuilder = $aggregationBuilderProphecy->reveal();
@@ -91,14 +90,14 @@ public function testGetCollectionWithExecuteOptions(): void
$repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder)->shouldBeCalled();
$managerProphecy = $this->prophesize(DocumentManager::class);
- $managerProphecy->getRepository(ProviderEntity::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
+ $managerProphecy->getRepository(ProviderDocument::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
- $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
+ $this->managerRegistryProphecy->getManagerForClass(ProviderDocument::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
- $operation = (new GetCollection())->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]])->withName('foo')->withClass(ProviderEntity::class);
+ $operation = (new GetCollection())->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]])->withName('foo')->withClass(ProviderDocument::class);
$extensionProphecy = $this->prophesize(AggregationCollectionExtensionInterface::class);
- $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, $operation, [])->shouldBeCalled();
+ $extensionProphecy->applyToCollection($aggregationBuilder, ProviderDocument::class, $operation, [])->shouldBeCalled();
$dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]);
$this->assertSame($iterator, $dataProvider->provide($operation, []));
@@ -113,16 +112,16 @@ public function testAggregationResultExtension(): void
$repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder)->shouldBeCalled();
$managerProphecy = $this->prophesize(DocumentManager::class);
- $managerProphecy->getRepository(ProviderEntity::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
+ $managerProphecy->getRepository(ProviderDocument::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
- $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
+ $this->managerRegistryProphecy->getManagerForClass(ProviderDocument::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
- $operation = (new GetCollection())->withName('foo')->withClass(ProviderEntity::class);
+ $operation = (new GetCollection())->withName('foo')->withClass(ProviderDocument::class);
$extensionProphecy = $this->prophesize(AggregationResultCollectionExtensionInterface::class);
- $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, $operation, [])->shouldBeCalled();
- $extensionProphecy->supportsResult(ProviderEntity::class, $operation, [])->willReturn(true)->shouldBeCalled();
- $extensionProphecy->getResult($aggregationBuilder, ProviderEntity::class, $operation, [])->willReturn([])->shouldBeCalled();
+ $extensionProphecy->applyToCollection($aggregationBuilder, ProviderDocument::class, $operation, [])->shouldBeCalled();
+ $extensionProphecy->supportsResult(ProviderDocument::class, $operation, [])->willReturn(true)->shouldBeCalled();
+ $extensionProphecy->getResult($aggregationBuilder, ProviderDocument::class, $operation, [])->willReturn([])->shouldBeCalled();
$dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]);
$this->assertEquals([], $dataProvider->provide($operation, []));
@@ -131,17 +130,17 @@ public function testAggregationResultExtension(): void
public function testCannotCreateAggregationBuilder(): void
{
$this->expectException(RuntimeException::class);
- $this->expectExceptionMessage('The repository for "ApiPlatform\Tests\Fixtures\TestBundle\Document\ProviderEntity" must be an instance of "Doctrine\ODM\MongoDB\Repository\DocumentRepository".');
+ $this->expectExceptionMessage('The repository for "ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\ProviderDocument" must be an instance of "Doctrine\ODM\MongoDB\Repository\DocumentRepository".');
$repositoryProphecy = $this->prophesize(ObjectRepository::class);
$managerProphecy = $this->prophesize(DocumentManager::class);
- $managerProphecy->getRepository(ProviderEntity::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
+ $managerProphecy->getRepository(ProviderDocument::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
- $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
+ $this->managerRegistryProphecy->getManagerForClass(ProviderDocument::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
$dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal());
- $operation = (new GetCollection())->withName('foo')->withClass(ProviderEntity::class);
+ $operation = (new GetCollection())->withName('foo')->withClass(ProviderDocument::class);
$this->assertEquals([], $dataProvider->provide($operation, []));
}
@@ -150,7 +149,7 @@ public function testOperationNotFound(): void
$iterator = $this->prophesize(Iterator::class)->reveal();
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
- $aggregationBuilderProphecy->hydrate(ProviderEntity::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
+ $aggregationBuilderProphecy->hydrate(ProviderDocument::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
$aggregationBuilderProphecy->execute([])->willReturn($iterator)->shouldBeCalled();
$aggregationBuilder = $aggregationBuilderProphecy->reveal();
@@ -158,40 +157,16 @@ public function testOperationNotFound(): void
$repositoryProphecy->createAggregationBuilder()->willReturn($aggregationBuilder)->shouldBeCalled();
$managerProphecy = $this->prophesize(DocumentManager::class);
- $managerProphecy->getRepository(ProviderEntity::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
+ $managerProphecy->getRepository(ProviderDocument::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
- $this->managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
+ $this->managerRegistryProphecy->getManagerForClass(ProviderDocument::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
- $operation = new GetCollection(name: 'bar', class: ProviderEntity::class);
+ $operation = new GetCollection(name: 'bar', class: ProviderDocument::class);
$extensionProphecy = $this->prophesize(AggregationCollectionExtensionInterface::class);
- $extensionProphecy->applyToCollection($aggregationBuilder, ProviderEntity::class, $operation, [])->shouldBeCalled();
+ $extensionProphecy->applyToCollection($aggregationBuilder, ProviderDocument::class, $operation, [])->shouldBeCalled();
$dataProvider = new CollectionProvider($this->resourceMetadataFactoryProphecy->reveal(), $this->managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]);
$this->assertSame($iterator, $dataProvider->provide($operation, []));
}
-
- /**
- * @group legacy
- */
- public function testHandleLinksCallable(): void
- {
- $this->expectDeprecation('The Doctrine\ODM\MongoDB\Aggregation\Builder::execute method is deprecated (This method was deprecated in doctrine/mongodb-odm 2.2. Please use getAggregation() instead.).');
- $class = 'foo';
- $resourceMetadata = $this->createStub(ResourceMetadataCollectionFactoryInterface::class);
- $it = $this->createStub(Iterator::class);
- $it->method('current')->willReturn(null);
- $aggregationBuilder = $this->createStub(Builder::class);
- $aggregationBuilder->method('hydrate')->willReturnSelf();
- $aggregationBuilder->method('execute')->willReturn($it);
- $repository = $this->createStub(DocumentRepository::class);
- $repository->method('createAggregationBuilder')->willReturn($aggregationBuilder);
- $manager = $this->createStub(DocumentManager::class);
- $manager->method('getRepository')->willReturn($repository);
- $managerRegistry = $this->createStub(ManagerRegistry::class);
- $managerRegistry->method('getManagerForClass')->willReturn($manager);
- $operation = new GetCollection(class: $class, stateOptions: new Options(handleLinks: fn () => $this->assertTrue(true)));
- $dataProvider = new CollectionProvider($resourceMetadata, $managerRegistry);
- $dataProvider->provide($operation, ['id' => 1]);
- }
}
diff --git a/tests/Doctrine/Odm/State/ItemProviderTest.php b/src/Doctrine/Odm/Tests/State/ItemProviderTest.php
similarity index 69%
rename from tests/Doctrine/Odm/State/ItemProviderTest.php
rename to src/Doctrine/Odm/Tests/State/ItemProviderTest.php
index 89bdc3ecd71..721c3fd8fa5 100644
--- a/tests/Doctrine/Odm/State/ItemProviderTest.php
+++ b/src/Doctrine/Odm/Tests/State/ItemProviderTest.php
@@ -11,17 +11,16 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Odm\State;
+namespace ApiPlatform\Doctrine\Odm\Tests\State;
use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface;
use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface;
use ApiPlatform\Doctrine\Odm\State\ItemProvider;
-use ApiPlatform\Doctrine\Odm\State\Options;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\ProviderDocument;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Document\ProviderEntity;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Aggregation\Stage\MatchStage as AggregationMatch;
use Doctrine\ODM\MongoDB\DocumentManager;
@@ -57,19 +56,19 @@ public function testGetItemSingleIdentifier(): void
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
$aggregationBuilderProphecy->match()->willReturn($matchProphecy->reveal())->shouldBeCalled();
- $aggregationBuilderProphecy->hydrate(ProviderEntity::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
+ $aggregationBuilderProphecy->hydrate(ProviderDocument::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
$aggregationBuilderProphecy->execute([])->willReturn($iterator->reveal())->shouldBeCalled();
$aggregationBuilder = $aggregationBuilderProphecy->reveal();
- $managerRegistry = $this->getManagerRegistry(ProviderEntity::class, $aggregationBuilder);
+ $managerRegistry = $this->getManagerRegistry(ProviderDocument::class, $aggregationBuilder);
$operation = (new Get())
- ->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])])
- ->withClass(ProviderEntity::class)
+ ->withUriVariables([(new Link())->withFromClass(ProviderDocument::class)->withIdentifiers(['id'])])
+ ->withClass(ProviderDocument::class)
->withName('foo');
$extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class);
- $extensionProphecy->applyToItem($aggregationBuilder, ProviderEntity::class, ['id' => 1], $operation, $context)->shouldBeCalled();
+ $extensionProphecy->applyToItem($aggregationBuilder, ProviderDocument::class, ['id' => 1], $operation, $context)->shouldBeCalled();
$dataProvider = new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistry, [$extensionProphecy->reveal()]);
@@ -90,20 +89,20 @@ public function testGetItemWithExecuteOptions(): void
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
$aggregationBuilderProphecy->match()->willReturn($matchProphecy->reveal())->shouldBeCalled();
- $aggregationBuilderProphecy->hydrate(ProviderEntity::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
+ $aggregationBuilderProphecy->hydrate(ProviderDocument::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
$aggregationBuilderProphecy->execute(['allowDiskUse' => true])->willReturn($iterator->reveal())->shouldBeCalled();
$aggregationBuilder = $aggregationBuilderProphecy->reveal();
- $managerRegistry = $this->getManagerRegistry(ProviderEntity::class, $aggregationBuilder);
+ $managerRegistry = $this->getManagerRegistry(ProviderDocument::class, $aggregationBuilder);
$operation = (new Get())
- ->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])])
- ->withClass(ProviderEntity::class)
+ ->withUriVariables([(new Link())->withFromClass(ProviderDocument::class)->withIdentifiers(['id'])])
+ ->withClass(ProviderDocument::class)
->withName('foo')
->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]]);
$extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class);
- $extensionProphecy->applyToItem($aggregationBuilder, ProviderEntity::class, ['id' => 1], $operation, $context)->shouldBeCalled();
+ $extensionProphecy->applyToItem($aggregationBuilder, ProviderDocument::class, ['id' => 1], $operation, $context)->shouldBeCalled();
$dataProvider = new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistry, [$extensionProphecy->reveal()]);
@@ -124,20 +123,20 @@ public function testGetItemDoubleIdentifier(): void
$aggregationBuilderProphecy = $this->prophesize(Builder::class);
$aggregationBuilderProphecy->match()->willReturn($matchProphecy->reveal())->shouldBeCalled();
- $aggregationBuilderProphecy->hydrate(ProviderEntity::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
+ $aggregationBuilderProphecy->hydrate(ProviderDocument::class)->willReturn($aggregationBuilderProphecy)->shouldBeCalled();
$aggregationBuilderProphecy->execute([])->willReturn($iterator->reveal())->shouldBeCalled();
$aggregationBuilder = $aggregationBuilderProphecy->reveal();
- $managerRegistry = $this->getManagerRegistry(ProviderEntity::class, $aggregationBuilder);
+ $managerRegistry = $this->getManagerRegistry(ProviderDocument::class, $aggregationBuilder);
$operation = (new Get())
- ->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['ida', 'idb'])])
- ->withClass(ProviderEntity::class)
+ ->withUriVariables([(new Link())->withFromClass(ProviderDocument::class)->withIdentifiers(['ida', 'idb'])])
+ ->withClass(ProviderDocument::class)
->withName('foo');
$context = [];
$extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class);
- $extensionProphecy->applyToItem($aggregationBuilder, ProviderEntity::class, ['ida' => 1, 'idb' => 2], $operation, $context)->shouldBeCalled();
+ $extensionProphecy->applyToItem($aggregationBuilder, ProviderDocument::class, ['ida' => 1, 'idb' => 2], $operation, $context)->shouldBeCalled();
$dataProvider = new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistry, [$extensionProphecy->reveal()]);
@@ -156,18 +155,18 @@ public function testAggregationResultExtension(): void
$aggregationBuilderProphecy->match()->willReturn($matchProphecy->reveal())->shouldBeCalled();
$aggregationBuilder = $aggregationBuilderProphecy->reveal();
- $managerRegistry = $this->getManagerRegistry(ProviderEntity::class, $aggregationBuilder);
+ $managerRegistry = $this->getManagerRegistry(ProviderDocument::class, $aggregationBuilder);
$operation = (new Get())
- ->withUriVariables([(new Link())->withFromClass(ProviderEntity::class)->withIdentifiers(['id'])])
- ->withClass(ProviderEntity::class)
+ ->withUriVariables([(new Link())->withFromClass(ProviderDocument::class)->withIdentifiers(['id'])])
+ ->withClass(ProviderDocument::class)
->withName('foo');
$context = [];
$extensionProphecy = $this->prophesize(AggregationResultItemExtensionInterface::class);
- $extensionProphecy->applyToItem($aggregationBuilder, ProviderEntity::class, ['id' => 1], $operation, $context)->shouldBeCalled();
- $extensionProphecy->supportsResult(ProviderEntity::class, $operation, $context)->willReturn(true)->shouldBeCalled();
- $extensionProphecy->getResult($aggregationBuilder, ProviderEntity::class, $operation, $context)->willReturn($returnObject)->shouldBeCalled();
+ $extensionProphecy->applyToItem($aggregationBuilder, ProviderDocument::class, ['id' => 1], $operation, $context)->shouldBeCalled();
+ $extensionProphecy->supportsResult(ProviderDocument::class, $operation, $context)->willReturn(true)->shouldBeCalled();
+ $extensionProphecy->getResult($aggregationBuilder, ProviderDocument::class, $operation, $context)->willReturn($returnObject)->shouldBeCalled();
$dataProvider = new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistry, [$extensionProphecy->reveal()]);
@@ -177,19 +176,19 @@ public function testAggregationResultExtension(): void
public function testCannotCreateAggregationBuilder(): void
{
$this->expectException(RuntimeException::class);
- $this->expectExceptionMessage('The repository for "ApiPlatform\Tests\Fixtures\TestBundle\Document\ProviderEntity" must be an instance of "Doctrine\ODM\MongoDB\Repository\DocumentRepository".');
+ $this->expectExceptionMessage('The repository for "ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\ProviderDocument" must be an instance of "Doctrine\ODM\MongoDB\Repository\DocumentRepository".');
$repositoryProphecy = $this->prophesize(ObjectRepository::class);
$managerProphecy = $this->prophesize(ObjectManager::class);
- $managerProphecy->getRepository(ProviderEntity::class)->willReturn($repositoryProphecy->reveal());
+ $managerProphecy->getRepository(ProviderDocument::class)->willReturn($repositoryProphecy->reveal());
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
- $managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal());
+ $managerRegistryProphecy->getManagerForClass(ProviderDocument::class)->willReturn($managerProphecy->reveal());
$extensionProphecy = $this->prophesize(AggregationItemExtensionInterface::class);
- (new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]))->provide((new Get())->withClass(ProviderEntity::class), [], []);
+ (new ItemProvider($this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal(), $managerRegistryProphecy->reveal(), [$extensionProphecy->reveal()]))->provide((new Get())->withClass(ProviderDocument::class), [], []);
}
/**
@@ -212,32 +211,8 @@ private function getManagerRegistry(string $resourceClass, Builder $aggregationB
$managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal());
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
- $managerRegistryProphecy->getManagerForClass(ProviderEntity::class)->willReturn($managerProphecy->reveal());
+ $managerRegistryProphecy->getManagerForClass(ProviderDocument::class)->willReturn($managerProphecy->reveal());
return $managerRegistryProphecy->reveal();
}
-
- /**
- * @group legacy
- */
- public function testHandleLinksCallable(): void
- {
- $this->expectDeprecation('The Doctrine\ODM\MongoDB\Aggregation\Builder::execute method is deprecated (This method was deprecated in doctrine/mongodb-odm 2.2. Please use getAggregation() instead.).');
- $class = 'foo';
- $resourceMetadata = $this->createStub(ResourceMetadataCollectionFactoryInterface::class);
- $it = $this->createStub(Iterator::class);
- $it->method('current')->willReturn(null);
- $aggregationBuilder = $this->createStub(Builder::class);
- $aggregationBuilder->method('hydrate')->willReturnSelf();
- $aggregationBuilder->method('execute')->willReturn($it);
- $repository = $this->createStub(DocumentRepository::class);
- $repository->method('createAggregationBuilder')->willReturn($aggregationBuilder);
- $manager = $this->createStub(DocumentManager::class);
- $manager->method('getRepository')->willReturn($repository);
- $managerRegistry = $this->createStub(ManagerRegistry::class);
- $managerRegistry->method('getManagerForClass')->willReturn($manager);
- $operation = new Get(class: $class, stateOptions: new Options(handleLinks: fn () => $this->assertTrue(true)));
- $dataProvider = new ItemProvider($resourceMetadata, $managerRegistry);
- $dataProvider->provide($operation, ['id' => 1]);
- }
}
diff --git a/src/Doctrine/Odm/Tests/bootstrap.php b/src/Doctrine/Odm/Tests/bootstrap.php
new file mode 100644
index 00000000000..567c2c9815b
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/bootstrap.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+$loader = require __DIR__.'/../vendor/autoload.php';
+require __DIR__.'/AppKernel.php';
+
+return $loader;
diff --git a/src/Doctrine/Odm/Tests/config.yml b/src/Doctrine/Odm/Tests/config.yml
new file mode 100644
index 00000000000..ab416fd583d
--- /dev/null
+++ b/src/Doctrine/Odm/Tests/config.yml
@@ -0,0 +1,32 @@
+parameters:
+ env(MONGODB_DB): api_platform_test
+ env(MONGODB_URL): mongodb://localhost:27017
+
+doctrine_mongodb:
+ connections:
+ default:
+ server: '%env(resolve:MONGODB_URL)%'
+ options: {}
+ default_database: '%env(resolve:MONGODB_DB)%'
+ document_managers:
+ default:
+ mappings:
+ TestBundle:
+ type: 'attribute'
+ dir: '%kernel.project_dir%/Fixtures/Document'
+ prefix: 'ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document'
+
+api_platform:
+ formats:
+ json: ['application/json']
+ doctrine: false
+ doctrine_mongodb_odm: true
+ mapping:
+ paths:
+ - '%kernel.project_dir%/Fixtures/Document'
+
+services:
+ test.property_accessor:
+ alias: property_accessor
+ public: true
+
diff --git a/src/Doctrine/Odm/composer.json b/src/Doctrine/Odm/composer.json
new file mode 100644
index 00000000000..29718fd5cee
--- /dev/null
+++ b/src/Doctrine/Odm/composer.json
@@ -0,0 +1,88 @@
+{
+ "name": "api-platform/doctrine-odm",
+ "description": "Doctrine MongoDB ODM bridge",
+ "type": "library",
+ "keywords": [
+ "Doctrine",
+ "ODM",
+ "MongoDB"
+ ],
+ "homepage": "https://api-platform.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Kévin Dunglas",
+ "email": "kevin@dunglas.fr",
+ "homepage": "https://dunglas.fr"
+ },
+ {
+ "name": "API Platform Community",
+ "homepage": "https://api-platform.com/community/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=8.1",
+ "api-platform/doctrine-common": "*@dev || ^3.1",
+ "api-platform/metadata": "*@dev || ^3.1",
+ "api-platform/state": "*@dev || ^3.1",
+ "doctrine/mongodb-odm": "^2.2",
+ "doctrine/mongodb-odm-bundle": "^5.0",
+ "symfony/property-info": "^6.4 || ^7.0"
+ },
+ "require-dev": {
+ "api-platform/parameter-validator": "*@dev || ^3.2",
+ "api-platform/symfony": "*@dev || ^3.2",
+ "doctrine/doctrine-bundle": "^2.11",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.0",
+ "symfony/cache": "^6.4 || ^7.0",
+ "symfony/framework-bundle": "^6.4 || ^7.0",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
+ "symfony/yaml": "^6.4 || ^7.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "ApiPlatform\\Doctrine\\Odm\\": ""
+ }
+ },
+ "config": {
+ "preferred-install": {
+ "*": "dist"
+ },
+ "sort-packages": true,
+ "allow-plugins": {
+ "php-http/discovery": false
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.2.x-dev"
+ },
+ "symfony": {
+ "require": "^6.4"
+ }
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../Metadata"
+ },
+ {
+ "type": "path",
+ "url": "../../State"
+ },
+ {
+ "type": "path",
+ "url": "../Common"
+ },
+ {
+ "type": "path",
+ "url": "../../Symfony"
+ },
+ {
+ "type": "path",
+ "url": "../../ParameterValidator"
+ }
+ ]
+}
diff --git a/src/Doctrine/Odm/phpunit.xml.dist b/src/Doctrine/Odm/phpunit.xml.dist
new file mode 100644
index 00000000000..44fe7563e18
--- /dev/null
+++ b/src/Doctrine/Odm/phpunit.xml.dist
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
diff --git a/src/Doctrine/Orm/.gitattributes b/src/Doctrine/Orm/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Doctrine/Orm/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Doctrine/Orm/.gitignore b/src/Doctrine/Orm/.gitignore
new file mode 100644
index 00000000000..2db77de0df4
--- /dev/null
+++ b/src/Doctrine/Orm/.gitignore
@@ -0,0 +1,5 @@
+/composer.lock
+/vendor
+/.phpunit.result.cache
+/.phpunit.cache
+/Tests/var
diff --git a/src/Doctrine/Orm/AbstractPaginator.php b/src/Doctrine/Orm/AbstractPaginator.php
index 3c3fa42e9ab..33d46fa787f 100644
--- a/src/Doctrine/Orm/AbstractPaginator.php
+++ b/src/Doctrine/Orm/AbstractPaginator.php
@@ -13,7 +13,7 @@
namespace ApiPlatform\Doctrine\Orm;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
use Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
diff --git a/src/Doctrine/Orm/Extension/EagerLoadingExtension.php b/src/Doctrine/Orm/Extension/EagerLoadingExtension.php
index dc6de300cb0..57f5c7b6591 100644
--- a/src/Doctrine/Orm/Extension/EagerLoadingExtension.php
+++ b/src/Doctrine/Orm/Extension/EagerLoadingExtension.php
@@ -15,10 +15,10 @@
use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Exception\InvalidArgumentException;
-use ApiPlatform\Exception\PropertyNotFoundException;
-use ApiPlatform\Exception\ResourceClassNotFoundException;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\PropertyNotFoundException;
+use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
diff --git a/src/Doctrine/Orm/Filter/DateFilter.php b/src/Doctrine/Orm/Filter/DateFilter.php
index d895cc68cbc..dd42e8ab5c4 100644
--- a/src/Doctrine/Orm/Filter/DateFilter.php
+++ b/src/Doctrine/Orm/Filter/DateFilter.php
@@ -16,7 +16,7 @@
use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface;
use ApiPlatform\Doctrine\Common\Filter\DateFilterTrait;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\Operation;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\DBAL\Types\Types;
diff --git a/src/Doctrine/Orm/Filter/SearchFilter.php b/src/Doctrine/Orm/Filter/SearchFilter.php
index 0ea085811d7..4133f33ad91 100644
--- a/src/Doctrine/Orm/Filter/SearchFilter.php
+++ b/src/Doctrine/Orm/Filter/SearchFilter.php
@@ -19,7 +19,7 @@
use ApiPlatform\Doctrine\Common\Filter\SearchFilterTrait;
use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\IdentifiersExtractorInterface;
use ApiPlatform\Metadata\IriConverterInterface;
use ApiPlatform\Metadata\Operation;
diff --git a/src/Doctrine/Orm/LICENSE b/src/Doctrine/Orm/LICENSE
new file mode 100644
index 00000000000..1ca98eeb824
--- /dev/null
+++ b/src/Doctrine/Orm/LICENSE
@@ -0,0 +1,21 @@
+The MIT license
+
+Copyright (c) 2015-present Kévin Dunglas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php
index 94141e5a108..0c0248eee85 100644
--- a/src/Doctrine/Orm/State/CollectionProvider.php
+++ b/src/Doctrine/Orm/State/CollectionProvider.php
@@ -17,7 +17,7 @@
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\ProviderInterface;
diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php
index c7235b58e3b..91896e7f685 100644
--- a/src/Doctrine/Orm/State/ItemProvider.php
+++ b/src/Doctrine/Orm/State/ItemProvider.php
@@ -17,7 +17,7 @@
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\ProviderInterface;
diff --git a/src/Doctrine/Orm/State/Options.php b/src/Doctrine/Orm/State/Options.php
index 50d13f1b7a4..397d2da758a 100644
--- a/src/Doctrine/Orm/State/Options.php
+++ b/src/Doctrine/Orm/State/Options.php
@@ -13,9 +13,10 @@
namespace ApiPlatform\Doctrine\Orm\State;
+use ApiPlatform\Doctrine\Common\State\Options as CommonOptions;
use ApiPlatform\State\OptionsInterface;
-class Options implements OptionsInterface
+class Options extends CommonOptions implements OptionsInterface
{
/**
* @param string|callable $handleLinks experimental callable, typed mixed as we may want a service name in the future
@@ -24,8 +25,9 @@ class Options implements OptionsInterface
*/
public function __construct(
protected ?string $entityClass = null,
- protected mixed $handleLinks = null,
+ mixed $handleLinks = null,
) {
+ parent::__construct(handleLinks: $handleLinks);
}
public function getEntityClass(): ?string
diff --git a/src/Doctrine/Orm/Tests/AppKernel.php b/src/Doctrine/Orm/Tests/AppKernel.php
new file mode 100644
index 00000000000..22409c74f2f
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/AppKernel.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests;
+
+use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
+use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
+use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
+use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
+use Symfony\Component\Config\Loader\LoaderInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Kernel;
+
+/**
+ * AppKernel for tests.
+ *
+ * @author Kévin Dunglas
+ */
+class AppKernel extends Kernel
+{
+ use MicroKernelTrait;
+
+ public function __construct(string $environment, bool $debug)
+ {
+ parent::__construct($environment, $debug);
+
+ // patch for behat/symfony2-extension not supporting %env(APP_ENV)%
+ $this->environment = $_SERVER['APP_ENV'] ?? $environment;
+ }
+
+ public function registerBundles(): array
+ {
+ return [
+ new ApiPlatformBundle(),
+ new FrameworkBundle(),
+ new DoctrineBundle(),
+ new TestBundle(),
+ ];
+ }
+
+ public function getProjectDir(): string
+ {
+ return __DIR__;
+ }
+
+ protected function configureRoutes($routes): void
+ {
+ }
+
+ protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader): void
+ {
+ $c->setParameter('kernel.project_dir', __DIR__);
+
+ $cookie = ['cookie_secure' => true, 'cookie_samesite' => 'lax', 'handler_id' => 'session.handler.native_file'];
+ $config = [
+ 'secret' => 'dunglas.fr',
+ 'validation' => ['enable_attributes' => true, 'email_validation_mode' => 'html5'],
+ 'serializer' => ['enable_attributes' => true],
+ 'test' => null,
+ 'session' => ['storage_factory_id' => 'session.storage.factory.mock_file'] + $cookie,
+ 'profiler' => ['enabled' => false],
+ 'property_access' => ['enabled' => true],
+ 'php_errors' => ['log' => true],
+ 'router' => ['utf8' => true],
+ 'http_method_override' => false,
+ 'annotations' => false,
+ 'handle_all_throwables' => true,
+ 'uid' => ['default_uuid_version' => 7, 'time_based_uuid_version' => 7],
+ ];
+
+ $c->prependExtensionConfig('framework', $config);
+
+ $loader->load(__DIR__.'/config.yml');
+ }
+
+ protected function build(ContainerBuilder $container): void
+ {
+ }
+}
diff --git a/src/Test/DoctrineOrmFilterTestCase.php b/src/Doctrine/Orm/Tests/DoctrineOrmFilterTestCase.php
similarity index 97%
rename from src/Test/DoctrineOrmFilterTestCase.php
rename to src/Doctrine/Orm/Tests/DoctrineOrmFilterTestCase.php
index ab90ca8107c..f901340ca6b 100644
--- a/src/Test/DoctrineOrmFilterTestCase.php
+++ b/src/Doctrine/Orm/Tests/DoctrineOrmFilterTestCase.php
@@ -11,11 +11,11 @@
declare(strict_types=1);
-namespace ApiPlatform\Test;
+namespace ApiPlatform\Doctrine\Orm\Tests;
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
diff --git a/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php b/src/Doctrine/Orm/Tests/Extension/EagerLoadingExtensionTest.php
similarity index 98%
rename from tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php
rename to src/Doctrine/Orm/Tests/Extension/EagerLoadingExtensionTest.php
index 922880847a8..0a29d5d1ada 100644
--- a/tests/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php
+++ b/src/Doctrine/Orm/Tests/Extension/EagerLoadingExtensionTest.php
@@ -11,28 +11,28 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Extension;
+namespace ApiPlatform\Doctrine\Orm\Tests\Extension;
use ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\AbstractDummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\ConcreteDummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\EmbeddableDummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\PropertyCollectionIriOnly;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\PropertyCollectionIriOnlyRelation;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\RelatedDummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\ThirdLevel;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\UnknownDummy;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
-use ApiPlatform\Exception\PropertyNotFoundException;
-use ApiPlatform\Exception\ResourceClassNotFoundException;
-use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\Exception\PropertyNotFoundException;
+use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Property\PropertyNameCollection;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\AbstractDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ConcreteDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\EmbeddableDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\PropertyCollectionIriOnly;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\PropertyCollectionIriOnlyRelation;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThirdLevel;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\UnknownDummy;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
diff --git a/tests/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php b/src/Doctrine/Orm/Tests/Extension/FilterEagerLoadingExtensionTest.php
similarity index 89%
rename from tests/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php
rename to src/Doctrine/Orm/Tests/Extension/FilterEagerLoadingExtensionTest.php
index fbbce89f3ed..7fe4a66002e 100644
--- a/tests/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php
+++ b/src/Doctrine/Orm/Tests/Extension/FilterEagerLoadingExtensionTest.php
@@ -11,19 +11,19 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Extension;
+namespace ApiPlatform\Doctrine\Orm\Tests\Extension;
use ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\CompositeItem;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\CompositeLabel;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\CompositeRelation;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyCar;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyTravel;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceNameCollection;
use ApiPlatform\Metadata\ResourceClassResolver;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositeItem;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositeLabel;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompositeRelation;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTravel;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Query\Expr;
@@ -123,7 +123,7 @@ public function testApplyCollection(): void
$filterEagerLoadingExtension = new FilterEagerLoadingExtension(true);
$filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, new Get(name: 'get'));
- $this->assertSame('SELECT o FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar o LEFT JOIN o.colors colors WHERE o IN(SELECT o_2 FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar o_2 LEFT JOIN o_2.colors colors_2 WHERE o_2.colors = :foo)', $qb->getDQL());
+ $this->assertSame('SELECT o FROM ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyCar o LEFT JOIN o.colors colors WHERE o IN(SELECT o_2 FROM ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyCar o_2 LEFT JOIN o_2.colors colors_2 WHERE o_2.colors = :foo)', $qb->getDQL());
}
public function testApplyCollectionWithManualJoin(): void
@@ -157,13 +157,13 @@ public function testApplyCollectionWithManualJoin(): void
$expected = <<<'SQL'
SELECT o
-FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar o
+FROM ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyCar o
LEFT JOIN o.colors colors
-INNER JOIN ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTravel t_a3 WITH o.id = t_a3.car AND t_a3.passenger = :user
+INNER JOIN ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyTravel t_a3 WITH o.id = t_a3.car AND t_a3.passenger = :user
WHERE o IN(
- SELECT o_2 FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar o_2
+ SELECT o_2 FROM ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyCar o_2
LEFT JOIN o_2.colors colors_2
- INNER JOIN ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTravel t_a3_a20 WITH o_2.id = t_a3_a20.car AND t_a3_a20.passenger = :user
+ INNER JOIN ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyTravel t_a3_a20 WITH o_2.id = t_a3_a20.car AND t_a3_a20.passenger = :user
WHERE o_2.colors = :foo AND t_a3_a20.confirmed = :confirmation
)
SQL;
@@ -199,10 +199,10 @@ public function testApplyCollectionCorrectlyReplacesJoinCondition(): void
$expected = <<<'SQL'
SELECT o
-FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar o
+FROM ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyCar o
LEFT JOIN o.colors colors ON o.id = colors.car AND colors.id IN (1,2,3)
WHERE o IN(
- SELECT o_2 FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyCar o_2
+ SELECT o_2 FROM ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyCar o_2
LEFT JOIN o_2.colors colors_2 ON o_2.id = colors_2.car AND colors_2.id IN (1,2,3)
WHERE o_2.colors = :foo AND o_2.info.name = :bar
)
@@ -237,10 +237,10 @@ public function testHiddenOrderBy(): void
$expected = <<
diff --git a/tests/Doctrine/Orm/Filter/DateFilterTest.php b/src/Doctrine/Orm/Tests/Filter/DateFilterTest.php
similarity index 93%
rename from tests/Doctrine/Orm/Filter/DateFilterTest.php
rename to src/Doctrine/Orm/Tests/Filter/DateFilterTest.php
index 1f974c35d3e..91e6aa4fd27 100644
--- a/tests/Doctrine/Orm/Filter/DateFilterTest.php
+++ b/src/Doctrine/Orm/Tests/Filter/DateFilterTest.php
@@ -11,15 +11,15 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Filter;
+namespace ApiPlatform\Doctrine\Orm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\DateFilterTestTrait;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
+use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyDate;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyImmutableDate;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
-use ApiPlatform\Test\DoctrineOrmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\DateFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyDate;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyImmutableDate;
/**
* @author Théo FIDRY
diff --git a/tests/Doctrine/Orm/Filter/ExistsFilterTest.php b/src/Doctrine/Orm/Tests/Filter/ExistsFilterTest.php
similarity index 96%
rename from tests/Doctrine/Orm/Filter/ExistsFilterTest.php
rename to src/Doctrine/Orm/Tests/Filter/ExistsFilterTest.php
index 82286240505..cb0782ec30b 100644
--- a/tests/Doctrine/Orm/Filter/ExistsFilterTest.php
+++ b/src/Doctrine/Orm/Tests/Filter/ExistsFilterTest.php
@@ -11,13 +11,13 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Filter;
+namespace ApiPlatform\Doctrine\Orm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\ExistsFilterTestTrait;
use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter;
-use ApiPlatform\Test\DoctrineOrmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\ExistsFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
+use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\CustomConverter;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
use Doctrine\Persistence\ManagerRegistry;
/**
diff --git a/tests/Doctrine/Orm/Filter/NumericFilterTest.php b/src/Doctrine/Orm/Tests/Filter/NumericFilterTest.php
similarity index 94%
rename from tests/Doctrine/Orm/Filter/NumericFilterTest.php
rename to src/Doctrine/Orm/Tests/Filter/NumericFilterTest.php
index 215d9771485..d3322ce064c 100644
--- a/tests/Doctrine/Orm/Filter/NumericFilterTest.php
+++ b/src/Doctrine/Orm/Tests/Filter/NumericFilterTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Filter;
+namespace ApiPlatform\Doctrine\Orm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\NumericFilterTestTrait;
use ApiPlatform\Doctrine\Orm\Filter\NumericFilter;
-use ApiPlatform\Test\DoctrineOrmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\NumericFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
/**
* @author Amrouche Hamza
diff --git a/tests/Doctrine/Orm/Filter/OrderFilterTest.php b/src/Doctrine/Orm/Tests/Filter/OrderFilterTest.php
similarity index 97%
rename from tests/Doctrine/Orm/Filter/OrderFilterTest.php
rename to src/Doctrine/Orm/Tests/Filter/OrderFilterTest.php
index 76454322505..2e1fa995750 100644
--- a/tests/Doctrine/Orm/Filter/OrderFilterTest.php
+++ b/src/Doctrine/Orm/Tests/Filter/OrderFilterTest.php
@@ -11,14 +11,14 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Filter;
+namespace ApiPlatform\Doctrine\Orm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\OrderFilterTestTrait;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
-use ApiPlatform\Test\DoctrineOrmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\OrderFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\EmbeddedDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
+use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\CustomConverter;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\EmbeddedDummy;
use Doctrine\Persistence\ManagerRegistry;
/**
diff --git a/tests/Doctrine/Orm/Filter/RangeFilterTest.php b/src/Doctrine/Orm/Tests/Filter/RangeFilterTest.php
similarity index 98%
rename from tests/Doctrine/Orm/Filter/RangeFilterTest.php
rename to src/Doctrine/Orm/Tests/Filter/RangeFilterTest.php
index 6229aa79a35..82305807c06 100644
--- a/tests/Doctrine/Orm/Filter/RangeFilterTest.php
+++ b/src/Doctrine/Orm/Tests/Filter/RangeFilterTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Filter;
+namespace ApiPlatform\Doctrine\Orm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\RangeFilterTestTrait;
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
-use ApiPlatform\Test\DoctrineOrmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\RangeFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
/**
* @author Lee Siong Chan
diff --git a/tests/Doctrine/Orm/Filter/SearchFilterTest.php b/src/Doctrine/Orm/Tests/Filter/SearchFilterTest.php
similarity index 98%
rename from tests/Doctrine/Orm/Filter/SearchFilterTest.php
rename to src/Doctrine/Orm/Tests/Filter/SearchFilterTest.php
index 8644ec7a289..aa1f8e4002a 100644
--- a/tests/Doctrine/Orm/Filter/SearchFilterTest.php
+++ b/src/Doctrine/Orm/Tests/Filter/SearchFilterTest.php
@@ -11,18 +11,18 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Filter;
+namespace ApiPlatform\Doctrine\Orm\Tests\Filter;
+use ApiPlatform\Doctrine\Common\Tests\Filter\SearchFilterTestTrait;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
+use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\CustomConverter;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\RelatedDummy;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\IriConverterInterface;
-use ApiPlatform\Test\DoctrineOrmFilterTestCase;
-use ApiPlatform\Tests\Doctrine\Common\Filter\SearchFilterTestTrait;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
use Doctrine\Persistence\ManagerRegistry;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
diff --git a/src/Doctrine/Orm/Tests/Fixtures/CustomConverter.php b/src/Doctrine/Orm/Tests/Fixtures/CustomConverter.php
new file mode 100644
index 00000000000..af1617c0a5a
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/CustomConverter.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures;
+
+use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
+
+/**
+ * Custom converter that will only convert a property named "nameConverted"
+ * with the same logic as Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter.
+ */
+class CustomConverter extends CamelCaseToSnakeCaseNameConverter
+{
+ public function normalize(string $propertyName): string
+ {
+ return 'nameConverted' === $propertyName ? parent::normalize($propertyName) : $propertyName;
+ }
+
+ public function denormalize(string $propertyName): string
+ {
+ return 'name_converted' === $propertyName ? parent::denormalize($propertyName) : $propertyName;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/AbstractDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/AbstractDummy.php
new file mode 100644
index 00000000000..180ed14406d
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/AbstractDummy.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Delete;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Abstract Dummy.
+ *
+ * @author Jérémy Derussé
+ */
+#[ApiResource(operations: [new Get(), new Put(), new Delete(), new GetCollection(), new Post()], filters: ['my_dummy.search', 'my_dummy.order', 'my_dummy.date'])]
+#[ORM\Entity]
+#[ORM\InheritanceType('SINGLE_TABLE')]
+#[ORM\DiscriminatorColumn(name: 'discr', type: 'string', length: 16)]
+#[ORM\DiscriminatorMap(['concrete' => ConcreteDummy::class])]
+abstract class AbstractDummy
+{
+ /**
+ * @var int The id
+ */
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+
+ /**
+ * @var string The dummy name
+ */
+ #[ApiProperty(types: ['https://schema.org/name'])]
+ #[ORM\Column]
+ #[Assert\NotBlank]
+ private $name;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/Company.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/Company.php
new file mode 100644
index 00000000000..6fd14985be6
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/Company.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
+use ApiPlatform\Metadata\Post;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+#[ApiResource]
+#[GetCollection]
+#[Get]
+#[Post]
+#[ApiResource(
+ uriTemplate: '/employees/{employeeId}/company',
+ uriVariables: [
+ 'employeeId' => ['from_class' => Employee::class, 'from_property' => 'company'],
+ ],
+)]
+#[ORM\Entity]
+class Company
+{
+ /**
+ * @var int|null The id
+ */
+ #[ORM\Column(type: 'integer', nullable: true)]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+
+ /**
+ * @var string The dummy name
+ */
+ #[ORM\Column]
+ #[Groups(['company_employees_read'])]
+ public string $name;
+
+ /** @var Employee[] */
+ #[Link(toProperty: 'company')]
+ public $employees = []; // only used to set metadata
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeItem.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeItem.php
new file mode 100644
index 00000000000..bbcea4fbbae
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeItem.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Composite Item.
+ */
+#[ApiResource]
+#[ORM\Entity]
+class CompositeItem implements \Stringable
+{
+ #[ORM\Id]
+ #[ORM\Column(type: 'integer')]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+ #[ORM\Column(type: 'string', nullable: true)]
+ #[Groups(['default'])]
+ private ?string $field1 = null;
+ #[ORM\OneToMany(targetEntity: CompositeRelation::class, mappedBy: 'compositeItem', fetch: 'EAGER')]
+ #[Groups(['default'])]
+ private Collection|iterable $compositeValues;
+
+ public function __construct()
+ {
+ $this->compositeValues = new ArrayCollection();
+ }
+
+ /**
+ * Gets id.
+ */
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ /**
+ * Gets field1.
+ */
+ public function getField1(): ?string
+ {
+ return $this->field1;
+ }
+
+ /**
+ * Sets field1.
+ *
+ * @param string|null $field1 the value to set
+ */
+ public function setField1($field1 = null): void
+ {
+ $this->field1 = $field1;
+ }
+
+ /**
+ * Gets compositeValues.
+ */
+ public function getCompositeValues(): Collection|iterable
+ {
+ return $this->compositeValues;
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeLabel.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeLabel.php
new file mode 100644
index 00000000000..a884b4e0b3c
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeLabel.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Composite Label.
+ */
+#[ApiResource]
+#[ORM\Entity]
+class CompositeLabel implements \Stringable
+{
+ #[ORM\Id]
+ #[ORM\Column(type: 'integer')]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+ #[ORM\Column(type: 'string', nullable: true)]
+ #[Groups(['default'])]
+ private ?string $value = null;
+
+ /**
+ * Gets id.
+ */
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ /**
+ * Gets value.
+ */
+ public function getValue(): ?string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Sets value.
+ *
+ * @param string|null $value the value to set
+ */
+ public function setValue(string $value = null): void
+ {
+ $this->value = $value;
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeRelation.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeRelation.php
new file mode 100644
index 00000000000..c274d1648d6
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/CompositeRelation.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Composite Relation.
+ */
+#[ApiResource]
+#[ORM\Entity]
+class CompositeRelation
+{
+ #[ORM\Column(type: 'string', nullable: true)]
+ #[Groups(['default'])]
+ private ?string $value = null;
+ #[ORM\Id]
+ #[ORM\ManyToOne(targetEntity: CompositeItem::class, inversedBy: 'compositeValues')]
+ #[ORM\JoinColumn(name: 'composite_item_id', referencedColumnName: 'id', nullable: false)]
+ #[Groups(['default'])]
+ private CompositeItem $compositeItem;
+ #[ORM\Id]
+ #[ORM\ManyToOne(targetEntity: CompositeLabel::class)]
+ #[ORM\JoinColumn(name: 'composite_label_id', referencedColumnName: 'id', nullable: false)]
+ #[Groups(['default'])]
+ private CompositeLabel $compositeLabel;
+
+ /**
+ * Gets value.
+ */
+ public function getValue(): ?string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Sets value.
+ *
+ * @param string|null $value the value to set
+ */
+ public function setValue($value = null): void
+ {
+ $this->value = $value;
+ }
+
+ /**
+ * Gets compositeItem.
+ */
+ public function getCompositeItem(): CompositeItem
+ {
+ return $this->compositeItem;
+ }
+
+ /**
+ * Sets compositeItem.
+ *
+ * @param CompositeItem $compositeItem the value to set
+ */
+ public function setCompositeItem(CompositeItem $compositeItem): void
+ {
+ $this->compositeItem = $compositeItem;
+ }
+
+ /**
+ * Gets compositeLabel.
+ */
+ public function getCompositeLabel(): CompositeLabel
+ {
+ return $this->compositeLabel;
+ }
+
+ /**
+ * Sets compositeLabel.
+ *
+ * @param CompositeLabel $compositeLabel the value to set
+ */
+ public function setCompositeLabel(CompositeLabel $compositeLabel): void
+ {
+ $this->compositeLabel = $compositeLabel;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/ConcreteDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/ConcreteDummy.php
new file mode 100644
index 00000000000..ec531887825
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/ConcreteDummy.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Concrete Dummy.
+ *
+ * @author Jérémy Derusse
+ */
+#[ApiResource]
+#[ORM\Entity]
+class ConcreteDummy extends AbstractDummy
+{
+ /**
+ * @var string a concrete thing
+ */
+ #[ORM\Column]
+ #[Assert\NotBlank]
+ private $instance;
+
+ public function setInstance($instance): void
+ {
+ $this->instance = $instance;
+ }
+
+ public function getInstance()
+ {
+ return $this->instance;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/Dummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/Dummy.php
new file mode 100644
index 00000000000..46d4bd43452
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/Dummy.php
@@ -0,0 +1,309 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Link;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Dummy.
+ *
+ * @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'], extraProperties: ['standard_put' => false, 'rfc_7807_compliant_errors' => false])]
+#[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
+{
+ /**
+ * @var int|null The id
+ */
+ #[ORM\Column(type: 'integer', nullable: true)]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private $id;
+
+ /**
+ * @var string The dummy name
+ */
+ #[ApiProperty(iris: ['https://schema.org/name'])]
+ #[ORM\Column]
+ #[Assert\NotBlank]
+ private string $name;
+
+ /**
+ * @var string|null The dummy name alias
+ */
+ #[ApiProperty(iris: ['https://schema.org/alternateName'])]
+ #[ORM\Column(nullable: true)]
+ private $alias;
+
+ /**
+ * @var array foo
+ */
+ private ?array $foo = null;
+
+ /**
+ * @var string|null A short description of the item
+ */
+ #[ApiProperty(iris: ['https://schema.org/description'])]
+ #[ORM\Column(nullable: true)]
+ public $description;
+
+ /**
+ * @var string|null A dummy
+ */
+ #[ORM\Column(nullable: true)]
+ public $dummy;
+
+ /**
+ * @var bool|null A dummy boolean
+ */
+ #[ORM\Column(type: 'boolean', nullable: true)]
+
+ public ?bool $dummyBoolean = null;
+ /**
+ * @var \DateTime|null A dummy date
+ */
+ #[ApiProperty(iris: ['https://schema.org/DateTime'])]
+ #[ORM\Column(type: 'datetime', nullable: true)]
+ public $dummyDate;
+
+ /**
+ * @var float|null A dummy float
+ */
+ #[ORM\Column(type: 'float', nullable: true)]
+ public $dummyFloat;
+
+ /**
+ * @var string|null A dummy price
+ */
+ #[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: true)]
+ public $dummyPrice;
+
+ #[ApiProperty(push: true)]
+ #[ORM\ManyToOne(targetEntity: RelatedDummy::class)]
+ public ?RelatedDummy $relatedDummy = null;
+
+ #[ORM\ManyToMany(targetEntity: RelatedDummy::class)]
+ public Collection|iterable $relatedDummies;
+
+ /**
+ * @var array|null serialize data
+ */
+ #[ORM\Column(type: 'json', nullable: true)]
+ public $jsonData = [];
+
+ /**
+ * @var array|null
+ */
+ #[ORM\Column(type: 'simple_array', nullable: true)]
+ public $arrayData = [];
+
+ /**
+ * @var string|null
+ */
+ #[ORM\Column(nullable: true)]
+ public $nameConverted;
+
+ /**
+ * @var RelatedOwnedDummy|null
+ */
+ #[ORM\OneToOne(targetEntity: RelatedOwnedDummy::class, cascade: ['persist'], mappedBy: 'owningDummy')]
+ public $relatedOwnedDummy;
+
+ /**
+ * @var RelatedOwningDummy|null
+ */
+ #[ORM\OneToOne(targetEntity: RelatedOwningDummy::class, cascade: ['persist'], inversedBy: 'ownedDummy')]
+ public $relatedOwningDummy;
+
+ public static function staticMethod(): void
+ {
+ }
+
+ public function __construct()
+ {
+ $this->relatedDummies = new ArrayCollection();
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setAlias($alias): void
+ {
+ $this->alias = $alias;
+ }
+
+ public function getAlias()
+ {
+ return $this->alias;
+ }
+
+ public function setDescription($description): void
+ {
+ $this->description = $description;
+ }
+
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ public function fooBar($baz): void
+ {
+ }
+
+ public function getFoo(): ?array
+ {
+ return $this->foo;
+ }
+
+ public function setFoo(array $foo = null): void
+ {
+ $this->foo = $foo;
+ }
+
+ public function setDummyDate(\DateTime $dummyDate = null): void
+ {
+ $this->dummyDate = $dummyDate;
+ }
+
+ public function getDummyDate()
+ {
+ return $this->dummyDate;
+ }
+
+ public function setDummyPrice($dummyPrice)
+ {
+ $this->dummyPrice = $dummyPrice;
+
+ return $this;
+ }
+
+ public function getDummyPrice()
+ {
+ return $this->dummyPrice;
+ }
+
+ public function setJsonData($jsonData): void
+ {
+ $this->jsonData = $jsonData;
+ }
+
+ public function getJsonData()
+ {
+ return $this->jsonData;
+ }
+
+ public function setArrayData($arrayData): void
+ {
+ $this->arrayData = $arrayData;
+ }
+
+ public function getArrayData()
+ {
+ return $this->arrayData;
+ }
+
+ public function getRelatedDummy(): ?RelatedDummy
+ {
+ return $this->relatedDummy;
+ }
+
+ public function setRelatedDummy(RelatedDummy $relatedDummy): void
+ {
+ $this->relatedDummy = $relatedDummy;
+ }
+
+ public function addRelatedDummy(RelatedDummy $relatedDummy): void
+ {
+ $this->relatedDummies->add($relatedDummy);
+ }
+
+ public function getRelatedOwnedDummy()
+ {
+ return $this->relatedOwnedDummy;
+ }
+
+ public function setRelatedOwnedDummy(RelatedOwnedDummy $relatedOwnedDummy): void
+ {
+ $this->relatedOwnedDummy = $relatedOwnedDummy;
+ if ($this !== $this->relatedOwnedDummy->getOwningDummy()) {
+ $this->relatedOwnedDummy->setOwningDummy($this);
+ }
+ }
+
+ public function getRelatedOwningDummy()
+ {
+ return $this->relatedOwningDummy;
+ }
+
+ public function setRelatedOwningDummy(RelatedOwningDummy $relatedOwningDummy): void
+ {
+ $this->relatedOwningDummy = $relatedOwningDummy;
+ }
+
+ public function isDummyBoolean(): ?bool
+ {
+ return $this->dummyBoolean;
+ }
+
+ /**
+ * @param bool $dummyBoolean
+ */
+ public function setDummyBoolean($dummyBoolean): void
+ {
+ $this->dummyBoolean = $dummyBoolean;
+ }
+
+ public function setDummy($dummy = null): void
+ {
+ $this->dummy = $dummy;
+ }
+
+ public function getDummy()
+ {
+ return $this->dummy;
+ }
+
+ public function getRelatedDummies(): Collection|iterable
+ {
+ return $this->relatedDummies;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCar.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCar.php
new file mode 100644
index 00000000000..ba9fd297690
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCar.php
@@ -0,0 +1,177 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
+use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
+use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
+use ApiPlatform\Metadata\ApiFilter;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Delete;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)]
+#[ApiFilter(BooleanFilter::class)]
+#[ApiResource(operations: [new Get(), new Put(), new Delete(), new Post(), new GetCollection()], sunset: '2050-01-01', normalizationContext: ['groups' => ['colors']])]
+#[ORM\Entity]
+class DummyCar
+{
+ /**
+ * @var DummyCarIdentifier The entity Id
+ */
+ #[ORM\Id]
+ #[ORM\OneToOne(targetEntity: DummyCarIdentifier::class, cascade: ['persist'])]
+ private DummyCarIdentifier $id;
+ #[ApiFilter(SearchFilter::class, properties: ['colors.prop' => 'ipartial', 'colors' => 'exact'])]
+ #[ORM\OneToMany(targetEntity: DummyCarColor::class, mappedBy: 'car')]
+ private Collection|iterable $colors;
+ #[ApiFilter(SearchFilter::class, strategy: 'exact')]
+ #[ORM\OneToMany(targetEntity: DummyCarColor::class, mappedBy: 'car')]
+ private Collection|iterable|null $secondColors = null;
+ #[ApiFilter(SearchFilter::class, strategy: 'exact')]
+ #[ORM\OneToMany(targetEntity: DummyCarColor::class, mappedBy: 'car')]
+ private Collection|iterable|null $thirdColors = null;
+ #[ApiFilter(SearchFilter::class, strategy: 'exact')]
+ #[ORM\ManyToMany(targetEntity: UuidIdentifierDummy::class, indexBy: 'uuid')]
+ #[ORM\JoinColumn(name: 'car_id', referencedColumnName: 'id_id')]
+ #[ORM\InverseJoinColumn(name: 'uuid_uuid', referencedColumnName: 'uuid')]
+ #[ORM\JoinTable(name: 'uuid_cars')]
+ private Collection|iterable|null $uuid = null;
+
+ #[ApiFilter(SearchFilter::class, strategy: 'partial')]
+ #[ORM\Column(type: 'string')]
+ private string $name;
+ #[ORM\Column(type: 'boolean')]
+ private bool $canSell;
+ #[ORM\Column(type: 'datetime')]
+ private \DateTime $availableAt;
+ #[ApiFilter(SearchFilter::class, strategy: SearchFilter::STRATEGY_IEXACT)]
+ #[ORM\Column]
+ private string $brand = 'DummyBrand';
+ #[ORM\Embedded(class: 'DummyCarInfo')]
+ private DummyCarInfo $info;
+
+ public function __construct()
+ {
+ $this->id = new DummyCarIdentifier();
+ $this->colors = new ArrayCollection();
+ $this->info = new DummyCarInfo();
+ }
+
+ public function getId(): DummyCarIdentifier
+ {
+ return $this->id;
+ }
+
+ public function getColors(): Collection|iterable
+ {
+ return $this->colors;
+ }
+
+ public function setColors(Collection|iterable $colors): self
+ {
+ $this->colors = $colors;
+
+ return $this;
+ }
+
+ public function getSecondColors(): ?iterable
+ {
+ return $this->secondColors;
+ }
+
+ public function setSecondColors($secondColors): void
+ {
+ $this->secondColors = $secondColors;
+ }
+
+ public function getThirdColors(): ?iterable
+ {
+ return $this->thirdColors;
+ }
+
+ public function setThirdColors($thirdColors): void
+ {
+ $this->thirdColors = $thirdColors;
+ }
+
+ public function getUuid(): ?iterable
+ {
+ return $this->uuid;
+ }
+
+ public function setUuid($uuid): void
+ {
+ $this->uuid = $uuid;
+ }
+
+ /**
+ * Get name.
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getCanSell(): bool
+ {
+ return $this->canSell;
+ }
+
+ public function setCanSell(bool $canSell): void
+ {
+ $this->canSell = $canSell;
+ }
+
+ public function getAvailableAt(): \DateTime
+ {
+ return $this->availableAt;
+ }
+
+ public function setAvailableAt(\DateTime $availableAt): void
+ {
+ $this->availableAt = $availableAt;
+ }
+
+ public function getBrand(): string
+ {
+ return $this->brand;
+ }
+
+ public function setBrand(string $brand): void
+ {
+ $this->brand = $brand;
+ }
+
+ public function getInfo(): DummyCarInfo
+ {
+ return $this->info;
+ }
+
+ public function setInfo(DummyCarInfo $info): void
+ {
+ $this->info = $info;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarColor.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarColor.php
new file mode 100644
index 00000000000..3d873996c0d
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarColor.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
+use ApiPlatform\Metadata\ApiFilter;
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation as Serializer;
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[ApiResource]
+#[ORM\Entity]
+class DummyCarColor
+{
+ /**
+ * @var int The entity Id
+ */
+ #[ORM\Id]
+ #[ORM\GeneratedValue]
+ #[ORM\Column(type: 'integer')]
+ private ?int $id = null;
+ #[ORM\ManyToOne(targetEntity: DummyCar::class, inversedBy: 'colors')]
+ #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE', referencedColumnName: 'id_id')]
+ #[Assert\NotBlank]
+ private DummyCar $car;
+ #[ApiFilter(SearchFilter::class)]
+ #[ORM\Column(nullable: false)]
+ #[Assert\NotBlank]
+ #[Serializer\Groups(['colors'])]
+ private string $prop = '';
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getCar(): ?DummyCar
+ {
+ return $this->car;
+ }
+
+ /**
+ * @return static
+ */
+ public function setCar(DummyCar $car)
+ {
+ $this->car = $car;
+
+ return $this;
+ }
+
+ public function getProp(): string
+ {
+ return $this->prop;
+ }
+
+ /**
+ * @param string $prop
+ *
+ * @return static
+ */
+ public function setProp($prop)
+ {
+ $this->prop = $prop;
+
+ return $this;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarIdentifier.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarIdentifier.php
new file mode 100644
index 00000000000..6a0a178ccd9
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarIdentifier.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+#[ORM\Entity]
+class DummyCarIdentifier implements \Stringable
+{
+ #[ORM\Id]
+ #[ORM\GeneratedValue]
+ #[ORM\Column(type: 'integer')]
+ private $id; // @phpstan-ignore-line
+
+ public function __toString(): string
+ {
+ return (string) $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarInfo.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarInfo.php
new file mode 100644
index 00000000000..a4b5a68df20
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyCarInfo.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @author Sergey Balasov
+ */
+#[ORM\Embeddable]
+class DummyCarInfo
+{
+ /**
+ * @var string
+ */
+ #[ORM\Column(nullable: true)]
+ public $name;
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyDate.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyDate.php
new file mode 100644
index 00000000000..33f487b092f
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyDate.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
+use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
+use ApiPlatform\Metadata\ApiFilter;
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Dummy Date.
+ *
+ * @author Antoine Bluchet
+ */
+#[ApiFilter(DateFilter::class, properties: [
+ 'dateIncludeNullAfter' => DateFilter::INCLUDE_NULL_AFTER,
+ 'dateIncludeNullBefore' => DateFilter::INCLUDE_NULL_BEFORE,
+ 'dateIncludeNullBeforeAndAfter' => DateFilter::INCLUDE_NULL_BEFORE_AND_AFTER,
+])]
+#[ApiFilter(SearchFilter::class, properties: ['dummyDate'])]
+#[ApiResource(filters: ['my_dummy_date.date'])]
+#[ORM\Entity]
+class DummyDate
+{
+ /**
+ * @var int|null The id
+ */
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+ /**
+ * @var \DateTime The dummy date
+ */
+ #[ORM\Column(type: 'date')]
+ public $dummyDate;
+ /**
+ * @var \DateTime|null
+ */
+ #[ORM\Column(type: 'date', nullable: true)]
+ public $dateIncludeNullAfter;
+ /**
+ * @var \DateTime|null
+ */
+ #[ORM\Column(type: 'date', nullable: true)]
+ public $dateIncludeNullBefore;
+ /**
+ * @var \DateTime|null
+ */
+ #[ORM\Column(type: 'date', nullable: true)]
+ public $dateIncludeNullBeforeAndAfter;
+
+ /**
+ * Get id.
+ */
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyFriend.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyFriend.php
new file mode 100644
index 00000000000..af44f589290
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyFriend.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Dummy Friend.
+ *
+ * @author Kévin Dunglas
+ */
+#[ApiResource]
+#[ORM\Entity]
+class DummyFriend implements \Stringable
+{
+ /**
+ * @var int|null The id
+ */
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+
+ /**
+ * @var string The dummy name
+ */
+ #[ApiProperty(types: ['https://schema.org/name'])]
+ #[ORM\Column]
+ #[Assert\NotBlank]
+ #[Groups(['fakemanytomany', 'friends'])]
+ private string $name;
+
+ /**
+ * Get id.
+ */
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set id.
+ *
+ * @param int $id the value to set
+ */
+ public function setId(int $id): void
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * Get name.
+ */
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set name.
+ *
+ * @param string $name the value to set
+ */
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->getId();
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyImmutableDate.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyImmutableDate.php
new file mode 100644
index 00000000000..026bce5dc35
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyImmutableDate.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Dummy Immutable Date.
+ */
+#[ApiResource(filters: ['my_dummy_immutable_date.date'])]
+#[ORM\Entity]
+class DummyImmutableDate
+{
+ /**
+ * @var int|null The id
+ */
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+ /**
+ * @var \DateTimeImmutable The dummy date
+ */
+ #[ORM\Column(type: 'date_immutable')]
+ public $dummyDate;
+
+ /**
+ * Get id.
+ */
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyPassenger.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyPassenger.php
new file mode 100644
index 00000000000..4883ff17342
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyPassenger.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource]
+#[ORM\Entity]
+class DummyPassenger
+{
+ /**
+ * @var int The entity Id
+ */
+ #[ORM\Id]
+ #[ORM\GeneratedValue]
+ #[ORM\Column(type: 'integer')]
+ private ?int $id = null;
+ #[ORM\Column(type: 'string')]
+ public $nickname;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyPropertyWithDefaultValue.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyPropertyWithDefaultValue.php
new file mode 100644
index 00000000000..7bec9f939af
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyPropertyWithDefaultValue.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * DummyPropertyWithDefaultValue.
+ */
+#[ApiResource(normalizationContext: ['groups' => ['dummy_read']], denormalizationContext: ['groups' => ['dummy_write']])]
+#[ORM\Entity]
+class DummyPropertyWithDefaultValue
+{
+ #[ORM\Id]
+ #[ORM\Column(type: 'integer')]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ #[Groups('dummy_read')]
+ private ?int $id = null;
+ /**
+ * @var string|null
+ */
+ #[ORM\Column(nullable: true)]
+ #[Groups(['dummy_read', 'dummy_write'])]
+ public $foo = 'foo';
+ /**
+ * @var string A dummy with a Doctrine default options
+ */
+ #[ORM\Column(options: ['default' => 'default value'])]
+ public $dummyDefaultOption;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyTravel.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyTravel.php
new file mode 100644
index 00000000000..cab1433c8c6
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/DummyTravel.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(filters: ['dummy_travel.property'])]
+#[ORM\Entity]
+class DummyTravel
+{
+ #[ORM\Id]
+ #[ORM\GeneratedValue]
+ #[ORM\Column(type: 'integer')]
+ private $id; // @phpstan-ignore-line
+ #[ORM\ManyToOne(targetEntity: DummyCar::class)]
+ #[ORM\JoinColumn(name: 'car_id', referencedColumnName: 'id_id')]
+ public $car;
+ #[ORM\Column(type: 'boolean')]
+ public $confirmed;
+ #[ORM\ManyToOne(targetEntity: DummyPassenger::class)]
+ #[ORM\JoinColumn(name: 'passenger_id', referencedColumnName: 'id')]
+ public $passenger;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/EmbeddableDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/EmbeddableDummy.php
new file mode 100644
index 00000000000..ed37405aab6
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/EmbeddableDummy.php
@@ -0,0 +1,128 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Embeddable Dummy.
+ *
+ * @author Jordan Samouh
+ */
+#[ORM\Embeddable]
+class EmbeddableDummy
+{
+ /**
+ * @var string The dummy name
+ */
+ #[ApiProperty(identifier: true)]
+ #[ORM\Column(nullable: true)]
+ #[Groups(['embed'])]
+ private ?string $dummyName = null;
+ /**
+ * @var bool|null A dummy boolean
+ */
+ #[ORM\Column(type: 'boolean', nullable: true)]
+ public ?bool $dummyBoolean = null;
+ /**
+ * @var \DateTime|null A dummy date
+ */
+ #[ORM\Column(type: 'datetime', nullable: true)]
+ #[Assert\DateTime]
+ public ?\DateTime $dummyDate = null;
+ /**
+ * @var float|null A dummy float
+ */
+ #[ORM\Column(type: 'float', nullable: true)]
+ public ?float $dummyFloat = null;
+ /**
+ * @var string|null A dummy price
+ */
+ #[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: true)]
+ public ?string $dummyPrice = null;
+ #[ORM\Column(type: 'string', nullable: true)]
+ #[Groups(['barcelona', 'chicago'])]
+ protected $symfony;
+
+ public static function staticMethod(): void
+ {
+ }
+
+ public function __construct()
+ {
+ }
+
+ public function getDummyName(): ?string
+ {
+ return $this->dummyName;
+ }
+
+ public function setDummyName(string $dummyName): void
+ {
+ $this->dummyName = $dummyName;
+ }
+
+ public function isDummyBoolean(): ?bool
+ {
+ return $this->dummyBoolean;
+ }
+
+ public function setDummyBoolean(bool $dummyBoolean): void
+ {
+ $this->dummyBoolean = $dummyBoolean;
+ }
+
+ public function getDummyDate(): ?\DateTime
+ {
+ return $this->dummyDate;
+ }
+
+ public function setDummyDate(\DateTime $dummyDate): void
+ {
+ $this->dummyDate = $dummyDate;
+ }
+
+ public function getDummyFloat(): ?float
+ {
+ return $this->dummyFloat;
+ }
+
+ public function setDummyFloat(float $dummyFloat): void
+ {
+ $this->dummyFloat = $dummyFloat;
+ }
+
+ public function getDummyPrice(): ?string
+ {
+ return $this->dummyPrice;
+ }
+
+ public function setDummyPrice(string $dummyPrice): void
+ {
+ $this->dummyPrice = $dummyPrice;
+ }
+
+ public function getSymfony()
+ {
+ return $this->symfony;
+ }
+
+ public function setSymfony($symfony): void
+ {
+ $this->symfony = $symfony;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/EmbeddedDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/EmbeddedDummy.php
new file mode 100644
index 00000000000..2341b0ae240
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/EmbeddedDummy.php
@@ -0,0 +1,116 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Delete;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Embedded Dummy.
+ *
+ * @author Jordan Samouh
+ */
+#[ApiResource(operations: [new Get(), new Put(), new Delete(), new Get(uriTemplate: '/embedded_dummies_groups/{id}', normalizationContext: ['groups' => ['embed']]), new Post(), new GetCollection()], filters: ['my_dummy.search', 'my_dummy.order', 'my_dummy.date', 'my_dummy.range', 'my_dummy.boolean', 'my_dummy.numeric'])]
+#[ORM\Entity]
+class EmbeddedDummy
+{
+ /**
+ * @var int The id
+ */
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+ /**
+ * @var string|null The dummy name
+ */
+ #[ORM\Column(nullable: true)]
+ #[Groups(['embed'])]
+ private ?string $name = null;
+ /**
+ * @var \DateTime|null A dummy date
+ */
+ #[ORM\Column(type: 'datetime', nullable: true)]
+ #[Assert\DateTime]
+ public ?\DateTime $dummyDate = null;
+ #[ORM\Embedded(class: EmbeddableDummy::class)]
+ #[Groups(['embed'])]
+ public ?EmbeddableDummy $embeddedDummy = null;
+ /**
+ * @var RelatedDummy|null A related dummy
+ */
+ #[ORM\ManyToOne(targetEntity: RelatedDummy::class)]
+ public ?RelatedDummy $relatedDummy = null;
+
+ public static function staticMethod(): void
+ {
+ }
+
+ public function __construct()
+ {
+ $this->embeddedDummy = new EmbeddableDummy();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getEmbeddedDummy(): EmbeddableDummy
+ {
+ return $this->embeddedDummy;
+ }
+
+ public function setEmbeddedDummy(EmbeddableDummy $embeddedDummy): void
+ {
+ $this->embeddedDummy = $embeddedDummy;
+ }
+
+ public function getDummyDate(): ?\DateTime
+ {
+ return $this->dummyDate;
+ }
+
+ public function setDummyDate(\DateTime $dummyDate): void
+ {
+ $this->dummyDate = $dummyDate;
+ }
+
+ public function getRelatedDummy(): ?RelatedDummy
+ {
+ return $this->relatedDummy;
+ }
+
+ public function setRelatedDummy(RelatedDummy $relatedDummy): void
+ {
+ $this->relatedDummy = $relatedDummy;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/Employee.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/Employee.php
new file mode 100644
index 00000000000..d2a7d47152a
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/Employee.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Post;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+#[ApiResource]
+#[Post]
+#[ApiResource(
+ uriTemplate: '/companies/{companyId}/employees/{id}',
+ uriVariables: [
+ 'companyId' => ['from_class' => Company::class, 'to_property' => 'company'],
+ 'id' => ['from_class' => Employee::class],
+ ]
+)]
+#[Get]
+#[ApiResource(
+ uriTemplate: '/companies/{companyId}/employees',
+ uriVariables: [
+ 'companyId' => ['from_class' => Company::class, 'to_property' => 'company'],
+ ],
+ normalizationContext: ['groups' => ['company_employees_read']]
+)]
+#[GetCollection]
+#[ORM\Entity]
+class Employee
+{
+ /**
+ * @var int|null The id
+ */
+ #[ORM\Column(type: 'integer', nullable: true)]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ public $id;
+
+ /**
+ * @var string The dummy name
+ */
+ #[ORM\Column]
+ #[Groups(['company_employees_read'])]
+ public string $name;
+
+ #[ORM\ManyToOne(targetEntity: Company::class)]
+ #[Groups(['company_employees_read'])]
+ public ?Company $company = null;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function getCompany(): Company
+ {
+ return $this->company;
+ }
+
+ public function setCompany(Company $company): void
+ {
+ $this->company = $company;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/FourthLevel.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/FourthLevel.php
new file mode 100644
index 00000000000..0e193cdc354
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/FourthLevel.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Link;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Fourth Level.
+ *
+ * @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()])]
+#[ORM\Entity]
+class FourthLevel
+{
+ /**
+ * @var int|null The id
+ */
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue]
+ private ?int $id = null;
+ #[ORM\Column(type: 'integer')]
+ #[Groups(['barcelona', 'chicago'])]
+ private int $level = 4;
+ #[ORM\OneToMany(targetEntity: ThirdLevel::class, cascade: ['persist'], mappedBy: 'badFourthLevel')]
+ public Collection|iterable|null $badThirdLevel = null;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getLevel(): ?int
+ {
+ return $this->level;
+ }
+
+ public function setLevel(int $level): void
+ {
+ $this->level = $level;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/OperationResource.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/OperationResource.php
new file mode 100644
index 00000000000..500fa110169
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/OperationResource.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\State\OperationResourceProcessor;
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Delete;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Patch;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ORM\Entity]
+#[ApiResource(normalizationContext: ['skip_null_values' => true], processor: OperationResourceProcessor::class)]
+#[Get]
+#[Patch(inputFormats: ['json' => ['application/merge-patch+json']])]
+#[Post]
+#[Put(extraProperties: ['standard_put' => false])]
+#[Delete]
+class OperationResource
+{
+ public function __construct(#[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] #[ApiProperty(identifier: true)] private int $identifier, public $name)
+ {
+ }
+
+ public function getIdentifier(): int
+ {
+ return $this->identifier;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/ParentDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/ParentDummy.php
new file mode 100644
index 00000000000..c8f152150ce
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/ParentDummy.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Parent Dummy.
+ *
+ * @author Kévin Dunglas
+ */
+#[ORM\MappedSuperclass]
+class ParentDummy
+{
+ /**
+ * @var int|null The age
+ */
+ #[ORM\Column(type: 'integer', nullable: true)]
+ #[Groups(['friends'])]
+ private $age;
+
+ public function getAge()
+ {
+ return $this->age;
+ }
+
+ public function setAge($age)
+ {
+ return $this->age = $age;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyCollectionIriOnly.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyCollectionIriOnly.php
new file mode 100644
index 00000000000..7aaaaa60298
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyCollectionIriOnly.php
@@ -0,0 +1,121 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Post;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Assert that a property being a collection set with ApiProperty::UriTemplate to true returns only the IRI of the collection.
+ */
+#[
+ Post,
+ Get(normalizationContext: ['groups' => ['read']]),
+ GetCollection(normalizationContext: ['groups' => ['read']]),
+]
+#[ORM\Entity]
+class PropertyCollectionIriOnly
+{
+ #[ORM\Id]
+ #[ORM\Column(type: 'integer')]
+ #[ORM\GeneratedValue]
+ private ?int $id = null;
+
+ #[ORM\OneToMany(mappedBy: 'propertyCollectionIriOnly', targetEntity: PropertyCollectionIriOnlyRelation::class)]
+ #[ApiProperty(uriTemplate: '/property-collection-relations')]
+ #[Groups('read')]
+ private Collection $propertyCollectionIriOnlyRelation;
+
+ /**
+ * @var array $iterableIri
+ */
+ #[ApiProperty(uriTemplate: '/parent/{parentId}/another-collection-operations')]
+ #[Groups('read')]
+ private array $iterableIri = [];
+
+ #[ApiProperty(uriTemplate: '/parent/{parentId}/property-uri-template/one-to-ones/{id}')]
+ #[ORM\OneToOne(mappedBy: 'propertyToOneIriOnly')]
+ #[Groups('read')]
+ private ?PropertyUriTemplateOneToOneRelation $toOneRelation = null;
+
+ public function __construct()
+ {
+ $this->propertyCollectionIriOnlyRelation = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return Collection
+ */
+ public function getPropertyCollectionIriOnlyRelation(): Collection
+ {
+ return $this->propertyCollectionIriOnlyRelation;
+ }
+
+ public function addPropertyCollectionIriOnlyRelation(PropertyCollectionIriOnlyRelation $propertyCollectionIriOnlyRelation): self
+ {
+ if (!$this->propertyCollectionIriOnlyRelation->contains($propertyCollectionIriOnlyRelation)) {
+ $this->propertyCollectionIriOnlyRelation->add($propertyCollectionIriOnlyRelation);
+ $propertyCollectionIriOnlyRelation->setPropertyCollectionIriOnly($this);
+ }
+
+ return $this;
+ }
+
+ public function removePropertyCollectionIriOnlyRelation(PropertyCollectionIriOnlyRelation $propertyCollectionIriOnlyRelation): self
+ {
+ if ($this->propertyCollectionIriOnlyRelation->removeElement($propertyCollectionIriOnlyRelation)) {
+ // set the owning side to null (unless already changed)
+ if ($propertyCollectionIriOnlyRelation->getPropertyCollectionIriOnly() === $this) {
+ $propertyCollectionIriOnlyRelation->setPropertyCollectionIriOnly(null);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getIterableIri(): array
+ {
+ $propertyCollectionIriOnlyRelation = new PropertyCollectionIriOnlyRelation();
+ $propertyCollectionIriOnlyRelation->name = 'Michel';
+
+ $this->iterableIri = [$propertyCollectionIriOnlyRelation];
+
+ return $this->iterableIri;
+ }
+
+ public function setToOneRelation(PropertyUriTemplateOneToOneRelation $toOneRelation): void
+ {
+ $toOneRelation->setPropertyToOneIriOnly($this);
+ $this->toOneRelation = $toOneRelation;
+ }
+
+ public function getToOneRelation(): ?PropertyUriTemplateOneToOneRelation
+ {
+ return $this->toOneRelation;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyCollectionIriOnlyRelation.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyCollectionIriOnlyRelation.php
new file mode 100644
index 00000000000..1bdd72ccbb5
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyCollectionIriOnlyRelation.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
+use ApiPlatform\Metadata\Post;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints\NotBlank;
+
+#[
+ Post,
+ GetCollection(uriTemplate: '/property-collection-relations'),
+ GetCollection(
+ uriTemplate: '/parent/{parentId}/another-collection-operations',
+ uriVariables: [
+ 'parentId' => new Link(toProperty: 'propertyCollectionIriOnly', fromClass: PropertyCollectionIriOnly::class),
+ ]
+ )
+]
+#[ORM\Entity]
+class PropertyCollectionIriOnlyRelation
+{
+ /**
+ * The entity ID.
+ */
+ #[ORM\Id]
+ #[ORM\Column(type: 'integer')]
+ #[ORM\GeneratedValue]
+ private ?int $id = null;
+
+ #[ORM\Column]
+ #[NotBlank]
+ #[Groups('read')]
+ public string $name = '';
+
+ #[ORM\ManyToOne(inversedBy: 'propertyCollectionIriOnlyRelation')]
+ private ?PropertyCollectionIriOnly $propertyCollectionIriOnly = null;
+
+ public function getId(): ?int
+ {
+ return $this->id ?? 9999;
+ }
+
+ public function getPropertyCollectionIriOnly(): ?PropertyCollectionIriOnly
+ {
+ return $this->propertyCollectionIriOnly;
+ }
+
+ public function setPropertyCollectionIriOnly(?PropertyCollectionIriOnly $propertyCollectionIriOnly): void
+ {
+ $this->propertyCollectionIriOnly = $propertyCollectionIriOnly;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyUriTemplateOneToOneRelation.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyUriTemplateOneToOneRelation.php
new file mode 100644
index 00000000000..7307e54984c
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/PropertyUriTemplateOneToOneRelation.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
+use ApiPlatform\Metadata\Post;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints\NotBlank;
+
+#[
+ Post,
+ GetCollection(uriTemplate: '/property-uri-template/one-to-ones'),
+ Get(
+ uriTemplate: '/parent/{parentId}/property-uri-template/one-to-ones/{id}',
+ uriVariables: [
+ 'parentId' => new Link(toProperty: 'propertyToOneIriOnly', fromClass: PropertyCollectionIriOnly::class),
+ 'id' => new Link(fromClass: PropertyUriTemplateOneToOneRelation::class),
+ ]
+ )
+]
+#[ORM\Entity]
+class PropertyUriTemplateOneToOneRelation
+{
+ #[ORM\Id]
+ #[ORM\Column(type: 'integer')]
+ #[ORM\GeneratedValue]
+ private ?int $id = null;
+
+ #[ORM\Column]
+ #[NotBlank]
+ #[Groups('read')]
+ public string $name = '';
+
+ #[ORM\OneToOne(inversedBy: 'toOneRelation')]
+ private ?PropertyCollectionIriOnly $propertyToOneIriOnly = null;
+
+ public function getId(): ?int
+ {
+ return $this->id ?? 42;
+ }
+
+ public function getPropertyToOneIriOnly(): ?PropertyCollectionIriOnly
+ {
+ return $this->propertyToOneIriOnly;
+ }
+
+ public function setPropertyToOneIriOnly(?PropertyCollectionIriOnly $propertyToOneIriOnly): void
+ {
+ $this->propertyToOneIriOnly = $propertyToOneIriOnly;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedDummy.php
new file mode 100644
index 00000000000..9c1580725f2
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedDummy.php
@@ -0,0 +1,209 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
+use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter;
+use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
+use ApiPlatform\Metadata\ApiFilter;
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\Link;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Related Dummy.
+ *
+ * @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()])]
+#[ApiFilter(filterClass: SearchFilter::class, properties: ['id'])]
+#[ORM\Entity]
+class RelatedDummy extends ParentDummy implements \Stringable
+{
+ #[ApiProperty(writable: false)]
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ #[Groups(['chicago', 'friends'])]
+ private $id;
+
+ /**
+ * @var string|null A name
+ */
+ #[ApiProperty(iris: ['RelatedDummy.name'])]
+ #[ORM\Column(nullable: true)]
+ #[Groups(['friends'])]
+ public $name;
+
+ #[ApiProperty(deprecationReason: 'This property is deprecated for upgrade test')]
+ #[ORM\Column]
+ #[Groups(['barcelona', 'chicago', 'friends'])]
+ #[ApiFilter(filterClass: SearchFilter::class)]
+ #[ApiFilter(filterClass: ExistsFilter::class)]
+ protected $symfony = 'symfony';
+
+ /**
+ * @var \DateTime|null A dummy date
+ */
+ #[ORM\Column(type: 'datetime', nullable: true)]
+ #[Assert\DateTime]
+ #[Groups(['friends'])]
+ #[ApiFilter(filterClass: DateFilter::class)]
+ public $dummyDate;
+
+ #[ORM\ManyToOne(targetEntity: ThirdLevel::class, cascade: ['persist'], inversedBy: 'relatedDummies')]
+ #[Groups(['barcelona', 'chicago', 'friends'])]
+ public ?ThirdLevel $thirdLevel = null;
+
+ #[ORM\OneToMany(targetEntity: RelatedToDummyFriend::class, cascade: ['persist'], mappedBy: 'relatedDummy')]
+ #[Groups(['fakemanytomany', 'friends'])]
+ public Collection|iterable $relatedToDummyFriend;
+
+ /**
+ * @var bool|null A dummy bool
+ */
+ #[ORM\Column(type: 'boolean', nullable: true)]
+ #[Groups(['friends'])]
+ public ?bool $dummyBoolean = null;
+
+ #[ORM\Embedded(class: 'EmbeddableDummy')]
+ #[Groups(['friends'])]
+ public ?EmbeddableDummy $embeddedDummy = null;
+
+ public function __construct()
+ {
+ $this->relatedToDummyFriend = new ArrayCollection();
+ $this->embeddedDummy = new EmbeddableDummy();
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getSymfony()
+ {
+ return $this->symfony;
+ }
+
+ public function setSymfony($symfony): void
+ {
+ $this->symfony = $symfony;
+ }
+
+ public function setDummyDate(\DateTime $dummyDate): void
+ {
+ $this->dummyDate = $dummyDate;
+ }
+
+ public function getDummyDate()
+ {
+ return $this->dummyDate;
+ }
+
+ public function isDummyBoolean(): ?bool
+ {
+ return $this->dummyBoolean;
+ }
+
+ /**
+ * @param bool $dummyBoolean
+ */
+ public function setDummyBoolean($dummyBoolean): void
+ {
+ $this->dummyBoolean = $dummyBoolean;
+ }
+
+ public function getThirdLevel(): ?ThirdLevel
+ {
+ return $this->thirdLevel;
+ }
+
+ public function setThirdLevel(ThirdLevel $thirdLevel = null): void
+ {
+ $this->thirdLevel = $thirdLevel;
+ }
+
+ /**
+ * Get relatedToDummyFriend.
+ */
+ public function getRelatedToDummyFriend(): Collection|iterable
+ {
+ return $this->relatedToDummyFriend;
+ }
+
+ /**
+ * Set relatedToDummyFriend.
+ *
+ * @param RelatedToDummyFriend $relatedToDummyFriend the value to set
+ */
+ public function addRelatedToDummyFriend(RelatedToDummyFriend $relatedToDummyFriend): void
+ {
+ $this->relatedToDummyFriend->add($relatedToDummyFriend);
+ }
+
+ public function getEmbeddedDummy(): EmbeddableDummy
+ {
+ return $this->embeddedDummy;
+ }
+
+ public function setEmbeddedDummy(EmbeddableDummy $embeddedDummy): void
+ {
+ $this->embeddedDummy = $embeddedDummy;
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->getId();
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedOwnedDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedOwnedDummy.php
new file mode 100644
index 00000000000..8ed76b54bfc
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedOwnedDummy.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Related Owned Dummy.
+ *
+ * @author Sergey V. Ryabov
+ */
+#[ApiResource(types: ['https://schema.org/Product'])]
+#[ORM\Entity]
+class RelatedOwnedDummy
+{
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+ /**
+ * @var string|null A name
+ */
+ #[ORM\Column(nullable: true)]
+ public ?string $name = null;
+ #[ORM\OneToOne(targetEntity: Dummy::class, cascade: ['persist'], inversedBy: 'relatedOwnedDummy')]
+ #[ORM\JoinColumn(nullable: false)]
+ public ?Dummy $owningDummy = null;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function setId(int $id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName(?string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get owning dummy.
+ */
+ public function getOwningDummy(): ?Dummy
+ {
+ return $this->owningDummy;
+ }
+
+ /**
+ * Set owning dummy.
+ *
+ * @param Dummy $owningDummy the value to set
+ */
+ public function setOwningDummy(Dummy $owningDummy): void
+ {
+ $this->owningDummy = $owningDummy;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedOwningDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedOwningDummy.php
new file mode 100644
index 00000000000..72538e1d41d
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedOwningDummy.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Related Owning Dummy.
+ *
+ * @author Sergey V. Ryabov
+ */
+#[ApiResource(types: ['https://schema.org/Product'])]
+#[ORM\Entity]
+class RelatedOwningDummy
+{
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private $id;
+ /**
+ * @var string|null A name
+ */
+ #[ORM\Column(nullable: true)]
+ public $name;
+ #[ORM\OneToOne(targetEntity: Dummy::class, cascade: ['persist'], mappedBy: 'relatedOwningDummy')]
+ public ?Dummy $ownedDummy = null;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id): void
+ {
+ $this->id = $id;
+ }
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get owned dummy.
+ */
+ public function getOwnedDummy(): Dummy
+ {
+ return $this->ownedDummy;
+ }
+
+ /**
+ * Set owned dummy.
+ *
+ * @param Dummy $ownedDummy the value to set
+ */
+ public function setOwnedDummy(Dummy $ownedDummy): void
+ {
+ $this->ownedDummy = $ownedDummy;
+ if ($this !== $this->ownedDummy->getRelatedOwningDummy()) {
+ $this->ownedDummy->setRelatedOwningDummy($this);
+ }
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedToDummyFriend.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedToDummyFriend.php
new file mode 100644
index 00000000000..3e95aa912b4
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/RelatedToDummyFriend.php
@@ -0,0 +1,120 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Related To Dummy Friend represent an association table for a manytomany relation.
+ */
+#[ApiResource(normalizationContext: ['groups' => ['fakemanytomany']], filters: ['related_to_dummy_friend.name'], extraProperties: ['rfc_7807_compliant_errors' => false])]
+#[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
+{
+ /**
+ * @var string The dummy name
+ */
+ #[ApiProperty(types: ['https://schema.org/name'])]
+ #[ORM\Column]
+ #[Assert\NotBlank]
+ #[Groups(['fakemanytomany', 'friends'])]
+ private $name;
+ /**
+ * @var string|null The dummy description
+ */
+ #[ORM\Column(nullable: true)]
+ #[Groups(['fakemanytomany', 'friends'])]
+ private ?string $description = null;
+ #[ORM\Id]
+ #[ORM\ManyToOne(targetEntity: DummyFriend::class)]
+ #[ORM\JoinColumn(name: 'dummyfriend_id', referencedColumnName: 'id', nullable: false)]
+ #[Groups(['fakemanytomany', 'friends'])]
+ #[Assert\NotNull]
+ private DummyFriend $dummyFriend;
+ #[ORM\Id]
+ #[ORM\ManyToOne(targetEntity: RelatedDummy::class, inversedBy: 'relatedToDummyFriend')]
+ #[ORM\JoinColumn(name: 'relateddummy_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
+ #[Assert\NotNull]
+ private RelatedDummy $relatedDummy;
+
+ public function setName($name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getDescription(): ?string
+ {
+ return $this->description;
+ }
+
+ /**
+ * @param string|null $description
+ */
+ public function setDescription($description): void
+ {
+ $this->description = $description;
+ }
+
+ /**
+ * Gets dummyFriend.
+ */
+ public function getDummyFriend(): DummyFriend
+ {
+ return $this->dummyFriend;
+ }
+
+ /**
+ * Sets dummyFriend.
+ *
+ * @param DummyFriend $dummyFriend the value to set
+ */
+ public function setDummyFriend(DummyFriend $dummyFriend): void
+ {
+ $this->dummyFriend = $dummyFriend;
+ }
+
+ /**
+ * Gets relatedDummy.
+ */
+ public function getRelatedDummy(): RelatedDummy
+ {
+ return $this->relatedDummy;
+ }
+
+ /**
+ * Sets relatedDummy.
+ *
+ * @param RelatedDummy $relatedDummy the value to set
+ */
+ public function setRelatedDummy(RelatedDummy $relatedDummy): void
+ {
+ $this->relatedDummy = $relatedDummy;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/ThirdLevel.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/ThirdLevel.php
new file mode 100644
index 00000000000..3203c97e5aa
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/ThirdLevel.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Link;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+/**
+ * Third Level.
+ *
+ * @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()])]
+#[ORM\Entity]
+class ThirdLevel
+{
+ /**
+ * @var int|null The id
+ */
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+ #[ORM\Column(type: 'integer')]
+ #[Groups(['barcelona', 'chicago'])]
+ private int $level = 3;
+ #[ORM\Column(type: 'boolean')]
+ private bool $test = true;
+ #[ORM\ManyToOne(targetEntity: FourthLevel::class, cascade: ['persist'])]
+ #[Groups(['barcelona', 'chicago', 'friends'])]
+ public ?FourthLevel $fourthLevel = null;
+ #[ORM\ManyToOne(targetEntity: FourthLevel::class, cascade: ['persist'])]
+ public $badFourthLevel;
+
+ #[ORM\OneToMany(mappedBy: 'thirdLevel', targetEntity: RelatedDummy::class)]
+ public Collection|iterable $relatedDummies;
+
+ public function __construct()
+ {
+ $this->relatedDummies = new ArrayCollection();
+ }
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+
+ public function getLevel(): ?int
+ {
+ return $this->level;
+ }
+
+ /**
+ * @param int $level
+ */
+ public function setLevel($level): void
+ {
+ $this->level = $level;
+ }
+
+ public function isTest(): bool
+ {
+ return $this->test;
+ }
+
+ /**
+ * @param bool $test
+ */
+ public function setTest($test): void
+ {
+ $this->test = $test;
+ }
+
+ public function getFourthLevel(): ?FourthLevel
+ {
+ return $this->fourthLevel;
+ }
+
+ public function setFourthLevel(FourthLevel $fourthLevel = null): void
+ {
+ $this->fourthLevel = $fourthLevel;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/UnknownDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/UnknownDummy.php
new file mode 100644
index 00000000000..27c72283c96
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/UnknownDummy.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Unknown dummy.
+ */
+#[ORM\Entity]
+class UnknownDummy
+{
+ /**
+ * @var int The id
+ */
+ #[ORM\Column(type: 'integer')]
+ #[ORM\Id]
+ #[ORM\GeneratedValue(strategy: 'AUTO')]
+ private ?int $id = null;
+
+ public function getId(): ?int
+ {
+ return $this->id;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Entity/UuidIdentifierDummy.php b/src/Doctrine/Orm/Tests/Fixtures/Entity/UuidIdentifierDummy.php
new file mode 100644
index 00000000000..12535642a5b
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Entity/UuidIdentifierDummy.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Custom identifier dummy.
+ */
+#[ApiResource]
+#[ORM\Entity]
+class UuidIdentifierDummy
+{
+ #[ORM\Column(type: 'guid')]
+ #[ORM\Id]
+ private ?string $uuid = null;
+ #[ORM\Column(length: 30)]
+ private ?string $name = null;
+
+ public function getUuid(): ?string
+ {
+ return $this->uuid;
+ }
+
+ public function setUuid(string $uuid): void
+ {
+ $this->uuid = $uuid;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Model/Car.php b/src/Doctrine/Orm/Tests/Fixtures/Model/Car.php
new file mode 100644
index 00000000000..f4371ea28d4
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Model/Car.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\Model;
+
+/**
+ * Class with multiple resources, each with a GetCollection, a Get and a Post operations.
+ * Using itemUriTemplate on GetCollection and Post operations should specify which operation to use to generate the IRI.
+ *
+ * @author Vincent Chalamon
+ */
+class Car
+{
+ public $id;
+ public $owner;
+
+ public function __construct($id = null, $owner = null)
+ {
+ $this->id = $id;
+ $this->owner = $owner;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/Query.php b/src/Doctrine/Orm/Tests/Fixtures/Query.php
new file mode 100644
index 00000000000..b0003a82265
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/Query.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures;
+
+/**
+ * Replace Doctrine\ORM\Query in tests because it cannot be mocked.
+ */
+class Query
+{
+ public function getFirstResult(): ?int
+ {
+ return null;
+ }
+
+ public function getMaxResults(): ?int
+ {
+ return null;
+ }
+}
diff --git a/src/Doctrine/Orm/Tests/Fixtures/State/OperationResourceProcessor.php b/src/Doctrine/Orm/Tests/Fixtures/State/OperationResourceProcessor.php
new file mode 100644
index 00000000000..7c3fc16bbe0
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/Fixtures/State/OperationResourceProcessor.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures\State;
+
+use ApiPlatform\Metadata\DeleteOperationInterface;
+use ApiPlatform\Metadata\Operation;
+use ApiPlatform\Metadata\Util\ClassInfoTrait;
+use ApiPlatform\State\ProcessorInterface;
+use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+use Doctrine\Persistence\ManagerRegistry;
+use Doctrine\Persistence\ObjectManager as DoctrineObjectManager;
+
+final class OperationResourceProcessor implements ProcessorInterface
+{
+ use ClassInfoTrait;
+
+ public function __construct(private readonly ManagerRegistry $managerRegistry)
+ {
+ }
+
+ private function persist($data, array $context = [])
+ {
+ if (!$manager = $this->getManager($data)) {
+ return $data;
+ }
+
+ if (!$manager->contains($data) || $this->isDeferredExplicit($manager, $data)) {
+ $manager->persist($data);
+ }
+
+ $manager->flush();
+ $manager->refresh($data);
+
+ return $data;
+ }
+
+ private function remove($data): void
+ {
+ if (!$manager = $this->getManager($data)) {
+ return;
+ }
+
+ $manager->remove($data);
+ $manager->flush();
+ }
+
+ public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
+ {
+ if ($operation instanceof DeleteOperationInterface) {
+ $this->remove($data);
+
+ return $data;
+ }
+
+ return $this->persist($data);
+ }
+
+ /**
+ * Gets the Doctrine object manager associated with given data.
+ */
+ private function getManager($data): ?DoctrineObjectManager
+ {
+ return \is_object($data) ? $this->managerRegistry->getManagerForClass($this->getObjectClass($data)) : null;
+ }
+
+ /**
+ * Checks if doctrine does not manage data automatically.
+ */
+ private function isDeferredExplicit(DoctrineObjectManager $manager, $data): bool
+ {
+ $classMetadata = $manager->getClassMetadata($this->getObjectClass($data));
+ if (($classMetadata instanceof ClassMetadataInfo || $classMetadata instanceof ClassMetadata) && method_exists($classMetadata, 'isChangeTrackingDeferredExplicit')) {
+ return $classMetadata->isChangeTrackingDeferredExplicit();
+ }
+
+ return false;
+ }
+}
diff --git a/tests/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php b/src/Doctrine/Orm/Tests/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php
similarity index 97%
rename from tests/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php
rename to src/Doctrine/Orm/Tests/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php
index 33aa2abc66e..d4c7b1fff23 100644
--- a/tests/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php
+++ b/src/Doctrine/Orm/Tests/Metadata/Property/DoctrineOrmPropertyMetadataFactoryTest.php
@@ -11,13 +11,13 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Metadata\Property;
+namespace ApiPlatform\Doctrine\Orm\Tests\Metadata\Property;
use ApiPlatform\Doctrine\Orm\Metadata\Property\DoctrineOrmPropertyMetadataFactory;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\DummyPropertyWithDefaultValue;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPropertyWithDefaultValue;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\Mapping\ClassMetadata;
diff --git a/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmLinkFactoryTest.php b/src/Doctrine/Orm/Tests/Metadata/Resource/DoctrineOrmLinkFactoryTest.php
similarity index 94%
rename from tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmLinkFactoryTest.php
rename to src/Doctrine/Orm/Tests/Metadata/Resource/DoctrineOrmLinkFactoryTest.php
index e7968e40653..921bbd181a6 100644
--- a/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmLinkFactoryTest.php
+++ b/src/Doctrine/Orm/Tests/Metadata/Resource/DoctrineOrmLinkFactoryTest.php
@@ -11,10 +11,13 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Metadata\Resource;
+namespace ApiPlatform\Doctrine\Orm\Tests\Metadata\Resource;
use ApiPlatform\Api\ResourceClassResolverInterface;
use ApiPlatform\Doctrine\Orm\Metadata\Resource\DoctrineOrmLinkFactory;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\RelatedDummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Model\Car;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Metadata;
@@ -22,9 +25,6 @@
use ApiPlatform\Metadata\Property\PropertyNameCollection;
use ApiPlatform\Metadata\Resource\Factory\LinkFactoryInterface;
use ApiPlatform\Metadata\Resource\Factory\PropertyLinkFactoryInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Model\Car;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\Mapping\ClassMetadata;
diff --git a/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php b/src/Doctrine/Orm/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php
similarity index 97%
rename from tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php
rename to src/Doctrine/Orm/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php
index 4188aec389a..34433a553b1 100644
--- a/tests/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php
+++ b/src/Doctrine/Orm/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php
@@ -11,11 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Metadata\Resource;
+namespace ApiPlatform\Doctrine\Orm\Tests\Metadata\Resource;
use ApiPlatform\Doctrine\Orm\Metadata\Resource\DoctrineOrmResourceCollectionMetadataFactory;
use ApiPlatform\Doctrine\Orm\State\CollectionProvider;
use ApiPlatform\Doctrine\Orm\State\ItemProvider;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
@@ -24,7 +25,6 @@
use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase;
diff --git a/tests/Doctrine/Orm/PaginatorTest.php b/src/Doctrine/Orm/Tests/PaginatorTest.php
similarity index 95%
rename from tests/Doctrine/Orm/PaginatorTest.php
rename to src/Doctrine/Orm/Tests/PaginatorTest.php
index 77eabe85f92..87f73249c57 100644
--- a/tests/Doctrine/Orm/PaginatorTest.php
+++ b/src/Doctrine/Orm/Tests/PaginatorTest.php
@@ -11,11 +11,11 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm;
+namespace ApiPlatform\Doctrine\Orm\Tests;
use ApiPlatform\Doctrine\Orm\Paginator;
-use ApiPlatform\Exception\InvalidArgumentException;
-use ApiPlatform\Tests\Fixtures\Query;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Query;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
diff --git a/tests/Doctrine/Orm/State/CollectionProviderTest.php b/src/Doctrine/Orm/Tests/State/CollectionProviderTest.php
similarity index 97%
rename from tests/Doctrine/Orm/State/CollectionProviderTest.php
rename to src/Doctrine/Orm/Tests/State/CollectionProviderTest.php
index f376b550fcd..178db7302ff 100644
--- a/tests/Doctrine/Orm/State/CollectionProviderTest.php
+++ b/src/Doctrine/Orm/Tests/State/CollectionProviderTest.php
@@ -11,17 +11,17 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\State;
+namespace ApiPlatform\Doctrine\Orm\Tests\State;
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\State\CollectionProvider;
use ApiPlatform\Doctrine\Orm\State\Options;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\OperationResource;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
diff --git a/tests/Doctrine/Orm/State/ItemProviderTest.php b/src/Doctrine/Orm/Tests/State/ItemProviderTest.php
similarity index 98%
rename from tests/Doctrine/Orm/State/ItemProviderTest.php
rename to src/Doctrine/Orm/Tests/State/ItemProviderTest.php
index 725fa11c244..ecf87013983 100644
--- a/tests/Doctrine/Orm/State/ItemProviderTest.php
+++ b/src/Doctrine/Orm/Tests/State/ItemProviderTest.php
@@ -11,21 +11,21 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\State;
+namespace ApiPlatform\Doctrine\Orm\Tests\State;
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\State\ItemProvider;
use ApiPlatform\Doctrine\Orm\State\Options;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Company;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Employee;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\OperationResource;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Company;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Employee;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\OperationResource;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Types;
diff --git a/src/Doctrine/Orm/Tests/TestBundle.php b/src/Doctrine/Orm/Tests/TestBundle.php
new file mode 100644
index 00000000000..03ef0e4fe2b
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/TestBundle.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Doctrine\Orm\Tests;
+
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Test Bundle.
+ *
+ * @author Kévin Dunglas
+ */
+class TestBundle extends Bundle
+{
+}
diff --git a/tests/Doctrine/Orm/Util/QueryBuilderHelperTest.php b/src/Doctrine/Orm/Tests/Util/QueryBuilderHelperTest.php
similarity index 96%
rename from tests/Doctrine/Orm/Util/QueryBuilderHelperTest.php
rename to src/Doctrine/Orm/Tests/Util/QueryBuilderHelperTest.php
index 30845d11e48..e6beea53dbc 100644
--- a/tests/Doctrine/Orm/Util/QueryBuilderHelperTest.php
+++ b/src/Doctrine/Orm/Tests/Util/QueryBuilderHelperTest.php
@@ -11,12 +11,12 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Util;
+namespace ApiPlatform\Doctrine\Orm\Tests\Util;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\RelatedDummy;
use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\QueryBuilder;
diff --git a/tests/Doctrine/Orm/Util/QueryCheckerTest.php b/src/Doctrine/Orm/Tests/Util/QueryCheckerTest.php
similarity index 98%
rename from tests/Doctrine/Orm/Util/QueryCheckerTest.php
rename to src/Doctrine/Orm/Tests/Util/QueryCheckerTest.php
index 2ea23fefe15..37969167be0 100644
--- a/tests/Doctrine/Orm/Util/QueryCheckerTest.php
+++ b/src/Doctrine/Orm/Tests/Util/QueryCheckerTest.php
@@ -11,11 +11,11 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Util;
+namespace ApiPlatform\Doctrine\Orm\Tests\Util;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
+use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\RelatedDummy;
use ApiPlatform\Doctrine\Orm\Util\QueryChecker;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\QueryBuilder;
diff --git a/tests/Doctrine/Orm/Util/QueryNameGeneratorTest.php b/src/Doctrine/Orm/Tests/Util/QueryNameGeneratorTest.php
similarity index 94%
rename from tests/Doctrine/Orm/Util/QueryNameGeneratorTest.php
rename to src/Doctrine/Orm/Tests/Util/QueryNameGeneratorTest.php
index 13c3b9bfb55..8555967030a 100644
--- a/tests/Doctrine/Orm/Util/QueryNameGeneratorTest.php
+++ b/src/Doctrine/Orm/Tests/Util/QueryNameGeneratorTest.php
@@ -11,7 +11,7 @@
declare(strict_types=1);
-namespace ApiPlatform\Tests\Doctrine\Orm\Util;
+namespace ApiPlatform\Doctrine\Orm\Tests\Util;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
use PHPUnit\Framework\TestCase;
diff --git a/src/Doctrine/Orm/Tests/bootstrap.php b/src/Doctrine/Orm/Tests/bootstrap.php
new file mode 100644
index 00000000000..567c2c9815b
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/bootstrap.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+$loader = require __DIR__.'/../vendor/autoload.php';
+require __DIR__.'/AppKernel.php';
+
+return $loader;
diff --git a/src/Doctrine/Orm/Tests/config.yml b/src/Doctrine/Orm/Tests/config.yml
new file mode 100644
index 00000000000..42d86cc5de5
--- /dev/null
+++ b/src/Doctrine/Orm/Tests/config.yml
@@ -0,0 +1,31 @@
+doctrine:
+ dbal:
+ driver: 'pdo_sqlite'
+ charset: 'UTF8'
+ types:
+ uuid: Ramsey\Uuid\Doctrine\UuidType
+ symfony_uuid: Symfony\Bridge\Doctrine\Types\UuidType
+
+ orm:
+ auto_generate_proxy_classes: '%kernel.debug%'
+ mappings:
+ ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity:
+ type: attribute
+ dir: '%kernel.project_dir%/Fixtures/Entity'
+ is_bundle: false
+ prefix: ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity
+ alias: App
+
+api_platform:
+ formats:
+ json: ['application/json']
+ doctrine: true
+ mapping:
+ paths:
+ - '%kernel.project_dir%/Fixtures/Entity'
+
+services:
+ test.property_accessor:
+ alias: property_accessor
+ public: true
+
diff --git a/src/Doctrine/Orm/composer.json b/src/Doctrine/Orm/composer.json
new file mode 100644
index 00000000000..982e56fdd90
--- /dev/null
+++ b/src/Doctrine/Orm/composer.json
@@ -0,0 +1,88 @@
+{
+ "name": "api-platform/doctrine-orm",
+ "description": "Doctrine ORM bridge",
+ "type": "library",
+ "keywords": [
+ "Doctrine",
+ "ORM"
+ ],
+ "homepage": "https://api-platform.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Kévin Dunglas",
+ "email": "kevin@dunglas.fr",
+ "homepage": "https://dunglas.fr"
+ },
+ {
+ "name": "API Platform Community",
+ "homepage": "https://api-platform.com/community/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=8.1",
+ "api-platform/doctrine-common": "*@dev || ^3.1",
+ "api-platform/metadata": "*@dev || ^3.1",
+ "api-platform/state": "*@dev || ^3.1",
+ "doctrine/doctrine-bundle": "^2.11",
+ "doctrine/orm": "^2.17",
+ "symfony/property-info": "^6.4 || ^7.0"
+ },
+ "require-dev": {
+ "api-platform/parameter-validator": "*@dev || ^3.2",
+ "api-platform/symfony": "*@dev || ^3.2",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpunit/phpunit": "^10.0",
+ "ramsey/uuid": "^4.7",
+ "ramsey/uuid-doctrine": "^2.0",
+ "symfony/cache": "^6.4 || ^7.0",
+ "symfony/framework-bundle": "^6.4 || ^7.0",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
+ "symfony/yaml": "^6.4 || ^7.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "ApiPlatform\\Doctrine\\Orm\\": ""
+ }
+ },
+ "config": {
+ "preferred-install": {
+ "*": "dist"
+ },
+ "sort-packages": true,
+ "allow-plugins": {
+ "php-http/discovery": false
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.2.x-dev"
+ },
+ "symfony": {
+ "require": "^6.4"
+ }
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../Metadata"
+ },
+ {
+ "type": "path",
+ "url": "../../State"
+ },
+ {
+ "type": "path",
+ "url": "../Common"
+ },
+ {
+ "type": "path",
+ "url": "../../Symfony"
+ },
+ {
+ "type": "path",
+ "url": "../../ParameterValidator"
+ }
+ ]
+}
diff --git a/src/Doctrine/Orm/phpunit.xml.dist b/src/Doctrine/Orm/phpunit.xml.dist
new file mode 100644
index 00000000000..913b48c606a
--- /dev/null
+++ b/src/Doctrine/Orm/phpunit.xml.dist
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
diff --git a/src/Documentation/.gitattributes b/src/Documentation/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Documentation/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Documentation/.gitignore b/src/Documentation/.gitignore
new file mode 100644
index 00000000000..2db77de0df4
--- /dev/null
+++ b/src/Documentation/.gitignore
@@ -0,0 +1,5 @@
+/composer.lock
+/vendor
+/.phpunit.result.cache
+/.phpunit.cache
+/Tests/var
diff --git a/src/Documentation/LICENSE b/src/Documentation/LICENSE
new file mode 100644
index 00000000000..1ca98eeb824
--- /dev/null
+++ b/src/Documentation/LICENSE
@@ -0,0 +1,21 @@
+The MIT license
+
+Copyright (c) 2015-present Kévin Dunglas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/Elasticsearch/.gitattributes b/src/Elasticsearch/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Elasticsearch/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Elasticsearch/composer.json b/src/Elasticsearch/composer.json
index adab91c9b14..08bc79a9e94 100644
--- a/src/Elasticsearch/composer.json
+++ b/src/Elasticsearch/composer.json
@@ -26,16 +26,16 @@
"api-platform/serializer": "*@dev || ^3.1",
"api-platform/state": "*@dev || ^3.1",
"elasticsearch/elasticsearch": "^8.9",
- "symfony/cache": "^6.1",
- "symfony/console": "^6.2",
- "symfony/property-access": "^6.1",
- "symfony/property-info": "^6.1",
- "symfony/serializer": "^6.1",
- "symfony/uid": "^6.1"
+ "symfony/cache": "^6.4 || ^7.0",
+ "symfony/console": "^6.4 || ^7.0",
+ "symfony/property-access": "^6.4 || ^7.0",
+ "symfony/property-info": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
+ "symfony/uid": "^6.4 || ^7.0"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0",
- "symfony/phpunit-bridge": "^6.1",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
"sebastian/comparator": "<5.0"
},
"autoload": {
@@ -62,7 +62,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
diff --git a/src/GraphQl/.gitattributes b/src/GraphQl/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/GraphQl/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php b/src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php
index c1da97158a7..1df6eba8290 100644
--- a/src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php
+++ b/src/GraphQl/Serializer/Exception/ValidationExceptionNormalizer.php
@@ -13,13 +13,12 @@
namespace ApiPlatform\GraphQl\Serializer\Exception;
-use ApiPlatform\Symfony\Validator\Exception\ValidationException;
use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
+use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
-use Symfony\Component\Validator\ConstraintViolation;
/**
* Normalize validation exceptions.
@@ -38,8 +37,11 @@ public function __construct(private readonly array $exceptionToStatus = [])
*/
public function normalize(mixed $object, string $format = null, array $context = []): array
{
- /** @var ConstraintViolationListAwareExceptionInterface */
$validationException = $object->getPrevious();
+ if (!$validationException instanceof ConstraintViolationListAwareExceptionInterface) {
+ throw new RuntimeException(sprintf('Object is not a "%s".', ConstraintViolationListAwareExceptionInterface::class));
+ }
+
$error = FormattedError::createFromException($object);
$error['message'] = $validationException->getMessage();
@@ -60,7 +62,6 @@ public function normalize(mixed $object, string $format = null, array $context =
}
$error['extensions']['violations'] = [];
- /** @var ConstraintViolation $violation */
foreach ($validationException->getConstraintViolationList() as $violation) {
$error['extensions']['violations'][] = [
'path' => $violation->getPropertyPath(),
@@ -76,10 +77,7 @@ public function normalize(mixed $object, string $format = null, array $context =
*/
public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
{
- return $data instanceof Error && (
- $data->getPrevious() instanceof ConstraintViolationListAwareExceptionInterface
- || $data->getPrevious() instanceof ValidationException
- );
+ return $data instanceof Error && ($data->getPrevious() instanceof ConstraintViolationListAwareExceptionInterface);
}
public function getSupportedTypes($format): array
diff --git a/src/GraphQl/Serializer/ItemNormalizer.php b/src/GraphQl/Serializer/ItemNormalizer.php
index c12be102481..c35ed14f0b2 100644
--- a/src/GraphQl/Serializer/ItemNormalizer.php
+++ b/src/GraphQl/Serializer/ItemNormalizer.php
@@ -25,7 +25,6 @@
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Serializer\CacheKeyTrait;
use ApiPlatform\Serializer\ItemNormalizer as BaseItemNormalizer;
-use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -49,10 +48,7 @@ final class ItemNormalizer extends BaseItemNormalizer
private array $safeCacheKeysCache = [];
- /**
- * @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface $resourceAccessChecker
- */
- public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, private readonly IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, $resourceAccessChecker = null)
+ public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, private readonly IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null)
{
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $logger ?: new NullLogger(), $resourceMetadataCollectionFactory, $resourceAccessChecker);
}
diff --git a/src/GraphQl/Tests/Resolver/Stage/ValidateStageTest.php b/src/GraphQl/Tests/Resolver/Stage/ValidateStageTest.php
index dab437b9b09..41c683f3d8c 100644
--- a/src/GraphQl/Tests/Resolver/Stage/ValidateStageTest.php
+++ b/src/GraphQl/Tests/Resolver/Stage/ValidateStageTest.php
@@ -23,6 +23,7 @@
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
+use Symfony\Component\Validator\ConstraintViolationList;
/**
* @author Alan Poulain
@@ -80,7 +81,7 @@ public function testApplyNotValidated(): void
$context = ['info' => $info];
$object = new \stdClass();
- $this->validatorProphecy->validate($object, ['groups' => $validationGroups])->shouldBeCalled()->willThrow(new ValidationException());
+ $this->validatorProphecy->validate($object, ['groups' => $validationGroups])->shouldBeCalled()->willThrow(new ValidationException(new ConstraintViolationList()));
$this->expectException(ValidationException::class);
diff --git a/src/GraphQl/composer.json b/src/GraphQl/composer.json
index b7cf2961b0f..2cdd36942d8 100644
--- a/src/GraphQl/composer.json
+++ b/src/GraphQl/composer.json
@@ -25,8 +25,8 @@
"api-platform/serializer": "*@dev || ^3.1",
"api-platform/state": "*@dev || ^3.1",
"api-platform/validator": "*@dev || ^3.1",
- "symfony/property-info": "^6.1",
- "symfony/serializer": "^6.1",
+ "symfony/property-info": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
"webonyx/graphql-php": "^14.0 || ^15.0",
"willdurand/negotiation": "^3.1"
},
@@ -35,10 +35,13 @@
"api-platform/validator": "*@dev || ^3.1",
"twig/twig": "^3.7",
"symfony/mercure-bundle": "*",
- "symfony/phpunit-bridge": "^6.1",
- "symfony/routing": "^6.1",
- "symfony/validator": "^6.1",
- "sebastian/comparator": "<5.0"
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/routing": "^6.4 || ^7.0",
+ "symfony/validator": "^6.4 || ^7.0",
+ "sebastian/comparator": "<5.0",
+ "api-platform/doctrine-common": "*@dev || ^3.2",
+ "api-platform/doctrine-odm": "*@dev || ^3.2",
+ "api-platform/doctrine-orm": "*@dev || ^3.2"
},
"autoload": {
"psr-4": {
@@ -64,7 +67,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
@@ -83,6 +86,18 @@
{
"type": "path",
"url": "../Validator"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Orm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Odm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Common"
}
]
}
diff --git a/src/HttpCache/EventListener/AddHeadersListener.php b/src/HttpCache/EventListener/AddHeadersListener.php
index ef12bbba8c7..434473d7c51 100644
--- a/src/HttpCache/EventListener/AddHeadersListener.php
+++ b/src/HttpCache/EventListener/AddHeadersListener.php
@@ -15,7 +15,7 @@
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
-use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
+use ApiPlatform\State\Util\RequestAttributesExtractor;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
/**
diff --git a/src/HttpCache/EventListener/AddTagsListener.php b/src/HttpCache/EventListener/AddTagsListener.php
index 9d600a79d7e..e686982a099 100644
--- a/src/HttpCache/EventListener/AddTagsListener.php
+++ b/src/HttpCache/EventListener/AddTagsListener.php
@@ -20,7 +20,7 @@
use ApiPlatform\Metadata\UrlGeneratorInterface;
use ApiPlatform\State\UriVariablesResolverTrait;
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
-use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
+use ApiPlatform\State\Util\RequestAttributesExtractor;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
/**
@@ -35,7 +35,7 @@
*
* @author Kévin Dunglas
*
- * @deprecated use \Symfony\EventListener\AddTagsListener.php instead
+ * @deprecated use \Symfony\EventListener\AddTagsListener.php instead
*/
final class AddTagsListener
{
diff --git a/src/HttpCache/composer.json b/src/HttpCache/composer.json
index 83ad3911346..2f47dd3d6d2 100644
--- a/src/HttpCache/composer.json
+++ b/src/HttpCache/composer.json
@@ -23,14 +23,14 @@
"php": ">=8.1",
"api-platform/metadata": "*@dev || ^3.1",
"api-platform/state": "*@dev || ^3.1",
- "symfony/http-foundation": "^6.1"
+ "symfony/http-foundation": "^6.4 || ^7.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.0 || ^7.0",
- "symfony/dependency-injection": "^6.1",
+ "symfony/dependency-injection": "^6.4 || ^7.0",
"phpspec/prophecy-phpunit": "^2.0",
- "symfony/phpunit-bridge": "^6.1",
- "symfony/http-client": "^6.1",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/http-client": "^6.4 || ^7.0",
"sebastian/comparator": "<5.0"
},
"autoload": {
@@ -56,7 +56,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
diff --git a/src/Hydra/.gitattributes b/src/Hydra/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Hydra/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Hydra/LICENSE b/src/Hydra/LICENSE
new file mode 100644
index 00000000000..1ca98eeb824
--- /dev/null
+++ b/src/Hydra/LICENSE
@@ -0,0 +1,21 @@
+The MIT license
+
+Copyright (c) 2015-present Kévin Dunglas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/Hydra/Serializer/ErrorNormalizer.php b/src/Hydra/Serializer/ErrorNormalizer.php
index 66257d2ab99..c4325a6f332 100644
--- a/src/Hydra/Serializer/ErrorNormalizer.php
+++ b/src/Hydra/Serializer/ErrorNormalizer.php
@@ -14,7 +14,6 @@
namespace ApiPlatform\Hydra\Serializer;
use ApiPlatform\Api\UrlGeneratorInterface;
-use ApiPlatform\Problem\Serializer\ErrorNormalizerTrait;
use ApiPlatform\Serializer\CacheableSupportsMethodInterface;
use ApiPlatform\State\ApiResource\Error;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
@@ -24,6 +23,8 @@
/**
* Converts {@see \Exception} or {@see FlattenException} to a Hydra error representation.
*
+ * @deprecated Errors are resources since API Platform 3.2 we use the ItemNormalizer
+ *
* @author Kévin Dunglas
* @author Samuel ROZE
*/
diff --git a/src/Hydra/Serializer/ErrorNormalizerTrait.php b/src/Hydra/Serializer/ErrorNormalizerTrait.php
new file mode 100644
index 00000000000..12fec8ff0fc
--- /dev/null
+++ b/src/Hydra/Serializer/ErrorNormalizerTrait.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Hydra\Serializer;
+
+use ApiPlatform\Exception\ErrorCodeSerializableInterface;
+use Symfony\Component\ErrorHandler\Exception\FlattenException;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * @internal
+ */
+trait ErrorNormalizerTrait
+{
+ private function getErrorMessage($object, array $context, bool $debug = false): string
+ {
+ $message = $object->getMessage();
+
+ if ($debug) {
+ return $message;
+ }
+
+ if ($object instanceof FlattenException) {
+ $statusCode = $context['statusCode'] ?? $object->getStatusCode();
+ if ($statusCode >= 500 && $statusCode < 600) {
+ $message = Response::$statusTexts[$statusCode] ?? Response::$statusTexts[Response::HTTP_INTERNAL_SERVER_ERROR];
+ }
+ }
+
+ return $message;
+ }
+
+ private function getErrorCode(object $object): ?string
+ {
+ if ($object instanceof FlattenException) {
+ $exceptionClass = $object->getClass();
+ } else {
+ $exceptionClass = $object::class;
+ }
+
+ if (is_a($exceptionClass, ErrorCodeSerializableInterface::class, true)) {
+ return $exceptionClass::getErrorCode();
+ }
+
+ return null;
+ }
+}
diff --git a/src/Hydra/composer.json b/src/Hydra/composer.json
index 719b20c9290..d1c5b6629d8 100644
--- a/src/Hydra/composer.json
+++ b/src/Hydra/composer.json
@@ -29,13 +29,17 @@
"require": {
"php": ">=8.1",
"api-platform/state": "*@dev || ^3.1",
+ "api-platform/documentation": "*@dev || ^3.1",
"api-platform/metadata": "*@dev || ^3.1",
"api-platform/jsonld": "*@dev || ^3.1",
"api-platform/json-schema": "*@dev || ^3.1",
"api-platform/serializer": "*@dev || ^3.1"
},
"require-dev": {
- "sebastian/comparator": "<5.0"
+ "sebastian/comparator": "<5.0",
+ "api-platform/doctrine-odm": "*@dev || ^3.2",
+ "api-platform/doctrine-orm": "*@dev || ^3.2",
+ "api-platform/doctrine-common": "*@dev || ^3.2"
},
"autoload": {
"psr-4": {
@@ -75,6 +79,30 @@
{
"type": "path",
"url": "../JsonLd"
+ },
+ {
+ "type": "path",
+ "url": "../Serializer"
+ },
+ {
+ "type": "path",
+ "url": "../JsonSchema"
+ },
+ {
+ "type": "path",
+ "url": "../Documentation"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Orm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Odm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Common"
}
]
}
diff --git a/src/JsonLd/.gitattributes b/src/JsonLd/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/JsonLd/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php
index 0df22398487..247898cd791 100644
--- a/src/JsonLd/Serializer/ItemNormalizer.php
+++ b/src/JsonLd/Serializer/ItemNormalizer.php
@@ -22,13 +22,13 @@
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
+use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
use ApiPlatform\Metadata\ResourceClassResolverInterface;
use ApiPlatform\Metadata\UrlGeneratorInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Serializer\AbstractItemNormalizer;
use ApiPlatform\Serializer\ContextTrait;
use ApiPlatform\Serializer\TagCollectorInterface;
-use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
diff --git a/src/JsonSchema/.gitattributes b/src/JsonSchema/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/JsonSchema/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/JsonSchema/Tests/Fixtures/Enum/GamePlayMode.php b/src/JsonSchema/Tests/Fixtures/Enum/GamePlayMode.php
index fbd9e5a715e..feaf25bf17b 100644
--- a/src/JsonSchema/Tests/Fixtures/Enum/GamePlayMode.php
+++ b/src/JsonSchema/Tests/Fixtures/Enum/GamePlayMode.php
@@ -13,11 +13,11 @@
namespace ApiPlatform\JsonSchema\Tests\Fixtures\Enum;
+use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\Operation;
-use ApiPlatform\Tests\Fixtures\TestBundle\Metadata\Get;
#[Get(description: 'Indicates whether this game is multi-player, co-op or single-player.', provider: self::class.'::getCase')]
#[GetCollection(provider: self::class.'::getCases')]
diff --git a/src/JsonSchema/composer.json b/src/JsonSchema/composer.json
index afa7c7a9511..e022cdd8153 100644
--- a/src/JsonSchema/composer.json
+++ b/src/JsonSchema/composer.json
@@ -26,14 +26,14 @@
"require": {
"php": ">=8.1",
"api-platform/metadata": "*@dev || ^3.1",
- "symfony/console": "^6.2",
- "symfony/property-info": "^6.1",
- "symfony/serializer": "^6.1",
- "symfony/uid": "^6.1"
+ "symfony/console": "^6.4 || ^7.0",
+ "symfony/property-info": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
+ "symfony/uid": "^6.4 || ^7.0"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0",
- "symfony/phpunit-bridge": "^6.1",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
"sebastian/comparator": "<5.0"
},
"autoload": {
@@ -59,7 +59,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
diff --git a/src/Metadata/.gitattributes b/src/Metadata/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Metadata/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Metadata/Tests/Fixtures/ApiResource/RelationMultiple.php b/src/Metadata/Tests/Fixtures/ApiResource/RelationMultiple.php
index 69488222e9a..8b7bf6b9bd6 100644
--- a/src/Metadata/Tests/Fixtures/ApiResource/RelationMultiple.php
+++ b/src/Metadata/Tests/Fixtures/ApiResource/RelationMultiple.php
@@ -19,7 +19,6 @@
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Post;
-use ApiPlatform\Tests\Fixtures\TestBundle\State\RelationMultipleProvider;
#[ApiResource(
mercure: true,
@@ -39,7 +38,6 @@
identifiers: ['id'],
),
],
- provider: RelationMultipleProvider::class,
),
new GetCollection(
uriTemplate : '/dummy/{firstId}/relations',
@@ -50,7 +48,6 @@
identifiers: ['id'],
),
],
- provider: RelationMultipleProvider::class,
),
]
)]
diff --git a/src/Metadata/Tests/Fixtures/DummyIgnoreProperty.php b/src/Metadata/Tests/Fixtures/DummyIgnoreProperty.php
new file mode 100644
index 00000000000..7ebae40c9b2
--- /dev/null
+++ b/src/Metadata/Tests/Fixtures/DummyIgnoreProperty.php
@@ -0,0 +1,29 @@
+
+ *
+ * For 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\Tests\Fixtures;
+
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Serializer\Annotation\Ignore;
+
+class DummyIgnoreProperty
+{
+ public $visibleWithoutGroup;
+
+ #[Groups('dummy')]
+ public $visibleWithGroup;
+
+ #[Groups('dummy')]
+ #[Ignore]
+ public $ignored;
+}
diff --git a/src/Metadata/Tests/Fixtures/State/RelationMultipleProvider.php b/src/Metadata/Tests/Fixtures/State/RelationMultipleProvider.php
deleted file mode 100644
index 5f8ae004747..00000000000
--- a/src/Metadata/Tests/Fixtures/State/RelationMultipleProvider.php
+++ /dev/null
@@ -1,53 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-declare(strict_types=1);
-
-namespace ApiPlatform\Metadata\Tests\Fixtures\State;
-
-use ApiPlatform\Metadata\GetCollection;
-use ApiPlatform\Metadata\Operation;
-use ApiPlatform\State\ProviderInterface;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelationMultiple;
-
-class RelationMultipleProvider implements ProviderInterface
-{
- /**
- * {@inheritDoc}
- */
- public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|object|null
- {
- $firstDummy = new Dummy();
- $firstDummy->setId($uriVariables['firstId']);
- $secondDummy = new Dummy();
- $relationMultiple = new RelationMultiple();
- $relationMultiple->id = 1;
- $relationMultiple->first = $firstDummy;
- $relationMultiple->second = $secondDummy;
-
- if ($operation instanceof GetCollection) {
- $secondDummy->setId(2);
- $thirdDummy = new Dummy();
- $thirdDummy->setId(3);
- $relationMultiple2 = new RelationMultiple();
- $relationMultiple2->id = 2;
- $relationMultiple2->first = $firstDummy;
- $relationMultiple2->second = $thirdDummy;
-
- return [$relationMultiple, $relationMultiple2];
- }
-
- $relationMultiple->second->setId($uriVariables['secondId']);
-
- return $relationMultiple;
- }
-}
diff --git a/src/Metadata/Tests/IdentifiersExtractorTest.php b/src/Metadata/Tests/IdentifiersExtractorTest.php
index 88de9d6fa32..9693e3499b1 100644
--- a/src/Metadata/Tests/IdentifiersExtractorTest.php
+++ b/src/Metadata/Tests/IdentifiersExtractorTest.php
@@ -24,7 +24,6 @@
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\Dummy;
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\DummyWithEnumIdentifier;
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\RelationMultiple;
-use ApiPlatform\Metadata\Tests\Fixtures\State\RelationMultipleProvider;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
@@ -150,7 +149,6 @@ public function testGetIdentifiersFromItemWithToProperty(): void
->withIdentifiers(['id'])
->withParameterName('secondId'),
])
- ->withProvider(RelationMultipleProvider::class)
->withClass(RelationMultiple::class);
$first = new Dummy();
diff --git a/src/Metadata/Tests/Property/Factory/SerializerPropertyMetadataFactoryTest.php b/src/Metadata/Tests/Property/Factory/SerializerPropertyMetadataFactoryTest.php
index 17fd71d1d4d..1e282c256fc 100644
--- a/src/Metadata/Tests/Property/Factory/SerializerPropertyMetadataFactoryTest.php
+++ b/src/Metadata/Tests/Property/Factory/SerializerPropertyMetadataFactoryTest.php
@@ -20,7 +20,7 @@
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\Dummy;
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\DummyCar;
use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\RelatedDummy;
-use ApiPlatform\Tests\Fixtures\DummyIgnoreProperty;
+use ApiPlatform\Metadata\Tests\Fixtures\DummyIgnoreProperty;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\PropertyInfo\Type;
diff --git a/src/Metadata/composer.json b/src/Metadata/composer.json
index f94fc17d488..73f8aea2f91 100644
--- a/src/Metadata/composer.json
+++ b/src/Metadata/composer.json
@@ -31,8 +31,8 @@
"doctrine/inflector": "^2.0",
"psr/cache": "^3.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
- "symfony/property-info": "^6.1 || ^7.0",
- "symfony/string": "^6.1 || ^7.0"
+ "symfony/property-info": "^6.4 || ^7.0",
+ "symfony/string": "^6.4 || ^7.0"
},
"require-dev": {
"api-platform/json-schema": "*@dev || ^3.1",
@@ -41,12 +41,12 @@
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/phpdoc-parser": "^1.16",
"sebastian/comparator": "<5.0",
- "symfony/config": "^6.1 || ^7.0",
- "symfony/phpunit-bridge": "^6.1 || ^7.0",
- "symfony/routing": "^6.1 || ^7.0",
- "symfony/var-dumper": "^6.3 || ^7.0",
- "symfony/web-link": "^6.3 || ^7.0",
- "symfony/yaml": "^6.1 || ^7.0"
+ "symfony/config": "^6.4 || 7.0",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/routing": "^6.4 || ^7.0",
+ "symfony/var-dumper": "^6.4 || ^7.0",
+ "symfony/web-link": "^6.4 || ^7.0",
+ "symfony/yaml": "^6.4 || ^7.0"
},
"suggest": {
"phpstan/phpdoc-parser": "For PHP documentation support.",
@@ -76,7 +76,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
diff --git a/src/OpenApi/.gitattributes b/src/OpenApi/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/OpenApi/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/OpenApi/Tests/Fixtures/OutputDto.php b/src/OpenApi/Tests/Fixtures/OutputDto.php
index 0ca32276dee..068aaa7f3bc 100644
--- a/src/OpenApi/Tests/Fixtures/OutputDto.php
+++ b/src/OpenApi/Tests/Fixtures/OutputDto.php
@@ -13,8 +13,6 @@
namespace ApiPlatform\OpenApi\Tests\Fixtures;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
-
/**
* @author Kévin Dunglas
*/
diff --git a/src/OpenApi/Tests/Serializer/ApiGatewayNormalizerTest.php b/src/OpenApi/Tests/Serializer/ApiGatewayNormalizerTest.php
index 1e07ea49e88..d52dad9e977 100644
--- a/src/OpenApi/Tests/Serializer/ApiGatewayNormalizerTest.php
+++ b/src/OpenApi/Tests/Serializer/ApiGatewayNormalizerTest.php
@@ -21,29 +21,12 @@
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
-use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class ApiGatewayNormalizerTest extends TestCase
{
use ProphecyTrait;
- /**
- * @group legacy
- */
- public function testSupportsNormalization(): void
- {
- $normalizerProphecy = $this->prophesize(NormalizerInterface::class);
- $normalizerProphecy->willImplement(CacheableSupportsMethodInterface::class);
- $normalizerProphecy->supportsNormalization(OpenApiNormalizer::FORMAT, OpenApi::class)->willReturn(true);
- $normalizerProphecy->hasCacheableSupportsMethod()->willReturn(true);
-
- $normalizer = new ApiGatewayNormalizer($normalizerProphecy->reveal());
-
- $this->assertTrue($normalizer->supportsNormalization(OpenApiNormalizer::FORMAT, OpenApi::class));
- $this->assertTrue($normalizer->hasCacheableSupportsMethod());
- }
-
public function testNormalize(): void
{
$swaggerDocument = [
diff --git a/src/OpenApi/Tests/Serializer/OpenApiNormalizerTest.php b/src/OpenApi/Tests/Serializer/OpenApiNormalizerTest.php
index 2ee459952b7..632ce4d3837 100644
--- a/src/OpenApi/Tests/Serializer/OpenApiNormalizerTest.php
+++ b/src/OpenApi/Tests/Serializer/OpenApiNormalizerTest.php
@@ -42,8 +42,8 @@
use ApiPlatform\OpenApi\OpenApi;
use ApiPlatform\OpenApi\Options;
use ApiPlatform\OpenApi\Serializer\OpenApiNormalizer;
+use ApiPlatform\OpenApi\Tests\Fixtures\Dummy;
use ApiPlatform\State\Pagination\PaginationOptions;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
diff --git a/src/OpenApi/composer.json b/src/OpenApi/composer.json
index 04fc5f7f39d..076ae9975aa 100644
--- a/src/OpenApi/composer.json
+++ b/src/OpenApi/composer.json
@@ -31,14 +31,17 @@
"api-platform/json-schema": "*@dev || ^3.1",
"api-platform/metadata": "*@dev || ^3.1",
"api-platform/state": "*@dev || ^3.1",
- "symfony/console": "^6.1",
- "symfony/property-access": "^6.1",
- "symfony/serializer": "^6.1",
+ "symfony/console": "^6.4 || ^7.0",
+ "symfony/property-access": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
"sebastian/comparator": "<5.0"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0",
- "symfony/phpunit-bridge": "^6.1"
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "api-platform/doctrine-common": "*@dev || ^3.2",
+ "api-platform/doctrine-orm": "*@dev || ^3.2",
+ "api-platform/doctrine-odm": "*@dev || ^3.2"
},
"autoload": {
"psr-4": {
@@ -66,7 +69,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
@@ -81,6 +84,18 @@
{
"type": "path",
"url": "../State"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Orm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Odm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Common"
}
]
}
diff --git a/src/ParameterValidator/.gitattributes b/src/ParameterValidator/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/ParameterValidator/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/ParameterValidator/Tests/Fixtures/Dummy.php b/src/ParameterValidator/Tests/Fixtures/Dummy.php
new file mode 100644
index 00000000000..a87df579665
--- /dev/null
+++ b/src/ParameterValidator/Tests/Fixtures/Dummy.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\ParameterValidator\Tests\Fixtures;
+
+class Dummy
+{
+}
diff --git a/src/ParameterValidator/Tests/ParameterValidatorTest.php b/src/ParameterValidator/Tests/ParameterValidatorTest.php
index 7749b711345..cc2223fb7df 100644
--- a/src/ParameterValidator/Tests/ParameterValidatorTest.php
+++ b/src/ParameterValidator/Tests/ParameterValidatorTest.php
@@ -13,11 +13,10 @@
namespace ApiPlatform\ParameterValidator\Tests;
-use ApiPlatform\Exception\FilterValidationException;
use ApiPlatform\Metadata\FilterInterface;
use ApiPlatform\ParameterValidator\Exception\ValidationException;
use ApiPlatform\ParameterValidator\ParameterValidator;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
+use ApiPlatform\ParameterValidator\Tests\Fixtures\Dummy;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
diff --git a/src/ParameterValidator/composer.json b/src/ParameterValidator/composer.json
index 9cd7b3d42ca..5065c3104ed 100644
--- a/src/ParameterValidator/composer.json
+++ b/src/ParameterValidator/composer.json
@@ -29,13 +29,14 @@
"require": {
"php": ">=8.1",
"api-platform/state": "*@dev || ^3.1",
+ "api-platform/metadata": "*@dev || ^3.1",
"psr/container": "1.1|2.0",
"symfony/deprecation-contracts": "^3.1"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.1",
"sebastian/comparator": "<5.0",
- "symfony/phpunit-bridge": "^6.1"
+ "symfony/phpunit-bridge": "^6.4 || ^7.0"
},
"suggest": {
},
@@ -62,13 +63,17 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
{
"type": "path",
"url": "../State"
+ },
+ {
+ "type": "path",
+ "url": "../Metadata"
}
]
}
diff --git a/src/RamseyUuid/.gitattributes b/src/RamseyUuid/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/RamseyUuid/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/RamseyUuid/composer.json b/src/RamseyUuid/composer.json
index 73712079f05..fbb9d33a194 100644
--- a/src/RamseyUuid/composer.json
+++ b/src/RamseyUuid/composer.json
@@ -22,11 +22,11 @@
"require": {
"php": ">=8.1",
"api-platform/metadata": "*@dev || ^3.1",
- "symfony/serializer": "^6.1"
+ "symfony/serializer": "^6.4 || ^7.0"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0",
- "symfony/phpunit-bridge": "^6.1",
+ "symfony/phpunit-bridge": "^6.4",
"ramsey/uuid": "^3.7 || ^4.0",
"ramsey/uuid-doctrine": "^1.4",
"sebastian/comparator": "<5.0"
@@ -51,7 +51,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
diff --git a/src/Serializer/.gitattributes b/src/Serializer/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Serializer/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php
index 837f67ceae7..7ca6958f627 100644
--- a/src/Serializer/AbstractItemNormalizer.php
+++ b/src/Serializer/AbstractItemNormalizer.php
@@ -28,7 +28,6 @@
use ApiPlatform\Metadata\UrlGeneratorInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Metadata\Util\CloneTrait;
-use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -63,12 +62,9 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
protected PropertyAccessorInterface $propertyAccessor;
protected array $localCache = [];
protected array $localFactoryOptionsCache = [];
- protected $resourceAccessChecker;
+ protected ?ResourceAccessCheckerInterface $resourceAccessChecker;
- /**
- * @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface $resourceAccessChecker
- */
- public function __construct(protected PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, protected PropertyMetadataFactoryInterface $propertyMetadataFactory, protected LegacyIriConverterInterface|IriConverterInterface $iriConverter, protected LegacyResourceClassResolverInterface|ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null)
+ public function __construct(protected PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, protected PropertyMetadataFactoryInterface $propertyMetadataFactory, protected LegacyIriConverterInterface|IriConverterInterface $iriConverter, protected LegacyResourceClassResolverInterface|ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null)
{
if (!isset($defaultContext['circular_reference_handler'])) {
$defaultContext['circular_reference_handler'] = fn ($object): ?string => $this->iriConverter->getIriFromResource($object);
diff --git a/src/Serializer/ItemNormalizer.php b/src/Serializer/ItemNormalizer.php
index d94b5923ac6..c7ba4c99bc8 100644
--- a/src/Serializer/ItemNormalizer.php
+++ b/src/Serializer/ItemNormalizer.php
@@ -23,7 +23,6 @@
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
use ApiPlatform\Metadata\ResourceClassResolverInterface;
use ApiPlatform\Metadata\UrlGeneratorInterface;
-use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -40,10 +39,7 @@ class ItemNormalizer extends AbstractItemNormalizer
{
private readonly LoggerInterface $logger;
- /**
- * @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface $resourceAccessChecker
- */
- public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, $resourceAccessChecker = null, array $defaultContext = [], protected ?TagCollectorInterface $tagCollector = null)
+ public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, LoggerInterface $logger = null, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null, array $defaultContext = [], protected ?TagCollectorInterface $tagCollector = null)
{
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, $defaultContext, $resourceMetadataFactory, $resourceAccessChecker, $tagCollector);
diff --git a/src/Serializer/Tests/Filter/GroupFilterTest.php b/src/Serializer/Tests/Filter/GroupFilterTest.php
index fd8707d9e53..e1e1426dd7f 100644
--- a/src/Serializer/Tests/Filter/GroupFilterTest.php
+++ b/src/Serializer/Tests/Filter/GroupFilterTest.php
@@ -14,7 +14,7 @@
namespace ApiPlatform\Serializer\Tests\Filter;
use ApiPlatform\Serializer\Filter\GroupFilter;
-use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyGroup;
+use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\DummyGroup;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
diff --git a/src/Serializer/composer.json b/src/Serializer/composer.json
index ca70f2c40b2..0ac51490705 100644
--- a/src/Serializer/composer.json
+++ b/src/Serializer/composer.json
@@ -24,18 +24,21 @@
"api-platform/metadata": "*@dev || ^3.1",
"api-platform/state": "*@dev || ^3.1",
"doctrine/collections": "^2.1",
- "symfony/property-access": "^6.3 || ^7.0",
- "symfony/property-info": "^6.1 || ^7.0",
- "symfony/serializer": "^6.1 || ^7.0",
- "symfony/validator": "^6.3 || ^7.0"
+ "symfony/property-access": "^6.4 || ^7.0",
+ "symfony/property-info": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
+ "symfony/validator": "^6.4 || ^7.0"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0",
"sebastian/comparator": "<5.0",
"symfony/mercure-bundle": "*",
- "symfony/phpunit-bridge": "^6.1 || ^7.0",
- "symfony/var-dumper": "^6.1 || ^7.0",
- "symfony/yaml": "^6.3 || ^7.0"
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/var-dumper": "^6.4 || ^7.0",
+ "symfony/yaml": "^6.4 || ^7.0",
+ "api-platform/doctrine-odm": "*@dev || ^3.2",
+ "api-platform/doctrine-orm": "*@dev || ^3.2",
+ "api-platform/doctrine-common": "*@dev || ^3.2"
},
"autoload": {
"psr-4": {
@@ -60,7 +63,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
@@ -71,6 +74,18 @@
{
"type": "path",
"url": "../State"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Orm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Odm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Common"
}
]
}
diff --git a/src/State/.gitattributes b/src/State/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/State/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/State/Pagination/Pagination.php b/src/State/Pagination/Pagination.php
index e680a474db6..45c51ecb1f1 100644
--- a/src/State/Pagination/Pagination.php
+++ b/src/State/Pagination/Pagination.php
@@ -13,7 +13,7 @@
namespace ApiPlatform\State\Pagination;
-use ApiPlatform\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\Operation;
/**
diff --git a/src/State/Provider/DeserializeProvider.php b/src/State/Provider/DeserializeProvider.php
index b4b4131d47e..bea57d63c5b 100644
--- a/src/State/Provider/DeserializeProvider.php
+++ b/src/State/Provider/DeserializeProvider.php
@@ -17,7 +17,7 @@
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
use ApiPlatform\State\ProviderInterface;
-use ApiPlatform\Symfony\Validator\Exception\ValidationException;
+use ApiPlatform\Validator\Exception\ValidationException;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
@@ -87,6 +87,10 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
try {
return $this->serializer->deserialize((string) $request->getContent(), $operation->getClass(), $format, $serializerContext);
} catch (PartialDenormalizationException $e) {
+ if (!class_exists(ConstraintViolationList::class)) {
+ throw $e;
+ }
+
$violations = new ConstraintViolationList();
foreach ($e->getErrors() as $exception) {
if (!$exception instanceof NotNormalizableValueException) {
diff --git a/src/State/Util/RequestAttributesExtractor.php b/src/State/Util/RequestAttributesExtractor.php
new file mode 100644
index 00000000000..87f6f38d2f9
--- /dev/null
+++ b/src/State/Util/RequestAttributesExtractor.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\State\Util;
+
+use ApiPlatform\Metadata\Util\AttributesExtractor;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Extracts data used by the library form a Request instance.
+ *
+ * @internal
+ *
+ * @author Kévin Dunglas
+ */
+final class RequestAttributesExtractor
+{
+ private function __construct()
+ {
+ }
+
+ /**
+ * Extracts resource class, operation name and format request attributes. Returns an empty array if the request does
+ * not contain required attributes.
+ */
+ public static function extractAttributes(Request $request): array
+ {
+ return AttributesExtractor::extractAttributes($request->attributes->all());
+ }
+}
diff --git a/src/State/composer.json b/src/State/composer.json
index 55eeaf67fe0..e5526fa6f45 100644
--- a/src/State/composer.json
+++ b/src/State/composer.json
@@ -19,14 +19,16 @@
"require": {
"php": ">=8.1",
"api-platform/metadata": "*@dev || ^3.1",
+ "api-platform/jsonld": "*@dev || ^3.1",
"psr/container": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^10.3",
- "symfony/web-link": "^6.3",
- "symfony/http-foundation": "^6.3",
+ "symfony/web-link": "^6.4 || ^7.0",
+ "symfony/http-foundation": "^6.4 || 7.0",
"willdurand/negotiation": "^3.1",
- "sebastian/comparator": "<5.0"
+ "api-platform/serializer": "*@dev || ^3.1",
+ "api-platform/validator": "*@dev || ^3.1"
},
"autoload": {
"psr-4": {
@@ -51,18 +53,32 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"suggest": {
"symfony/web-link": "To support adding web links to the response headers.",
"symfony/http-foundation": "To use our HTTP providers and processor.",
- "willdurand/negotiation": "To use the API Platform content negoatiation provider."
+ "willdurand/negotiation": "To use the API Platform content negoatiation provider.",
+ "api-platform/serializer": "To use API Platform serializer.",
+ "api-platform/validator": "To use API Platform validation."
},
"repositories": [
{
"type": "path",
"url": "../Metadata"
+ },
+ {
+ "type": "path",
+ "url": "../Serializer"
+ },
+ {
+ "type": "path",
+ "url": "../Validator"
+ },
+ {
+ "type": "path",
+ "url": "../JsonLd"
}
]
}
diff --git a/src/Symfony/.gitattributes b/src/Symfony/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Symfony/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Symfony/EventListener/ErrorListener.php b/src/Symfony/EventListener/ErrorListener.php
index 0918791e32e..99815f88fae 100644
--- a/src/Symfony/EventListener/ErrorListener.php
+++ b/src/Symfony/EventListener/ErrorListener.php
@@ -27,7 +27,7 @@
use ApiPlatform\State\ApiResource\Error;
use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
-use ApiPlatform\Validator\Exception\ValidationException;
+use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
use Negotiation\Negotiator;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
@@ -192,7 +192,7 @@ private function getStatusCode(?HttpOperation $apiOperation, Request $request, ?
return 400;
}
- if ($exception instanceof ValidationException) {
+ if ($exception instanceof ConstraintViolationListAwareExceptionInterface) {
return 422;
}
diff --git a/src/Symfony/Routing/IriConverter.php b/src/Symfony/Routing/IriConverter.php
index 6134ab8c5a7..c6ef94fa54b 100644
--- a/src/Symfony/Routing/IriConverter.php
+++ b/src/Symfony/Routing/IriConverter.php
@@ -16,12 +16,12 @@
use ApiPlatform\Api\IdentifiersExtractorInterface as LegacyIdentifiersExtractorInterface;
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
use ApiPlatform\Api\UriVariablesConverterInterface as LegacyUriVariablesConverterInterface;
-use ApiPlatform\Exception\InvalidArgumentException;
-use ApiPlatform\Exception\InvalidIdentifierException;
-use ApiPlatform\Exception\ItemNotFoundException;
-use ApiPlatform\Exception\OperationNotFoundException;
-use ApiPlatform\Exception\RuntimeException;
use ApiPlatform\Metadata\CollectionOperationInterface;
+use ApiPlatform\Metadata\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Exception\InvalidIdentifierException;
+use ApiPlatform\Metadata\Exception\ItemNotFoundException;
+use ApiPlatform\Metadata\Exception\OperationNotFoundException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\HttpOperation;
diff --git a/src/Symfony/UriVariableTransformer/UlidUriVariableTransformer.php b/src/Symfony/UriVariableTransformer/UlidUriVariableTransformer.php
index df1f39b91eb..d39ae9c5a72 100644
--- a/src/Symfony/UriVariableTransformer/UlidUriVariableTransformer.php
+++ b/src/Symfony/UriVariableTransformer/UlidUriVariableTransformer.php
@@ -13,8 +13,8 @@
namespace ApiPlatform\Symfony\UriVariableTransformer;
-use ApiPlatform\Api\UriVariableTransformerInterface;
use ApiPlatform\Exception\InvalidUriVariableException;
+use ApiPlatform\Metadata\UriVariableTransformerInterface;
use Symfony\Component\Uid\Ulid;
/**
diff --git a/src/Symfony/UriVariableTransformer/UuidUriVariableTransformer.php b/src/Symfony/UriVariableTransformer/UuidUriVariableTransformer.php
index 4482f5f6036..7aa58a29b32 100644
--- a/src/Symfony/UriVariableTransformer/UuidUriVariableTransformer.php
+++ b/src/Symfony/UriVariableTransformer/UuidUriVariableTransformer.php
@@ -13,8 +13,8 @@
namespace ApiPlatform\Symfony\UriVariableTransformer;
-use ApiPlatform\Api\UriVariableTransformerInterface;
use ApiPlatform\Exception\InvalidUriVariableException;
+use ApiPlatform\Metadata\UriVariableTransformerInterface;
use Symfony\Component\Uid\Uuid;
/**
diff --git a/src/Symfony/Util/RequestAttributesExtractor.php b/src/Symfony/Util/RequestAttributesExtractor.php
index 74d549e505b..f5d1c7a759a 100644
--- a/src/Symfony/Util/RequestAttributesExtractor.php
+++ b/src/Symfony/Util/RequestAttributesExtractor.php
@@ -21,6 +21,8 @@
*
* @internal
*
+ * @deprecated moved to ApiPlatform\State\Util\RequestAttributesExtractor
+ *
* @author Kévin Dunglas
*/
final class RequestAttributesExtractor
diff --git a/src/Symfony/Validator/EventListener/ValidationExceptionListener.php b/src/Symfony/Validator/EventListener/ValidationExceptionListener.php
index 36b370e5ab3..9b558890022 100644
--- a/src/Symfony/Validator/EventListener/ValidationExceptionListener.php
+++ b/src/Symfony/Validator/EventListener/ValidationExceptionListener.php
@@ -15,8 +15,9 @@
use ApiPlatform\Exception\FilterValidationException;
use ApiPlatform\Symfony\EventListener\ExceptionListener;
-use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
+use ApiPlatform\Symfony\Validator\Exception\ConstraintViolationListAwareExceptionInterface as SymfonyConstraintViolationListAwareExceptionInterface;
use ApiPlatform\Util\ErrorFormatGuesser;
+use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface;
use ApiPlatform\Validator\Exception\ValidationException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
@@ -49,9 +50,11 @@ public function onKernelException(ExceptionEvent $event): void
trigger_deprecation('api-platform', '3.2', sprintf('The class "%s" is deprecated and will be removed in 4.x.', __CLASS__));
$exception = $event->getThrowable();
- if (!$exception instanceof ConstraintViolationListAwareExceptionInterface && !$exception instanceof FilterValidationException) {
+ $hasConstraintViolationList = ($exception instanceof ConstraintViolationListAwareExceptionInterface || $exception instanceof SymfonyConstraintViolationListAwareExceptionInterface);
+ if (!$hasConstraintViolationList && !$exception instanceof FilterValidationException) {
return;
}
+
$exceptionClass = $exception::class;
$statusCode = Response::HTTP_UNPROCESSABLE_ENTITY;
@@ -71,7 +74,7 @@ public function onKernelException(ExceptionEvent $event): void
}
$event->setResponse(new Response(
- $this->serializer->serialize($exception instanceof ConstraintViolationListAwareExceptionInterface ? $exception->getConstraintViolationList() : $exception, $format['key'], $context),
+ $this->serializer->serialize($hasConstraintViolationList ? $exception->getConstraintViolationList() : $exception, $format['key'], $context),
$statusCode,
[
'Content-Type' => sprintf('%s; charset=utf-8', $format['value'][0]),
diff --git a/src/Symfony/Validator/Exception/ValidationException.php b/src/Symfony/Validator/Exception/ValidationException.php
index cf3fdc8902c..03060d6e319 100644
--- a/src/Symfony/Validator/Exception/ValidationException.php
+++ b/src/Symfony/Validator/Exception/ValidationException.php
@@ -16,13 +16,11 @@
use ApiPlatform\JsonLd\ContextBuilderInterface;
use ApiPlatform\Metadata\Error as ErrorOperation;
use ApiPlatform\Metadata\ErrorResource;
+use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
-use ApiPlatform\Metadata\Util\CompositeIdentifierParser;
+use ApiPlatform\Validator\Exception\ConstraintViolationListAwareExceptionInterface as ApiPlatformConstraintViolationListAwareExceptionInterface;
use ApiPlatform\Validator\Exception\ValidationException as BaseValidationException;
-use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
-use Symfony\Component\Serializer\Annotation\Groups;
-use Symfony\Component\Serializer\Annotation\SerializedName;
-use Symfony\Component\Validator\ConstraintViolationListInterface;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface as SymfonyHttpExceptionInterface;
use Symfony\Component\WebLink\Link;
/**
@@ -64,118 +62,6 @@
],
graphQlOperations: []
)]
-final class ValidationException extends BaseValidationException implements ConstraintViolationListAwareExceptionInterface, \Stringable, ProblemExceptionInterface, HttpExceptionInterface
+final class ValidationException extends BaseValidationException implements ConstraintViolationListAwareExceptionInterface, ApiPlatformConstraintViolationListAwareExceptionInterface, \Stringable, ProblemExceptionInterface, HttpExceptionInterface, SymfonyHttpExceptionInterface
{
- private int $status = 422;
-
- public function __construct(private readonly ConstraintViolationListInterface $constraintViolationList, string $message = '', int $code = 0, \Throwable $previous = null, string $errorTitle = null)
- {
- parent::__construct($message ?: $this->__toString(), $code, $previous, $errorTitle);
- }
-
- public function getId(): string
- {
- $ids = [];
- foreach ($this->getConstraintViolationList() as $violation) {
- $ids[] = $violation->getCode();
- }
-
- $id = 1 < \count($ids) ? CompositeIdentifierParser::stringify(identifiers: $ids) : ($ids[0] ?? null);
-
- if (!$id) {
- return spl_object_hash($this);
- }
-
- return $id;
- }
-
- #[SerializedName('hydra:title')]
- #[Groups(['jsonld'])]
- public function getHydraTitle(): string
- {
- return $this->errorTitle ?? 'An error occurred';
- }
-
- #[Groups(['jsonld'])]
- #[SerializedName('hydra:description')]
- public function getHydraDescription(): string
- {
- return $this->detail;
- }
-
- #[Groups(['jsonld', 'json'])]
- public function getType(): string
- {
- return '/validation_errors/'.$this->getId();
- }
-
- #[Groups(['jsonld', 'json'])]
- public function getTitle(): ?string
- {
- return $this->errorTitle ?? 'An error occurred';
- }
-
- #[Groups(['jsonld', 'json'])]
- private string $detail;
-
- public function getDetail(): ?string
- {
- return $this->detail;
- }
-
- public function setDetail(string $detail): void
- {
- $this->detail = $detail;
- }
-
- #[Groups(['jsonld', 'json'])]
- public function getStatus(): ?int
- {
- return $this->status;
- }
-
- public function setStatus(int $status): void
- {
- $this->status = $status;
- }
-
- #[Groups(['jsonld', 'json'])]
- public function getInstance(): ?string
- {
- return null;
- }
-
- #[SerializedName('violations')]
- #[Groups(['json', 'jsonld'])]
- public function getConstraintViolationList(): ConstraintViolationListInterface
- {
- return $this->constraintViolationList;
- }
-
- public function __toString(): string
- {
- $message = '';
- foreach ($this->constraintViolationList as $violation) {
- if ('' !== $message) {
- $message .= "\n";
- }
- if ($propertyPath = $violation->getPropertyPath()) {
- $message .= "$propertyPath: ";
- }
-
- $message .= $violation->getMessage();
- }
-
- return $message;
- }
-
- public function getStatusCode(): int
- {
- return $this->status;
- }
-
- public function getHeaders(): array
- {
- return [];
- }
}
diff --git a/src/Symfony/Validator/Serializer/ValidationExceptionNormalizer.php b/src/Symfony/Validator/Serializer/ValidationExceptionNormalizer.php
index caa0ac72fa1..fe4e6cc7104 100644
--- a/src/Symfony/Validator/Serializer/ValidationExceptionNormalizer.php
+++ b/src/Symfony/Validator/Serializer/ValidationExceptionNormalizer.php
@@ -14,7 +14,7 @@
namespace ApiPlatform\Symfony\Validator\Serializer;
use ApiPlatform\Serializer\CacheableSupportsMethodInterface;
-use ApiPlatform\Symfony\Validator\Exception\ValidationException;
+use ApiPlatform\Validator\Exception\ValidationException;
use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
diff --git a/src/Symfony/Validator/State/ErrorProvider.php b/src/Symfony/Validator/State/ErrorProvider.php
index 7231cd305ff..ea0bf84fd32 100644
--- a/src/Symfony/Validator/State/ErrorProvider.php
+++ b/src/Symfony/Validator/State/ErrorProvider.php
@@ -16,7 +16,6 @@
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
-use ApiPlatform\Symfony\Validator\Exception\ValidationException;
/**
* @internal
@@ -27,7 +26,7 @@ public function __construct()
{
}
- public function provide(Operation $operation, array $uriVariables = [], array $context = []): ValidationException
+ public function provide(Operation $operation, array $uriVariables = [], array $context = []): \Throwable
{
if (!($request = $context['request'] ?? null) || !$operation instanceof HttpOperation) {
throw new \RuntimeException('Not an HTTP request');
diff --git a/src/Symfony/composer.json b/src/Symfony/composer.json
index 28c5bc3749d..48fe9a2daa8 100644
--- a/src/Symfony/composer.json
+++ b/src/Symfony/composer.json
@@ -26,23 +26,29 @@
"api-platform/http-cache": "*@dev || ^3.1",
"api-platform/json-schema": "*@dev || ^3.1",
"api-platform/jsonld": "*@dev || ^3.1",
+ "api-platform/hydra": "*@dev || ^3.1",
"api-platform/metadata": "*@dev || ^3.1",
- "api-platform/parameter-validator": "*@dev || ^3.1",
"api-platform/serializer": "*@dev || ^3.1",
"api-platform/state": "*@dev || ^3.1",
"api-platform/validator": "*@dev || ^3.1",
- "api-platform/doctrine-common": "*@dev || ^3.1",
- "symfony/property-info": "^6.1",
- "symfony/serializer": "^6.1",
- "symfony/security-core": "^6.1"
+ "api-platform/openapi": "*@dev || ^3.1",
+ "symfony/property-info": "^6.4 || ^7.0",
+ "symfony/property-access": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
+ "symfony/security-core": "^6.4 || ^7.0"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0",
- "phpunit/phpunit": "^10",
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/routing": "^6.4 || ^7.0",
+ "symfony/validator": "^6.4 || ^7.0",
"symfony/mercure-bundle": "*",
- "symfony/routing": "^6.1",
- "symfony/validator": "^6.1",
- "webonyx/graphql-php": "^14.0 || ^15.0"
+ "webonyx/graphql-php": "^14.0 || ^15.0",
+ "sebastian/comparator": "<5.0",
+ "api-platform/doctrine-common": "*@dev || ^3.2",
+ "api-platform/doctrine-orm": "*@dev || ^3.2",
+ "api-platform/doctrine-odm": "*@dev || ^3.2",
+ "api-platform/parameter-validator": "*@dev || ^3.1"
},
"autoload": {
"psr-4": {
@@ -68,7 +74,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
@@ -87,6 +93,54 @@
{
"type": "path",
"url": "../Doctrine/Common"
+ },
+ {
+ "type": "path",
+ "url": "../Documentation"
+ },
+ {
+ "type": "path",
+ "url": "../Elasticsearch"
+ },
+ {
+ "type": "path",
+ "url": "../GraphQl"
+ },
+ {
+ "type": "path",
+ "url": "../JsonLd"
+ },
+ {
+ "type": "path",
+ "url": "../HttpCache"
+ },
+ {
+ "type": "path",
+ "url": "../JsonSchema"
+ },
+ {
+ "type": "path",
+ "url": "../Serializer"
+ },
+ {
+ "type": "path",
+ "url": "../Validator"
+ },
+ {
+ "type": "path",
+ "url": "../OpenApi"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Orm"
+ },
+ {
+ "type": "path",
+ "url": "../Doctrine/Odm"
+ },
+ {
+ "type": "path",
+ "url": "../Hydra"
}
]
}
diff --git a/src/Util/RequestAttributesExtractor.php b/src/Util/RequestAttributesExtractor.php
index 2d180f6742e..64cd19d4c84 100644
--- a/src/Util/RequestAttributesExtractor.php
+++ b/src/Util/RequestAttributesExtractor.php
@@ -18,7 +18,7 @@
/**
* Extracts data used by the library form a Request instance.
*
- * @deprecated use \ApiPlatform\Symfony\Util\RequestAttributesExtractor although it should've been internal
+ * @deprecated use \ApiPlatform\State\Util\RequestAttributesExtractor
*
* @author Kévin Dunglas
*/
diff --git a/src/Validator/.gitattributes b/src/Validator/.gitattributes
new file mode 100644
index 00000000000..ae3c2e1685a
--- /dev/null
+++ b/src/Validator/.gitattributes
@@ -0,0 +1,2 @@
+/.gitignore export-ignore
+/Tests export-ignore
diff --git a/src/Validator/Exception/ValidationException.php b/src/Validator/Exception/ValidationException.php
index c710cfee867..0b55a707b35 100644
--- a/src/Validator/Exception/ValidationException.php
+++ b/src/Validator/Exception/ValidationException.php
@@ -13,22 +13,189 @@
namespace ApiPlatform\Validator\Exception;
+use ApiPlatform\JsonLd\ContextBuilderInterface;
+use ApiPlatform\Metadata\Error as ErrorOperation;
+use ApiPlatform\Metadata\ErrorResource;
+use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
+use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
use ApiPlatform\Metadata\Exception\RuntimeException;
+use ApiPlatform\Metadata\Util\CompositeIdentifierParser;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface as SymfonyHttpExceptionInterface;
+use Symfony\Component\Serializer\Annotation\Groups;
+use Symfony\Component\Serializer\Annotation\SerializedName;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+use Symfony\Component\WebLink\Link;
/**
* Thrown when a validation error occurs.
*
* @author Kévin Dunglas
*/
-class ValidationException extends RuntimeException
+#[ErrorResource(
+ uriTemplate: '/validation_errors/{id}',
+ status: 422,
+ openapi: false,
+ uriVariables: ['id'],
+ provider: 'api_platform.validator.state.error_provider',
+ shortName: 'ConstraintViolationList',
+ operations: [
+ new ErrorOperation(
+ name: '_api_validation_errors_problem',
+ outputFormats: ['json' => ['application/problem+json']],
+ normalizationContext: ['groups' => ['json'],
+ 'skip_null_values' => true,
+ 'rfc_7807_compliant_errors' => true,
+ ]),
+ new ErrorOperation(
+ name: '_api_validation_errors_hydra',
+ outputFormats: ['jsonld' => ['application/problem+json']],
+ links: [new Link(rel: ContextBuilderInterface::JSONLD_NS.'error', href: 'http://www.w3.org/ns/hydra/error')],
+ normalizationContext: [
+ 'groups' => ['jsonld'],
+ 'skip_null_values' => true,
+ 'rfc_7807_compliant_errors' => true,
+ ]
+ ),
+ new ErrorOperation(
+ name: '_api_validation_errors_jsonapi',
+ outputFormats: ['jsonapi' => ['application/vnd.api+json']],
+ normalizationContext: ['groups' => ['jsonapi'], 'skip_null_values' => true, 'rfc_7807_compliant_errors' => true]
+ ),
+ ],
+ graphQlOperations: []
+)]
+class ValidationException extends RuntimeException implements ConstraintViolationListAwareExceptionInterface, \Stringable, ProblemExceptionInterface, HttpExceptionInterface, SymfonyHttpExceptionInterface
{
- public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, protected readonly ?string $errorTitle = null)
+ private int $status = 422;
+ protected ?string $errorTitle = null;
+ private ConstraintViolationListInterface $constraintViolationList;
+
+ public function __construct(string|ConstraintViolationListInterface $message = '', string|int $code = null, int|\Throwable $previous = null, \Throwable|string $errorTitle = null)
{
- parent::__construct($message, $code, $previous);
+ $this->errorTitle = $errorTitle;
+
+ if ($message instanceof ConstraintViolationListInterface) {
+ $this->constraintViolationList = $message;
+ parent::__construct($code ?? $this->__toString(), $previous ?? 0, $errorTitle instanceof \Throwable ? $errorTitle : null);
+
+ return;
+ }
+
+ trigger_deprecation('api_platform/core', '3.3', sprintf('The "%s" exception will have a "%s" first argument in 4.x.', self::class, ConstraintViolationListInterface::class));
+ parent::__construct($message ?: $this->__toString(), $code, $previous);
}
+ /**
+ * @deprecated
+ */
public function getErrorTitle(): ?string
{
return $this->errorTitle;
}
+
+ public function getId(): string
+ {
+ $ids = [];
+ foreach ($this->getConstraintViolationList() as $violation) {
+ $ids[] = $violation->getCode();
+ }
+
+ $id = 1 < \count($ids) ? CompositeIdentifierParser::stringify(identifiers: $ids) : ($ids[0] ?? null);
+
+ if (!$id) {
+ return spl_object_hash($this);
+ }
+
+ return $id;
+ }
+
+ #[SerializedName('hydra:title')]
+ #[Groups(['jsonld'])]
+ public function getHydraTitle(): string
+ {
+ return $this->errorTitle ?? 'An error occurred';
+ }
+
+ #[Groups(['jsonld'])]
+ #[SerializedName('hydra:description')]
+ public function getHydraDescription(): string
+ {
+ return $this->detail;
+ }
+
+ #[Groups(['jsonld', 'json'])]
+ public function getType(): string
+ {
+ return '/validation_errors/'.$this->getId();
+ }
+
+ #[Groups(['jsonld', 'json'])]
+ public function getTitle(): ?string
+ {
+ return $this->errorTitle ?? 'An error occurred';
+ }
+
+ #[Groups(['jsonld', 'json'])]
+ private string $detail;
+
+ public function getDetail(): ?string
+ {
+ return $this->detail;
+ }
+
+ public function setDetail(string $detail): void
+ {
+ $this->detail = $detail;
+ }
+
+ #[Groups(['jsonld', 'json'])]
+ public function getStatus(): ?int
+ {
+ return $this->status;
+ }
+
+ public function setStatus(int $status): void
+ {
+ $this->status = $status;
+ }
+
+ #[Groups(['jsonld', 'json'])]
+ public function getInstance(): ?string
+ {
+ return null;
+ }
+
+ #[SerializedName('violations')]
+ #[Groups(['json', 'jsonld'])]
+ public function getConstraintViolationList(): ConstraintViolationListInterface
+ {
+ return $this->constraintViolationList;
+ }
+
+ public function __toString(): string
+ {
+ $message = '';
+ foreach ($this->getConstraintViolationList() as $violation) {
+ if ('' !== $message) {
+ $message .= "\n";
+ }
+ if ($propertyPath = $violation->getPropertyPath()) {
+ $message .= "$propertyPath: ";
+ }
+
+ $message .= $violation->getMessage();
+ }
+
+ return $message;
+ }
+
+ public function getStatusCode(): int
+ {
+ return $this->status;
+ }
+
+ public function getHeaders(): array
+ {
+ return [];
+ }
}
diff --git a/src/Validator/Tests/Exception/ValidationExceptionTest.php b/src/Validator/Tests/Exception/ValidationExceptionTest.php
new file mode 100644
index 00000000000..35afcd05cfa
--- /dev/null
+++ b/src/Validator/Tests/Exception/ValidationExceptionTest.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Validator\Tests\Exception;
+
+use ApiPlatform\Metadata\Exception\RuntimeException;
+use ApiPlatform\Validator\Exception\ValidationException;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\ConstraintViolationList;
+
+/**
+ * @author Kévin Dunglas
+ */
+class ValidationExceptionTest extends TestCase
+{
+ public function testToString(): void
+ {
+ $e = new ValidationException(new ConstraintViolationList([
+ new ConstraintViolation('message 1', '', [], '', '', 'invalid'),
+ new ConstraintViolation('message 2', '', [], '', 'foo', 'invalid'),
+ ]));
+ $this->assertInstanceOf(RuntimeException::class, $e);
+ $this->assertInstanceOf(\RuntimeException::class, $e);
+
+ $this->assertSame(str_replace(\PHP_EOL, "\n", <<__toString());
+ }
+}
diff --git a/src/Validator/composer.json b/src/Validator/composer.json
index a220920dded..36df45e992d 100644
--- a/src/Validator/composer.json
+++ b/src/Validator/composer.json
@@ -21,12 +21,17 @@
],
"require": {
"php": ">=8.1",
- "api-platform/metadata": "*@dev || ^3.1"
+ "api-platform/metadata": "*@dev || ^3.1",
+ "api-platform/jsonld": "*@dev || ^3.1",
+ "symfony/web-link": "^6.4 || ^7.0"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0",
- "symfony/phpunit-bridge": "^6.1",
- "sebastian/comparator": "<5.0"
+ "symfony/phpunit-bridge": "^6.4 || ^7.0",
+ "symfony/serializer": "^6.4 || ^7.0",
+ "sebastian/comparator": "<5.0",
+ "symfony/validator": "^6.4 || ^7.0",
+ "symfony/http-kernel": "^6.4 || ^7.0"
},
"autoload": {
"psr-4": {
@@ -48,7 +53,7 @@
"dev-main": "3.2.x-dev"
},
"symfony": {
- "require": "^6.1"
+ "require": "^6.4"
}
},
"repositories": [
diff --git a/src/Validator/phpunit.xml.dist b/src/Validator/phpunit.xml.dist
new file mode 100644
index 00000000000..6774dbf7dbc
--- /dev/null
+++ b/src/Validator/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
diff --git a/tests/Fixtures/TestBundle/Entity/CompositeItem.php b/tests/Fixtures/TestBundle/Entity/CompositeItem.php
index e03a399f605..e049b5bbf8f 100644
--- a/tests/Fixtures/TestBundle/Entity/CompositeItem.php
+++ b/tests/Fixtures/TestBundle/Entity/CompositeItem.php
@@ -35,7 +35,7 @@ class CompositeItem implements \Stringable
private ?string $field1 = null;
#[ORM\OneToMany(targetEntity: CompositeRelation::class, mappedBy: 'compositeItem', fetch: 'EAGER')]
#[Groups(['default'])]
- private Collection|iterable $compositeValues; // @phpstan-ignore-line
+ private Collection|iterable $compositeValues;
public function __construct()
{
diff --git a/tests/Symfony/Validator/Metadata/ValidationExceptionListenerTest.php b/tests/Symfony/Validator/EventListener/ValidationExceptionListenerTest.php
similarity index 100%
rename from tests/Symfony/Validator/Metadata/ValidationExceptionListenerTest.php
rename to tests/Symfony/Validator/EventListener/ValidationExceptionListenerTest.php
diff --git a/tests/Symfony/Validator/Metadata/Exception/ValidationExceptionTest.php b/tests/Symfony/Validator/Exception/ValidationExceptionTest.php
similarity index 96%
rename from tests/Symfony/Validator/Metadata/Exception/ValidationExceptionTest.php
rename to tests/Symfony/Validator/Exception/ValidationExceptionTest.php
index 82a3abc3c0f..cb9f9cae154 100644
--- a/tests/Symfony/Validator/Metadata/Exception/ValidationExceptionTest.php
+++ b/tests/Symfony/Validator/Exception/ValidationExceptionTest.php
@@ -13,7 +13,7 @@
namespace ApiPlatform\Tests\Symfony\Validator\Exception;
-use ApiPlatform\Exception\RuntimeException;
+use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
use ApiPlatform\Validator\Exception\ValidationException as MainValidationException;
use PHPUnit\Framework\TestCase;
diff --git a/tests/Symfony/Validator/Metadata/ValidatorTest.php b/tests/Symfony/Validator/ValidatorTest.php
similarity index 92%
rename from tests/Symfony/Validator/Metadata/ValidatorTest.php
rename to tests/Symfony/Validator/ValidatorTest.php
index 0cf59d7f8fb..7d11b46ff6e 100644
--- a/tests/Symfony/Validator/Metadata/ValidatorTest.php
+++ b/tests/Symfony/Validator/ValidatorTest.php
@@ -20,6 +20,8 @@
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Container\ContainerInterface;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface as SymfonyValidatorInterface;
@@ -50,14 +52,10 @@ public function testInvalid(): void
$this->expectException(ValidationException::class);
$data = new DummyEntity();
-
- $constraintViolationListProphecy = $this->prophesize(ConstraintViolationListInterface::class);
- $constraintViolationListProphecy->rewind()->shouldBeCalled();
- $constraintViolationListProphecy->valid()->shouldBeCalled();
- $constraintViolationListProphecy->count()->willReturn(2)->shouldBeCalled();
+ $constraintViolationList = new ConstraintViolationList([new ConstraintViolation('test', null, [], null, 'test', null), new ConstraintViolation('test', null, [], null, 'test', null)]);
$symfonyValidatorProphecy = $this->prophesize(SymfonyValidatorInterface::class);
- $symfonyValidatorProphecy->validate($data, null, null)->willReturn($constraintViolationListProphecy->reveal())->shouldBeCalled();
+ $symfonyValidatorProphecy->validate($data, null, null)->willReturn($constraintViolationList)->shouldBeCalled();
$symfonyValidator = $symfonyValidatorProphecy->reveal();
$validator = new Validator($symfonyValidator);