mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8291a7f09b | ||
|
|
26e274e373 | ||
|
|
5209184a60 | ||
|
|
23f54885bc | ||
|
|
7f29b576d8 | ||
|
|
a8425a5248 | ||
|
|
86ce0e5e35 | ||
|
|
39fd5f4d46 | ||
|
|
a9309d748b | ||
|
|
fe09af6df1 | ||
|
|
ed50e3d967 | ||
|
|
1d59e46245 | ||
|
|
a0a0b0e476 | ||
|
|
0078a67786 | ||
|
|
38d1124be9 | ||
|
|
e9d3c218ef | ||
|
|
6d2ca8fe40 | ||
|
|
a06011daf3 | ||
|
|
0846b8b102 | ||
|
|
1dd2b44982 | ||
|
|
d9508e97df | ||
|
|
534ed9c3c2 | ||
|
|
a552df66a9 | ||
|
|
85238d4d98 | ||
|
|
b7e9dd023c | ||
|
|
1ac05f5e4e | ||
|
|
2e4a872272 | ||
|
|
d550364431 | ||
|
|
5b2bf9d74c | ||
|
|
2fe40679f4 | ||
|
|
7029965d3a | ||
|
|
7e49c70320 | ||
|
|
f7fe5ad1bb | ||
|
|
035c52ce3c | ||
|
|
7e7e38b60e | ||
|
|
36ab133e62 | ||
|
|
e13422ab5e | ||
|
|
fda79b8e21 | ||
|
|
5a345b01dc | ||
|
|
03f4468be2 | ||
|
|
a3d82f8e2f | ||
|
|
d9e8e839fe | ||
|
|
e8472c8f1a | ||
|
|
deaab5133e | ||
|
|
cffe31fc9d | ||
|
|
0e9c7533fb | ||
|
|
18d6bc3757 | ||
|
|
7c4ae58517 | ||
|
|
05f8fcf836 | ||
|
|
acff29fddd | ||
|
|
58659f6c4f | ||
|
|
9952350c64 | ||
|
|
3bc78caba9 | ||
|
|
0f1c9ec72a | ||
|
|
80f65d6f77 | ||
|
|
0f04a82857 | ||
|
|
17903346cf | ||
|
|
98b468da57 | ||
|
|
bccb4c7bd9 | ||
|
|
dc53628faf | ||
|
|
21f339e6eb | ||
|
|
08de12e962 | ||
|
|
7c83373f1e | ||
|
|
021164fbe5 | ||
|
|
b2d0c21fe0 | ||
|
|
7391e2586a | ||
|
|
3532ce9a25 | ||
|
|
2c769acf8c | ||
|
|
61cb557b18 | ||
|
|
4471ad9f6b | ||
|
|
cd57768b08 | ||
|
|
b64824addb | ||
|
|
c7104c9471 | ||
|
|
bc6c6c9f0c | ||
|
|
89d0a6a67c | ||
|
|
1febeaca7f | ||
|
|
229dcb082b | ||
|
|
3849aed6fb | ||
|
|
f82db6a894 | ||
|
|
a8a859cf5e | ||
|
|
7be96f64ab | ||
|
|
947935e4c9 | ||
|
|
40af1fcfc6 | ||
|
|
021444b322 | ||
|
|
ec7c637cf2 | ||
|
|
0a0779c4a9 | ||
|
|
a52d9880cc | ||
|
|
08eaba44ca | ||
|
|
05c35c398f | ||
|
|
dac1875a79 | ||
|
|
5a55772559 | ||
|
|
d78fa52ad7 | ||
|
|
4ddaa5fc20 | ||
|
|
d7abcb01bc | ||
|
|
8cff7dcdaf | ||
|
|
601728045c | ||
|
|
b18cd893be | ||
|
|
21390a12b9 | ||
|
|
182bcbae23 | ||
|
|
978f687df9 | ||
|
|
fd1690431f | ||
|
|
3cfcd4ad13 | ||
|
|
69b0b764e3 | ||
|
|
e11cef5fca | ||
|
|
395c02caf4 | ||
|
|
0c4e739e94 | ||
|
|
7a72526e47 | ||
|
|
5f882b1cdd | ||
|
|
b3d849dd38 | ||
|
|
aa1dd881d8 | ||
|
|
92d27f2fea | ||
|
|
f8de44c35f | ||
|
|
f81980e1fa | ||
|
|
e9e54d8f65 | ||
|
|
04bfdf85de | ||
|
|
43f67c6164 | ||
|
|
f5be4183ce | ||
|
|
eed031fab0 | ||
|
|
328f36846e | ||
|
|
f7822c775d | ||
|
|
8c08792f0e | ||
|
|
026bba23f1 | ||
|
|
4305cb9230 | ||
|
|
2886d0dc92 | ||
|
|
07f1c4e8f8 | ||
|
|
0809a2b671 |
2
.github/workflows/continuous-integration.yml
vendored
2
.github/workflows/continuous-integration.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
extensions: "pdo, pdo_sqlite"
|
||||
extensions: "apcu, pdo, pdo_sqlite"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1"
|
||||
|
||||
|
||||
20
.github/workflows/static-analysis.yml
vendored
20
.github/workflows/static-analysis.yml
vendored
@@ -21,7 +21,15 @@ jobs:
|
||||
- "8.1"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "2.13"
|
||||
persistence-version:
|
||||
- "default"
|
||||
include:
|
||||
- php-version: "8.1"
|
||||
dbal-version: "2.13"
|
||||
persistence-version: "default"
|
||||
- php-version: "8.1"
|
||||
dbal-version: "default"
|
||||
persistence-version: "2.5"
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
@@ -37,6 +45,10 @@ jobs:
|
||||
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Require specific persistence version"
|
||||
run: "composer require doctrine/persistence ^${{ matrix.persistence-version }} --no-update"
|
||||
if: "${{ matrix.persistence-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v1"
|
||||
with:
|
||||
@@ -44,12 +56,16 @@ jobs:
|
||||
|
||||
- name: "Run a static analysis with phpstan/phpstan"
|
||||
run: "vendor/bin/phpstan analyse"
|
||||
if: "${{ matrix.dbal-version == 'default' }}"
|
||||
if: "${{ matrix.dbal-version == 'default' && matrix.persistence-version == 'default'}}"
|
||||
|
||||
- name: "Run a static analysis with phpstan/phpstan"
|
||||
run: "vendor/bin/phpstan analyse -c phpstan-dbal2.neon"
|
||||
if: "${{ matrix.dbal-version == '2.13' }}"
|
||||
|
||||
- name: "Run a static analysis with phpstan/phpstan"
|
||||
run: "vendor/bin/phpstan analyse -c phpstan-persistence2.neon"
|
||||
if: "${{ matrix.dbal-version == 'default' && matrix.persistence-version != 'default'}}"
|
||||
|
||||
static-analysis-psalm:
|
||||
name: "Static Analysis with Psalm"
|
||||
runs-on: "ubuntu-20.04"
|
||||
|
||||
101
UPGRADE.md
101
UPGRADE.md
@@ -1,3 +1,104 @@
|
||||
# Upgrade to 2.12
|
||||
|
||||
## Deprecated the `doctrine` binary.
|
||||
|
||||
The documentation explains how the console tools can be bootstrapped for
|
||||
standalone usage.
|
||||
|
||||
The method `ConsoleRunner::printCliConfigTemplate()` is deprecated because it
|
||||
was only useful in the context of the `doctrine` binary.
|
||||
|
||||
## Deprecate omitting `$class` argument to `ORMInvalidArgumentException::invalidIdentifierBindingEntity()`
|
||||
|
||||
To make it easier to identify understand the cause for that exception, it is
|
||||
deprecated to omit the class name when calling
|
||||
`ORMInvalidArgumentException::invalidIdentifierBindingEntity()`.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper`
|
||||
|
||||
Using a console helper to provide the ORM's console commands with one or
|
||||
multiple entity managers had been deprecated with 2.9 already. This leaves
|
||||
The `EntityManagerHelper` class with no purpose which is why it is now
|
||||
deprecated too. Applications that still rely on the `em` console helper, can
|
||||
easily recreate that class in their own codebase.
|
||||
|
||||
## Deprecate custom repository classes that don't extend `EntityRepository`
|
||||
|
||||
Although undocumented, it is currently possible to configure a custom repository
|
||||
class that implements `ObjectRepository` but does not extend the
|
||||
`EntityRepository` base class.
|
||||
|
||||
This is now deprecated. Please extend `EntityRepository` instead.
|
||||
|
||||
## Deprecated more APIs related to entity namespace aliases
|
||||
|
||||
```diff
|
||||
-$config = $entityManager->getConfiguration();
|
||||
-$config->addEntityNamespace('CMS', 'My\App\Cms');
|
||||
+use My\App\Cms\CmsUser;
|
||||
|
||||
-$entityManager->getRepository('CMS:CmsUser');
|
||||
+$entityManager->getRepository(CmsUser::class);
|
||||
```
|
||||
|
||||
## Deprecate `AttributeDriver::getReader()` and `AnnotationDriver::getReader()`
|
||||
|
||||
That method was inherited from the abstract `AnnotationDriver` class of
|
||||
`doctrine/persistence`, and does not seem to serve any purpose.
|
||||
|
||||
## Un-deprecate `Doctrine\ORM\Proxy\Proxy`
|
||||
|
||||
Because no forward-compatible new proxy solution had been implemented yet, the
|
||||
current proxy mechanism is not considered deprecated anymore for the time
|
||||
being. This applies to the following interfaces/classes:
|
||||
|
||||
* `Doctrine\ORM\Proxy\Proxy`
|
||||
* `Doctrine\ORM\Proxy\ProxyFactory`
|
||||
|
||||
These methods have been un-deprecated:
|
||||
|
||||
* `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()`
|
||||
* `Doctrine\ORM\Configuration::getProxyDir()`
|
||||
* `Doctrine\ORM\Configuration::getProxyNamespace()`
|
||||
|
||||
Note that the `Doctrine\ORM\Proxy\Autoloader` remains deprecated and will be removed in 3.0.
|
||||
|
||||
## Deprecate helper methods from `AbstractCollectionPersister`
|
||||
|
||||
The following protected methods of
|
||||
`Doctrine\ORM\Cache\Persister\Collection\AbstractCollectionPersister`
|
||||
are not in use anymore and will be removed.
|
||||
|
||||
* `evictCollectionCache()`
|
||||
* `evictElementCache()`
|
||||
|
||||
## Deprecate `Doctrine\ORM\Query\TreeWalkerChainIterator`
|
||||
|
||||
This class won't have a replacement.
|
||||
|
||||
## Deprecate `OnClearEventArgs::getEntityClass()` and `OnClearEventArgs::clearsAllEntities()`
|
||||
|
||||
These methods will be removed in 3.0 along with the ability to partially clear
|
||||
the entity manager.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Configuration::newDefaultAnnotationDriver`
|
||||
|
||||
This functionality has been moved to the new `ORMSetup` class. Call
|
||||
`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create
|
||||
a new annotation driver.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Tools\Setup`
|
||||
|
||||
In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which
|
||||
accepted a Doctrine Cache instance in each method has been deprecated.
|
||||
|
||||
The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6
|
||||
cache instead.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Cache\MultiGetRegion`
|
||||
|
||||
The interface will be merged with `Doctrine\ORM\Cache\Region` in 3.0.
|
||||
|
||||
# Upgrade to 2.11
|
||||
|
||||
## Rename `AbstractIdGenerator::generate()` to `generateId()`
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
<?php
|
||||
|
||||
fwrite(
|
||||
STDERR,
|
||||
'[Warning] The use of this script is discouraged. See'
|
||||
. ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console'
|
||||
. ' for instructions on bootstrapping the console runner.'
|
||||
. PHP_EOL
|
||||
);
|
||||
|
||||
echo PHP_EOL . PHP_EOL;
|
||||
|
||||
require_once 'Doctrine/Common/ClassLoader.php';
|
||||
|
||||
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
fwrite(
|
||||
STDERR,
|
||||
'[Warning] The use of this script is discouraged. See'
|
||||
. ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console'
|
||||
. ' for instructions on bootstrapping the console runner.'
|
||||
. PHP_EOL
|
||||
);
|
||||
|
||||
echo PHP_EOL . PHP_EOL;
|
||||
|
||||
$autoloadFiles = [
|
||||
__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/../../../autoload.php'
|
||||
|
||||
@@ -27,27 +27,28 @@
|
||||
"doctrine/collections": "^1.5",
|
||||
"doctrine/common": "^3.0.3",
|
||||
"doctrine/dbal": "^2.13.1 || ^3.2",
|
||||
"doctrine/deprecations": "^0.5.3",
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
"doctrine/event-manager": "^1.1",
|
||||
"doctrine/inflector": "^1.4 || ^2.0",
|
||||
"doctrine/instantiator": "^1.3",
|
||||
"doctrine/lexer": "^1.0",
|
||||
"doctrine/persistence": "^2.2",
|
||||
"doctrine/lexer": "^1.2.3",
|
||||
"doctrine/persistence": "^2.4 || ^3",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/polyfill-php72": "^1.23",
|
||||
"symfony/polyfill-php80": "^1.15"
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.13",
|
||||
"doctrine/coding-standard": "^9.0",
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.5.0",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.6.3",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"squizlabs/php_codesniffer": "3.6.2",
|
||||
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
|
||||
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
|
||||
"vimeo/psalm": "4.22.0"
|
||||
"vimeo/psalm": "4.23.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >= 2.0"
|
||||
|
||||
@@ -47,7 +47,7 @@ appropriate autoloaders.
|
||||
}
|
||||
|
||||
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
|
||||
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) {
|
||||
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
|
||||
$mappedTableName = $mapping['joinTable']['name'];
|
||||
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ steps of configuration.
|
||||
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
|
||||
|
||||
@@ -27,7 +28,7 @@ steps of configuration.
|
||||
|
||||
$config = new Configuration;
|
||||
$config->setMetadataCache($metadataCache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$driverImpl = ORMSetup::createDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCache($queryCache);
|
||||
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
|
||||
@@ -129,7 +130,9 @@ the ``Doctrine\ORM\Configuration``:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
$driverImpl = ORMSetup::createDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
|
||||
The path information to the entities is required for the annotation
|
||||
@@ -454,18 +457,5 @@ Setting up the Console
|
||||
----------------------
|
||||
|
||||
Doctrine uses the Symfony Console component for generating the command
|
||||
line interface. You can take a look at the ``vendor/bin/doctrine.php``
|
||||
script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command
|
||||
for inspiration how to setup the cli.
|
||||
|
||||
In general the required code looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
|
||||
$cli->setCatchExceptions(true);
|
||||
$cli->setHelperSet($helperSet);
|
||||
Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
|
||||
$cli->run();
|
||||
|
||||
line interface. You can take a look at the
|
||||
:doc:`tools chapter <../reference/tools>` for inspiration how to setup the cli.
|
||||
|
||||
@@ -41,8 +41,8 @@ access point to ORM functionality provided by Doctrine.
|
||||
// bootstrap.php
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
$paths = array("/path/to/entity-files");
|
||||
$isDevMode = false;
|
||||
@@ -55,16 +55,21 @@ access point to ORM functionality provided by Doctrine.
|
||||
'dbname' => 'foo',
|
||||
);
|
||||
|
||||
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createAnnotationMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
.. note::
|
||||
|
||||
The ``ORMSetup`` class has been introduced with ORM 2.12. It's predecessor ``Setup`` is deprecated and will
|
||||
be removed in version 3.0.
|
||||
|
||||
Or if you prefer XML:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/xml-mappings");
|
||||
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
Or if you prefer YAML:
|
||||
@@ -73,7 +78,7 @@ Or if you prefer YAML:
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/yml-mappings");
|
||||
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
.. note::
|
||||
@@ -83,13 +88,18 @@ Or if you prefer YAML:
|
||||
|
||||
"symfony/yaml": "*"
|
||||
|
||||
Inside the ``Setup`` methods several assumptions are made:
|
||||
Inside the ``ORMSetup`` methods several assumptions are made:
|
||||
|
||||
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
|
||||
- If ``$isDevMode`` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
|
||||
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayAdapter``. Proxy objects are recreated on every request.
|
||||
- If ``$isDevMode`` is false, check for Caches in the order APCu, Redis (127.0.0.1:6379), Memcache (127.0.0.1:11211) unless `$cache` is passed as fourth argument.
|
||||
- If ``$isDevMode`` is false, set then proxy classes have to be explicitly created through the command line.
|
||||
- If third argument `$proxyDir` is not set, use the systems temporary directory.
|
||||
|
||||
.. note::
|
||||
|
||||
In order to have ``ORMSetup`` configure the cache automatically, the library ``symfony/cache``
|
||||
has to be installed as a dependency.
|
||||
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
|
||||
|
||||
.. note::
|
||||
@@ -101,26 +111,6 @@ Setting up the Commandline Tool
|
||||
-------------------------------
|
||||
|
||||
Doctrine ships with a number of command line tools that are very helpful
|
||||
during development. You can call this command from the Composer binary
|
||||
directory:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ php vendor/bin/doctrine
|
||||
|
||||
You need to register your applications EntityManager to the console tool
|
||||
to make use of the tasks by creating a ``cli-config.php`` file with the
|
||||
following content:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
// replace with file to your own project bootstrap
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
// replace with mechanism to retrieve EntityManager in your app
|
||||
$entityManager = GetEntityManager();
|
||||
|
||||
return ConsoleRunner::createHelperSet($entityManager);
|
||||
during development. In order to make use of them, create an executable PHP
|
||||
script in your project as described in the
|
||||
:doc:`tools chapter <../reference/tools>`.
|
||||
|
||||
@@ -79,7 +79,9 @@ array of events it should be subscribed to.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
|
||||
class TestEventSubscriber implements EventSubscriber
|
||||
{
|
||||
public $preFooInvoked = false;
|
||||
|
||||
@@ -211,7 +213,6 @@ specific to a particular entity class's lifecycle.
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
@@ -245,7 +246,6 @@ specific to a particular entity class's lifecycle.
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
/**
|
||||
@@ -504,7 +504,6 @@ result in an infinite loop.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
|
||||
class PreFlushExampleListener
|
||||
@@ -590,7 +589,6 @@ This event is not a lifecycle callback.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Event\PostFlushEventArgs;
|
||||
|
||||
class PostFlushExampleListener
|
||||
@@ -615,7 +613,7 @@ Changes to associations of the updated entity are never allowed in
|
||||
this event, since Doctrine cannot guarantee to correctly handle
|
||||
referential integrity at this point of the flush operation. This
|
||||
event has a powerful feature however, it is executed with a
|
||||
`_PreUpdateEventArgs`_ instance, which contains a reference to the
|
||||
`PreUpdateEventArgs`_ instance, which contains a reference to the
|
||||
computed change-set of this entity.
|
||||
|
||||
This means you have access to all the fields that have changed for
|
||||
@@ -637,6 +635,8 @@ A simple example for this event looks like:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class NeverAliceOnlyBobListener
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||
@@ -656,6 +656,8 @@ lifecycle callback when there are expensive validations to call:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class ValidCreditCardListener
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||
@@ -807,6 +809,8 @@ An ``Entity Listener`` could be any class, by default it should be a class with
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class UserListener
|
||||
{
|
||||
public function preUpdate(User $user, PreUpdateEventArgs $event)
|
||||
@@ -823,6 +827,10 @@ you need to map the listener method using the event type mapping:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
|
||||
class UserListener
|
||||
{
|
||||
/** @PrePersist */
|
||||
@@ -907,6 +915,8 @@ Specifying an entity listener instance :
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
// User.php
|
||||
|
||||
/** @Entity @EntityListeners({"UserListener"}) */
|
||||
@@ -933,12 +943,14 @@ Specifying an entity listener instance :
|
||||
$listener = $container->get('user_listener');
|
||||
$em->getConfiguration()->getEntityListenerResolver()->register($listener);
|
||||
|
||||
Implementing your own resolver :
|
||||
Implementing your own resolver:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntityListenerResolver extends \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
|
||||
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
|
||||
|
||||
class MyEntityListenerResolver extends DefaultEntityListenerResolver
|
||||
{
|
||||
public function __construct($container)
|
||||
{
|
||||
@@ -972,13 +984,15 @@ This event is not a lifecycle callback.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||
|
||||
$test = new TestEventListener();
|
||||
$evm = $em->getEventManager();
|
||||
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $test);
|
||||
|
||||
class TestEventListener
|
||||
{
|
||||
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
|
||||
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
|
||||
{
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
$fieldMapping = array(
|
||||
@@ -1011,13 +1025,16 @@ instance and class metadata.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\ToolEvents;
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
|
||||
|
||||
$test = new TestEventListener();
|
||||
$evm = $em->getEventManager();
|
||||
$evm->addEventListener(\Doctrine\ORM\Tools\ToolEvents::postGenerateSchemaTable, $test);
|
||||
$evm->addEventListener(ToolEvents::postGenerateSchemaTable, $test);
|
||||
|
||||
class TestEventListener
|
||||
{
|
||||
public function postGenerateSchemaTable(\Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs $eventArgs)
|
||||
public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
|
||||
{
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
$schema = $eventArgs->getSchema();
|
||||
@@ -1035,13 +1052,16 @@ and the EntityManager.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\ToolEvents;
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
|
||||
$test = new TestEventListener();
|
||||
$evm = $em->getEventManager();
|
||||
$evm->addEventListener(\Doctrine\ORM\Tools\ToolEvents::postGenerateSchema, $test);
|
||||
$evm->addEventListener(ToolEvents::postGenerateSchema, $test);
|
||||
|
||||
class TestEventListener
|
||||
{
|
||||
public function postGenerateSchema(\Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs $eventArgs)
|
||||
public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs)
|
||||
{
|
||||
$schema = $eventArgs->getSchema();
|
||||
$em = $eventArgs->getEntityManager();
|
||||
|
||||
@@ -55,51 +55,66 @@ Implementing Metadata Drivers
|
||||
|
||||
In addition to the included metadata drivers you can very easily
|
||||
implement your own. All you need to do is define a class which
|
||||
implements the ``Driver`` interface:
|
||||
implements the ``MappingDriver`` interface:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
|
||||
interface Driver
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Persistence\Mapping\Driver;
|
||||
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Contract for metadata drivers.
|
||||
*/
|
||||
interface MappingDriver
|
||||
{
|
||||
/**
|
||||
* Loads the metadata for the specified class into the provided container.
|
||||
*
|
||||
* @param string $className
|
||||
* @param ClassMetadataInfo $metadata
|
||||
*
|
||||
* @psalm-param class-string<T> $className
|
||||
* @psalm-param ClassMetadata<T> $metadata
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
|
||||
|
||||
public function loadMetadataForClass(string $className, ClassMetadata $metadata);
|
||||
|
||||
/**
|
||||
* Gets the names of all mapped classes known to this driver.
|
||||
*
|
||||
* @return array The names of all mapped classes known to this driver.
|
||||
*/
|
||||
function getAllClassNames();
|
||||
|
||||
/**
|
||||
* Whether the class with the specified name should have its metadata loaded.
|
||||
* This is only the case if it is either mapped as an Entity or a
|
||||
* MappedSuperclass.
|
||||
*
|
||||
* @param string $className
|
||||
* @return boolean
|
||||
* @return array<int, string> The names of all mapped classes known to this driver.
|
||||
* @psalm-return list<class-string>
|
||||
*/
|
||||
function isTransient($className);
|
||||
public function getAllClassNames();
|
||||
|
||||
/**
|
||||
* Returns whether the class with the specified name should have its metadata loaded.
|
||||
* This is only the case if it is either mapped as an Entity or a MappedSuperclass.
|
||||
*
|
||||
* @psalm-param class-string $className
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isTransient(string $className);
|
||||
}
|
||||
|
||||
If you want to write a metadata driver to parse information from
|
||||
some file format we've made your life a little easier by providing
|
||||
the ``AbstractFileDriver`` implementation for you to extend from:
|
||||
the ``FileDriver`` implementation for you to extend from:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyMetadataDriver extends AbstractFileDriver
|
||||
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\FileDriver;
|
||||
|
||||
class MyMetadataDriver extends FileDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@@ -109,11 +124,11 @@ the ``AbstractFileDriver`` implementation for you to extend from:
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata)
|
||||
{
|
||||
$data = $this->_loadMappingFile($file);
|
||||
|
||||
// populate ClassMetadataInfo instance from $data
|
||||
// populate ClassMetadata instance from $data
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,13 +142,12 @@ the ``AbstractFileDriver`` implementation for you to extend from:
|
||||
|
||||
.. note::
|
||||
|
||||
When using the ``AbstractFileDriver`` it requires that you
|
||||
only have one entity defined per file and the file named after the
|
||||
class described inside where namespace separators are replaced by
|
||||
periods. So if you have an entity named ``Entities\User`` and you
|
||||
wanted to write a mapping file for your driver above you would need
|
||||
to name the file ``Entities.User.dcm.ext`` for it to be
|
||||
recognized.
|
||||
When using the ``FileDriver`` it requires that you only have one
|
||||
entity defined per file and the file named after the class described
|
||||
inside where namespace separators are replaced by periods. So if you
|
||||
have an entity named ``Entities\User`` and you wanted to write a
|
||||
mapping file for your driver above you would need to name the file
|
||||
``Entities.User.dcm.ext`` for it to be recognized.
|
||||
|
||||
|
||||
Now you can use your ``MyMetadataDriver`` implementation by setting
|
||||
@@ -156,14 +170,6 @@ entity when needed.
|
||||
|
||||
You have all the methods you need to manually specify the mapping
|
||||
information instead of using some mapping file to populate it from.
|
||||
The base ``ClassMetadataInfo`` class is responsible for only data
|
||||
storage and is not meant for runtime use. It does not require that
|
||||
the class actually exists yet so it is useful for describing some
|
||||
entity before it exists and using that information to generate for
|
||||
example the entities themselves. The class ``ClassMetadata``
|
||||
extends ``ClassMetadataInfo`` and adds some functionality required
|
||||
for runtime usage and requires that the PHP class is present and
|
||||
can be autoloaded.
|
||||
|
||||
You can read more about the API of the ``ClassMetadata`` classes in
|
||||
the PHP Mapping chapter.
|
||||
|
||||
@@ -185,13 +185,12 @@ It also has several methods that create builders (which are necessary for advanc
|
||||
- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance
|
||||
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
|
||||
|
||||
ClassMetadataInfo API
|
||||
---------------------
|
||||
ClassMetadata API
|
||||
-----------------
|
||||
|
||||
The ``ClassMetadataInfo`` class is the base data object for storing
|
||||
the mapping metadata for a single entity. It contains all the
|
||||
getters and setters you need populate and retrieve information for
|
||||
an entity.
|
||||
The ``ClassMetadata`` class is the data object for storing the mapping
|
||||
metadata for a single entity. It contains all the getters and setters
|
||||
you need populate and retrieve information for an entity.
|
||||
|
||||
General Setters
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -309,13 +308,11 @@ Lifecycle Callback Getters
|
||||
- ``hasLifecycleCallbacks($lifecycleEvent)``
|
||||
- ``getLifecycleCallbacks($event)``
|
||||
|
||||
ClassMetadata API
|
||||
-----------------
|
||||
Runtime reflection methods
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds
|
||||
the runtime functionality required by Doctrine. It adds a few extra
|
||||
methods related to runtime reflection for working with the entities
|
||||
themselves.
|
||||
These are methods related to runtime reflection for working with the
|
||||
entities themselves.
|
||||
|
||||
|
||||
- ``getReflectionClass()``
|
||||
|
||||
@@ -7,24 +7,10 @@ Doctrine Console
|
||||
The Doctrine Console is a Command Line Interface tool for simplifying common
|
||||
administration tasks during the development of a project that uses ORM.
|
||||
|
||||
Take a look at the :doc:`Installation and Configuration <configuration>`
|
||||
chapter for more information how to setup the console command.
|
||||
For the following examples, we will set up the CLI as ``bin/doctrine``.
|
||||
|
||||
Display Help Information
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Type ``php vendor/bin/doctrine`` on the command line and you should see an
|
||||
overview of the available commands or use the --help flag to get
|
||||
information on the available commands. If you want to know more
|
||||
about the use of generate entities for example, you can call:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$> php vendor/bin/doctrine orm:generate-entities --help
|
||||
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
Setting Up the Console
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever the ``doctrine`` command line tool is invoked, it can
|
||||
access all Commands that were registered by a developer. There is no
|
||||
@@ -34,25 +20,33 @@ Doctrine DBAL and ORM. If you want to use additional commands you
|
||||
have to register them yourself.
|
||||
|
||||
All the commands of the Doctrine Console require access to the
|
||||
``EntityManager``. You have to inject it into the console application with
|
||||
``ConsoleRunner::createHelperSet``. Whenever you invoke the Doctrine
|
||||
binary, it searches the current directory for the file ``cli-config.php``.
|
||||
This file contains the project-specific configuration.
|
||||
``EntityManager``. You have to inject it into the console application.
|
||||
|
||||
Here is an example of a the project-specific ``cli-config.php``:
|
||||
Here is an example of a the project-specific ``bin/doctrine`` binary.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
// replace this with the path to your own project bootstrap file.
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
|
||||
// replace with path to your own project bootstrap file
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
// replace with mechanism to retrieve EntityManager in your app
|
||||
$entityManager = GetEntityManager();
|
||||
|
||||
return ConsoleRunner::createHelperSet($entityManager);
|
||||
$commands = [
|
||||
// If you want to add your own custom console commands,
|
||||
// you can do so here.
|
||||
];
|
||||
|
||||
ConsoleRunner::run(
|
||||
new SingleManagerProvider($entityManager),
|
||||
$commands
|
||||
);
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -60,6 +54,18 @@ Here is an example of a the project-specific ``cli-config.php``:
|
||||
and use their facilities to access the Doctrine EntityManager and
|
||||
Connection Resources.
|
||||
|
||||
Display Help Information
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Type ``php bin/doctrine`` on the command line and you should see an
|
||||
overview of the available commands or use the ``--help`` flag to get
|
||||
information on the available commands. If you want to know more
|
||||
about the use of generate entities for example, you can call:
|
||||
|
||||
::
|
||||
|
||||
$> php bin/doctrine orm:generate-entities --help
|
||||
|
||||
Command Overview
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -143,7 +149,7 @@ When using the SchemaTool class directly, create your schema using
|
||||
the ``createSchema()`` method. First create an instance of the
|
||||
``SchemaTool`` and pass it an instance of the ``EntityManager``
|
||||
that you want to use to create the schema. This method receives an
|
||||
array of ``ClassMetadataInfo`` instances.
|
||||
array of ``ClassMetadata`` instances.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -174,8 +180,8 @@ tables of the current model to clean up with orphaned tables.
|
||||
|
||||
You can also use database introspection to update your schema
|
||||
easily with the ``updateSchema()`` method. It will compare your
|
||||
existing database schema to the passed array of
|
||||
``ClassMetadataInfo`` instances.
|
||||
existing database schema to the passed array of ``ClassMetadata``
|
||||
instances.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -189,38 +195,35 @@ To create the schema use the ``create`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:create
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
|
||||
To drop the schema use the ``drop`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:drop
|
||||
$ php bin/doctrine orm:schema-tool:drop
|
||||
|
||||
If you want to drop and then recreate the schema then use both
|
||||
options:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:drop
|
||||
$ php doctrine orm:schema-tool:create
|
||||
$ php bin/doctrine orm:schema-tool:drop
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
|
||||
As you would think, if you want to update your schema use the
|
||||
``update`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:update
|
||||
$ php bin/doctrine orm:schema-tool:update
|
||||
|
||||
All of the above commands also accept a ``--dump-sql`` option that
|
||||
will output the SQL for the ran operation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:create --dump-sql
|
||||
|
||||
Before using the orm:schema-tool commands, remember to configure
|
||||
your cli-config.php properly.
|
||||
$ php bin/doctrine orm:schema-tool:create --dump-sql
|
||||
|
||||
Entity Generation
|
||||
-----------------
|
||||
@@ -229,9 +232,9 @@ Generate entity classes and method stubs from your mapping information.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:generate-entities
|
||||
$ php doctrine orm:generate-entities --update-entities
|
||||
$ php doctrine orm:generate-entities --regenerate-entities
|
||||
$ php bin/doctrine orm:generate-entities
|
||||
$ php bin/doctrine orm:generate-entities --update-entities
|
||||
$ php bin/doctrine orm:generate-entities --regenerate-entities
|
||||
|
||||
This command is not suited for constant usage. It is a little helper and does
|
||||
not support all the mapping edge cases very well. You still have to put work
|
||||
@@ -316,14 +319,14 @@ convert to and the path to generate it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
|
||||
$ php bin/doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
|
||||
|
||||
Reverse Engineering
|
||||
-------------------
|
||||
|
||||
You can use the ``DatabaseDriver`` to reverse engineer a database
|
||||
to an array of ``ClassMetadataInfo`` instances and generate YAML,
|
||||
XML, etc. from them.
|
||||
You can use the ``DatabaseDriver`` to reverse engineer a database to an
|
||||
array of ``ClassMetadata`` instances and generate YAML, XML, etc. from
|
||||
them.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -366,7 +369,7 @@ You can also reverse engineer a database using the
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
|
||||
$ php bin/doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -393,6 +396,11 @@ You can either use the Doctrine Command Line Tool:
|
||||
|
||||
doctrine orm:validate-schema
|
||||
|
||||
If the validation fails, you can change the verbosity level to
|
||||
check the detected errors:
|
||||
|
||||
doctrine orm:validate-schema -v
|
||||
|
||||
Or you can trigger the validation manually:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -137,8 +137,8 @@ step:
|
||||
|
||||
<?php
|
||||
// bootstrap.php
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
@@ -147,10 +147,10 @@ step:
|
||||
$proxyDir = null;
|
||||
$cache = null;
|
||||
$useSimpleAnnotationReader = false;
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
|
||||
// or if you prefer yaml or XML
|
||||
// $config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
|
||||
// $config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
|
||||
$config = ORMSetup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
|
||||
// or if you prefer YAML or XML
|
||||
// $config = ORMSetup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
|
||||
// $config = ORMSetup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
|
||||
|
||||
// database configuration parameters
|
||||
$conn = array(
|
||||
@@ -173,7 +173,7 @@ The ``require_once`` statement sets up the class autoloading for Doctrine and
|
||||
its dependencies using Composer's autoloader.
|
||||
|
||||
The second block consists of the instantiation of the ORM
|
||||
``Configuration`` object using the Setup helper. It assumes a bunch
|
||||
``Configuration`` object using the ``ORMSetup`` helper. It assumes a bunch
|
||||
of defaults that you don't have to bother about for now. You can
|
||||
read up on the configuration details in the
|
||||
:doc:`reference chapter on configuration <../reference/configuration>`.
|
||||
@@ -191,22 +191,35 @@ Generating the Database Schema
|
||||
|
||||
Doctrine has a command-line interface that allows you to access the SchemaTool,
|
||||
a component that can generate a relational database schema based entirely on the
|
||||
defined entity classes and their metadata. For this tool to work, a
|
||||
``cli-config.php`` file must exist in the project root directory:
|
||||
defined entity classes and their metadata. For this tool to work, you need to
|
||||
create an executable console script as described in the
|
||||
:doc:`tools chapter <../reference/tools>`.
|
||||
|
||||
If you created the ``bootstrap.php`` file as described in the previous section,
|
||||
that script could look like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
// cli-config.php
|
||||
require_once "bootstrap.php";
|
||||
// bin/doctrine
|
||||
|
||||
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
|
||||
Now call the Doctrine command-line tool:
|
||||
// Adjust this path to your actual bootstrap.php
|
||||
require __DIR__ . 'path/to/your/bootstrap.php';
|
||||
|
||||
ConsoleRunner::run(
|
||||
new SingleManagerProvider($entityManager)
|
||||
);
|
||||
|
||||
In the following examples, we will assume that this script has been created as
|
||||
``bin/doctrine``.
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
|
||||
Since we haven't added any entity metadata in ``src`` yet, you'll see a message
|
||||
stating "No Metadata Classes to process." In the next section, we'll create a
|
||||
@@ -218,14 +231,14 @@ You can easily recreate the database using the following commands:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:drop --force
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
$ php bin/doctrine orm:schema-tool:drop --force
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
|
||||
Or you can use the update functionality:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
$ php bin/doctrine orm:schema-tool:update --force
|
||||
|
||||
The updating of databases uses a diff algorithm for a given
|
||||
database schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
|
||||
@@ -571,7 +584,7 @@ let's update the database schema:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
|
||||
$ php bin/doctrine orm:schema-tool:update --force --dump-sql
|
||||
|
||||
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
|
||||
statements to be executed and then printed to the screen.
|
||||
@@ -1268,7 +1281,7 @@ class that holds the owning sides.
|
||||
Update your database schema by running:
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
$ php bin/doctrine orm:schema-tool:update --force
|
||||
|
||||
|
||||
Implementing more Requirements
|
||||
|
||||
@@ -4,11 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use BackedEnum;
|
||||
use Countable;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
@@ -424,15 +426,20 @@ abstract class AbstractQuery
|
||||
return $value->name;
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
if (! is_object($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
try {
|
||||
$class = ClassUtils::getClass($value);
|
||||
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
|
||||
|
||||
if ($value === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);
|
||||
}
|
||||
} catch (MappingException | ORMMappingException $e) {
|
||||
/* Silence any mapping exceptions. These can occur if the object in
|
||||
|
||||
@@ -141,6 +141,8 @@ interface Cache
|
||||
* Evicts all cached query results under the given name, or default query cache if the region name is NULL.
|
||||
*
|
||||
* @param string|null $regionName The cache name associated to the queries being cached.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictQueryRegion($regionName = null);
|
||||
|
||||
|
||||
@@ -10,20 +10,26 @@ namespace Doctrine\ORM\Cache;
|
||||
class AssociationCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* The entity identifier
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string, mixed> The entity identifier
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $identifier;
|
||||
|
||||
/**
|
||||
* The entity class name
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The entity class name
|
||||
* @var string
|
||||
* @psalm-var class-string
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $class The entity class.
|
||||
* @param array<string, mixed> $identifier The entity identifier.
|
||||
* @psalm-param class-string $class
|
||||
*/
|
||||
public function __construct($class, array $identifier)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,8 @@ class CacheException extends ORMException
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is not used anymore.
|
||||
*
|
||||
* @param string $entityName
|
||||
*
|
||||
* @return CacheException
|
||||
@@ -45,6 +47,8 @@ class CacheException extends ORMException
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is not used anymore.
|
||||
*
|
||||
* @param string $entityName
|
||||
* @param string $field
|
||||
*
|
||||
|
||||
@@ -31,9 +31,7 @@ interface CacheFactory
|
||||
/**
|
||||
* Build a collection persister for the given relation mapping.
|
||||
*
|
||||
* @param EntityManagerInterface $em The entity manager.
|
||||
* @param CollectionPersister $persister The collection persister that will be cached.
|
||||
* @param mixed[] $mapping The association mapping.
|
||||
* @param mixed[] $mapping The association mapping.
|
||||
*
|
||||
* @return CachedCollectionPersister
|
||||
*/
|
||||
@@ -42,8 +40,7 @@ interface CacheFactory
|
||||
/**
|
||||
* Build a query cache based on the given region name
|
||||
*
|
||||
* @param EntityManagerInterface $em The Entity manager.
|
||||
* @param string $regionName The region name.
|
||||
* @param string|null $regionName The region name.
|
||||
*
|
||||
* @return QueryCache The built query cache.
|
||||
*/
|
||||
@@ -52,9 +49,6 @@ interface CacheFactory
|
||||
/**
|
||||
* Build an entity hydrator
|
||||
*
|
||||
* @param EntityManagerInterface $em The Entity manager.
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
*
|
||||
* @return EntityHydrator The built entity hydrator.
|
||||
*/
|
||||
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata);
|
||||
@@ -62,8 +56,7 @@ interface CacheFactory
|
||||
/**
|
||||
* Build a collection hydrator
|
||||
*
|
||||
* @param EntityManagerInterface $em The Entity manager.
|
||||
* @param mixed[] $mapping The association mapping.
|
||||
* @param mixed[] $mapping The association mapping.
|
||||
*
|
||||
* @return CollectionHydrator The built collection hydrator.
|
||||
*/
|
||||
|
||||
@@ -11,8 +11,10 @@ namespace Doctrine\ORM\Cache;
|
||||
abstract class CacheKey
|
||||
{
|
||||
/**
|
||||
* Unique identifier
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string Unique identifier
|
||||
* @var string
|
||||
*/
|
||||
public $hash;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ namespace Doctrine\ORM\Cache;
|
||||
class CollectionCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* The list of entity identifiers hold by the collection
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var CacheKey[] The list of entity identifiers hold by the collection
|
||||
* @var CacheKey[]
|
||||
*/
|
||||
public $identifiers;
|
||||
|
||||
|
||||
@@ -15,20 +15,27 @@ use function strtolower;
|
||||
class CollectionCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* The owner entity identifier
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string, mixed> The owner entity identifier
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $ownerIdentifier;
|
||||
|
||||
/**
|
||||
* The owner entity class
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The owner entity class
|
||||
* @var string
|
||||
* @psalm-var class-string
|
||||
*/
|
||||
public $entityClass;
|
||||
|
||||
/**
|
||||
* The association name
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The association name
|
||||
* @var string
|
||||
*/
|
||||
public $association;
|
||||
|
||||
@@ -36,6 +43,7 @@ class CollectionCacheKey extends CacheKey
|
||||
* @param string $entityClass The entity class.
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param array<string, mixed> $ownerIdentifier The identifier of the owning entity.
|
||||
* @psalm-param class-string $entityClass
|
||||
*/
|
||||
public function __construct($entityClass, $association, array $ownerIdentifier)
|
||||
{
|
||||
|
||||
@@ -14,8 +14,6 @@ use Doctrine\ORM\PersistentCollection;
|
||||
interface CollectionHydrator
|
||||
{
|
||||
/**
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param CollectionCacheKey $key The cached collection key.
|
||||
* @param array|mixed[]|Collection $collection The collection.
|
||||
*
|
||||
* @return CollectionCacheEntry
|
||||
@@ -23,11 +21,6 @@ interface CollectionHydrator
|
||||
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection);
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $metadata The owning entity metadata.
|
||||
* @param CollectionCacheKey $key The cached collection key.
|
||||
* @param CollectionCacheEntry $entry The cached collection entry.
|
||||
* @param PersistentCollection $collection The collection to load the cache into.
|
||||
*
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
|
||||
|
||||
@@ -29,7 +29,10 @@ class DefaultCache implements Cache
|
||||
/** @var CacheFactory */
|
||||
private $cacheFactory;
|
||||
|
||||
/** @var QueryCache[] */
|
||||
/**
|
||||
* @var QueryCache[]
|
||||
* @psalm-var array<string, QueryCache>
|
||||
*/
|
||||
private $queryCaches = [];
|
||||
|
||||
/** @var QueryCache|null */
|
||||
@@ -260,8 +263,7 @@ class DefaultCache implements Cache
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*/
|
||||
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier): EntityCacheKey
|
||||
{
|
||||
@@ -273,9 +275,7 @@ class DefaultCache implements Cache
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param mixed $ownerIdentifier The identifier of the owning entity.
|
||||
* @param mixed $ownerIdentifier The identifier of the owning entity.
|
||||
*/
|
||||
private function buildCollectionCacheKey(
|
||||
ClassMetadata $metadata,
|
||||
@@ -290,18 +290,20 @@ class DefaultCache implements Cache
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function toIdentifierArray(ClassMetadata $metadata, $identifier): array
|
||||
{
|
||||
if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) {
|
||||
$identifier = $this->uow->getSingleIdentifierValue($identifier);
|
||||
if (is_object($identifier)) {
|
||||
$class = ClassUtils::getClass($identifier);
|
||||
if ($this->em->getMetadataFactory()->hasMetadataFor($class)) {
|
||||
$identifier = $this->uow->getSingleIdentifierValue($identifier);
|
||||
|
||||
if ($identifier === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
if ($identifier === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -384,7 +384,8 @@ class DefaultQueryCache implements QueryCache
|
||||
/**
|
||||
* @param object $entity
|
||||
*
|
||||
* @return array<object>|object
|
||||
* @return mixed[]|object|null
|
||||
* @psalm-return list<mixed>|object|null
|
||||
*/
|
||||
private function getAssociationValue(
|
||||
ResultSetMapping $rsm,
|
||||
@@ -411,10 +412,11 @@ class DefaultQueryCache implements QueryCache
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array<mixed> $path
|
||||
* @param mixed $value
|
||||
* @psalm-param array<array-key, array{field: string, class: string}> $path
|
||||
*
|
||||
* @return mixed
|
||||
* @return mixed[]|object|null
|
||||
* @psalm-return list<mixed>|object|null
|
||||
*/
|
||||
private function getAssociationPathValue($value, array $path)
|
||||
{
|
||||
|
||||
@@ -14,14 +14,18 @@ use function array_map;
|
||||
class EntityCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* The entity map data
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string,mixed> The entity map data
|
||||
* @var array<string,mixed>
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* The entity class name
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The entity class name
|
||||
* @var string
|
||||
* @psalm-var class-string
|
||||
*/
|
||||
public $class;
|
||||
|
||||
@@ -15,20 +15,26 @@ use function strtolower;
|
||||
class EntityCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* The entity identifier
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string, mixed> The entity identifier
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $identifier;
|
||||
|
||||
/**
|
||||
* The entity class name
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The entity class name
|
||||
* @var string
|
||||
* @psalm-var class-string
|
||||
*/
|
||||
public $entityClass;
|
||||
|
||||
/**
|
||||
* @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
|
||||
* @param array<string, mixed> $identifier The entity identifier
|
||||
* @psalm-param class-string $entityClass
|
||||
*/
|
||||
public function __construct($entityClass, array $identifier)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param EntityCacheKey $key The cache key of the entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function entityCachePut($regionName, EntityCacheKey $key);
|
||||
|
||||
@@ -26,6 +28,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param EntityCacheKey $key The cache key of the entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function entityCacheHit($regionName, EntityCacheKey $key);
|
||||
|
||||
@@ -34,6 +38,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param EntityCacheKey $key The cache key of the entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function entityCacheMiss($regionName, EntityCacheKey $key);
|
||||
|
||||
@@ -42,6 +48,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param CollectionCacheKey $key The cache key of the collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectionCachePut($regionName, CollectionCacheKey $key);
|
||||
|
||||
@@ -50,6 +58,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param CollectionCacheKey $key The cache key of the collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectionCacheHit($regionName, CollectionCacheKey $key);
|
||||
|
||||
@@ -58,6 +68,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param CollectionCacheKey $key The cache key of the collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectionCacheMiss($regionName, CollectionCacheKey $key);
|
||||
|
||||
@@ -66,6 +78,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param QueryCacheKey $key The cache key of the query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function queryCachePut($regionName, QueryCacheKey $key);
|
||||
|
||||
@@ -74,6 +88,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param QueryCacheKey $key The cache key of the query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function queryCacheHit($regionName, QueryCacheKey $key);
|
||||
|
||||
@@ -82,6 +98,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param QueryCacheKey $key The cache key of the query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function queryCacheMiss($regionName, QueryCacheKey $key);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
|
||||
class CacheLoggerChain implements CacheLogger
|
||||
{
|
||||
/** @var array<CacheLogger> */
|
||||
/** @var array<string, CacheLogger> */
|
||||
private $loggers = [];
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ class CacheLoggerChain implements CacheLogger
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<CacheLogger>
|
||||
* @return array<string, CacheLogger>
|
||||
*/
|
||||
public function getLoggers()
|
||||
{
|
||||
|
||||
@@ -15,13 +15,13 @@ use function array_sum;
|
||||
*/
|
||||
class StatisticsCacheLogger implements CacheLogger
|
||||
{
|
||||
/** @var int[] */
|
||||
/** @var array<string, int> */
|
||||
private $cacheMissCountMap = [];
|
||||
|
||||
/** @var int[] */
|
||||
/** @var array<string, int> */
|
||||
private $cacheHitCountMap = [];
|
||||
|
||||
/** @var int[] */
|
||||
/** @var array<string, int> */
|
||||
private $cachePutCountMap = [];
|
||||
|
||||
/**
|
||||
@@ -29,9 +29,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheMissCountMap[$regionName]
|
||||
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,9 +38,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function collectionCacheHit($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheHitCountMap[$regionName]
|
||||
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +47,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function collectionCachePut($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cachePutCountMap[$regionName]
|
||||
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,9 +56,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function entityCacheMiss($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheMissCountMap[$regionName]
|
||||
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,9 +65,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function entityCacheHit($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheHitCountMap[$regionName]
|
||||
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,9 +74,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function entityCachePut($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cachePutCountMap[$regionName]
|
||||
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,9 +83,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function queryCacheHit($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheHitCountMap[$regionName]
|
||||
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,9 +92,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function queryCacheMiss($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheMissCountMap[$regionName]
|
||||
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,9 +101,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function queryCachePut($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cachePutCountMap[$regionName]
|
||||
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Doctrine\ORM\Cache;
|
||||
* Defines a region that supports multi-get reading.
|
||||
*
|
||||
* With one method call we can get multiple items.
|
||||
*
|
||||
* @deprecated Implement {@see Region} instead.
|
||||
*/
|
||||
interface MultiGetRegion
|
||||
{
|
||||
|
||||
@@ -13,11 +13,15 @@ interface CachedPersister
|
||||
{
|
||||
/**
|
||||
* Perform whatever processing is encapsulated here after completion of the transaction.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function afterTransactionComplete();
|
||||
|
||||
/**
|
||||
* Perform whatever processing is encapsulated here after completion of the rolled-back.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function afterTransactionRolledBack();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Cache\Persister\Collection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\CollectionHydrator;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
@@ -108,7 +109,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object[]|null
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key)
|
||||
{
|
||||
@@ -224,10 +225,18 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
/**
|
||||
* Clears cache entries related to the current collection
|
||||
*
|
||||
* @deprecated This method is not used anymore.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function evictCollectionCache(PersistentCollection $collection)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9512',
|
||||
'The method %s() is deprecated and will be removed without replacement.'
|
||||
);
|
||||
|
||||
$key = new CollectionCacheKey(
|
||||
$this->sourceEntity->rootEntityName,
|
||||
$this->association['fieldName'],
|
||||
@@ -242,13 +251,22 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is not used anymore.
|
||||
*
|
||||
* @param string $targetEntity
|
||||
* @param object $element
|
||||
* @psalm-param class-string $targetEntity
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function evictElementCache($targetEntity, $element)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9512',
|
||||
'The method %s() is deprecated and will be removed without replacement.'
|
||||
);
|
||||
|
||||
$targetPersister = $this->uow->getEntityPersister($targetEntity);
|
||||
assert($targetPersister instanceof CachedEntityPersister);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
|
||||
@@ -29,14 +29,14 @@ interface CachedCollectionPersister extends CachedPersister, CollectionPersister
|
||||
/**
|
||||
* Loads a collection from cache
|
||||
*
|
||||
* @return PersistentCollection|null
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key);
|
||||
|
||||
/**
|
||||
* Stores a collection into cache
|
||||
*
|
||||
* @param array|mixed[]|Collection $elements
|
||||
* @param mixed[]|Collection $elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
@@ -15,10 +15,7 @@ use function spl_object_id;
|
||||
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
{
|
||||
/**
|
||||
* @param CollectionPersister $persister The collection persister that will be cached.
|
||||
* @param ConcurrentRegion $region The collection region.
|
||||
* @param EntityManagerInterface $em The entity manager.
|
||||
* @param mixed[] $association The association mapping.
|
||||
* @param mixed[] $association The association mapping.
|
||||
*/
|
||||
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
|
||||
{
|
||||
|
||||
@@ -251,8 +251,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
* @param string $query
|
||||
* @param string[]|Criteria $criteria
|
||||
* @param string[] $orderBy
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -15,12 +15,6 @@ use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
*/
|
||||
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
{
|
||||
/**
|
||||
* @param EntityPersister $persister The entity persister to cache.
|
||||
* @param ConcurrentRegion $region The entity cache region.
|
||||
* @param EntityManagerInterface $em The entity manager.
|
||||
* @param ClassMetadata $class The entity metadata.
|
||||
*/
|
||||
public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class)
|
||||
{
|
||||
parent::__construct($persister, $region, $em, $class);
|
||||
|
||||
@@ -12,20 +12,24 @@ use function microtime;
|
||||
class QueryCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* List of entity identifiers
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string, mixed> List of entity identifiers
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $result;
|
||||
|
||||
/**
|
||||
* Time creation of this cache entry
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var float Time creation of this cache entry
|
||||
* @var float
|
||||
*/
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $result
|
||||
* @param float $time
|
||||
* @param float|null $time
|
||||
*/
|
||||
public function __construct($result, $time = null)
|
||||
{
|
||||
|
||||
@@ -12,8 +12,10 @@ use Doctrine\ORM\Cache;
|
||||
class QueryCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* Cache key lifetime
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var int Cache key lifetime
|
||||
* @var int
|
||||
*/
|
||||
public $lifetime;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class TimestampCacheEntry implements CacheEntry
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* @param float $time
|
||||
* @param float|null $time
|
||||
*/
|
||||
public function __construct($time = null)
|
||||
{
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace Doctrine\ORM\Cache;
|
||||
interface TimestampRegion extends Region
|
||||
{
|
||||
/**
|
||||
* Update an specific key into the cache region.
|
||||
* Update a specific key into the cache region.
|
||||
*
|
||||
* @param CacheKey $key The key of the item to update the timestamp.
|
||||
* @return void
|
||||
*
|
||||
* @throws LockException Indicates a problem accessing the region.
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,7 @@ use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache as CacheDriver;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
|
||||
use Doctrine\Common\Persistence\PersistentObject;
|
||||
use Doctrine\Common\Proxy\AbstractProxyFactory;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
@@ -23,6 +24,7 @@ use Doctrine\ORM\Cache\Exception\QueryCacheUsesNonPersistentCache;
|
||||
use Doctrine\ORM\Exception\InvalidEntityRepository;
|
||||
use Doctrine\ORM\Exception\NamedNativeQueryNotFound;
|
||||
use Doctrine\ORM\Exception\NamedQueryNotFound;
|
||||
use Doctrine\ORM\Exception\NotSupported;
|
||||
use Doctrine\ORM\Exception\ProxyClassesAlwaysRegenerating;
|
||||
use Doctrine\ORM\Exception\UnknownEntityNamespace;
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
@@ -34,6 +36,8 @@ use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
use Doctrine\ORM\Mapping\EntityListenerResolver;
|
||||
use Doctrine\ORM\Mapping\NamingStrategy;
|
||||
use Doctrine\ORM\Mapping\QuoteStrategy;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Query\Filter\SQLFilter;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
|
||||
use Doctrine\ORM\Repository\RepositoryFactory;
|
||||
@@ -41,9 +45,9 @@ use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use LogicException;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use ReflectionClass;
|
||||
|
||||
use function class_exists;
|
||||
use function is_a;
|
||||
use function method_exists;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
@@ -54,6 +58,8 @@ use function trim;
|
||||
* It combines all configuration options from DBAL & ORM.
|
||||
*
|
||||
* Internal note: When adding a new configuration option just write a getter/setter pair.
|
||||
*
|
||||
* @psalm-import-type AutogenerateMode from ProxyFactory
|
||||
*/
|
||||
class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
@@ -75,10 +81,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the directory where Doctrine generates any necessary proxy class files.
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
*
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProxyDir()
|
||||
@@ -89,11 +91,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the strategy for automatically generating proxy classes.
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
*
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*
|
||||
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
|
||||
* @psalm-return AutogenerateMode
|
||||
*/
|
||||
public function getAutoGenerateProxyClasses()
|
||||
{
|
||||
@@ -116,10 +115,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the namespace where proxy classes reside.
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
*
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProxyNamespace()
|
||||
@@ -156,6 +151,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Adds a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader
|
||||
* is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported.
|
||||
*
|
||||
* @deprecated Use {@see ORMSetup::createDefaultAnnotationDriver()} instead.
|
||||
*
|
||||
* @param string|string[] $paths
|
||||
* @param bool $useSimpleAnnotationReader
|
||||
* @psalm-param string|list<string> $paths
|
||||
@@ -164,6 +161,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9443',
|
||||
'%s is deprecated, call %s::createDefaultAnnotationDriver() instead.',
|
||||
__METHOD__,
|
||||
ORMSetup::class
|
||||
);
|
||||
|
||||
if (! class_exists(AnnotationReader::class)) {
|
||||
throw new LogicException(sprintf(
|
||||
'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library'
|
||||
@@ -193,6 +198,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated No replacement planned.
|
||||
*
|
||||
* Adds a namespace under a certain alias.
|
||||
*
|
||||
* @param string $alias
|
||||
@@ -202,6 +209,21 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function addEntityNamespace($alias, $namespace)
|
||||
{
|
||||
if (class_exists(PersistentObject::class)) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8818',
|
||||
'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.',
|
||||
$alias
|
||||
);
|
||||
} else {
|
||||
NotSupported::createForPersistence3(sprintf(
|
||||
'Using short namespace alias "%s" by calling %s',
|
||||
$alias,
|
||||
__METHOD__
|
||||
));
|
||||
}
|
||||
|
||||
$this->_attributes['entityNamespaces'][$alias] = $namespace;
|
||||
}
|
||||
|
||||
@@ -548,7 +570,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
throw QueryCacheUsesNonPersistentCache::fromDriver($queryCacheImpl);
|
||||
}
|
||||
|
||||
if ($this->getAutoGenerateProxyClasses()) {
|
||||
if ($this->getAutoGenerateProxyClasses() !== AbstractProxyFactory::AUTOGENERATE_NEVER) {
|
||||
throw ProxyClassesAlwaysRegenerating::create();
|
||||
}
|
||||
|
||||
@@ -794,6 +816,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name The name of the filter.
|
||||
* @param string $className The class name of the filter.
|
||||
* @psalm-param class-string<SQLFilter> $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -809,7 +832,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @return string|null The class name of the filter, or null if it is not
|
||||
* defined.
|
||||
* @psalm-return ?class-string
|
||||
* @psalm-return class-string<SQLFilter>|null
|
||||
*/
|
||||
public function getFilterClassName($name)
|
||||
{
|
||||
@@ -820,6 +843,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets default repository class.
|
||||
*
|
||||
* @param string $className
|
||||
* @psalm-param class-string<EntityRepository> $className
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
@@ -827,12 +851,20 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setDefaultRepositoryClassName($className)
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($className);
|
||||
|
||||
if (! $reflectionClass->implementsInterface(ObjectRepository::class)) {
|
||||
if (! class_exists($className) || ! is_a($className, ObjectRepository::class, true)) {
|
||||
throw InvalidEntityRepository::fromClassName($className);
|
||||
}
|
||||
|
||||
if (! is_a($className, EntityRepository::class, true)) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9533',
|
||||
'Configuring %s as default repository class is deprecated because it does not extend %s.',
|
||||
$className,
|
||||
EntityRepository::class
|
||||
);
|
||||
}
|
||||
|
||||
$this->_attributes['defaultRepositoryClassName'] = $className;
|
||||
}
|
||||
|
||||
@@ -840,7 +872,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Get default repository class.
|
||||
*
|
||||
* @return string
|
||||
* @psalm-return class-string
|
||||
* @psalm-return class-string<EntityRepository>
|
||||
*/
|
||||
public function getDefaultRepositoryClassName()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Decorator;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Persistence\ObjectManagerDecorator;
|
||||
|
||||
@@ -43,6 +44,28 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
|
||||
return $this->wrapped->getExpressionBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @psalm-param class-string<T> $className
|
||||
*
|
||||
* @psalm-return EntityRepository<T>
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
public function getRepository($className)
|
||||
{
|
||||
return $this->wrapped->getRepository($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClassMetadata($className)
|
||||
{
|
||||
return $this->wrapped->getClassMetadata($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -4,9 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use BackedEnum;
|
||||
use BadMethodCallException;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\Common\Persistence\PersistentObject;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
@@ -17,6 +19,7 @@ use Doctrine\ORM\Exception\InvalidHydrationMode;
|
||||
use Doctrine\ORM\Exception\MismatchedEventManager;
|
||||
use Doctrine\ORM\Exception\MissingIdentifierField;
|
||||
use Doctrine\ORM\Exception\MissingMappingDriverImplementation;
|
||||
use Doctrine\ORM\Exception\NotSupported;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Exception\UnrecognizedIdentifierFields;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -33,6 +36,7 @@ use Throwable;
|
||||
|
||||
use function array_keys;
|
||||
use function call_user_func;
|
||||
use function class_exists;
|
||||
use function get_debug_type;
|
||||
use function gettype;
|
||||
use function is_array;
|
||||
@@ -41,6 +45,7 @@ use function is_object;
|
||||
use function is_string;
|
||||
use function ltrim;
|
||||
use function sprintf;
|
||||
use function strpos;
|
||||
|
||||
/**
|
||||
* The EntityManager is the central access point to ORM functionality.
|
||||
@@ -433,11 +438,14 @@ use function sprintf;
|
||||
}
|
||||
|
||||
foreach ($id as $i => $value) {
|
||||
if (is_object($value) && $this->metadataFactory->hasMetadataFor(ClassUtils::getClass($value))) {
|
||||
$id[$i] = $this->unitOfWork->getSingleIdentifierValue($value);
|
||||
if (is_object($value)) {
|
||||
$className = ClassUtils::getClass($value);
|
||||
if ($this->metadataFactory->hasMetadataFor($className)) {
|
||||
$id[$i] = $this->unitOfWork->getSingleIdentifierValue($value);
|
||||
|
||||
if ($id[$i] === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
if ($id[$i] === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,7 +457,12 @@ use function sprintf;
|
||||
throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name);
|
||||
}
|
||||
|
||||
$sortedId[$identifier] = $id[$identifier];
|
||||
if ($id[$identifier] instanceof BackedEnum) {
|
||||
$sortedId[$identifier] = $id[$identifier]->value;
|
||||
} else {
|
||||
$sortedId[$identifier] = $id[$identifier];
|
||||
}
|
||||
|
||||
unset($id[$identifier]);
|
||||
}
|
||||
|
||||
@@ -783,7 +796,35 @@ use function sprintf;
|
||||
*/
|
||||
public function getRepository($entityName)
|
||||
{
|
||||
return $this->repositoryFactory->getRepository($this, $entityName);
|
||||
if (strpos($entityName, ':') !== false) {
|
||||
if (class_exists(PersistentObject::class)) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8818',
|
||||
'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.',
|
||||
$entityName
|
||||
);
|
||||
} else {
|
||||
NotSupported::createForPersistence3(sprintf(
|
||||
'Using short namespace alias "%s" when calling %s',
|
||||
$entityName,
|
||||
__METHOD__
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$repository = $this->repositoryFactory->getRepository($this, $entityName);
|
||||
if (! $repository instanceof EntityRepository) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9533',
|
||||
'Not returning an instance of %s from %s::getRepository() is deprecated and will cause a TypeError on 3.0.',
|
||||
EntityRepository::class,
|
||||
get_debug_type($this->repositoryFactory)
|
||||
);
|
||||
}
|
||||
|
||||
return $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,22 +5,25 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\AbstractLazyCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\Common\Persistence\PersistentObject;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\Inflector\Inflector;
|
||||
use Doctrine\Inflector\InflectorFactory;
|
||||
use Doctrine\ORM\Exception\NotSupported;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
use function array_slice;
|
||||
use function class_exists;
|
||||
use function lcfirst;
|
||||
use function sprintf;
|
||||
use function strpos;
|
||||
use function str_starts_with;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
@@ -36,22 +39,36 @@ use function substr;
|
||||
*/
|
||||
class EntityRepository implements ObjectRepository, Selectable
|
||||
{
|
||||
/** @var string */
|
||||
/**
|
||||
* @internal This property will be private in 3.0, call {@see getEntityName()} instead.
|
||||
*
|
||||
* @var string
|
||||
* @psalm-var class-string<T>
|
||||
*/
|
||||
protected $_entityName;
|
||||
|
||||
/** @var EntityManagerInterface */
|
||||
/**
|
||||
* @internal This property will be private in 3.0, call {@see getEntityManager()} instead.
|
||||
*
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $_em;
|
||||
|
||||
/** @var ClassMetadata */
|
||||
/**
|
||||
* @internal This property will be private in 3.0, call {@see getClassMetadata()} instead.
|
||||
*
|
||||
* @var ClassMetadata
|
||||
* @psalm-var ClassMetadata<T>
|
||||
*/
|
||||
protected $_class;
|
||||
|
||||
/** @var Inflector */
|
||||
/** @var Inflector|null */
|
||||
private static $inflector;
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>EntityRepository</tt>.
|
||||
* @psalm-param ClassMetadata<T> $class
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
|
||||
public function __construct(EntityManagerInterface $em, ClassMetadata $class)
|
||||
{
|
||||
$this->_entityName = $class->name;
|
||||
$this->_em = $em;
|
||||
@@ -61,8 +78,8 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
/**
|
||||
* Creates a new QueryBuilder instance that is prepopulated for this entity name.
|
||||
*
|
||||
* @param string $alias
|
||||
* @param string $indexBy The index for the from.
|
||||
* @param string $alias
|
||||
* @param string|null $indexBy The index for the from.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
@@ -154,6 +171,13 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
if (! class_exists(PersistentObject::class)) {
|
||||
throw NotSupported::createForPersistence3(sprintf(
|
||||
'Partial clearing of entities for class %s',
|
||||
$this->_class->rootEntityName
|
||||
));
|
||||
}
|
||||
|
||||
$this->_em->clear($this->_class->rootEntityName);
|
||||
}
|
||||
|
||||
@@ -246,15 +270,15 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
*/
|
||||
public function __call($method, $arguments)
|
||||
{
|
||||
if (strpos($method, 'findBy') === 0) {
|
||||
if (str_starts_with($method, 'findBy')) {
|
||||
return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
|
||||
}
|
||||
|
||||
if (strpos($method, 'findOneBy') === 0) {
|
||||
if (str_starts_with($method, 'findOneBy')) {
|
||||
return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
|
||||
}
|
||||
|
||||
if (strpos($method, 'countBy') === 0) {
|
||||
if (str_starts_with($method, 'countBy')) {
|
||||
return $this->resolveMagicCall('count', substr($method, 7), $arguments);
|
||||
}
|
||||
|
||||
@@ -267,6 +291,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @psalm-return class-string<T>
|
||||
*/
|
||||
protected function getEntityName()
|
||||
{
|
||||
@@ -274,7 +299,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClassName()
|
||||
{
|
||||
@@ -290,7 +315,8 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Mapping\ClassMetadata
|
||||
* @return ClassMetadata
|
||||
* @psalm-return ClassMetadata<T>
|
||||
*/
|
||||
protected function getClassMetadata()
|
||||
{
|
||||
@@ -301,8 +327,8 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
* Select all elements from a selectable that match the expression and
|
||||
* return a new collection containing these elements.
|
||||
*
|
||||
* @return LazyCriteriaCollection
|
||||
* @psalm-return Collection<int, T>
|
||||
* @return AbstractLazyCollection
|
||||
* @psalm-return AbstractLazyCollection<int, T>&Selectable<int, T>
|
||||
*/
|
||||
public function matching(Criteria $criteria)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ use Doctrine\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
|
||||
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
|
||||
* of entities.
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
* @extends BaseLifecycleEventArgs<EntityManagerInterface>
|
||||
*/
|
||||
class LifecycleEventArgs extends BaseLifecycleEventArgs
|
||||
{
|
||||
|
||||
@@ -11,8 +11,7 @@ use Doctrine\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetada
|
||||
/**
|
||||
* Class that holds event arguments for a loadMetadata event.
|
||||
*
|
||||
* @method __construct(ClassMetadata $classMetadata, EntityManagerInterface $objectManager)
|
||||
* @method ClassMetadata getClassMetadata()
|
||||
* @extends BaseLoadClassMetadataEventArgs<ClassMetadata<object>, EntityManagerInterface>
|
||||
*/
|
||||
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\Event\ManagerEventArgs;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
@@ -13,6 +14,8 @@ use Doctrine\Persistence\ObjectManager;
|
||||
*
|
||||
* This object is mutable by design, allowing callbacks having access to it to set the
|
||||
* found metadata in it, and therefore "cancelling" a `onClassMetadataNotFound` event
|
||||
*
|
||||
* @extends ManagerEventArgs<EntityManagerInterface>
|
||||
*/
|
||||
class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs
|
||||
{
|
||||
@@ -23,7 +26,8 @@ class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs
|
||||
private $foundMetadata;
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param string $className
|
||||
* @param EntityManagerInterface $objectManager
|
||||
*/
|
||||
public function __construct($className, ObjectManager $objectManager)
|
||||
{
|
||||
|
||||
@@ -42,6 +42,8 @@ class OnClearEventArgs extends EventArgs
|
||||
/**
|
||||
* Name of the entity class that is cleared, or empty if all are cleared.
|
||||
*
|
||||
* @deprecated Clearing the entity manager partially is deprecated. This method will be removed in 3.0.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEntityClass()
|
||||
@@ -52,6 +54,8 @@ class OnClearEventArgs extends EventArgs
|
||||
/**
|
||||
* Checks if event clears all entities.
|
||||
*
|
||||
* @deprecated Clearing the entity manager partially is deprecated. This method will be removed in 3.0.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function clearsAllEntities()
|
||||
|
||||
@@ -4,14 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Exception;
|
||||
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
final class InvalidEntityRepository extends ORMException implements ConfigurationException
|
||||
{
|
||||
public static function fromClassName(string $className): self
|
||||
{
|
||||
return new self(
|
||||
"Invalid repository class '" . $className . "'. It must be a " . ObjectRepository::class . '.'
|
||||
"Invalid repository class '" . $className . "'. It must be a " . EntityRepository::class . '.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Exception;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
final class NotSupported extends ORMException
|
||||
{
|
||||
public static function create(): self
|
||||
@@ -11,8 +13,29 @@ final class NotSupported extends ORMException
|
||||
return new self('This behaviour is (currently) not supported by Doctrine 2');
|
||||
}
|
||||
|
||||
public static function createForDbal3(): self
|
||||
public static function createForDbal3(string $context): self
|
||||
{
|
||||
return new self('Feature was deprecated in doctrine/dbal 2.x and is not supported by installed doctrine/dbal:3.x, please see the doctrine/deprecations logs for new alternative approaches.');
|
||||
return new self(sprintf(
|
||||
<<<'EXCEPTION'
|
||||
Context: %s
|
||||
Problem: Feature was deprecated in doctrine/dbal 2.x and is not supported by installed doctrine/dbal:3.x
|
||||
Solution: See the doctrine/deprecations logs for new alternative approaches.
|
||||
EXCEPTION
|
||||
,
|
||||
$context
|
||||
));
|
||||
}
|
||||
|
||||
public static function createForPersistence3(string $context): self
|
||||
{
|
||||
return new self(sprintf(
|
||||
<<<'EXCEPTION'
|
||||
Context: %s
|
||||
Problem: Feature was deprecated in doctrine/persistence 2.x and is not supported by installed doctrine/persistence:3.x
|
||||
Solution: See the doctrine/deprecations logs for new alternative approaches.
|
||||
EXCEPTION
|
||||
,
|
||||
$context
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace Doctrine\ORM\Exception;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* @deprecated No replacement planned.
|
||||
*/
|
||||
final class UnknownEntityNamespace extends ORMException implements ConfigurationException
|
||||
{
|
||||
public static function fromNamespaceAlias(string $entityNamespaceAlias): self
|
||||
|
||||
@@ -62,7 +62,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
|
||||
$connection->ensureConnectedToPrimary();
|
||||
}
|
||||
|
||||
$this->_nextValue = (int) $connection->executeQuery($sql)->fetchOne();
|
||||
$this->_nextValue = (int) $connection->fetchOne($sql);
|
||||
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Exception\NotSupported;
|
||||
|
||||
use function method_exists;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Represents an ID generator that uses the database UUID expression
|
||||
@@ -29,7 +30,10 @@ class UuidGenerator extends AbstractIdGenerator
|
||||
);
|
||||
|
||||
if (! method_exists(AbstractPlatform::class, 'getGuidExpression')) {
|
||||
throw NotSupported::createForDbal3();
|
||||
throw NotSupported::createForDbal3(sprintf(
|
||||
'Using the database to generate a UUID through %s',
|
||||
self::class
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\Hydration;
|
||||
|
||||
use BackedEnum;
|
||||
use Doctrine\DBAL\Driver\ResultStatement;
|
||||
use Doctrine\DBAL\ForwardCompatibility\Result as ForwardCompatibilityResult;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
@@ -27,6 +28,7 @@ use function count;
|
||||
use function end;
|
||||
use function get_debug_type;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
@@ -429,7 +431,20 @@ abstract class AbstractHydrator
|
||||
$type = $cacheKeyInfo['type'];
|
||||
$value = $type->convertToPHPValue($value, $this->_platform);
|
||||
|
||||
// Reimplement ReflectionEnumProperty code
|
||||
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
|
||||
$enumType = $cacheKeyInfo['enumType'];
|
||||
if (is_array($value)) {
|
||||
$value = array_map(static function ($value) use ($enumType): BackedEnum {
|
||||
return $enumType::from($value);
|
||||
}, $value);
|
||||
} else {
|
||||
$value = $enumType::from($value);
|
||||
}
|
||||
}
|
||||
|
||||
$rowData['scalars'][$fieldName] = $value;
|
||||
|
||||
break;
|
||||
|
||||
//case (isset($cacheKeyInfo['isMetaColumn'])):
|
||||
@@ -572,6 +587,7 @@ abstract class AbstractHydrator
|
||||
'fieldName' => $this->_rsm->scalarMappings[$key],
|
||||
'type' => Type::getType($this->_rsm->typeMappings[$key]),
|
||||
'dqlAlias' => '',
|
||||
'enumType' => $this->_rsm->enumMappings[$key] ?? null,
|
||||
];
|
||||
|
||||
case isset($this->_rsm->scalarMappings[$key]):
|
||||
@@ -579,6 +595,7 @@ abstract class AbstractHydrator
|
||||
'isScalar' => true,
|
||||
'fieldName' => $this->_rsm->scalarMappings[$key],
|
||||
'type' => Type::getType($this->_rsm->typeMappings[$key]),
|
||||
'enumType' => $this->_rsm->enumMappings[$key] ?? null,
|
||||
];
|
||||
|
||||
case isset($this->_rsm->metaMappings[$key]):
|
||||
|
||||
@@ -84,17 +84,17 @@ class HydrationException extends ORMException
|
||||
|
||||
/**
|
||||
* @param string $discrValue
|
||||
* @param string[] $discrMap
|
||||
* @psalm-param array<string, string> $discrMap
|
||||
* @param string[] $discrValues
|
||||
* @psalm-param list<string> $discrValues
|
||||
*
|
||||
* @return HydrationException
|
||||
*/
|
||||
public static function invalidDiscriminatorValue($discrValue, $discrMap)
|
||||
public static function invalidDiscriminatorValue($discrValue, $discrValues)
|
||||
{
|
||||
return new self(sprintf(
|
||||
'The discriminator value "%s" is invalid. It must be one of "%s".',
|
||||
$discrValue,
|
||||
implode('", "', $discrMap)
|
||||
implode('", "', $discrValues)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,8 +394,11 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$reflFieldValue->hydrateSet($indexValue, $element);
|
||||
$this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
|
||||
} else {
|
||||
$reflFieldValue->hydrateAdd($element);
|
||||
$reflFieldValue->last();
|
||||
if (! $reflFieldValue->contains($element)) {
|
||||
$reflFieldValue->hydrateAdd($element);
|
||||
$reflFieldValue->last();
|
||||
}
|
||||
|
||||
$this->identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,18 @@ use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* A lazy collection that allows a fast count when using criteria object
|
||||
* Once count gets executed once without collection being initialized, result
|
||||
* is cached and returned on subsequent calls until collection gets loaded,
|
||||
* then returning the number of loaded results.
|
||||
*
|
||||
* @template TKey of array-key
|
||||
* @template TValue of object
|
||||
* @extends AbstractLazyCollection<TKey, TValue>
|
||||
* @implements Selectable<TKey, TValue>
|
||||
*/
|
||||
class LazyCriteriaCollection extends AbstractLazyCollection implements Selectable
|
||||
{
|
||||
@@ -72,6 +79,7 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
|
||||
* Do an optimized search of an element
|
||||
*
|
||||
* @param object $element
|
||||
* @psalm-param TValue $element
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
@@ -90,6 +98,7 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
|
||||
public function matching(Criteria $criteria)
|
||||
{
|
||||
$this->initialize();
|
||||
assert($this->collection instanceof Selectable);
|
||||
|
||||
return $this->collection->matching($criteria);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ use function end;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use function is_subclass_of;
|
||||
use function str_contains;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
|
||||
@@ -193,6 +193,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
$class->containsForeignIdentifier = true;
|
||||
}
|
||||
|
||||
if ($parent->containsEnumIdentifier) {
|
||||
$class->containsEnumIdentifier = true;
|
||||
}
|
||||
|
||||
if (! empty($parent->namedQueries)) {
|
||||
$this->addInheritedNamedQueries($class, $parent);
|
||||
}
|
||||
@@ -338,7 +342,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
*/
|
||||
private function getShortName(string $className): string
|
||||
{
|
||||
if (strpos($className, '\\') === false) {
|
||||
if (! str_contains($className, '\\')) {
|
||||
return strtolower($className);
|
||||
}
|
||||
|
||||
@@ -718,7 +722,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @deprecated This method will be removed in ORM 3.0.
|
||||
*
|
||||
* @return class-string
|
||||
*/
|
||||
protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
|
||||
{
|
||||
|
||||
@@ -49,8 +49,8 @@ use function is_subclass_of;
|
||||
use function ltrim;
|
||||
use function method_exists;
|
||||
use function spl_object_id;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trait_exists;
|
||||
use function trim;
|
||||
@@ -519,7 +519,9 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*
|
||||
* @see discriminatorColumn
|
||||
*
|
||||
* @var mixed
|
||||
* @var array<string, string>
|
||||
*
|
||||
* @psalm-var array<string, class-string>
|
||||
*/
|
||||
public $discriminatorMap = [];
|
||||
|
||||
@@ -543,10 +545,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* @var mixed[]
|
||||
* @psalm-var array{
|
||||
* name: string,
|
||||
* schema: string,
|
||||
* indexes: array,
|
||||
* uniqueConstraints: array,
|
||||
* options: array<string, mixed>,
|
||||
* schema?: string,
|
||||
* indexes?: array,
|
||||
* uniqueConstraints?: array,
|
||||
* options?: array<string, mixed>,
|
||||
* quoted?: bool
|
||||
* }
|
||||
*/
|
||||
@@ -639,6 +641,15 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*/
|
||||
public $containsForeignIdentifier = false;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Flag indicating whether the identifier/primary key contains at least one ENUM type.
|
||||
*
|
||||
* This flag is necessary because some code blocks require special treatment of this cases.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $containsEnumIdentifier = false;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The ID generator used for generating IDs for this class.
|
||||
*
|
||||
@@ -958,6 +969,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$serialized[] = 'containsForeignIdentifier';
|
||||
}
|
||||
|
||||
if ($this->containsEnumIdentifier) {
|
||||
$serialized[] = 'containsEnumIdentifier';
|
||||
}
|
||||
|
||||
if ($this->isVersioned) {
|
||||
$serialized[] = 'isVersioned';
|
||||
$serialized[] = 'versionField';
|
||||
@@ -1675,6 +1690,10 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
if (! enum_exists($mapping['enumType'])) {
|
||||
throw MappingException::nonEnumTypeMapped($this->name, $mapping['fieldName'], $mapping['enumType']);
|
||||
}
|
||||
|
||||
if (! empty($mapping['id'])) {
|
||||
$this->containsEnumIdentifier = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
@@ -2671,7 +2690,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
{
|
||||
if (isset($table['name'])) {
|
||||
// Split schema and table name from a table name like "myschema.mytable"
|
||||
if (strpos($table['name'], '.') !== false) {
|
||||
if (str_contains($table['name'], '.')) {
|
||||
[$this->table['schema'], $table['name']] = explode('.', $table['name'], 2);
|
||||
}
|
||||
|
||||
@@ -2917,7 +2936,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
|
||||
if (! isset($field['column'])) {
|
||||
$fieldName = $field['name'];
|
||||
if (strpos($fieldName, '.')) {
|
||||
if (str_contains($fieldName, '.')) {
|
||||
[, $fieldName] = explode('.', $fieldName);
|
||||
}
|
||||
|
||||
@@ -3701,7 +3720,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
return $className;
|
||||
}
|
||||
|
||||
if (strpos($className, '\\') === false && $this->namespace) {
|
||||
if (! str_contains($className, '\\') && $this->namespace) {
|
||||
return $this->namespace . '\\' . $className;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use function strpos;
|
||||
use function str_contains;
|
||||
use function strrpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
@@ -21,7 +21,7 @@ class DefaultNamingStrategy implements NamingStrategy
|
||||
*/
|
||||
public function classToTableName($className)
|
||||
{
|
||||
if (strpos($className, '\\') !== false) {
|
||||
if (str_contains($className, '\\')) {
|
||||
return substr($className, strrpos($className, '\\') + 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,15 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionProperty;
|
||||
@@ -29,8 +31,19 @@ use function is_numeric;
|
||||
/**
|
||||
* The AnnotationDriver reads the mapping metadata from docblock annotations.
|
||||
*/
|
||||
class AnnotationDriver extends AbstractAnnotationDriver
|
||||
class AnnotationDriver extends CompatibilityAnnotationDriver
|
||||
{
|
||||
use ColocatedMappingDriver;
|
||||
|
||||
/**
|
||||
* The annotation reader.
|
||||
*
|
||||
* @internal this property will be private in 3.0
|
||||
*
|
||||
* @var Reader
|
||||
*/
|
||||
protected $reader;
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
* @psalm-var array<class-string, int>
|
||||
@@ -41,11 +54,29 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
|
||||
* docblock annotations.
|
||||
*
|
||||
* @param Reader $reader The AnnotationReader to use
|
||||
* @param string|string[]|null $paths One or multiple paths where mapping classes can be found.
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata)
|
||||
public function __construct($reader, $paths = null)
|
||||
{
|
||||
$this->reader = $reader;
|
||||
|
||||
$this->addPaths((array) $paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @psalm-param class-string<T> $className
|
||||
* @psalm-param ClassMetadata<T> $metadata
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
|
||||
{
|
||||
assert($metadata instanceof Mapping\ClassMetadata);
|
||||
$class = $metadata->getReflectionClass()
|
||||
// this happens when running annotation driver in combination with
|
||||
// static reflection services. This is not the nicest fix
|
||||
@@ -271,7 +302,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value)
|
||||
);
|
||||
|
||||
if ($metadata->inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
|
||||
if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
// Evaluate DiscriminatorColumn annotation
|
||||
if (isset($classAnnotations[Mapping\DiscriminatorColumn::class])) {
|
||||
$discrColumnAnnot = $classAnnotations[Mapping\DiscriminatorColumn::class];
|
||||
@@ -516,7 +547,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
private function loadRelationShipMapping(
|
||||
ReflectionProperty $property,
|
||||
array &$mapping,
|
||||
ClassMetadata $metadata,
|
||||
PersistenceClassMetadata $metadata,
|
||||
array $joinColumns,
|
||||
string $className
|
||||
): void {
|
||||
@@ -620,7 +651,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
/**
|
||||
* Attempts to resolve the fetch mode.
|
||||
*
|
||||
* @psalm-return \Doctrine\ORM\Mapping\ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
|
||||
* @psalm-return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
|
||||
*
|
||||
* @throws MappingException If the fetch mode is not valid.
|
||||
*/
|
||||
@@ -636,7 +667,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
/**
|
||||
* Attempts to resolve the generated mode.
|
||||
*
|
||||
* @psalm-return ClassMetadataInfo::GENERATED_*
|
||||
* @psalm-return ClassMetadata::GENERATED_*
|
||||
*
|
||||
* @throws MappingException If the fetch mode is not valid.
|
||||
*/
|
||||
@@ -736,7 +767,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
* precision: int,
|
||||
* notInsertable?: bool,
|
||||
* notUpdateble?: bool,
|
||||
* generated?: ClassMetadataInfo::GENERATED_*,
|
||||
* generated?: ClassMetadata::GENERATED_*,
|
||||
* enumType?: class-string,
|
||||
* options?: mixed[],
|
||||
* columnName?: string,
|
||||
@@ -786,6 +817,39 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current annotation reader
|
||||
*
|
||||
* @return Reader
|
||||
*/
|
||||
public function getReader()
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9587',
|
||||
'%s is deprecated with no replacement',
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
return $this->reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isTransient($className)
|
||||
{
|
||||
$classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
|
||||
|
||||
foreach ($classAnnotations as $annot) {
|
||||
if (isset($this->entityAnnotationClasses[get_class($annot)])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for the Annotation Driver.
|
||||
*
|
||||
|
||||
@@ -4,13 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\AnnotationDriver;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
|
||||
use LogicException;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
@@ -25,8 +26,10 @@ use function sprintf;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
class AttributeDriver extends AnnotationDriver
|
||||
class AttributeDriver extends CompatibilityAnnotationDriver
|
||||
{
|
||||
use ColocatedMappingDriver;
|
||||
|
||||
/** @var array<string,int> */
|
||||
// @phpcs:ignore
|
||||
protected $entityAnnotationClasses = [
|
||||
@@ -34,6 +37,15 @@ class AttributeDriver extends AnnotationDriver
|
||||
Mapping\MappedSuperclass::class => 2,
|
||||
];
|
||||
|
||||
/**
|
||||
* The annotation reader.
|
||||
*
|
||||
* @internal this property will be private in 3.0
|
||||
*
|
||||
* @var AttributeReader
|
||||
*/
|
||||
protected $reader;
|
||||
|
||||
/**
|
||||
* @param array<string> $paths
|
||||
*/
|
||||
@@ -46,7 +58,27 @@ class AttributeDriver extends AnnotationDriver
|
||||
));
|
||||
}
|
||||
|
||||
parent::__construct(new AttributeReader(), $paths);
|
||||
$this->reader = new AttributeReader();
|
||||
$this->addPaths($paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current annotation reader
|
||||
*
|
||||
* @deprecated no replacement planned.
|
||||
*
|
||||
* @return AttributeReader
|
||||
*/
|
||||
public function getReader()
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9587',
|
||||
'%s is deprecated with no replacement',
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
return $this->reader;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,10 +98,16 @@ class AttributeDriver extends AnnotationDriver
|
||||
return true;
|
||||
}
|
||||
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata): void
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @psalm-param class-string<T> $className
|
||||
* @psalm-param ClassMetadata<T> $metadata
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
|
||||
{
|
||||
assert($metadata instanceof ClassMetadataInfo);
|
||||
|
||||
$reflectionClass = $metadata->getReflectionClass();
|
||||
|
||||
$classAttributes = $this->reader->getClassAnnotations($reflectionClass);
|
||||
@@ -206,7 +244,7 @@ class AttributeDriver extends AnnotationDriver
|
||||
constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value)
|
||||
);
|
||||
|
||||
if ($metadata->inheritanceType !== Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
// Evaluate DiscriminatorColumn annotation
|
||||
if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
|
||||
$discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
|
||||
@@ -271,7 +309,7 @@ class AttributeDriver extends AnnotationDriver
|
||||
// Check for JoinColumn/JoinColumns annotations
|
||||
$joinColumns = [];
|
||||
|
||||
$joinColumnAttributes = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class);
|
||||
$joinColumnAttributes = $this->reader->getPropertyAnnotationCollection($property, Mapping\JoinColumn::class);
|
||||
|
||||
foreach ($joinColumnAttributes as $joinColumnAttribute) {
|
||||
$joinColumns[] = $this->joinColumnToArray($joinColumnAttribute);
|
||||
@@ -376,11 +414,11 @@ class AttributeDriver extends AnnotationDriver
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class) as $joinColumn) {
|
||||
foreach ($this->reader->getPropertyAnnotationCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
|
||||
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
|
||||
}
|
||||
|
||||
foreach ($this->reader->getPropertyAnnotation($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
|
||||
foreach ($this->reader->getPropertyAnnotationCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
|
||||
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
|
||||
}
|
||||
|
||||
@@ -459,7 +497,7 @@ class AttributeDriver extends AnnotationDriver
|
||||
|
||||
// Check for `fetch`
|
||||
if ($associationOverride->fetch) {
|
||||
$override['fetch'] = constant(Mapping\ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
|
||||
$override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
|
||||
}
|
||||
|
||||
$metadata->setAssociationOverride($fieldName, $override);
|
||||
@@ -526,7 +564,7 @@ class AttributeDriver extends AnnotationDriver
|
||||
* @param string $className The class name.
|
||||
* @param string $fetchMode The fetch mode.
|
||||
*
|
||||
* @return int The fetch mode as defined in ClassMetadata.
|
||||
* @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
|
||||
*
|
||||
* @throws MappingException If the fetch mode is not valid.
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\ORM\Mapping\Annotation;
|
||||
use LogicException;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
@@ -14,58 +15,93 @@ use ReflectionProperty;
|
||||
use function assert;
|
||||
use function is_string;
|
||||
use function is_subclass_of;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class AttributeReader
|
||||
{
|
||||
/** @var array<string,bool> */
|
||||
/** @var array<class-string<Annotation>,bool> */
|
||||
private array $isRepeatableAttribute = [];
|
||||
|
||||
/** @return array<Annotation|RepeatableAttributeCollection> */
|
||||
/**
|
||||
* @psalm-return class-string-map<T, T|RepeatableAttributeCollection<T>>
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getClassAnnotations(ReflectionClass $class): array
|
||||
{
|
||||
return $this->convertToAttributeInstances($class->getAttributes());
|
||||
}
|
||||
|
||||
/** @return Annotation|RepeatableAttributeCollection|null */
|
||||
public function getClassAnnotation(ReflectionClass $class, $annotationName)
|
||||
{
|
||||
return $this->getClassAnnotations($class)[$annotationName]
|
||||
?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
|
||||
}
|
||||
|
||||
/** @return array<Annotation|RepeatableAttributeCollection> */
|
||||
/**
|
||||
* @return class-string-map<T, T|RepeatableAttributeCollection<T>>
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getMethodAnnotations(ReflectionMethod $method): array
|
||||
{
|
||||
return $this->convertToAttributeInstances($method->getAttributes());
|
||||
}
|
||||
|
||||
/** @return Annotation|RepeatableAttributeCollection|null */
|
||||
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
|
||||
{
|
||||
return $this->getMethodAnnotations($method)[$annotationName]
|
||||
?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
|
||||
}
|
||||
|
||||
/** @return array<Annotation|RepeatableAttributeCollection> */
|
||||
/**
|
||||
* @return class-string-map<T, T|RepeatableAttributeCollection<T>>
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getPropertyAnnotations(ReflectionProperty $property): array
|
||||
{
|
||||
return $this->convertToAttributeInstances($property->getAttributes());
|
||||
}
|
||||
|
||||
/** @return Annotation|RepeatableAttributeCollection|null */
|
||||
/**
|
||||
* @param class-string<T> $annotationName The name of the annotation.
|
||||
*
|
||||
* @return T|null
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
|
||||
{
|
||||
if ($this->isRepeatable($annotationName)) {
|
||||
throw new LogicException(sprintf(
|
||||
'The attribute "%s" is repeatable. Call getPropertyAnnotationCollection() instead.',
|
||||
$annotationName
|
||||
));
|
||||
}
|
||||
|
||||
return $this->getPropertyAnnotations($property)[$annotationName]
|
||||
?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<T> $annotationName The name of the annotation.
|
||||
*
|
||||
* @return RepeatableAttributeCollection<T>
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
public function getPropertyAnnotationCollection(
|
||||
ReflectionProperty $property,
|
||||
string $annotationName
|
||||
): RepeatableAttributeCollection {
|
||||
if (! $this->isRepeatable($annotationName)) {
|
||||
throw new LogicException(sprintf(
|
||||
'The attribute "%s" is not repeatable. Call getPropertyAnnotation() instead.',
|
||||
$annotationName
|
||||
));
|
||||
}
|
||||
|
||||
return $this->getPropertyAnnotations($property)[$annotationName] ?? new RepeatableAttributeCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<ReflectionAttribute> $attributes
|
||||
*
|
||||
* @return array<Annotation|RepeatableAttributeCollection>
|
||||
* @return class-string-map<T, T|RepeatableAttributeCollection<T>>
|
||||
*
|
||||
* @template T of Annotation
|
||||
*/
|
||||
private function convertToAttributeInstances(array $attributes): array
|
||||
{
|
||||
@@ -98,6 +134,9 @@ final class AttributeReader
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Annotation> $attributeClassName
|
||||
*/
|
||||
private function isRepeatable(string $attributeClassName): bool
|
||||
{
|
||||
if (isset($this->isRepeatableAttribute[$attributeClassName])) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as PersistenceAnnotationDriver;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
|
||||
use function class_exists;
|
||||
|
||||
if (class_exists(PersistenceAnnotationDriver::class)) {
|
||||
/**
|
||||
* @internal This class will be removed in ORM 3.0.
|
||||
*/
|
||||
abstract class CompatibilityAnnotationDriver extends PersistenceAnnotationDriver
|
||||
{
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* @internal This class will be removed in ORM 3.0.
|
||||
*/
|
||||
abstract class CompatibilityAnnotationDriver implements MappingDriver
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,10 @@ use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Inflector\Inflector;
|
||||
use Doctrine\Inflector\InflectorFactory;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
use InvalidArgumentException;
|
||||
|
||||
@@ -166,8 +167,13 @@ class DatabaseDriver implements MappingDriver
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @psalm-param class-string<T> $className
|
||||
* @psalm-param ClassMetadata<T> $metadata
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata)
|
||||
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
|
||||
{
|
||||
$this->reverseEngineerMappingFromDatabase();
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ use ArrayObject;
|
||||
use Doctrine\ORM\Mapping\Annotation;
|
||||
|
||||
/**
|
||||
* @template-extends ArrayObject<int,Annotation>
|
||||
* @template-extends ArrayObject<int, T>
|
||||
* @template T of Annotation
|
||||
*/
|
||||
final class RepeatableAttributeCollection extends ArrayObject
|
||||
{
|
||||
|
||||
@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata as Metadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\FileDriver;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
@@ -53,8 +53,13 @@ class XmlDriver extends FileDriver
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @psalm-param class-string<T> $className
|
||||
* @psalm-param ClassMetadata<T> $metadata
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata)
|
||||
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
|
||||
{
|
||||
$xmlRoot = $this->getElement($className);
|
||||
assert($xmlRoot instanceof SimpleXMLElement);
|
||||
@@ -168,7 +173,7 @@ class XmlDriver extends FileDriver
|
||||
$inheritanceType = (string) $xmlRoot['inheritance-type'];
|
||||
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType));
|
||||
|
||||
if ($metadata->inheritanceType !== Metadata::INHERITANCE_TYPE_NONE) {
|
||||
if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
// Evaluate <discriminator-column...>
|
||||
if (isset($xmlRoot->{'discriminator-column'})) {
|
||||
$discrColumn = $xmlRoot->{'discriminator-column'};
|
||||
@@ -685,7 +690,7 @@ class XmlDriver extends FileDriver
|
||||
|
||||
// Check for `fetch`
|
||||
if (isset($overrideElement['fetch'])) {
|
||||
$override['fetch'] = constant(Metadata::class . '::FETCH_' . (string) $overrideElement['fetch']);
|
||||
$override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . (string) $overrideElement['fetch']);
|
||||
}
|
||||
|
||||
$metadata->setAssociationOverride($fieldName, $override);
|
||||
|
||||
@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata as Metadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\FileDriver;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
@@ -60,8 +60,13 @@ class YamlDriver extends FileDriver
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @psalm-param class-string<T> $className
|
||||
* @psalm-param ClassMetadata<T> $metadata
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata)
|
||||
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
|
||||
{
|
||||
$element = $this->getElement($className);
|
||||
|
||||
@@ -186,7 +191,7 @@ class YamlDriver extends FileDriver
|
||||
if (isset($element['inheritanceType'])) {
|
||||
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType'])));
|
||||
|
||||
if ($metadata->inheritanceType !== Metadata::INHERITANCE_TYPE_NONE) {
|
||||
if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
// Evaluate discriminatorColumn
|
||||
if (isset($element['discriminatorColumn'])) {
|
||||
$discrColumn = $element['discriminatorColumn'];
|
||||
@@ -683,7 +688,7 @@ class YamlDriver extends FileDriver
|
||||
|
||||
// Check for `fetch`
|
||||
if (isset($associationOverrideElement['fetch'])) {
|
||||
$override['fetch'] = constant(Metadata::class . '::FETCH_' . $associationOverrideElement['fetch']);
|
||||
$override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverrideElement['fetch']);
|
||||
}
|
||||
|
||||
$metadata->setAssociationOverride($fieldName, $override);
|
||||
|
||||
@@ -12,13 +12,14 @@ use Doctrine\ORM\EntityRepository;
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("CLASS")
|
||||
* @template T of object
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class Entity implements Annotation
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @psalm-var class-string<EntityRepository>|null
|
||||
* @psalm-var class-string<EntityRepository<T>>|null
|
||||
*/
|
||||
public $repositoryClass;
|
||||
|
||||
@@ -26,7 +27,7 @@ final class Entity implements Annotation
|
||||
public $readOnly = false;
|
||||
|
||||
/**
|
||||
* @psalm-param class-string<EntityRepository>|null $repositoryClass
|
||||
* @psalm-param class-string<EntityRepository<T>>|null $repositoryClass
|
||||
*/
|
||||
public function __construct(?string $repositoryClass = null, bool $readOnly = false)
|
||||
{
|
||||
|
||||
@@ -9,10 +9,9 @@ use ReflectionProperty;
|
||||
use ReturnTypeWillChange;
|
||||
use ValueError;
|
||||
|
||||
use function assert;
|
||||
use function array_map;
|
||||
use function get_class;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use function is_array;
|
||||
|
||||
class ReflectionEnumProperty extends ReflectionProperty
|
||||
{
|
||||
@@ -41,7 +40,7 @@ class ReflectionEnumProperty extends ReflectionProperty
|
||||
*
|
||||
* @param object|null $object
|
||||
*
|
||||
* @return int|string|null
|
||||
* @return int|string|int[]|string[]|null
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getValue($object = null)
|
||||
@@ -56,32 +55,52 @@ class ReflectionEnumProperty extends ReflectionProperty
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_array($enum)) {
|
||||
return array_map(static function (BackedEnum $item): mixed {
|
||||
return $item->value;
|
||||
}, $enum);
|
||||
}
|
||||
|
||||
return $enum->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
* @param mixed $value
|
||||
* @param object $object
|
||||
* @param int|string|int[]|string[]|null $value
|
||||
*/
|
||||
public function setValue($object, $value = null): void
|
||||
{
|
||||
if ($value !== null) {
|
||||
$enumType = $this->enumType;
|
||||
try {
|
||||
$value = $enumType::from($value);
|
||||
} catch (ValueError $e) {
|
||||
assert(is_string($value) || is_int($value));
|
||||
|
||||
throw MappingException::invalidEnumValue(
|
||||
get_class($object),
|
||||
$this->originalReflectionProperty->getName(),
|
||||
(string) $value,
|
||||
$enumType,
|
||||
$e
|
||||
);
|
||||
if (is_array($value)) {
|
||||
$value = array_map(function ($item) use ($object): BackedEnum {
|
||||
return $this->initializeEnumValue($object, $item);
|
||||
}, $value);
|
||||
} else {
|
||||
$value = $this->initializeEnumValue($object, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->originalReflectionProperty->setValue($object, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
* @param int|string $value
|
||||
*/
|
||||
private function initializeEnumValue($object, $value): BackedEnum
|
||||
{
|
||||
$enumType = $this->enumType;
|
||||
|
||||
try {
|
||||
return $enumType::from($value);
|
||||
} catch (ValueError $e) {
|
||||
throw MappingException::invalidEnumValue(
|
||||
get_class($object),
|
||||
$this->originalReflectionProperty->getName(),
|
||||
(string) $value,
|
||||
$enumType,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Doctrine\ORM\Mapping;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function preg_replace;
|
||||
use function strpos;
|
||||
use function str_contains;
|
||||
use function strrpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
@@ -79,7 +79,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
|
||||
*/
|
||||
public function classToTableName($className)
|
||||
{
|
||||
if (strpos($className, '\\') !== false) {
|
||||
if (str_contains($className, '\\')) {
|
||||
$className = substr($className, strrpos($className, '\\') + 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function func_get_arg;
|
||||
use function func_num_args;
|
||||
use function get_debug_type;
|
||||
use function gettype;
|
||||
use function implode;
|
||||
@@ -197,9 +200,27 @@ class ORMInvalidArgumentException extends InvalidArgumentException
|
||||
/**
|
||||
* @return ORMInvalidArgumentException
|
||||
*/
|
||||
public static function invalidIdentifierBindingEntity()
|
||||
public static function invalidIdentifierBindingEntity(/* string $class */)
|
||||
{
|
||||
return new self('Binding entities to query parameters only allowed for entities that have an identifier.');
|
||||
if (func_num_args() === 0) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9642',
|
||||
'Omitting the class name in the exception method %s is deprecated.',
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
return new self('Binding entities to query parameters only allowed for entities that have an identifier.');
|
||||
}
|
||||
|
||||
return new self(sprintf(
|
||||
<<<'EXCEPTION'
|
||||
Binding entities to query parameters only allowed for entities that have an identifier.
|
||||
Class "%s" does not have an identifier.
|
||||
EXCEPTION
|
||||
,
|
||||
func_get_arg(0)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,12 +245,21 @@ class ORMInvalidArgumentException extends InvalidArgumentException
|
||||
/**
|
||||
* Used when a given entityName hasn't the good type
|
||||
*
|
||||
* @deprecated This method will be removed in 3.0.
|
||||
*
|
||||
* @param mixed $entityName The given entity (which shouldn't be a string)
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function invalidEntityName($entityName)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9471',
|
||||
'%s() is deprecated',
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
return new self(sprintf('Entity name must be a string, %s given', get_debug_type($entityName)));
|
||||
}
|
||||
|
||||
|
||||
204
lib/Doctrine/ORM/ORMSetup.php
Normal file
204
lib/Doctrine/ORM/ORMSetup.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\PsrCachedReader;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
use Doctrine\ORM\Mapping\Driver\XmlDriver;
|
||||
use Doctrine\ORM\Mapping\Driver\YamlDriver;
|
||||
use LogicException;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Redis;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Cache\Adapter\ApcuAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
|
||||
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||
|
||||
use function class_exists;
|
||||
use function extension_loaded;
|
||||
use function md5;
|
||||
use function sprintf;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
final class ORMSetup
|
||||
{
|
||||
/**
|
||||
* Creates a configuration with an annotation metadata driver.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public static function createAnnotationMetadataConfiguration(
|
||||
array $paths,
|
||||
bool $isDevMode = false,
|
||||
?string $proxyDir = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
): Configuration {
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(self::createDefaultAnnotationDriver($paths));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new default annotation driver with a correctly configured annotation reader.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public static function createDefaultAnnotationDriver(
|
||||
array $paths = [],
|
||||
?CacheItemPoolInterface $cache = null
|
||||
): AnnotationDriver {
|
||||
if (! class_exists(AnnotationReader::class)) {
|
||||
throw new LogicException(sprintf(
|
||||
'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library'
|
||||
. ' is not installed. Please run "composer require doctrine/annotations" or choose a different'
|
||||
. ' metadata driver.'
|
||||
));
|
||||
}
|
||||
|
||||
$reader = new AnnotationReader();
|
||||
|
||||
if ($cache === null && class_exists(ArrayAdapter::class)) {
|
||||
$cache = new ArrayAdapter();
|
||||
}
|
||||
|
||||
if ($cache !== null) {
|
||||
$reader = new PsrCachedReader($reader, $cache);
|
||||
}
|
||||
|
||||
return new AnnotationDriver($reader, $paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with an attribute metadata driver.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public static function createAttributeMetadataConfiguration(
|
||||
array $paths,
|
||||
bool $isDevMode = false,
|
||||
?string $proxyDir = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
): Configuration {
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new AttributeDriver($paths));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with an XML metadata driver.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public static function createXMLMetadataConfiguration(
|
||||
array $paths,
|
||||
bool $isDevMode = false,
|
||||
?string $proxyDir = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
): Configuration {
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new XmlDriver($paths));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with a YAML metadata driver.
|
||||
*
|
||||
* @deprecated YAML metadata mapping is deprecated and will be removed in 3.0
|
||||
*
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public static function createYAMLMetadataConfiguration(
|
||||
array $paths,
|
||||
bool $isDevMode = false,
|
||||
?string $proxyDir = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
): Configuration {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8465',
|
||||
'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to attribute or XML driver.'
|
||||
);
|
||||
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new YamlDriver($paths));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration without a metadata driver.
|
||||
*/
|
||||
public static function createConfiguration(
|
||||
bool $isDevMode = false,
|
||||
?string $proxyDir = null,
|
||||
?CacheItemPoolInterface $cache = null
|
||||
): Configuration {
|
||||
$proxyDir = $proxyDir ?: sys_get_temp_dir();
|
||||
|
||||
$cache = self::createCacheInstance($isDevMode, $proxyDir, $cache);
|
||||
|
||||
$config = new Configuration();
|
||||
|
||||
$config->setMetadataCache($cache);
|
||||
$config->setQueryCache($cache);
|
||||
$config->setResultCache($cache);
|
||||
$config->setProxyDir($proxyDir);
|
||||
$config->setProxyNamespace('DoctrineProxies');
|
||||
$config->setAutoGenerateProxyClasses($isDevMode);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private static function createCacheInstance(
|
||||
bool $isDevMode,
|
||||
string $proxyDir,
|
||||
?CacheItemPoolInterface $cache
|
||||
): CacheItemPoolInterface {
|
||||
if ($cache !== null) {
|
||||
return $cache;
|
||||
}
|
||||
|
||||
if (! class_exists(ArrayAdapter::class)) {
|
||||
throw new RuntimeException(
|
||||
'The Doctrine setup tool cannot configure caches without symfony/cache.'
|
||||
. ' Please add symfony/cache as explicit dependency or pass your own cache implementation.'
|
||||
);
|
||||
}
|
||||
|
||||
if ($isDevMode) {
|
||||
return new ArrayAdapter();
|
||||
}
|
||||
|
||||
$namespace = 'dc2_' . md5($proxyDir);
|
||||
|
||||
if (extension_loaded('apcu')) {
|
||||
return new ApcuAdapter($namespace);
|
||||
}
|
||||
|
||||
if (MemcachedAdapter::isSupported()) {
|
||||
return new MemcachedAdapter(MemcachedAdapter::createConnection('memcached://127.0.0.1'), $namespace);
|
||||
}
|
||||
|
||||
if (extension_loaded('redis')) {
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1');
|
||||
|
||||
return new RedisAdapter($redis, $namespace);
|
||||
}
|
||||
|
||||
return new ArrayAdapter();
|
||||
}
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ interface CollectionPersister
|
||||
/**
|
||||
* Slices elements.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @param int $offset
|
||||
* @param int|null $length
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
|
||||
@@ -501,7 +501,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
*
|
||||
* Internal note: Order of the parameters must be the same as the order of the columns in getInsertRowSql.
|
||||
*
|
||||
* @param mixed $element
|
||||
* @param object $element
|
||||
*
|
||||
* @return mixed[]
|
||||
* @psalm-return list<mixed>
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Persisters\Entity;
|
||||
|
||||
use BackedEnum;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
@@ -13,6 +14,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
@@ -47,7 +49,7 @@ use function is_object;
|
||||
use function reset;
|
||||
use function spl_object_id;
|
||||
use function sprintf;
|
||||
use function strpos;
|
||||
use function str_contains;
|
||||
use function strtoupper;
|
||||
use function trim;
|
||||
|
||||
@@ -163,7 +165,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
* The INSERT SQL statement used for entities handled by this persister.
|
||||
* This SQL is only generated once per request, if at all.
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
private $insertSql;
|
||||
|
||||
@@ -1579,11 +1581,22 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
protected function getLockTablesSql($lockMode)
|
||||
{
|
||||
if ($lockMode === null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9466',
|
||||
'Passing null as argument to %s is deprecated, pass LockMode::NONE instead.',
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
$lockMode = LockMode::NONE;
|
||||
}
|
||||
|
||||
return $this->platform->appendLockHint(
|
||||
'FROM '
|
||||
. $this->quoteStrategy->getTableName($this->class, $this->platform) . ' '
|
||||
. $this->getSQLTableAlias($this->class->name),
|
||||
$lockMode ?? LockMode::NONE
|
||||
$lockMode
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1732,7 +1745,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
return $columns;
|
||||
}
|
||||
|
||||
if ($assoc !== null && strpos($field, ' ') === false && strpos($field, '(') === false) {
|
||||
if ($assoc !== null && ! str_contains($field, ' ') && ! str_contains($field, '(')) {
|
||||
// very careless developers could potentially open up this normally hidden api for userland attacks,
|
||||
// therefore checking for spaces and function calls which are not allowed.
|
||||
|
||||
@@ -1976,15 +1989,18 @@ class BasicEntityPersister implements EntityPersister
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return array<mixed>
|
||||
* @psalm-return list<mixed>
|
||||
*/
|
||||
private function getIndividualValue($value)
|
||||
private function getIndividualValue($value): array
|
||||
{
|
||||
if (! is_object($value)) {
|
||||
return [$value];
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return [$value->value];
|
||||
}
|
||||
|
||||
$valueClass = ClassUtils::getClass($value);
|
||||
|
||||
if ($this->em->getMetadataFactory()->isTransient($valueClass)) {
|
||||
@@ -2020,7 +2036,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
$alias = $this->getSQLTableAlias($this->class->name);
|
||||
|
||||
$sql = 'SELECT 1 '
|
||||
. $this->getLockTablesSql(null)
|
||||
. $this->getLockTablesSql(LockMode::NONE)
|
||||
. ' WHERE ' . $this->getSelectConditionSQL($criteria);
|
||||
|
||||
[$params, $types] = $this->expandParameters($criteria);
|
||||
|
||||
@@ -32,17 +32,17 @@ interface EntityPersister
|
||||
/**
|
||||
* Get all queued inserts.
|
||||
*
|
||||
* @psalm-return array<string|int, object>
|
||||
* @return object[]
|
||||
*/
|
||||
public function getInserts();
|
||||
|
||||
/**
|
||||
* Gets the INSERT SQL used by the persister to persist a new entity.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @TODO - It should not be here.
|
||||
* But its necessary since JoinedSubclassPersister#executeInserts invoke the root persister.
|
||||
*
|
||||
* Gets the INSERT SQL used by the persister to persist a new entity.
|
||||
* @TODO It should not be here.
|
||||
* But its necessary since JoinedSubclassPersister#executeInserts invoke the root persister.
|
||||
*/
|
||||
public function getInsertSQL();
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ use Doctrine\Common\Proxy\Proxy as BaseProxy;
|
||||
|
||||
/**
|
||||
* Interface for proxy classes.
|
||||
*
|
||||
* @deprecated 2.7 This interface is being removed from the ORM and won't have any replacement, proxies will no longer implement it.
|
||||
*/
|
||||
interface Proxy extends BaseProxy
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
/**
|
||||
* This factory is used to create proxy objects for entities at runtime.
|
||||
*
|
||||
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
|
||||
* @psalm-type AutogenerateMode = AbstractProxyFactory::AUTOGENERATE_NEVER|AbstractProxyFactory::AUTOGENERATE_ALWAYS|AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS|AbstractProxyFactory::AUTOGENERATE_EVAL|AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED
|
||||
*/
|
||||
class ProxyFactory extends AbstractProxyFactory
|
||||
{
|
||||
@@ -48,7 +48,8 @@ class ProxyFactory extends AbstractProxyFactory
|
||||
* @param string $proxyDir The directory to use for the proxy classes. It must exist.
|
||||
* @param string $proxyNs The namespace to use for the proxy classes.
|
||||
* @param bool|int $autoGenerate The strategy for automatically generating proxy classes. Possible
|
||||
* values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
|
||||
* values are constants of {@see AbstractProxyFactory}.
|
||||
* @psalm-param bool|AutogenerateMode $autoGenerate
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, $proxyDir, $proxyNs, $autoGenerate = AbstractProxyFactory::AUTOGENERATE_NEVER)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
use function assert;
|
||||
use function reset;
|
||||
use function sprintf;
|
||||
|
||||
@@ -23,7 +24,7 @@ class IdentityFunction extends FunctionNode
|
||||
/** @var PathExpression */
|
||||
public $pathExpression;
|
||||
|
||||
/** @var string */
|
||||
/** @var string|null */
|
||||
public $fieldMapping;
|
||||
|
||||
/**
|
||||
@@ -31,14 +32,14 @@ class IdentityFunction extends FunctionNode
|
||||
*/
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
$platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform();
|
||||
$quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy();
|
||||
assert($this->pathExpression->field !== null);
|
||||
$entityManager = $sqlWalker->getEntityManager();
|
||||
$platform = $entityManager->getConnection()->getDatabasePlatform();
|
||||
$quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy();
|
||||
$dqlAlias = $this->pathExpression->identificationVariable;
|
||||
$assocField = $this->pathExpression->field;
|
||||
$qComp = $sqlWalker->getQueryComponent($dqlAlias);
|
||||
$class = $qComp['metadata'];
|
||||
$assoc = $class->associationMappings[$assocField];
|
||||
$targetEntity = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
|
||||
$assoc = $sqlWalker->getMetadataForDqlAlias($dqlAlias)->associationMappings[$assocField];
|
||||
$targetEntity = $entityManager->getClassMetadata($assoc['targetEntity']);
|
||||
$joinColumn = reset($assoc['joinColumns']);
|
||||
|
||||
if ($this->fieldMapping !== null) {
|
||||
@@ -63,7 +64,7 @@ class IdentityFunction extends FunctionNode
|
||||
}
|
||||
|
||||
// The table with the relation may be a subclass, so get the table name from the association definition
|
||||
$tableName = $sqlWalker->getEntityManager()->getClassMetadata($assoc['sourceEntity'])->getTableName();
|
||||
$tableName = $entityManager->getClassMetadata($assoc['sourceEntity'])->getTableName();
|
||||
|
||||
$tableAlias = $sqlWalker->getSQLTableAlias($tableName, $dqlAlias);
|
||||
$columnName = $quoteStrategy->getJoinColumnName($joinColumn, $targetEntity, $platform);
|
||||
|
||||
@@ -10,6 +10,8 @@ use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* "SIZE" "(" CollectionValuedPathExpression ")"
|
||||
*
|
||||
@@ -27,18 +29,19 @@ class SizeFunction extends FunctionNode
|
||||
*/
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
$platform = $sqlWalker->getEntityManager()->getConnection()->getDatabasePlatform();
|
||||
$quoteStrategy = $sqlWalker->getEntityManager()->getConfiguration()->getQuoteStrategy();
|
||||
assert($this->collectionPathExpression->field !== null);
|
||||
$entityManager = $sqlWalker->getEntityManager();
|
||||
$platform = $entityManager->getConnection()->getDatabasePlatform();
|
||||
$quoteStrategy = $entityManager->getConfiguration()->getQuoteStrategy();
|
||||
$dqlAlias = $this->collectionPathExpression->identificationVariable;
|
||||
$assocField = $this->collectionPathExpression->field;
|
||||
|
||||
$qComp = $sqlWalker->getQueryComponent($dqlAlias);
|
||||
$class = $qComp['metadata'];
|
||||
$class = $sqlWalker->getMetadataForDqlAlias($dqlAlias);
|
||||
$assoc = $class->associationMappings[$assocField];
|
||||
$sql = 'SELECT COUNT(*) FROM ';
|
||||
|
||||
if ($assoc['type'] === ClassMetadata::ONE_TO_MANY) {
|
||||
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
|
||||
$targetClass = $entityManager->getClassMetadata($assoc['targetEntity']);
|
||||
$targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->getTableName());
|
||||
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->getTableName(), $dqlAlias);
|
||||
|
||||
@@ -60,7 +63,7 @@ class SizeFunction extends FunctionNode
|
||||
. $sourceTableAlias . '.' . $quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $platform);
|
||||
}
|
||||
} else { // many-to-many
|
||||
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
|
||||
$targetClass = $entityManager->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
$owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
|
||||
$joinTable = $owningAssoc['joinTable'];
|
||||
|
||||
@@ -16,7 +16,10 @@ class Join extends Node
|
||||
public const JOIN_TYPE_LEFTOUTER = 2;
|
||||
public const JOIN_TYPE_INNER = 3;
|
||||
|
||||
/** @var int */
|
||||
/**
|
||||
* @var int
|
||||
* @psalm-var self::JOIN_TYPE_*
|
||||
*/
|
||||
public $joinType = self::JOIN_TYPE_INNER;
|
||||
|
||||
/** @var Node|null */
|
||||
@@ -28,6 +31,7 @@ class Join extends Node
|
||||
/**
|
||||
* @param int $joinType
|
||||
* @param Node $joinAssociationDeclaration
|
||||
* @psalm-param self::JOIN_TYPE_* $joinType
|
||||
*/
|
||||
public function __construct($joinType, $joinAssociationDeclaration)
|
||||
{
|
||||
|
||||
@@ -26,12 +26,4 @@ class JoinAssociationPathExpression extends Node
|
||||
$this->identificationVariable = $identificationVariable;
|
||||
$this->associationField = $associationField;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkPathExpression($this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
|
||||
/**
|
||||
* LikeExpression ::= StringExpression ["NOT"] "LIKE" string ["ESCAPE" char]
|
||||
*
|
||||
@@ -12,21 +14,21 @@ namespace Doctrine\ORM\Query\AST;
|
||||
class LikeExpression extends Node
|
||||
{
|
||||
/** @var bool */
|
||||
public $not;
|
||||
public $not = false;
|
||||
|
||||
/** @var Node */
|
||||
/** @var Node|string */
|
||||
public $stringExpression;
|
||||
|
||||
/** @var InputParameter */
|
||||
/** @var InputParameter|FunctionNode|PathExpression|Literal */
|
||||
public $stringPattern;
|
||||
|
||||
/** @var Literal|null */
|
||||
public $escapeChar;
|
||||
|
||||
/**
|
||||
* @param Node $stringExpression
|
||||
* @param InputParameter $stringPattern
|
||||
* @param Literal|null $escapeChar
|
||||
* @param Node|string $stringExpression
|
||||
* @param InputParameter|FunctionNode|PathExpression|Literal $stringPattern
|
||||
* @param Literal|null $escapeChar
|
||||
*/
|
||||
public function __construct($stringExpression, $stringPattern, $escapeChar = null)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,10 @@ class Literal extends Node
|
||||
public const BOOLEAN = 2;
|
||||
public const NUMERIC = 3;
|
||||
|
||||
/** @var int */
|
||||
/**
|
||||
* @var int
|
||||
* @psalm-var self::*
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/** @var mixed */
|
||||
@@ -19,6 +22,7 @@ class Literal extends Node
|
||||
/**
|
||||
* @param int $type
|
||||
* @param mixed $value
|
||||
* @psalm-param self::* $type
|
||||
*/
|
||||
public function __construct($type, $value)
|
||||
{
|
||||
|
||||
@@ -19,10 +19,16 @@ class PathExpression extends Node
|
||||
public const TYPE_SINGLE_VALUED_ASSOCIATION = 4;
|
||||
public const TYPE_STATE_FIELD = 8;
|
||||
|
||||
/** @var int */
|
||||
/**
|
||||
* @var int|null
|
||||
* @psalm-var self::TYPE_*|null
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/** @var int */
|
||||
/**
|
||||
* @var int
|
||||
* @psalm-var int-mask-of<self::TYPE_*>
|
||||
*/
|
||||
public $expectedType;
|
||||
|
||||
/** @var string */
|
||||
@@ -35,6 +41,7 @@ class PathExpression extends Node
|
||||
* @param int $expectedType
|
||||
* @param string $identificationVariable
|
||||
* @param string|null $field
|
||||
* @psalm-param int-mask-of<self::TYPE_*> $expectedType
|
||||
*/
|
||||
public function __construct($expectedType, $identificationVariable, $field = null)
|
||||
{
|
||||
|
||||
@@ -19,7 +19,10 @@ class Join
|
||||
public const ON = 'ON';
|
||||
public const WITH = 'WITH';
|
||||
|
||||
/** @var string */
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var self::INNER_JOIN|self::LEFT_JOIN
|
||||
*/
|
||||
protected $joinType;
|
||||
|
||||
/** @var string */
|
||||
@@ -28,7 +31,10 @@ class Join
|
||||
/** @var string|null */
|
||||
protected $alias;
|
||||
|
||||
/** @var string|null */
|
||||
/**
|
||||
* @var string|null
|
||||
* @psalm-var self::ON|self::WITH|null
|
||||
*/
|
||||
protected $conditionType;
|
||||
|
||||
/** @var string|Comparison|Composite|null */
|
||||
@@ -44,6 +50,8 @@ class Join
|
||||
* @param string|null $conditionType The condition type constant. Either ON or WITH.
|
||||
* @param string|Comparison|Composite|null $condition The condition for the join.
|
||||
* @param string|null $indexBy The index for the join.
|
||||
* @psalm-param self::INNER_JOIN|self::LEFT_JOIN $joinType
|
||||
* @psalm-param self::ON|self::WITH|null $conditionType
|
||||
*/
|
||||
public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null, $indexBy = null)
|
||||
{
|
||||
@@ -57,6 +65,7 @@ class Join
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @psalm-return self::INNER_JOIN|self::LEFT_JOIN
|
||||
*/
|
||||
public function getJoinType()
|
||||
{
|
||||
@@ -81,6 +90,7 @@ class Join
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
* @psalm-return self::ON|self::WITH|null
|
||||
*/
|
||||
public function getConditionType()
|
||||
{
|
||||
|
||||
@@ -47,13 +47,23 @@ class FilterCollection
|
||||
* Instances of enabled filters.
|
||||
*
|
||||
* @var SQLFilter[]
|
||||
* @psalm-var array<string, SQLFilter>
|
||||
*/
|
||||
private $enabledFilters = [];
|
||||
|
||||
/** @var string The filter hash from the last time the query was parsed. */
|
||||
private $filterHash;
|
||||
/**
|
||||
* The filter hash from the last time the query was parsed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $filterHash = '';
|
||||
|
||||
/** @var int The current state of this filter. */
|
||||
/**
|
||||
* The current state of this filter.
|
||||
*
|
||||
* @var int
|
||||
* @psalm-var self::FILTERS_STATE_*
|
||||
*/
|
||||
private $filtersState = self::FILTERS_STATE_CLEAN;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
@@ -66,6 +76,7 @@ class FilterCollection
|
||||
* Gets all the enabled filters.
|
||||
*
|
||||
* @return SQLFilter[] The enabled filters.
|
||||
* @psalm-return array<string, SQLFilter>
|
||||
*/
|
||||
public function getEnabledFilters()
|
||||
{
|
||||
@@ -167,7 +178,9 @@ class FilterCollection
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True, if the filter collection is clean.
|
||||
* Checks if the filter collection is clean.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClean()
|
||||
{
|
||||
|
||||
@@ -5,15 +5,16 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\Common\Lexer\AbstractLexer;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function constant;
|
||||
use function ctype_alpha;
|
||||
use function defined;
|
||||
use function is_numeric;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function stripos;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
|
||||
@@ -44,6 +45,7 @@ class Lexer extends AbstractLexer
|
||||
public const T_CLOSE_CURLY_BRACE = 19;
|
||||
|
||||
// All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100
|
||||
/** @deprecated No Replacement planned. */
|
||||
public const T_ALIASED_NAME = 100;
|
||||
public const T_FULLY_QUALIFIED_NAME = 101;
|
||||
public const T_IDENTIFIER = 102;
|
||||
@@ -149,7 +151,7 @@ class Lexer extends AbstractLexer
|
||||
switch (true) {
|
||||
// Recognize numeric values
|
||||
case is_numeric($value):
|
||||
if (strpos($value, '.') !== false || stripos($value, 'e') !== false) {
|
||||
if (str_contains($value, '.') || stripos($value, 'e') !== false) {
|
||||
return self::T_FLOAT;
|
||||
}
|
||||
|
||||
@@ -173,11 +175,18 @@ class Lexer extends AbstractLexer
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($value, ':') !== false) {
|
||||
if (str_contains($value, ':')) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8818',
|
||||
'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.',
|
||||
$value
|
||||
);
|
||||
|
||||
return self::T_ALIASED_NAME;
|
||||
}
|
||||
|
||||
if (strpos($value, '\\') !== false) {
|
||||
if (str_contains($value, '\\')) {
|
||||
return self::T_FULLY_QUALIFIED_NAME;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ class Parameter
|
||||
private $typeSpecified;
|
||||
|
||||
/**
|
||||
* @param string $name Parameter name
|
||||
* @param mixed $value Parameter value
|
||||
* @param mixed $type Parameter type
|
||||
* @param string|int $name Parameter name
|
||||
* @param mixed $value Parameter value
|
||||
* @param mixed $type Parameter type
|
||||
*/
|
||||
public function __construct($name, $value, $type = null)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use BackedEnum;
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
@@ -54,8 +55,19 @@ class ParameterTypeInferer
|
||||
return Types::DATEINTERVAL;
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return is_int($value->value)
|
||||
? Types::INTEGER
|
||||
: Types::STRING;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return is_int(current($value))
|
||||
$firstValue = current($value);
|
||||
if ($firstValue instanceof BackedEnum) {
|
||||
$firstValue = $firstValue->value;
|
||||
}
|
||||
|
||||
return is_int($firstValue)
|
||||
? Connection::PARAM_INT_ARRAY
|
||||
: Connection::PARAM_STR_ARRAY;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\Common\Lexer\AbstractLexer;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -60,6 +61,7 @@ use Doctrine\ORM\Query\AST\UpdateItem;
|
||||
use Doctrine\ORM\Query\AST\UpdateStatement;
|
||||
use Doctrine\ORM\Query\AST\WhenClause;
|
||||
use Doctrine\ORM\Query\AST\WhereClause;
|
||||
use LogicException;
|
||||
use ReflectionClass;
|
||||
|
||||
use function array_intersect;
|
||||
@@ -74,6 +76,7 @@ use function in_array;
|
||||
use function interface_exists;
|
||||
use function is_string;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function strrpos;
|
||||
@@ -83,6 +86,17 @@ use function substr;
|
||||
/**
|
||||
* An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
|
||||
* Parses a DQL query, reports any errors in it, and generates an AST.
|
||||
*
|
||||
* @psalm-import-type Token from AbstractLexer
|
||||
* @psalm-type QueryComponent = array{
|
||||
* metadata?: ClassMetadata<object>,
|
||||
* parent?: string|null,
|
||||
* relation?: mixed[]|null,
|
||||
* map?: string|null,
|
||||
* resultVariable?: AST\Node|string,
|
||||
* nestingLevel: int,
|
||||
* token: Token,
|
||||
* }
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
@@ -139,19 +153,19 @@ class Parser
|
||||
* and still need to be validated.
|
||||
*/
|
||||
|
||||
/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
|
||||
/** @psalm-var list<array{token: Token|null, expression: mixed, nestingLevel: int}> */
|
||||
private $deferredIdentificationVariables = [];
|
||||
|
||||
/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
|
||||
/** @psalm-var list<array{token: Token|null, expression: AST\PartialObjectExpression, nestingLevel: int}> */
|
||||
private $deferredPartialObjectExpressions = [];
|
||||
|
||||
/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
|
||||
/** @psalm-var list<array{token: Token|null, expression: AST\PathExpression, nestingLevel: int}> */
|
||||
private $deferredPathExpressions = [];
|
||||
|
||||
/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
|
||||
/** @psalm-var list<array{token: Token|null, expression: mixed, nestingLevel: int}> */
|
||||
private $deferredResultVariables = [];
|
||||
|
||||
/** @psalm-var list<array{token: mixed, expression: mixed, nestingLevel: int}> */
|
||||
/** @psalm-var list<array{token: Token|null, expression: AST\NewObjectExpression, nestingLevel: int}> */
|
||||
private $deferredNewObjectExpressions = [];
|
||||
|
||||
/**
|
||||
@@ -185,7 +199,7 @@ class Parser
|
||||
/**
|
||||
* Map of declared query components in the parsed query.
|
||||
*
|
||||
* @psalm-var array<string, array<string, mixed>>
|
||||
* @psalm-var array<string, QueryComponent>
|
||||
*/
|
||||
private $queryComponents = [];
|
||||
|
||||
@@ -206,7 +220,7 @@ class Parser
|
||||
/**
|
||||
* The custom last tree walker, if any, that is responsible for producing the output.
|
||||
*
|
||||
* @var class-string<TreeWalker>
|
||||
* @var class-string<SqlWalker>|null
|
||||
*/
|
||||
private $customOutputWalker;
|
||||
|
||||
@@ -231,6 +245,7 @@ class Parser
|
||||
* This tree walker will be run last over the AST, after any other walkers.
|
||||
*
|
||||
* @param string $className
|
||||
* @psalm-param class-string<SqlWalker> $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -243,7 +258,7 @@ class Parser
|
||||
* Adds a custom tree walker for modifying the AST.
|
||||
*
|
||||
* @param string $className
|
||||
* @psalm-param class-string $className
|
||||
* @psalm-param class-string<TreeWalker> $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -472,7 +487,7 @@ class Parser
|
||||
*
|
||||
* @param string $expected Expected string.
|
||||
* @param mixed[]|null $token Got token.
|
||||
* @psalm-param array<string, mixed>|null $token
|
||||
* @psalm-param Token|null $token
|
||||
*
|
||||
* @return void
|
||||
* @psalm-return no-return
|
||||
@@ -499,9 +514,10 @@ class Parser
|
||||
*
|
||||
* @param string $message Optional message.
|
||||
* @param mixed[]|null $token Optional token.
|
||||
* @psalm-param array<string, mixed>|null $token
|
||||
* @psalm-param Token|null $token
|
||||
*
|
||||
* @return void
|
||||
* @psalm-return no-return
|
||||
*
|
||||
* @throws QueryException
|
||||
*/
|
||||
@@ -570,7 +586,7 @@ class Parser
|
||||
/**
|
||||
* Checks if the given token indicates a mathematical operator.
|
||||
*
|
||||
* @psalm-param array<string, mixed>|null $token
|
||||
* @psalm-param Token|null $token
|
||||
*/
|
||||
private function isMathOperator(?array $token): bool
|
||||
{
|
||||
@@ -670,7 +686,7 @@ class Parser
|
||||
$fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
|
||||
|
||||
// If the namespace is not given then assumes the first FROM entity namespace
|
||||
if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
|
||||
if (! str_contains($className, '\\') && ! class_exists($className) && str_contains($fromClassName, '\\')) {
|
||||
$namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
|
||||
$fqcn = $namespace . '\\' . $className;
|
||||
|
||||
@@ -708,7 +724,7 @@ class Parser
|
||||
{
|
||||
foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
|
||||
$expr = $deferredItem['expression'];
|
||||
$class = $this->queryComponents[$expr->identificationVariable]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($expr->identificationVariable);
|
||||
|
||||
foreach ($expr->partialFieldSet as $field) {
|
||||
if (isset($class->fieldMappings[$field])) {
|
||||
@@ -790,8 +806,7 @@ class Parser
|
||||
foreach ($this->deferredPathExpressions as $deferredItem) {
|
||||
$pathExpression = $deferredItem['expression'];
|
||||
|
||||
$qComp = $this->queryComponents[$pathExpression->identificationVariable];
|
||||
$class = $qComp['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($pathExpression->identificationVariable);
|
||||
|
||||
$field = $pathExpression->field;
|
||||
if ($field === null) {
|
||||
@@ -859,7 +874,7 @@ class Parser
|
||||
}
|
||||
|
||||
foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
|
||||
if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
|
||||
if (isset($this->queryComponents[$dqlAlias]) && ! isset($this->queryComponents[$dqlAlias]['parent'])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1012,6 +1027,13 @@ class Parser
|
||||
|
||||
$this->match(Lexer::T_ALIASED_NAME);
|
||||
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8818',
|
||||
'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.',
|
||||
$this->lexer->token['value']
|
||||
);
|
||||
|
||||
[$namespaceAlias, $simpleClassName] = explode(':', $this->lexer->token['value']);
|
||||
|
||||
return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
|
||||
@@ -1095,11 +1117,11 @@ class Parser
|
||||
$this->match(Lexer::T_DOT);
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
assert($this->lexer->token !== null);
|
||||
$field = $this->lexer->token['value'];
|
||||
|
||||
// Validate association field
|
||||
$qComp = $this->queryComponents[$identVariable];
|
||||
$class = $qComp['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($identVariable);
|
||||
|
||||
if (! $class->hasAssociation($field)) {
|
||||
$this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
|
||||
@@ -1115,6 +1137,7 @@ class Parser
|
||||
* PathExpression ::= IdentificationVariable {"." identifier}*
|
||||
*
|
||||
* @param int $expectedTypes
|
||||
* @psalm-param int-mask-of<PathExpression::TYPE_*> $expectedTypes
|
||||
*
|
||||
* @return PathExpression
|
||||
*/
|
||||
@@ -1262,6 +1285,7 @@ class Parser
|
||||
public function UpdateClause()
|
||||
{
|
||||
$this->match(Lexer::T_UPDATE);
|
||||
assert($this->lexer->lookahead !== null);
|
||||
|
||||
$token = $this->lexer->lookahead;
|
||||
$abstractSchemaName = $this->AbstractSchemaName();
|
||||
@@ -1318,6 +1342,7 @@ class Parser
|
||||
$this->match(Lexer::T_FROM);
|
||||
}
|
||||
|
||||
assert($this->lexer->lookahead !== null);
|
||||
$token = $this->lexer->lookahead;
|
||||
$abstractSchemaName = $this->AbstractSchemaName();
|
||||
|
||||
@@ -1786,6 +1811,7 @@ class Parser
|
||||
$this->match(Lexer::T_AS);
|
||||
}
|
||||
|
||||
assert($this->lexer->lookahead !== null);
|
||||
$token = $this->lexer->lookahead;
|
||||
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
|
||||
$classMetadata = $this->em->getClassMetadata($abstractSchemaName);
|
||||
@@ -1818,13 +1844,15 @@ class Parser
|
||||
$this->match(Lexer::T_AS);
|
||||
}
|
||||
|
||||
assert($this->lexer->lookahead !== null);
|
||||
|
||||
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
|
||||
$indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
|
||||
|
||||
$identificationVariable = $joinAssociationPathExpression->identificationVariable;
|
||||
$field = $joinAssociationPathExpression->associationField;
|
||||
|
||||
$class = $this->queryComponents[$identificationVariable]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($identificationVariable);
|
||||
$targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
|
||||
|
||||
// Building queryComponent
|
||||
@@ -1896,6 +1924,7 @@ class Parser
|
||||
|
||||
$partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
|
||||
|
||||
assert($this->lexer->token !== null);
|
||||
// Defer PartialObjectExpression validation
|
||||
$this->deferredPartialObjectExpressions[] = [
|
||||
'expression' => $partialObjectExpression,
|
||||
@@ -2329,6 +2358,8 @@ class Parser
|
||||
$aliasResultVariable = null;
|
||||
|
||||
if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||
assert($this->lexer->lookahead !== null);
|
||||
assert($expression instanceof AST\Node || is_string($expression));
|
||||
$token = $this->lexer->lookahead;
|
||||
$aliasResultVariable = $this->AliasResultVariable();
|
||||
|
||||
@@ -2425,6 +2456,7 @@ class Parser
|
||||
}
|
||||
|
||||
if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||
assert($this->lexer->lookahead !== null);
|
||||
$token = $this->lexer->lookahead;
|
||||
$resultVariable = $this->AliasResultVariable();
|
||||
$expr->fieldIdentificationVariable = $resultVariable;
|
||||
@@ -3620,4 +3652,13 @@ class Parser
|
||||
|
||||
return $function;
|
||||
}
|
||||
|
||||
private function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata
|
||||
{
|
||||
if (! isset($this->queryComponents[$dqlAlias]['metadata'])) {
|
||||
throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
|
||||
}
|
||||
|
||||
return $this->queryComponents[$dqlAlias]['metadata'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Query\AST\PathExpression;
|
||||
use Exception;
|
||||
use Stringable;
|
||||
|
||||
class QueryException extends ORMException
|
||||
{
|
||||
@@ -122,7 +123,7 @@ class QueryException extends ORMException
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $pathExpr
|
||||
* @param PathExpression $pathExpr
|
||||
*
|
||||
* @return QueryException
|
||||
*/
|
||||
@@ -134,7 +135,7 @@ class QueryException extends ORMException
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $literal
|
||||
* @param string|Stringable $literal
|
||||
*
|
||||
* @return QueryException
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,7 @@ use RuntimeException;
|
||||
|
||||
use function count;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function str_starts_with;
|
||||
|
||||
/**
|
||||
* Converts Collection expressions to Query expressions.
|
||||
@@ -114,7 +114,7 @@ class QueryExpressionVisitor extends ExpressionVisitor
|
||||
$field = $this->queryAliases[0] . '.' . $comparison->getField();
|
||||
|
||||
foreach ($this->queryAliases as $alias) {
|
||||
if (strpos($comparison->getField() . '.', $alias . '.') === 0) {
|
||||
if (str_starts_with($comparison->getField() . '.', $alias . '.')) {
|
||||
$field = $comparison->getField();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -73,10 +73,18 @@ class ResultSetMapping
|
||||
* Maps column names in the result set to the alias/field name to use in the mapped result.
|
||||
*
|
||||
* @ignore
|
||||
* @psalm-var array<string, string>
|
||||
* @psalm-var array<string, string|int>
|
||||
*/
|
||||
public $scalarMappings = [];
|
||||
|
||||
/**
|
||||
* Maps scalar columns to enums
|
||||
*
|
||||
* @ignore
|
||||
* @psalm-var array<string, string>
|
||||
*/
|
||||
public $enumMappings = [];
|
||||
|
||||
/**
|
||||
* Maps column names in the result set to the alias/field type to use in the mapped result.
|
||||
*
|
||||
@@ -169,6 +177,7 @@ class ResultSetMapping
|
||||
* results or joined entity results within this ResultSetMapping.
|
||||
* @param string|null $resultAlias The result alias with which the entity result should be
|
||||
* placed in the result structure.
|
||||
* @psalm-param class-string $class
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
@@ -315,6 +324,7 @@ class ResultSetMapping
|
||||
* the field $fieldName is defined on a subclass, specify that here.
|
||||
* If not specified, the field is assumed to belong to the class
|
||||
* designated by $alias.
|
||||
* @psalm-param class-string|null $declaringClass
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
@@ -344,6 +354,7 @@ class ResultSetMapping
|
||||
* @param string $parentAlias The alias of the entity result that is the parent of this joined result.
|
||||
* @param string $relation The association field that connects the parent entity result
|
||||
* with the joined entity result.
|
||||
* @psalm-param class-string $class
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
@@ -361,9 +372,9 @@ class ResultSetMapping
|
||||
/**
|
||||
* Adds a scalar result mapping.
|
||||
*
|
||||
* @param string $columnName The name of the column in the SQL result set.
|
||||
* @param string $alias The result alias with which the scalar result should be placed in the result structure.
|
||||
* @param string $type The column type
|
||||
* @param string $columnName The name of the column in the SQL result set.
|
||||
* @param string|int $alias The result alias with which the scalar result should be placed in the result structure.
|
||||
* @param string $type The column type
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
@@ -381,11 +392,26 @@ class ResultSetMapping
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a scalar result mapping.
|
||||
*
|
||||
* @param string $columnName The name of the column in the SQL result set.
|
||||
* @param string $enumType The enum type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addEnumResult($columnName, $enumType)
|
||||
{
|
||||
$this->enumMappings[$columnName] = $enumType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a metadata parameter mappings.
|
||||
*
|
||||
* @param mixed $parameter The parameter name in the SQL result set.
|
||||
* @param string $attribute The metadata attribute.
|
||||
* @param string|int $parameter The parameter name in the SQL result set.
|
||||
* @param string $attribute The metadata attribute.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -426,7 +452,7 @@ class ResultSetMapping
|
||||
*
|
||||
* @param string $columnName The name of the column in the SQL result set.
|
||||
*
|
||||
* @return string
|
||||
* @return string|int
|
||||
*/
|
||||
public function getScalarAlias($columnName)
|
||||
{
|
||||
@@ -549,11 +575,11 @@ class ResultSetMapping
|
||||
/**
|
||||
* Adds a meta column (foreign key or discriminator column) to the result set.
|
||||
*
|
||||
* @param string $alias The result alias with which the meta result should be placed in the result structure.
|
||||
* @param string $columnName The name of the column in the SQL result set.
|
||||
* @param string $fieldName The name of the field on the declaring class.
|
||||
* @param bool $isIdentifierColumn
|
||||
* @param string $type The column type
|
||||
* @param string $alias The result alias with which the meta result should be placed in the result structure.
|
||||
* @param string $columnName The name of the column in the SQL result set.
|
||||
* @param string $fieldName The name of the field on the declaring class.
|
||||
* @param bool $isIdentifierColumn
|
||||
* @param string|null $type The column type
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
|
||||
@@ -11,11 +11,13 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use function sprintf;
|
||||
use function strpos;
|
||||
use function str_contains;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
@@ -57,11 +59,13 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
* Default column renaming mode.
|
||||
*
|
||||
* @var int
|
||||
* @psalm-var self::COLUMN_RENAMING_*
|
||||
*/
|
||||
private $defaultRenameMode;
|
||||
|
||||
/**
|
||||
* @param int $defaultRenameMode
|
||||
* @psalm-param self::COLUMN_RENAMING_* $defaultRenameMode
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, $defaultRenameMode = self::COLUMN_RENAMING_NONE)
|
||||
{
|
||||
@@ -76,7 +80,9 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
* @param string $alias The unique alias to use for the root entity.
|
||||
* @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
|
||||
* @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).
|
||||
* @psalm-param class-string $class
|
||||
* @psalm-param array<string, string> $renamedColumns
|
||||
* @psalm-param self::COLUMN_RENAMING_*|null $renameMode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -99,7 +105,9 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
* with the joined entity result.
|
||||
* @param string[] $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
|
||||
* @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).
|
||||
* @psalm-param class-string $class
|
||||
* @psalm-param array<string, string> $renamedColumns
|
||||
* @psalm-param self::COLUMN_RENAMING_*|null $renameMode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -186,6 +194,8 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
* Gets column alias for a given column.
|
||||
*
|
||||
* @psalm-param array<string, string> $customRenameColumns
|
||||
*
|
||||
* @psalm-assert self::COLUMN_RENAMING_* $mode
|
||||
*/
|
||||
private function getColumnAlias(string $columnName, int $mode, array $customRenameColumns): string
|
||||
{
|
||||
@@ -273,8 +283,10 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
public function addNamedNativeQueryResultClassMapping(ClassMetadataInfo $class, $resultClassName)
|
||||
{
|
||||
$classMetadata = $this->em->getClassMetadata($resultClassName);
|
||||
$shortName = $classMetadata->reflClass->getShortName();
|
||||
$alias = strtolower($shortName[0]) . '0';
|
||||
assert($classMetadata->reflClass !== null);
|
||||
|
||||
$shortName = $classMetadata->reflClass->getShortName();
|
||||
$alias = strtolower($shortName[0]) . '0';
|
||||
|
||||
$this->addEntityResult($class->name, $alias);
|
||||
|
||||
@@ -316,14 +328,19 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
*/
|
||||
public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName)
|
||||
{
|
||||
if ($class->reflClass === null) {
|
||||
throw new LogicException('Given class metadata has now class reflector.');
|
||||
}
|
||||
|
||||
$counter = 0;
|
||||
$resultMapping = $class->getSqlResultSetMapping($resultSetMappingName);
|
||||
$rootShortName = $class->reflClass->getShortName();
|
||||
$rootAlias = strtolower($rootShortName[0]) . $counter;
|
||||
|
||||
if (isset($resultMapping['entities'])) {
|
||||
foreach ($resultMapping['entities'] as $key => $entityMapping) {
|
||||
foreach ($resultMapping['entities'] as $entityMapping) {
|
||||
$classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']);
|
||||
assert($classMetadata->reflClass !== null);
|
||||
|
||||
if ($class->reflClass->name === $classMetadata->reflClass->name) {
|
||||
$this->addEntityResult($classMetadata->name, $rootAlias);
|
||||
@@ -381,7 +398,7 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
$fieldName = $field['name'];
|
||||
$relation = null;
|
||||
|
||||
if (strpos($fieldName, '.') !== false) {
|
||||
if (str_contains($fieldName, '.')) {
|
||||
[$relation, $fieldName] = explode('.', $fieldName);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,15 @@ use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
use function array_diff;
|
||||
use function array_filter;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
@@ -40,6 +43,9 @@ use function trim;
|
||||
/**
|
||||
* The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
|
||||
* the corresponding SQL.
|
||||
*
|
||||
* @psalm-import-type QueryComponent from Parser
|
||||
* @psalm-consistent-constructor
|
||||
*/
|
||||
class SqlWalker implements TreeWalker
|
||||
{
|
||||
@@ -106,7 +112,7 @@ class SqlWalker implements TreeWalker
|
||||
/**
|
||||
* Map from result variable names to their SQL column alias names.
|
||||
*
|
||||
* @psalm-var array<string, string|list<string>>
|
||||
* @psalm-var array<string|int, string|list<string>>
|
||||
*/
|
||||
private $scalarResultAliasMap = [];
|
||||
|
||||
@@ -127,21 +133,14 @@ class SqlWalker implements TreeWalker
|
||||
/**
|
||||
* Map of all components/classes that appear in the DQL query.
|
||||
*
|
||||
* @psalm-var array<string, array{
|
||||
* metadata: ClassMetadata,
|
||||
* parent: string,
|
||||
* relation: mixed[],
|
||||
* map: mixed,
|
||||
* nestingLevel: int,
|
||||
* token: array
|
||||
* }>
|
||||
* @psalm-var array<string, QueryComponent>
|
||||
*/
|
||||
private $queryComponents;
|
||||
|
||||
/**
|
||||
* A list of classes that appear in non-scalar SelectExpressions.
|
||||
*
|
||||
* @psalm-var list<array{class: ClassMetadata, dqlAlias: string, resultAlias: string}>
|
||||
* @psalm-var array<string, array{class: ClassMetadata, dqlAlias: string, resultAlias: string|null}>
|
||||
*/
|
||||
private $selectedClasses = [];
|
||||
|
||||
@@ -175,7 +174,9 @@ class SqlWalker implements TreeWalker
|
||||
private $quoteStrategy;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @param Query $query The parsed Query.
|
||||
* @param ParserResult $parserResult The result of the parsing process.
|
||||
* @psalm-param array<string, QueryComponent> $queryComponents The query components (symbol table).
|
||||
*/
|
||||
public function __construct($query, $parserResult, array $queryComponents)
|
||||
{
|
||||
@@ -225,20 +226,22 @@ class SqlWalker implements TreeWalker
|
||||
* @param string $dqlAlias The DQL alias.
|
||||
*
|
||||
* @return mixed[]
|
||||
* @psalm-return array{
|
||||
* metadata: ClassMetadata,
|
||||
* parent: string,
|
||||
* relation: mixed[],
|
||||
* map: mixed,
|
||||
* nestingLevel: int,
|
||||
* token: array
|
||||
* }
|
||||
* @psalm-return QueryComponent
|
||||
*/
|
||||
public function getQueryComponent($dqlAlias)
|
||||
{
|
||||
return $this->queryComponents[$dqlAlias];
|
||||
}
|
||||
|
||||
public function getMetadataForDqlAlias(string $dqlAlias): ClassMetadata
|
||||
{
|
||||
if (! isset($this->queryComponents[$dqlAlias]['metadata'])) {
|
||||
throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
|
||||
}
|
||||
|
||||
return $this->queryComponents[$dqlAlias]['metadata'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -411,6 +414,7 @@ class SqlWalker implements TreeWalker
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(isset($qComp['metadata']));
|
||||
$persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
|
||||
|
||||
foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
|
||||
@@ -444,7 +448,7 @@ class SqlWalker implements TreeWalker
|
||||
$sqlParts = [];
|
||||
|
||||
foreach ($dqlAliases as $dqlAlias) {
|
||||
$class = $this->queryComponents[$dqlAlias]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
|
||||
if (! $class->isInheritanceTypeSingleTable()) {
|
||||
continue;
|
||||
@@ -613,7 +617,7 @@ class SqlWalker implements TreeWalker
|
||||
*/
|
||||
public function walkEntityIdentificationVariable($identVariable)
|
||||
{
|
||||
$class = $this->queryComponents[$identVariable]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($identVariable);
|
||||
$tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
|
||||
$sqlParts = [];
|
||||
|
||||
@@ -634,7 +638,7 @@ class SqlWalker implements TreeWalker
|
||||
*/
|
||||
public function walkIdentificationVariable($identificationVariable, $fieldName = null)
|
||||
{
|
||||
$class = $this->queryComponents[$identificationVariable]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($identificationVariable);
|
||||
|
||||
if (
|
||||
$fieldName !== null && $class->isInheritanceTypeJoined() &&
|
||||
@@ -652,12 +656,13 @@ class SqlWalker implements TreeWalker
|
||||
public function walkPathExpression($pathExpr)
|
||||
{
|
||||
$sql = '';
|
||||
assert($pathExpr->field !== null);
|
||||
|
||||
switch ($pathExpr->type) {
|
||||
case AST\PathExpression::TYPE_STATE_FIELD:
|
||||
$fieldName = $pathExpr->field;
|
||||
$dqlAlias = $pathExpr->identificationVariable;
|
||||
$class = $this->queryComponents[$dqlAlias]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
|
||||
if ($this->useSqlTableAliases) {
|
||||
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
|
||||
@@ -671,7 +676,7 @@ class SqlWalker implements TreeWalker
|
||||
// Just use the foreign key, i.e. u.group_id
|
||||
$fieldName = $pathExpr->field;
|
||||
$dqlAlias = $pathExpr->identificationVariable;
|
||||
$class = $this->queryComponents[$dqlAlias]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
|
||||
if (isset($class->associationMappings[$fieldName]['inherited'])) {
|
||||
$class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
|
||||
@@ -724,9 +729,11 @@ class SqlWalker implements TreeWalker
|
||||
$resultAlias = $selectedClass['resultAlias'];
|
||||
|
||||
// Register as entity or joined entity result
|
||||
if ($this->queryComponents[$dqlAlias]['relation'] === null) {
|
||||
if (! isset($this->queryComponents[$dqlAlias]['relation'])) {
|
||||
$this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
|
||||
} else {
|
||||
assert(isset($this->queryComponents[$dqlAlias]['parent']));
|
||||
|
||||
$this->rsm->addJoinedEntityResult(
|
||||
$class->name,
|
||||
$dqlAlias,
|
||||
@@ -864,6 +871,7 @@ class SqlWalker implements TreeWalker
|
||||
{
|
||||
$pathExpression = $indexBy->singleValuedPathExpression;
|
||||
$alias = $pathExpression->identificationVariable;
|
||||
assert($pathExpression->field !== null);
|
||||
|
||||
switch ($pathExpression->type) {
|
||||
case AST\PathExpression::TYPE_STATE_FIELD:
|
||||
@@ -873,7 +881,7 @@ class SqlWalker implements TreeWalker
|
||||
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
|
||||
// Just use the foreign key, i.e. u.group_id
|
||||
$fieldName = $pathExpression->field;
|
||||
$class = $this->queryComponents[$alias]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($alias);
|
||||
|
||||
if (isset($class->associationMappings[$fieldName]['inherited'])) {
|
||||
$class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
|
||||
@@ -956,6 +964,7 @@ class SqlWalker implements TreeWalker
|
||||
* @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
|
||||
* @param int $joinType
|
||||
* @param AST\ConditionalExpression $condExpr
|
||||
* @psalm-param AST\Join::JOIN_TYPE_* $joinType
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
@@ -969,7 +978,8 @@ class SqlWalker implements TreeWalker
|
||||
$joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable;
|
||||
$indexBy = $joinAssociationDeclaration->indexBy;
|
||||
|
||||
$relation = $this->queryComponents[$joinedDqlAlias]['relation'];
|
||||
$relation = $this->queryComponents[$joinedDqlAlias]['relation'] ?? null;
|
||||
assert($relation !== null);
|
||||
$targetClass = $this->em->getClassMetadata($relation['targetEntity']);
|
||||
$sourceClass = $this->em->getClassMetadata($relation['sourceEntity']);
|
||||
$targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
|
||||
@@ -1321,10 +1331,10 @@ class SqlWalker implements TreeWalker
|
||||
throw QueryException::invalidPathExpression($expr);
|
||||
}
|
||||
|
||||
assert($expr->field !== null);
|
||||
$fieldName = $expr->field;
|
||||
$dqlAlias = $expr->identificationVariable;
|
||||
$qComp = $this->queryComponents[$dqlAlias];
|
||||
$class = $qComp['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
|
||||
$resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
|
||||
$tableName = $class->isInheritanceTypeJoined()
|
||||
@@ -1349,6 +1359,10 @@ class SqlWalker implements TreeWalker
|
||||
if (! $hidden) {
|
||||
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
|
||||
$this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
|
||||
|
||||
if (! empty($fieldMapping['enumType'])) {
|
||||
$this->rsm->addEnumResult($columnAlias, $fieldMapping['enumType']);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1418,8 +1432,7 @@ class SqlWalker implements TreeWalker
|
||||
$partialFieldSet = [];
|
||||
}
|
||||
|
||||
$queryComp = $this->queryComponents[$dqlAlias];
|
||||
$class = $queryComp['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
$resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
|
||||
|
||||
if (! isset($this->selectedClasses[$dqlAlias])) {
|
||||
@@ -1590,12 +1603,12 @@ class SqlWalker implements TreeWalker
|
||||
break;
|
||||
|
||||
case $e instanceof AST\PathExpression:
|
||||
assert($e->field !== null);
|
||||
$dqlAlias = $e->identificationVariable;
|
||||
$qComp = $this->queryComponents[$dqlAlias];
|
||||
$class = $qComp['metadata'];
|
||||
$fieldType = $class->fieldMappings[$e->field]['type'];
|
||||
$class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
$fieldName = $e->field;
|
||||
$fieldMapping = $class->fieldMappings[$fieldName];
|
||||
$fieldType = $fieldMapping['type'];
|
||||
$col = trim($e->dispatch($this));
|
||||
|
||||
if (isset($fieldMapping['requireSQLConversion'])) {
|
||||
@@ -1730,7 +1743,7 @@ class SqlWalker implements TreeWalker
|
||||
return $this->walkPathExpression($resultVariable);
|
||||
}
|
||||
|
||||
if (isset($resultVariable->pathExpression)) {
|
||||
if ($resultVariable instanceof AST\Node && isset($resultVariable->pathExpression)) {
|
||||
return $this->walkPathExpression($resultVariable->pathExpression);
|
||||
}
|
||||
|
||||
@@ -1740,14 +1753,14 @@ class SqlWalker implements TreeWalker
|
||||
// IdentificationVariable
|
||||
$sqlParts = [];
|
||||
|
||||
foreach ($this->queryComponents[$groupByItem]['metadata']->fieldNames as $field) {
|
||||
foreach ($this->getMetadataForDqlAlias($groupByItem)->fieldNames as $field) {
|
||||
$item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
|
||||
$item->type = AST\PathExpression::TYPE_STATE_FIELD;
|
||||
|
||||
$sqlParts[] = $this->walkPathExpression($item);
|
||||
}
|
||||
|
||||
foreach ($this->queryComponents[$groupByItem]['metadata']->associationMappings as $mapping) {
|
||||
foreach ($this->getMetadataForDqlAlias($groupByItem)->associationMappings as $mapping) {
|
||||
if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
|
||||
$item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
|
||||
$item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
|
||||
@@ -1830,7 +1843,7 @@ class SqlWalker implements TreeWalker
|
||||
if ($this->em->hasFilters()) {
|
||||
$filterClauses = [];
|
||||
foreach ($this->rootAliases as $dqlAlias) {
|
||||
$class = $this->queryComponents[$dqlAlias]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
$tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
|
||||
$filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
|
||||
@@ -1937,11 +1950,12 @@ class SqlWalker implements TreeWalker
|
||||
|
||||
$entityExpr = $collMemberExpr->entityExpression;
|
||||
$collPathExpr = $collMemberExpr->collectionValuedPathExpression;
|
||||
assert($collPathExpr->field !== null);
|
||||
|
||||
$fieldName = $collPathExpr->field;
|
||||
$dqlAlias = $collPathExpr->identificationVariable;
|
||||
|
||||
$class = $this->queryComponents[$dqlAlias]['metadata'];
|
||||
$class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
|
||||
switch (true) {
|
||||
// InputParameter
|
||||
@@ -2081,7 +2095,7 @@ class SqlWalker implements TreeWalker
|
||||
$sql = '';
|
||||
|
||||
$dqlAlias = $instanceOfExpr->identificationVariable;
|
||||
$discrClass = $class = $this->queryComponents[$dqlAlias]['metadata'];
|
||||
$discrClass = $class = $this->getMetadataForDqlAlias($dqlAlias);
|
||||
|
||||
if ($class->discriminatorColumn) {
|
||||
$discrClass = $this->em->getClassMetadata($class->rootEntityName);
|
||||
@@ -2152,9 +2166,15 @@ class SqlWalker implements TreeWalker
|
||||
public function walkLikeExpression($likeExpr)
|
||||
{
|
||||
$stringExpr = $likeExpr->stringExpression;
|
||||
$leftExpr = is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable'])
|
||||
? $this->walkResultVariable($stringExpr)
|
||||
: $stringExpr->dispatch($this);
|
||||
if (is_string($stringExpr)) {
|
||||
if (! isset($this->queryComponents[$stringExpr]['resultVariable'])) {
|
||||
throw new LogicException(sprintf('No result variable found for string expression "%s".', $stringExpr));
|
||||
}
|
||||
|
||||
$leftExpr = $this->walkResultVariable($stringExpr);
|
||||
} else {
|
||||
$leftExpr = $stringExpr->dispatch($this);
|
||||
}
|
||||
|
||||
$sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
|
||||
|
||||
@@ -2323,6 +2343,10 @@ class SqlWalker implements TreeWalker
|
||||
*/
|
||||
public function walkResultVariable($resultVariable)
|
||||
{
|
||||
if (! isset($this->scalarResultAliasMap[$resultVariable])) {
|
||||
throw new InvalidArgumentException(sprintf('Unknown result variable: %s', $resultVariable));
|
||||
}
|
||||
|
||||
$resultAlias = $this->scalarResultAliasMap[$resultVariable];
|
||||
|
||||
if (is_array($resultAlias)) {
|
||||
|
||||
@@ -5,10 +5,11 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Interface for walkers of DQL ASTs (abstract syntax trees).
|
||||
*
|
||||
* @psalm-import-type QueryComponent from Parser
|
||||
*/
|
||||
interface TreeWalker
|
||||
{
|
||||
@@ -18,6 +19,7 @@ interface TreeWalker
|
||||
* @param AbstractQuery $query The parsed Query.
|
||||
* @param ParserResult $parserResult The result of the parsing process.
|
||||
* @param mixed[] $queryComponents The query components (symbol table).
|
||||
* @psalm-param array<string, QueryComponent> $queryComponents The query components (symbol table).
|
||||
*/
|
||||
public function __construct($query, $parserResult, array $queryComponents);
|
||||
|
||||
@@ -25,14 +27,7 @@ interface TreeWalker
|
||||
* Returns internal queryComponents array.
|
||||
*
|
||||
* @return array<string, array<string, mixed>>
|
||||
* @psalm-return array<string, array{
|
||||
* metadata: ClassMetadata,
|
||||
* parent: string,
|
||||
* relation: mixed[],
|
||||
* map: mixed,
|
||||
* nestingLevel: int,
|
||||
* token: array
|
||||
* }>
|
||||
* @psalm-return array<string, QueryComponent>
|
||||
*/
|
||||
public function getQueryComponents();
|
||||
|
||||
@@ -41,6 +36,7 @@ interface TreeWalker
|
||||
*
|
||||
* @param string $dqlAlias The DQL alias.
|
||||
* @param array<string, mixed> $queryComponent
|
||||
* @psalm-param QueryComponent $queryComponent
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -339,7 +335,7 @@ interface TreeWalker
|
||||
/**
|
||||
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @param mixed $literal
|
||||
* @param AST\Literal $literal
|
||||
*
|
||||
* @return string The SQL.
|
||||
*/
|
||||
@@ -438,7 +434,7 @@ interface TreeWalker
|
||||
/**
|
||||
* Walks down a PathExpression AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @param mixed $pathExpr
|
||||
* @param AST\PathExpression $pathExpr
|
||||
*
|
||||
* @return string The SQL.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
use function array_diff;
|
||||
use function array_keys;
|
||||
@@ -13,6 +12,8 @@ use function array_keys;
|
||||
/**
|
||||
* An adapter implementation of the TreeWalker interface. The methods in this class
|
||||
* are empty. This class exists as convenience for creating tree walkers.
|
||||
*
|
||||
* @psalm-import-type QueryComponent from Parser
|
||||
*/
|
||||
abstract class TreeWalkerAdapter implements TreeWalker
|
||||
{
|
||||
@@ -33,14 +34,7 @@ abstract class TreeWalkerAdapter implements TreeWalker
|
||||
/**
|
||||
* The query components of the original query (the "symbol table") that was produced by the Parser.
|
||||
*
|
||||
* @psalm-var array<string, array{
|
||||
* metadata: ClassMetadata,
|
||||
* parent: string,
|
||||
* relation: mixed[],
|
||||
* map: mixed,
|
||||
* nestingLevel: int,
|
||||
* token: array
|
||||
* }>
|
||||
* @psalm-var array<string, QueryComponent>
|
||||
*/
|
||||
private $_queryComponents;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user