diff --git a/.doctrine-project.json b/.doctrine-project.json index a79077189c3..70f63b8bd01 100644 --- a/.doctrine-project.json +++ b/.doctrine-project.json @@ -12,21 +12,27 @@ "upcoming": true }, { - "name": "2.12", - "branchName": "2.12.x", - "slug": "2.12", + "name": "2.13", + "branchName": "2.13.x", + "slug": "2.13", "upcoming": true }, { - "name": "2.11", - "branchName": "2.11.x", - "slug": "2.11", + "name": "2.12", + "branchName": "2.12.x", + "slug": "2.12", "current": true, "aliases": [ "current", "stable" ] }, + { + "name": "2.11", + "branchName": "2.11.x", + "slug": "2.11", + "maintained": false + }, { "name": "2.10", "branchName": "2.10.x", diff --git a/README.md b/README.md index 7a817e24d47..92b14b99ac0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -| [3.0.x][3.0] | [2.12.x][2.12] | [2.11.x][2.11] | +| [3.0.x][3.0] | [2.13.x][2.13] | [2.12.x][2.12] | |:----------------:|:----------------:|:----------:| -| [![Build status][3.0 image]][3.0] | [![Build status][2.12 image]][2.12] | [![Build status][2.11 image]][2.11] | -| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.12 coverage image]][2.12 coverage] | [![Coverage Status][2.11 coverage image]][2.11 coverage] | +| [![Build status][3.0 image]][3.0] | [![Build status][2.13 image]][2.13] | [![Build status][2.12 image]][2.12] | +| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.13 coverage image]][2.13 coverage] | [![Coverage Status][2.12 coverage image]][2.12 coverage] | [

πŸ‡ΊπŸ‡¦ UKRAINE NEEDS YOUR HELP NOW!

](https://www.doctrine-project.org/stop-war.html) @@ -22,11 +22,11 @@ without requiring unnecessary code duplication. [3.0]: https://github.com/doctrine/orm/tree/3.0.x [3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg [3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x + [2.13 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.13.x + [2.13]: https://github.com/doctrine/orm/tree/2.13.x + [2.13 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.13.x/graph/badge.svg + [2.13 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.13.x [2.12 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.12.x [2.12]: https://github.com/doctrine/orm/tree/2.12.x [2.12 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.12.x/graph/badge.svg [2.12 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.12.x - [2.11 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.11.x - [2.11]: https://github.com/doctrine/orm/tree/2.11.x - [2.11 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.11.x/graph/badge.svg - [2.11 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.11.x diff --git a/UPGRADE.md b/UPGRADE.md index 27dc3c7198d..8bf749997f8 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -460,6 +460,20 @@ Use `toIterable()` instead. # Upgrade to 2.13 +## Deprecated `QueryBuilder` methods and constants. + +1. The `QueryBuilder::getState()` method has been deprecated as the builder state is an internal concern. +2. Relying on the type of the query being built by using `QueryBuilder::getType()` has been deprecated. + If necessary, track the type of the query being built outside of the builder. + +The following `QueryBuilder` constants related to the above methods have been deprecated: + +1. `SELECT`, +2. `DELETE`, +3. `UPDATE`, +4. `STATE_DIRTY`, +5. `STATE_CLEAN`. + ## Deprecated omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete` When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted. diff --git a/composer.json b/composer.json index 183fae8d274..84dd72af332 100644 --- a/composer.json +++ b/composer.json @@ -39,12 +39,12 @@ "doctrine/annotations": "^1.13", "doctrine/coding-standard": "^9.0", "phpbench/phpbench": "^1.0", - "phpstan/phpstan": "1.8.0", + "phpstan/phpstan": "1.8.2", "phpunit/phpunit": "^9.5", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.1", "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.24.0" + "vimeo/psalm": "4.25.0" }, "conflict": { "doctrine/annotations": "<1.13 || >= 2.0" diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php index 209ffd15915..15e11db93ee 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php @@ -12,6 +12,7 @@ use Doctrine\ORM\Utility\IdentifierFlattener; use function array_merge; +use function assert; use function is_array; use function is_object; use function reset; @@ -41,6 +42,7 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, ob if ($metadata->requiresFetchAfterChange) { if ($metadata->isVersioned) { + assert($metadata->versionField !== null); $data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField); } diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 122b8d92851..a47c8647783 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -59,8 +59,10 @@ * is not a valid extension point for the EntityManager. Instead you * should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator} * and wrap your entity manager in a decorator. + * + * @final */ -/* final */class EntityManager implements EntityManagerInterface +class EntityManager implements EntityManagerInterface { /** * The used Configuration. @@ -121,11 +123,15 @@ * Creates a new EntityManager that operates on the given database connection * and uses the given Configuration and EventManager implementations. */ - protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager) + public function __construct(Connection $conn, Configuration $config) { + if (! $config->getMetadataDriverImpl()) { + throw MissingMappingDriverImplementation::create(); + } + $this->conn = $conn; $this->config = $config; - $this->eventManager = $eventManager; + $this->eventManager = $conn->getEventManager(); $metadataFactoryClassName = $config->getClassMetadataFactoryName(); @@ -637,13 +643,9 @@ public function initializeObject(object $obj): void */ public static function create(array|Connection $connection, Configuration $config, ?EventManager $eventManager = null): EntityManager { - if (! $config->getMetadataDriverImpl()) { - throw MissingMappingDriverImplementation::create(); - } - $connection = static::createConnection($connection, $config, $eventManager); - return new EntityManager($connection, $config, $connection->getEventManager()); + return new EntityManager($connection, $config); } /** diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index c46eacc60ed..5d22a9643c5 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -695,7 +695,7 @@ class ClassMetadataInfo implements ClassMetadata /** * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any). * - * @var mixed + * @var string|null */ public $versionField; @@ -3213,7 +3213,7 @@ public function setVersioned($bool) * Sets the name of the field that is to be used for versioning if this class is * versioned for optimistic locking. * - * @param string $versionField + * @param string|null $versionField * * @return void */ diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index 312d85e6fad..296b3f63f47 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -450,7 +450,8 @@ final protected function updateTable( } if ($versioned) { - $versionField = $this->class->versionField; + $versionField = $this->class->versionField; + assert($versionField !== null); $versionFieldType = $this->class->fieldMappings[$versionField]['type']; $versionColumn = $this->quoteStrategy->getColumnName($versionField, $this->class, $this->platform); diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index d757e79d0e4..8c0e23709fe 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -38,13 +38,19 @@ */ class QueryBuilder { - /* The query types. */ + /** @deprecated */ public const SELECT = 0; + + /** @deprecated */ public const DELETE = 1; + + /** @deprecated */ public const UPDATE = 2; - /* The builder states. */ + /** @deprecated */ public const STATE_DIRTY = 0; + + /** @deprecated */ public const STATE_CLEAN = 1; /** @@ -237,10 +243,19 @@ public function setCacheMode(int $cacheMode): static /** * Gets the type of the currently built query. * + * @deprecated If necessary, track the type of the query being built outside of the builder. + * * @psalm-return self::SELECT|self::DELETE|self::UPDATE */ public function getType(): int { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/orm/pull/9945', + 'Relying on the type of the query being built is deprecated.' + . ' If necessary, track the type of the query being built outside of the builder.' + ); + return $this->type; } @@ -255,11 +270,19 @@ public function getEntityManager(): EntityManagerInterface /** * Gets the state of this query builder instance. * + * @deprecated The builder state is an internal concern. + * * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. * @psalm-return self::STATE_* */ public function getState(): int { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/orm/pull/9945', + 'Relying on the query builder state is deprecated as it is an internal concern.' + ); + return $this->state; } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 8dcb54f165b..4ceddeb8406 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -55,6 +55,7 @@ use function array_pop; use function array_sum; use function array_values; +use function assert; use function count; use function current; use function get_debug_type; @@ -1455,6 +1456,8 @@ public function getEntityState(object $entity, ?int $assume = null): int case $class->isIdentifierNatural(): // Check for a version field, if available, to avoid a db lookup. if ($class->isVersioned) { + assert($class->versionField !== null); + return $class->getFieldValue($entity, $class->versionField) ? self::STATE_DETACHED : self::STATE_NEW; @@ -2035,6 +2038,7 @@ public function lock(object $entity, LockMode|int $lockMode, $lockVersion = null $entity->__load(); } + assert($class->versionField !== null); $entityVersion = $class->reflFields[$class->versionField]->getValue($entity); // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 0787c8953dd..1a94ae786d7 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null @@ -179,12 +179,9 @@ - + $className $connection - ltrim($className, '\\') - ltrim($entityName, '\\') - ltrim($entityName, '\\') $entity @@ -498,10 +495,9 @@ $sequenceGeneratorDefinition $table - + $this->associationMappings $this->associationMappings - $this->discriminatorMap $this->entityListeners $this->fieldMappings $this->fullyQualifiedClassName($repositoryClassName) @@ -743,6 +739,10 @@ + + $className + $entityName + $className $className @@ -1992,6 +1992,7 @@ + $this->conn->quote((string) $newValue) is_string($expression) @@ -2128,7 +2129,6 @@ $entity - $entity $state === UnitOfWork::STATE_DETACHED diff --git a/psalm.xml b/psalm.xml index b6a1d02eaa7..5d876ea7b27 100644 --- a/psalm.xml +++ b/psalm.xml @@ -17,11 +17,6 @@ - - - - - @@ -32,6 +27,12 @@ + + + + + + diff --git a/tests/Doctrine/Performance/EntityManagerFactory.php b/tests/Doctrine/Performance/EntityManagerFactory.php index 74416b2f33d..3ea47ee3e9b 100644 --- a/tests/Doctrine/Performance/EntityManagerFactory.php +++ b/tests/Doctrine/Performance/EntityManagerFactory.php @@ -71,6 +71,6 @@ public function executeQuery(string $sql, array $params = [], $types = [], ?Quer } }; - return EntityManager::create($connection, $config); + return EntityManager::create($connection, $config, $connection->getEventManager()); } } diff --git a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php index f22da389293..d1c1493b799 100644 --- a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php +++ b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php @@ -58,8 +58,6 @@ public static function create($conn, ?Configuration $config = null, ?EventManage $config->setMetadataDriverImpl(ORMSetup::createDefaultAnnotationDriver()); } - $eventManager ??= new EventManager(); - - return new EntityManagerMock($conn, $config, $eventManager); + return new EntityManagerMock($conn, $config); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php b/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php index 599e96b4d0b..73278d04794 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php @@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional; +use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Configuration; @@ -208,7 +209,11 @@ private function configureFilters(EntityManagerInterface $em): void */ private function getMockConnection(): Connection { - return $this->createMock(Connection::class); + $connection = $this->createMock(Connection::class); + $connection->method('getEventManager') + ->willReturn(new EventManager()); + + return $connection; } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7869Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7869Test.php index 8a60355d7ff..faf3332b504 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7869Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7869Test.php @@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional\Ticket; +use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\ORM\Decorator\EntityManagerDecorator; @@ -31,6 +32,8 @@ public function testDQLDeferredEagerLoad(): void $connection = $this->createMock(Connection::class); $connection->method('getDatabasePlatform') ->willReturn($platform); + $connection->method('getEventManager') + ->willReturn(new EventManager()); $em = new class (EntityManagerMock::create($connection)) extends EntityManagerDecorator { /** @var int */ diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php index e0101867488..a17b3c470a1 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php @@ -232,7 +232,8 @@ public function testAddDefaultDiscriminatorMap(): void public function testGetAllMetadataWorksWithBadConnection(): void { // DDC-3551 - $conn = $this->createMock(Connection::class); + $conn = $this->createMock(Connection::class); + $conn->method('getEventManager')->willReturn($this->createMock(EventManager::class)); $mockDriver = new MetadataDriverMock(); $em = $this->createEntityManager($mockDriver, $conn); @@ -269,7 +270,7 @@ protected function createEntityManager(MappingDriver $metadataDriver, $conn = nu $config->setMetadataDriverImpl($metadataDriver); - return EntityManagerMock::create($conn, $config, $eventManager); + return EntityManagerMock::create($conn, $config); } protected function createTestFactory(): ClassMetadataFactoryTestSubject diff --git a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php index f4d82b265e1..45733f148bd 100644 --- a/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/PersistentCollectionTest.php @@ -5,6 +5,7 @@ namespace Doctrine\Tests\ORM; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Result; @@ -41,6 +42,8 @@ protected function setUp(): void $connection = $this->createMock(Connection::class); $connection->method('getDatabasePlatform') ->willReturn($platform); + $connection->method('getEventManager') + ->willReturn(new EventManager()); $connection->method('executeQuery') ->willReturn($this->createMock(Result::class)); diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php index d6bc52397c4..ab21c480f41 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyFactoryTest.php @@ -5,6 +5,7 @@ namespace Doctrine\Tests\ORM\Proxy; use Closure; +use Doctrine\Common\EventManager; use Doctrine\Common\Proxy\AbstractProxyFactory; use Doctrine\Common\Proxy\Proxy; use Doctrine\DBAL\Connection; @@ -46,6 +47,8 @@ protected function setUp(): void $connection = $this->createMock(Connection::class); $connection->method('getDatabasePlatform') ->willReturn($platform); + $connection->method('getEventManager') + ->willReturn(new EventManager()); $this->emMock = EntityManagerMock::create($connection); $this->uowMock = new UnitOfWorkMock($this->emMock); diff --git a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php index 0beb4d05add..0df86f09cb9 100644 --- a/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php @@ -90,9 +90,9 @@ protected function setUp(): void $driver->method('connect') ->willReturn($driverConnection); - $this->connection = new Connection([], $driver); $this->eventManager = $this->getMockBuilder(EventManager::class)->getMock(); - $this->_emMock = EntityManagerMock::create($this->connection, null, $this->eventManager); + $this->connection = new Connection([], $driver, null, $this->eventManager); + $this->_emMock = EntityManagerMock::create($this->connection); // SUT $this->_unitOfWork = new UnitOfWorkMock($this->_emMock); $this->_emMock->setUnitOfWork($this->_unitOfWork); @@ -611,7 +611,7 @@ public function testCommitThrowOptimisticLockExceptionWhenConnectionCommitFails( ->onlyMethods(['commit']) ->setConstructorArgs([[], $driver]) ->getMock(); - $this->_emMock = EntityManagerMock::create($this->connection, null, $this->eventManager); + $this->_emMock = EntityManagerMock::create($this->connection); $this->_unitOfWork = new UnitOfWorkMock($this->_emMock); $this->_emMock->setUnitOfWork($this->_unitOfWork);