From ae842259f54eabfdc99330d9fb6c7224dfe2659a Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Sun, 17 Mar 2024 12:24:10 +0100 Subject: [PATCH 01/11] [Documentation] Query Result Formats Page: https://www.doctrine-project.org/projects/doctrine-orm/en/2.19/reference/dql-doctrine-query-language.html#query-result-formats Follow-up of https://github.com/doctrine/orm/pull/11359 The table I suggested is probably not working, since the text for each method is too long. And what I really wanted is to make it more *scanable*. So I tried boldfacing - if this doesn't work, I'll try something else. Questions: 1. This section here is basically the same as https://www.doctrine-project.org/projects/doctrine-orm/en/2.10/reference/dql-doctrine-query-language.html#hydration-modes ! So I'll try to merge them (in another PR), OK? I think the list is a better format (more scanable) - since those methods all work the same, there's no need for a full-blown code sample for each, IMO. 2. `getSingleColumnResult()` is missing. --- .../reference/dql-doctrine-query-language.rst | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/docs/en/reference/dql-doctrine-query-language.rst b/docs/en/reference/dql-doctrine-query-language.rst index 571190072e3..0e21c0aa0bc 100644 --- a/docs/en/reference/dql-doctrine-query-language.rst +++ b/docs/en/reference/dql-doctrine-query-language.rst @@ -1003,7 +1003,7 @@ The Query class --------------- An instance of the ``Doctrine\ORM\Query`` class represents a DQL -query. You create a Query instance be calling +query. You create a Query instance by calling ``EntityManager#createQuery($dql)``, passing the DQL query string. Alternatively you can create an empty ``Query`` instance and invoke ``Query#setDQL($dql)`` afterwards. Here are some examples: @@ -1023,26 +1023,21 @@ Alternatively you can create an empty ``Query`` instance and invoke Query Result Formats ~~~~~~~~~~~~~~~~~~~~ -The format in which the result of a DQL SELECT query is returned -can be influenced by a so-called ``hydration mode``. A hydration -mode specifies a particular way in which a SQL result set is -transformed. Each hydration mode has its own dedicated method on -the Query class. Here they are: +The way in which the SQL result set of a DQL SELECT query is transformed +to PHP is determined by the so-called "hydration mode": - -- ``Query#getResult()``: Retrieves a collection of objects. The +- **``getResult()``** (``HYDRATE_OBJECT``): Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array where the objects are nested in the result rows (mixed). -- ``Query#getSingleResult()``: Retrieves a single object. If the - result contains more than one object, an ``NonUniqueResultException`` - is thrown. If the result contains no objects, an ``NoResultException`` +- **``getSingleResult()``**: Retrieves a single object. If the + result contains more than one object, a ``NonUniqueResultException`` + is thrown. If the result contains no objects, a ``NoResultException`` is thrown. The pure/mixed distinction does not apply. -- ``Query#getOneOrNullResult()``: Retrieve a single object. If the +- **``getOneOrNullResult()``**: Retrieve a single object. If the result contains more than one object, a ``NonUniqueResultException`` is thrown. If no object is found null will be returned. -- ``Query#getArrayResult()``: Retrieves an array graph (a nested - array) that is largely interchangeable with the object graph - generated by ``Query#getResult()`` for read-only purposes. +- **``getArrayResult()``** (``HYDRATE_ARRAY``): Retrieves an array graph (a nested + array) for read-only purposes. .. note:: @@ -1050,28 +1045,17 @@ the Query class. Here they are: graph in certain scenarios due to the difference of the identity semantics between arrays and objects. - - -- ``Query#getScalarResult()``: Retrieves a flat/rectangular result +- **``getScalarResult()``** (``HYDRATE_SCALAR``): Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The pure/mixed distinction does not apply. -- ``Query#getSingleScalarResult()``: Retrieves a single scalar +- **``getSingleScalarResult()``** (``HYDRATE_SINGLE_SCALAR``: Retrieves a single scalar value from the result returned by the dbms. If the result contains more than a single scalar value, an exception is thrown. The pure/mixed distinction does not apply. -Instead of using these methods, you can alternatively use the -general-purpose method -``Query#execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``. -Using this method you can directly supply the hydration mode as the -second parameter via one of the Query constants. In fact, the -methods mentioned earlier are just convenient shortcuts for the -execute method. For example, the method ``Query#getResult()`` -internally invokes execute, passing in ``Query::HYDRATE_OBJECT`` as -the hydration mode. - -The use of the methods mentioned earlier is generally preferred as -it leads to more concise code. +In parentheses are the constants of the ``Query`` class which you can use with the +general-purpose method ``Query::execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``. +In fact, the recommended methods in the list are just convenient shortcuts for the hydration mode. Pure and Mixed Results ~~~~~~~~~~~~~~~~~~~~~~ From 4b4b9b7b6fdd01959e187b4ccfca4c7708a63f3a Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Sun, 17 Mar 2024 12:25:05 +0100 Subject: [PATCH 02/11] Adding `NonUniqueResultException` --- docs/en/reference/dql-doctrine-query-language.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/reference/dql-doctrine-query-language.rst b/docs/en/reference/dql-doctrine-query-language.rst index 0e21c0aa0bc..65ed4d67b02 100644 --- a/docs/en/reference/dql-doctrine-query-language.rst +++ b/docs/en/reference/dql-doctrine-query-language.rst @@ -1050,7 +1050,7 @@ to PHP is determined by the so-called "hydration mode": pure/mixed distinction does not apply. - **``getSingleScalarResult()``** (``HYDRATE_SINGLE_SCALAR``: Retrieves a single scalar value from the result returned by the dbms. If the result contains - more than a single scalar value, an exception is thrown. The + more than a single scalar value, a ``NonUniqueResultException`` is thrown. The pure/mixed distinction does not apply. In parentheses are the constants of the ``Query`` class which you can use with the From 46d0865339d00bfa02a35f1ce04e2de7f38600f5 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Thu, 21 Mar 2024 17:55:39 +0100 Subject: [PATCH 03/11] Update dql-doctrine-query-language.rst --- docs/en/reference/dql-doctrine-query-language.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/reference/dql-doctrine-query-language.rst b/docs/en/reference/dql-doctrine-query-language.rst index 65ed4d67b02..1cc4c22cec3 100644 --- a/docs/en/reference/dql-doctrine-query-language.rst +++ b/docs/en/reference/dql-doctrine-query-language.rst @@ -1055,7 +1055,7 @@ to PHP is determined by the so-called "hydration mode": In parentheses are the constants of the ``Query`` class which you can use with the general-purpose method ``Query::execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``. -In fact, the recommended methods in the list are just convenient shortcuts for the hydration mode. +In fact, the methods in the list are just convenient shortcuts for the hydration mode. Pure and Mixed Results ~~~~~~~~~~~~~~~~~~~~~~ From c54c557e02bda20bc84883ab1d0f94319f124dfe Mon Sep 17 00:00:00 2001 From: Ludwig Rafelsberger Date: Fri, 22 Mar 2024 11:05:00 +0100 Subject: [PATCH 04/11] Fix psalm errors: remove override of template type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/doctrine/collections/issues/368 for the same issue in doctrine/collections which has been fixed there. The issue happens when using ->contains(). Running psalm emits > InvalidArgument - Argument 1 of Doctrine\ORM\PersistentCollection::contains > expects > TMaybeContained:fn-doctrine\common\collections\readablecollection::contains > as mixed, but … provided. Solution: we should either not define @template TMaybeContained or re-define the complete psalm docblock from ReadableCollection. Repairing the docblock necessitates an update to the psalm baseline: one "known issue" is no longer an issue and thus removed. --- psalm-baseline.xml | 3 --- src/LazyCriteriaCollection.php | 6 +++--- src/PersistentCollection.php | 5 ----- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 7a3b729e849..d04835c1e85 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -462,9 +462,6 @@ - - - diff --git a/src/LazyCriteriaCollection.php b/src/LazyCriteriaCollection.php index 5580f971eb0..48040922616 100644 --- a/src/LazyCriteriaCollection.php +++ b/src/LazyCriteriaCollection.php @@ -76,11 +76,11 @@ public function isEmpty() } /** - * {@inheritDoc} - * * Do an optimized search of an element * - * @template TMaybeContained + * @param mixed $element The element to search for. + * + * @return bool TRUE if the collection contains $element, FALSE otherwise. */ public function contains($element) { diff --git a/src/PersistentCollection.php b/src/PersistentCollection.php index 4470a64a5cd..c94bb77496b 100644 --- a/src/PersistentCollection.php +++ b/src/PersistentCollection.php @@ -412,11 +412,6 @@ public function containsKey($key): bool return parent::containsKey($key); } - /** - * {@inheritDoc} - * - * @template TMaybeContained - */ public function contains($element): bool { if (! $this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) { From 753bc16c0b0535b7493628dfb87244aa43dc5483 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Fri, 22 Mar 2024 15:26:43 +0100 Subject: [PATCH 05/11] SchemaValidator: Changing mapping of BIGINT to string|int --- src/Tools/SchemaValidator.php | 29 ++++++- tests/Tests/ORM/Tools/SchemaValidatorTest.php | 81 +++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/Tools/SchemaValidator.php b/src/Tools/SchemaValidator.php index 6ebe991876c..7b4e6176a41 100644 --- a/src/Tools/SchemaValidator.php +++ b/src/Tools/SchemaValidator.php @@ -39,6 +39,7 @@ use function in_array; use function interface_exists; use function is_a; +use function method_exists; use function sprintf; /** @@ -49,9 +50,9 @@ class SchemaValidator { /** - * It maps built-in Doctrine types to PHP types + * Map built-in Doctrine DBAL 3 types to PHP types */ - private const BUILTIN_TYPES_MAP = [ + private const BUILTIN_TYPES_MAP_DBAL3 = [ AsciiStringType::class => 'string', BigIntType::class => 'string', BooleanType::class => 'bool', @@ -66,6 +67,24 @@ class SchemaValidator TextType::class => 'string', ]; + /** + * Map built-in Doctrine DBAL 4+ types to PHP types + */ + private const BUILTIN_TYPES_MAP = [ + AsciiStringType::class => 'string', + BigIntType::class => 'string|int', + BooleanType::class => 'bool', + DecimalType::class => 'string', + FloatType::class => 'float', + GuidType::class => 'string', + IntegerType::class => 'int', + JsonType::class => 'array', + SimpleArrayType::class => 'array', + SmallIntType::class => 'int', + StringType::class => 'string', + TextType::class => 'string', + ]; + public function __construct( private readonly EntityManagerInterface $em, private readonly bool $validatePropertyTypes = true, @@ -436,6 +455,10 @@ private function findBuiltInType(Type $type): string|null { $typeName = $type::class; - return self::BUILTIN_TYPES_MAP[$typeName] ?? null; + if (method_exists(BigIntType::class, 'getName')) { // DBAL 3 + return self::BUILTIN_TYPES_MAP_DBAL3[$typeName] ?? null; + } else { // DBAL 4+ + return self::BUILTIN_TYPES_MAP[$typeName] ?? null; + } } } diff --git a/tests/Tests/ORM/Tools/SchemaValidatorTest.php b/tests/Tests/ORM/Tools/SchemaValidatorTest.php index b7141b0d99d..a153e4e77b6 100644 --- a/tests/Tests/ORM/Tools/SchemaValidatorTest.php +++ b/tests/Tests/ORM/Tools/SchemaValidatorTest.php @@ -6,6 +6,8 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\BigIntType; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\DiscriminatorMap; @@ -30,6 +32,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; +use function method_exists; + class SchemaValidatorTest extends OrmTestCase { private EntityManagerInterface|null $em = null; @@ -228,6 +232,47 @@ public function testInvalidAssociationTowardsMappedSuperclass(): void $ce, ); } + + public function testBigintMappedToStringInt(): void + { + $class = $this->em->getClassMetadata(BigintMappedToStringInt::class); + $ce = $this->validator->validateClass($class); + + $this->assertEquals([], $ce); // Same for DBAL 3 and 4+ + } + + public function testBigintMappedToInt(): void + { + $class = $this->em->getClassMetadata(BigintMappedToInt::class); + $ce = $this->validator->validateClass($class); + + if (method_exists(BigIntType::class, 'getName')) { // DBAL 3 + $this->assertEquals( + ["The field 'Doctrine\Tests\ORM\Tools\BigintMappedToInt#bigint' has the property type 'int' that differs from the metadata field type 'string' returned by the 'bigint' DBAL type."], + $ce, + ); + } else { // DBAL 4+ + $this->assertEquals( + ["The field 'Doctrine\Tests\ORM\Tools\BigintMappedToInt#bigint' has the property type 'int' that differs from the metadata field type 'string|int' returned by the 'bigint' DBAL type."], + $ce, + ); + } + } + + public function testBigintMappedToString(): void + { + $class = $this->em->getClassMetadata(BigintMappedToString::class); + $ce = $this->validator->validateClass($class); + + if (method_exists(BigIntType::class, 'getName')) { // DBAL 3 + $this->assertEquals([], $ce); + } else { // DBAL 4+ + $this->assertEquals( + ["The field 'Doctrine\Tests\ORM\Tools\BigintMappedToString#bigint' has the property type 'string' that differs from the metadata field type 'string|int' returned by the 'bigint' DBAL type."], + $ce, + ); + } + } } #[MappedSuperclass] @@ -547,3 +592,39 @@ class InvalidMappedSuperClass #[ManyToMany(targetEntity: 'InvalidMappedSuperClass', mappedBy: 'invalid')] private $selfWhatever; } + +#[Entity] +class BigintMappedToStringInt +{ + #[Id] + #[Column] + #[GeneratedValue] + private int $id; + + #[Column(type: Types::BIGINT)] + private string|int $bigint; +} + +#[Entity] +class BigintMappedToInt +{ + #[Id] + #[Column] + #[GeneratedValue] + private int $id; + + #[Column(type: Types::BIGINT)] + private int $bigint; +} + +#[Entity] +class BigintMappedToString +{ + #[Id] + #[Column] + #[GeneratedValue] + private int $id; + + #[Column(type: Types::BIGINT)] + private string $bigint; +} From 5f3c1dbab8513d3529017b4df6558dcfd185b934 Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Tue, 26 Mar 2024 21:40:18 +0100 Subject: [PATCH 06/11] [Documentation] Merging "Query Result Formats" with "Hydration Modes" Page: https://www.doctrine-project.org/projects/doctrine-orm/en/2.19/reference/dql-doctrine-query-language.html#query-result-formats As announced in https://github.com/doctrine/orm/pull/11372#issue-2190613801, I merged the (mostly) identical sections. * I changed the `const`s from `Query` to `AbstractQuery` * I deleted this - mainly cause I didn't find a nice place for it: > In parentheses are the constants of the ``Query`` class which you can use with the general-purpose method ``Query::execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``. --- .../reference/dql-doctrine-query-language.rst | 331 ++++++++---------- 1 file changed, 138 insertions(+), 193 deletions(-) diff --git a/docs/en/reference/dql-doctrine-query-language.rst b/docs/en/reference/dql-doctrine-query-language.rst index 1cc4c22cec3..ce06d02b81a 100644 --- a/docs/en/reference/dql-doctrine-query-language.rst +++ b/docs/en/reference/dql-doctrine-query-language.rst @@ -1020,42 +1020,146 @@ Alternatively you can create an empty ``Query`` instance and invoke $q = $em->createQuery(); $q->setDQL('select u from MyProject\Model\User u'); -Query Result Formats -~~~~~~~~~~~~~~~~~~~~ +Query Result Formats (Hydration Modes) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The way in which the SQL result set of a DQL SELECT query is transformed -to PHP is determined by the so-called "hydration mode": - -- **``getResult()``** (``HYDRATE_OBJECT``): Retrieves a collection of objects. The - result is either a plain collection of objects (pure) or an array - where the objects are nested in the result rows (mixed). -- **``getSingleResult()``**: Retrieves a single object. If the - result contains more than one object, a ``NonUniqueResultException`` - is thrown. If the result contains no objects, a ``NoResultException`` - is thrown. The pure/mixed distinction does not apply. -- **``getOneOrNullResult()``**: Retrieve a single object. If the - result contains more than one object, a ``NonUniqueResultException`` - is thrown. If no object is found null will be returned. -- **``getArrayResult()``** (``HYDRATE_ARRAY``): Retrieves an array graph (a nested - array) for read-only purposes. - - .. note:: - - An array graph can differ from the corresponding object - graph in certain scenarios due to the difference of the identity - semantics between arrays and objects. - -- **``getScalarResult()``** (``HYDRATE_SCALAR``): Retrieves a flat/rectangular result - set of scalar values that can contain duplicate data. The - pure/mixed distinction does not apply. -- **``getSingleScalarResult()``** (``HYDRATE_SINGLE_SCALAR``: Retrieves a single scalar - value from the result returned by the dbms. If the result contains - more than a single scalar value, a ``NonUniqueResultException`` is thrown. The - pure/mixed distinction does not apply. - -In parentheses are the constants of the ``Query`` class which you can use with the -general-purpose method ``Query::execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``. -In fact, the methods in the list are just convenient shortcuts for the hydration mode. +to PHP is determined by the so-called "hydration mode". + +``getResult()`` +^^^^^^^^^^^^^^^ + +Retrieves a collection of objects. The result is either a plain collection of objects (pure) or an array +where the objects are nested in the result rows (mixed): + +.. code-block:: php + + createQuery('SELECT u FROM User u'); + $users = $query->getResult(); + // same as: + $users = $query->getResult(AbstractQuery::HYDRATE_OBJECT); + +- Objects fetched in a FROM clause are returned as a Set, that means every + object is only ever included in the resulting array once. This is the case + even when using JOIN or GROUP BY in ways that return the same row for an + object multiple times. If the hydrator sees the same object multiple times, + then it makes sure it is only returned once. + +- If an object is already in memory from a previous query of any kind, then + then the previous object is used, even if the database may contain more + recent data. This even happens if the previous object is still an unloaded proxy. + +``getArrayResult()`` +^^^^^^^^^^^^^^^^^^^^ + +Retrieves an array graph (a nested array) for read-only purposes. + +.. note:: + + An array graph can differ from the corresponding object + graph in certain scenarios due to the difference of the identity + semantics between arrays and objects. + +.. code-block:: php + + getArrayResult(); + // same as: + $users = $query->getResult(AbstractQuery::HYDRATE_ARRAY); + +``getScalarResult()`` +^^^^^^^^^^^^^^^^^^^^^ + +Retrieves a flat/rectangular result set of scalar values that can contain duplicate data. The +pure/mixed distinction does not apply. + +.. code-block:: php + + getScalarResult(); + // same as: + $users = $query->getResult(AbstractQuery::HYDRATE_SCALAR); + +Fields from classes are prefixed by the DQL alias in the result. +A query of the kind `SELECT u.name ...` returns a key `u_name` in the result rows. + +``getSingleScalarResult()`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Retrieves a single scalar value from the result returned by the database. If the result contains +more than a single scalar value, a ``NonUniqueResultException`` is thrown. The pure/mixed distinction does not apply. + +.. code-block:: php + + createQuery('SELECT COUNT(u.id) FROM User u'); + $numUsers = $query->getSingleScalarResult(); + // same as: + $numUsers = $query->getResult(AbstractQuery::HYDRATE_SINGLE_SCALAR); + +``getSingleColumnResult()`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Retrieves an array from a one-dimensional array of scalar values: + +.. code-block:: php + + createQuery('SELECT a.id FROM User u'); + $ids = $query->getSingleColumnResult(); + // same as: + $ids = $query->getResult(AbstractQuery::HYDRATE_SCALAR_COLUMN); + +``getSingleResult()`` +^^^^^^^^^^^^^^^^^^^^^ + +Retrieves a single object. If the result contains more than one object, a ``NonUniqueResultException`` +is thrown. If the result contains no objects, a ``NoResultException`` is thrown. The pure/mixed distinction does not apply. + +``getOneOrNullResult()`` +^^^^^^^^^^^^^^^^^^^^^^^^ + +Retrieves a single object. If the result contains more than one object, a ``NonUniqueResultException`` +is thrown. If no object is found, ``null`` will be returned. + +Custom Hydration Modes +^^^^^^^^^^^^^^^^^^^^^^ + +You can easily add your own custom hydration modes by first +creating a class which extends ``AbstractHydrator``: + +.. code-block:: php + + _stmt->fetchAllAssociative(); + } + } + +Next you just need to add the class to the ORM configuration: + +.. code-block:: php + + getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); + +Now the hydrator is ready to be used in your queries: + +.. code-block:: php + + createQuery('SELECT u FROM CmsUser u'); + $results = $query->getResult('CustomHydrator'); Pure and Mixed Results ~~~~~~~~~~~~~~~~~~~~~~ @@ -1159,165 +1263,6 @@ will return the rows iterating the different top-level entities. [2] => Object (User) [3] => Object (Group) - -Hydration Modes -~~~~~~~~~~~~~~~ - -Each of the Hydration Modes makes assumptions about how the result -is returned to user land. You should know about all the details to -make best use of the different result formats: - -The constants for the different hydration modes are: - - -- ``Query::HYDRATE_OBJECT`` -- ``Query::HYDRATE_ARRAY`` -- ``Query::HYDRATE_SCALAR`` -- ``Query::HYDRATE_SINGLE_SCALAR`` -- ``Query::HYDRATE_SCALAR_COLUMN`` - -Object Hydration -^^^^^^^^^^^^^^^^ - -Object hydration hydrates the result set into the object graph: - -.. code-block:: php - - createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_OBJECT); - -Sometimes the behavior in the object hydrator can be confusing, which is -why we are listing as many of the assumptions here for reference: - -- Objects fetched in a FROM clause are returned as a Set, that means every - object is only ever included in the resulting array once. This is the case - even when using JOIN or GROUP BY in ways that return the same row for an - object multiple times. If the hydrator sees the same object multiple times, - then it makes sure it is only returned once. - -- If an object is already in memory from a previous query of any kind, then - then the previous object is used, even if the database may contain more - recent data. Data from the database is discarded. This even happens if the - previous object is still an unloaded proxy. - -This list might be incomplete. - -Array Hydration -^^^^^^^^^^^^^^^ - -You can run the same query with array hydration and the result set -is hydrated into an array that represents the object graph: - -.. code-block:: php - - createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_ARRAY); - -You can use the ``getArrayResult()`` shortcut as well: - -.. code-block:: php - - getArrayResult(); - -Scalar Hydration -^^^^^^^^^^^^^^^^ - -If you want to return a flat rectangular result set instead of an -object graph you can use scalar hydration: - -.. code-block:: php - - createQuery('SELECT u FROM CmsUser u'); - $users = $query->getResult(Query::HYDRATE_SCALAR); - echo $users[0]['u_id']; - -The following assumptions are made about selected fields using -Scalar Hydration: - - -1. Fields from classes are prefixed by the DQL alias in the result. - A query of the kind 'SELECT u.name ..' returns a key 'u_name' in - the result rows. - -Single Scalar Hydration -^^^^^^^^^^^^^^^^^^^^^^^ - -If you have a query which returns just a single scalar value you can use -single scalar hydration: - -.. code-block:: php - - createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); - $query->setParameter(1, 'jwage'); - $numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR); - -You can use the ``getSingleScalarResult()`` shortcut as well: - -.. code-block:: php - - getSingleScalarResult(); - -Scalar Column Hydration -^^^^^^^^^^^^^^^^^^^^^^^ - -If you have a query which returns a one-dimensional array of scalar values -you can use scalar column hydration: - -.. code-block:: php - - createQuery('SELECT a.id FROM CmsUser u'); - $ids = $query->getResult(Query::HYDRATE_SCALAR_COLUMN); - -You can use the ``getSingleColumnResult()`` shortcut as well: - -.. code-block:: php - - getSingleColumnResult(); - -Custom Hydration Modes -^^^^^^^^^^^^^^^^^^^^^^ - -You can easily add your own custom hydration modes by first -creating a class which extends ``AbstractHydrator``: - -.. code-block:: php - - _stmt->fetchAllAssociative(); - } - } - -Next you just need to add the class to the ORM configuration: - -.. code-block:: php - - getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); - -Now the hydrator is ready to be used in your queries: - -.. code-block:: php - - createQuery('SELECT u FROM CmsUser u'); - $results = $query->getResult('CustomHydrator'); - Iterating Large Result Sets ~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 8709fb38b0b0c650b5e8113c56537c87e482ef81 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 1 Apr 2024 12:44:58 +0200 Subject: [PATCH 07/11] Fix templated phpdoc return type (#11407) * Improve getClassMetadata phpdoc * Update baseline --- phpstan-baseline.neon | 5 ----- psalm-baseline.xml | 8 -------- src/EntityManagerInterface.php | 2 +- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c33099dee7a..6f0a73dd5a3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -135,11 +135,6 @@ parameters: count: 2 path: src/EntityManager.php - - - message: "#^Template type T of method Doctrine\\\\ORM\\\\EntityManagerInterface\\:\\:getClassMetadata\\(\\) is not referenced in a parameter\\.$#" - count: 1 - path: src/EntityManagerInterface.php - - message: "#^Method Doctrine\\\\ORM\\\\EntityRepository\\:\\:matching\\(\\) should return Doctrine\\\\Common\\\\Collections\\\\AbstractLazyCollection\\&Doctrine\\\\Common\\\\Collections\\\\Selectable\\ but returns Doctrine\\\\ORM\\\\LazyCriteriaCollection\\<\\(int\\|string\\), object\\>\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index d04835c1e85..6c70101de13 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -257,12 +257,6 @@ - - wrapped->getClassMetadata($className)]]> - - - - @@ -309,11 +303,9 @@ name ? $entity : null]]> load($sortedId, null, null, [], $lockMode)]]> loadById($sortedId)]]> - metadataFactory->getMetadataFor($className)]]> - diff --git a/src/EntityManagerInterface.php b/src/EntityManagerInterface.php index 326cb933a59..b3b5ddf9e13 100644 --- a/src/EntityManagerInterface.php +++ b/src/EntityManagerInterface.php @@ -340,7 +340,7 @@ public function hasFilters(); * @psalm-param string|class-string $className * * @return Mapping\ClassMetadata - * @psalm-return Mapping\ClassMetadata + * @psalm-return ($className is class-string ? Mapping\ClassMetadata : Mapping\ClassMetadata) * * @psalm-template T of object */ From 146482722072acb52a9a3f32e3020787efffd5ac Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 3 Apr 2024 19:28:24 +0200 Subject: [PATCH 08/11] Fix fromMappingArray definition --- phpstan-dbal3.neon | 4 --- phpstan.neon | 4 --- psalm-baseline.xml | 4 +++ src/Mapping/AssociationMapping.php | 17 +++++++++-- src/Mapping/ClassMetadata.php | 2 +- src/Mapping/EmbeddedClassMapping.php | 10 ++++--- src/Mapping/FieldMapping.php | 31 ++++++++++++++++++-- src/Mapping/JoinColumnMapping.php | 12 +++++++- src/Mapping/JoinTableMapping.php | 4 +-- src/Mapping/ManyToManyOwningSideMapping.php | 14 ++++++++- src/Mapping/OneToManyAssociationMapping.php | 28 ++++++++++++++++-- src/Mapping/ToOneInverseSideMapping.php | 15 +++++++++- src/Mapping/ToOneOwningSideMapping.php | 32 +++++++++++++++++++-- 13 files changed, 151 insertions(+), 26 deletions(-) diff --git a/phpstan-dbal3.neon b/phpstan-dbal3.neon index a223cd4b792..b9ef942c965 100644 --- a/phpstan-dbal3.neon +++ b/phpstan-dbal3.neon @@ -20,10 +20,6 @@ parameters: message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~' path: src/Persisters/Entity/BasicEntityPersister.php - - - message: '~^Unreachable statement \- code above always terminates\.$~' - path: src/Mapping/AssociationMapping.php - - '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~' # To be removed in 4.0 diff --git a/phpstan.neon b/phpstan.neon index 4ea0a3630a2..d90ec9fe41f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -20,10 +20,6 @@ parameters: message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~' path: src/Persisters/Entity/BasicEntityPersister.php - - - message: '~^Unreachable statement \- code above always terminates\.$~' - path: src/Mapping/AssociationMapping.php - # Compatibility with DBAL 3 # See https://github.com/doctrine/dbal/pull/3480 - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 4faaf8ce91d..a54dfbd0954 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -298,6 +298,10 @@ columnNames]]> + + + + diff --git a/src/Mapping/AssociationMapping.php b/src/Mapping/AssociationMapping.php index 0253413e8d8..ce7bdb40ab4 100644 --- a/src/Mapping/AssociationMapping.php +++ b/src/Mapping/AssociationMapping.php @@ -27,7 +27,7 @@ abstract class AssociationMapping implements ArrayAccess /** * The fetching strategy to use for the association, usually defaults to FETCH_LAZY. * - * @var ClassMetadata::FETCH_* + * @var ClassMetadata::FETCH_*|null */ public int|null $fetch = null; @@ -96,13 +96,26 @@ final public function __construct( } /** + * @param mixed[] $mappingArray * @psalm-param array{ * fieldName: string, * sourceEntity: class-string, * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, * joinTable?: mixed[]|null, * type?: int, - * isOwningSide: bool, ...} $mappingArray + * isOwningSide: bool, + * } $mappingArray */ public static function fromMappingArray(array $mappingArray): static { diff --git a/src/Mapping/ClassMetadata.php b/src/Mapping/ClassMetadata.php index 85079ed049d..f58e00e72fe 100644 --- a/src/Mapping/ClassMetadata.php +++ b/src/Mapping/ClassMetadata.php @@ -1152,7 +1152,7 @@ private function validateAndCompleteTypedAssociationMapping(array $mapping): arr * fieldName?: string, * columnName?: string, * id?: bool, - * generated?: int, + * generated?: self::GENERATED_*, * enumType?: class-string, * } $mapping The field mapping to validate & complete. * diff --git a/src/Mapping/EmbeddedClassMapping.php b/src/Mapping/EmbeddedClassMapping.php index c1d464568a8..8fd02c9232c 100644 --- a/src/Mapping/EmbeddedClassMapping.php +++ b/src/Mapping/EmbeddedClassMapping.php @@ -49,10 +49,12 @@ public function __construct(public string $class) /** * @psalm-param array{ - * class: class-string, - * columnPrefix?: false|string|null, - * declaredField?: string|null, - * originalField?: string|null + * class: class-string, + * columnPrefix?: false|string|null, + * declaredField?: string|null, + * originalField?: string|null, + * inherited?: class-string|null, + * declared?: class-string|null, * } $mappingArray */ public static function fromMappingArray(array $mappingArray): self diff --git a/src/Mapping/FieldMapping.php b/src/Mapping/FieldMapping.php index 3e05eba9ec2..4c09196fd8b 100644 --- a/src/Mapping/FieldMapping.php +++ b/src/Mapping/FieldMapping.php @@ -26,7 +26,7 @@ final class FieldMapping implements ArrayAccess public bool|null $notInsertable = null; public bool|null $notUpdatable = null; public string|null $columnDefinition = null; - /** @psalm-var ClassMetadata::GENERATED_* */ + /** @psalm-var ClassMetadata::GENERATED_*|null */ public int|null $generated = null; /** @var class-string|null */ public string|null $enumType = null; @@ -83,7 +83,34 @@ public function __construct( ) { } - /** @param array{type: string, fieldName: string, columnName: string} $mappingArray */ + /** + * @param array $mappingArray + * @psalm-param array{ + * type: string, + * fieldName: string, + * columnName: string, + * length?: int|null, + * id?: bool|null, + * nullable?: bool|null, + * notInsertable?: bool|null, + * notUpdatable?: bool|null, + * columnDefinition?: string|null, + * generated?: ClassMetadata::GENERATED_*|null, + * enumType?: string|null, + * precision?: int|null, + * scale?: int|null, + * unique?: bool|null, + * inherited?: string|null, + * originalClass?: string|null, + * originalField?: string|null, + * quoted?: bool|null, + * declared?: string|null, + * declaredField?: string|null, + * options?: array|null, + * version?: bool|null, + * default?: string|int|null, + * } $mappingArray + */ public static function fromMappingArray(array $mappingArray): self { $mapping = new self( diff --git a/src/Mapping/JoinColumnMapping.php b/src/Mapping/JoinColumnMapping.php index bc469bb6494..172c25699c9 100644 --- a/src/Mapping/JoinColumnMapping.php +++ b/src/Mapping/JoinColumnMapping.php @@ -31,7 +31,17 @@ public function __construct( /** * @param array $mappingArray - * @psalm-param array{name: string, referencedColumnName: string, ...} $mappingArray + * @psalm-param array{ + * name: string, + * referencedColumnName: string, + * unique?: bool|null, + * quoted?: bool|null, + * fieldName?: string|null, + * onDelete?: string|null, + * columnDefinition?: string|null, + * nullable?: bool|null, + * options?: array|null, + * } $mappingArray */ public static function fromMappingArray(array $mappingArray): self { diff --git a/src/Mapping/JoinTableMapping.php b/src/Mapping/JoinTableMapping.php index df46f9722a9..c8b49681e51 100644 --- a/src/Mapping/JoinTableMapping.php +++ b/src/Mapping/JoinTableMapping.php @@ -35,10 +35,10 @@ public function __construct(public string $name) * @param mixed[] $mappingArray * @psalm-param array{ * name: string, - * quoted?: bool, + * quoted?: bool|null, * joinColumns?: mixed[], * inverseJoinColumns?: mixed[], - * schema?: string, + * schema?: string|null, * options?: array * } $mappingArray */ diff --git a/src/Mapping/ManyToManyOwningSideMapping.php b/src/Mapping/ManyToManyOwningSideMapping.php index c7a0eb4ad58..b09d56c7e30 100644 --- a/src/Mapping/ManyToManyOwningSideMapping.php +++ b/src/Mapping/ManyToManyOwningSideMapping.php @@ -41,9 +41,21 @@ public function toArray(): array * fieldName: string, * sourceEntity: class-string, * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, * joinTable?: mixed[]|null, * type?: int, - * isOwningSide: bool, ...} $mappingArray + * isOwningSide: bool, + * } $mappingArray */ public static function fromMappingArrayAndNamingStrategy(array $mappingArray, NamingStrategy $namingStrategy): self { diff --git a/src/Mapping/OneToManyAssociationMapping.php b/src/Mapping/OneToManyAssociationMapping.php index 804061a1a1d..786e9812774 100644 --- a/src/Mapping/OneToManyAssociationMapping.php +++ b/src/Mapping/OneToManyAssociationMapping.php @@ -12,9 +12,21 @@ final class OneToManyAssociationMapping extends ToManyInverseSideMapping * fieldName: string, * sourceEntity: class-string, * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, * joinTable?: mixed[]|null, * type?: int, - * isOwningSide: bool, ...} $mappingArray + * isOwningSide: bool, + * } $mappingArray */ public static function fromMappingArray(array $mappingArray): static { @@ -33,9 +45,21 @@ public static function fromMappingArray(array $mappingArray): static * fieldName: string, * sourceEntity: class-string, * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, * joinTable?: mixed[]|null, * type?: int, - * isOwningSide: bool, ...} $mappingArray + * isOwningSide: bool, + * } $mappingArray */ public static function fromMappingArrayAndName(array $mappingArray, string $name): static { diff --git a/src/Mapping/ToOneInverseSideMapping.php b/src/Mapping/ToOneInverseSideMapping.php index 232952be438..5be89e6db53 100644 --- a/src/Mapping/ToOneInverseSideMapping.php +++ b/src/Mapping/ToOneInverseSideMapping.php @@ -13,8 +13,21 @@ abstract class ToOneInverseSideMapping extends InverseSideMapping * fieldName: string, * sourceEntity: class-string, * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, * isOwningSide: bool, - * } $mappingArray + * } $mappingArray */ public static function fromMappingArrayAndName( array $mappingArray, diff --git a/src/Mapping/ToOneOwningSideMapping.php b/src/Mapping/ToOneOwningSideMapping.php index d28074bb03e..cb85afbde10 100644 --- a/src/Mapping/ToOneOwningSideMapping.php +++ b/src/Mapping/ToOneOwningSideMapping.php @@ -31,8 +31,22 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne * fieldName: string, * sourceEntity: class-string, * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, * joinColumns?: mixed[]|null, - * isOwningSide: bool, ...} $mappingArray + * } $mappingArray */ public static function fromMappingArray(array $mappingArray): static { @@ -64,8 +78,22 @@ public static function fromMappingArray(array $mappingArray): static * fieldName: string, * sourceEntity: class-string, * targetEntity: class-string, + * cascade?: list<'persist'|'remove'|'detach'|'refresh'|'all'>, + * fetch?: ClassMetadata::FETCH_*|null, + * inherited?: class-string|null, + * declared?: class-string|null, + * cache?: array|null, + * id?: bool|null, + * isOnDeleteCascade?: bool|null, + * originalClass?: class-string|null, + * originalField?: string|null, + * orphanRemoval?: bool, + * unique?: bool|null, + * joinTable?: mixed[]|null, + * type?: int, + * isOwningSide: bool, * joinColumns?: mixed[]|null, - * isOwningSide: bool, ...} $mappingArray + * } $mappingArray */ public static function fromMappingArrayAndName( array $mappingArray, From 010b1e0886929e0da207aa44cd823c23270a3aab Mon Sep 17 00:00:00 2001 From: Nayte Date: Mon, 15 Apr 2024 09:38:43 +0200 Subject: [PATCH 09/11] docs: update PHP version in doc --- docs/en/reference/architecture.rst | 2 +- docs/en/tutorials/getting-started.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/reference/architecture.rst b/docs/en/reference/architecture.rst index 00d1c419734..7ba2ef774d7 100644 --- a/docs/en/reference/architecture.rst +++ b/docs/en/reference/architecture.rst @@ -18,7 +18,7 @@ well. Requirements ------------ -Doctrine ORM requires a minimum of PHP 7.1. For greatly improved +Doctrine ORM requires a minimum of PHP 8.1. For greatly improved performance it is also recommended that you use APC with PHP. Doctrine ORM Packages diff --git a/docs/en/tutorials/getting-started.rst b/docs/en/tutorials/getting-started.rst index 5e405618bf4..3550daecf78 100644 --- a/docs/en/tutorials/getting-started.rst +++ b/docs/en/tutorials/getting-started.rst @@ -27,7 +27,7 @@ What is Doctrine? ----------------- Doctrine ORM is an `object-relational mapper (ORM) `_ -for PHP 7.1+ that provides transparent persistence for PHP objects. It uses the Data Mapper +for PHP that provides transparent persistence for PHP objects. It uses the Data Mapper pattern at the heart, aiming for a complete separation of your domain/business logic from the persistence in a relational database management system. From b27489348658cd718d18005de37b94f7f8561467 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 15 Apr 2024 15:11:10 +0200 Subject: [PATCH 10/11] Fix BIGINT validation (#11414) --- src/Tools/SchemaValidator.php | 40 ++++++++++--------- .../Tests/Models/BigIntegers/BigIntegers.php | 27 +++++++++++++ tests/Tests/ORM/Tools/SchemaValidatorTest.php | 14 +++++++ 3 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 tests/Tests/Models/BigIntegers/BigIntegers.php diff --git a/src/Tools/SchemaValidator.php b/src/Tools/SchemaValidator.php index 1ea5e688fab..212ad019846 100644 --- a/src/Tools/SchemaValidator.php +++ b/src/Tools/SchemaValidator.php @@ -64,18 +64,18 @@ class SchemaValidator * It maps built-in Doctrine types to PHP types */ private const BUILTIN_TYPES_MAP = [ - AsciiStringType::class => 'string', - BigIntType::class => 'string', - BooleanType::class => 'bool', - DecimalType::class => 'string', - FloatType::class => 'float', - GuidType::class => 'string', - IntegerType::class => 'int', - JsonType::class => 'array', - SimpleArrayType::class => 'array', - SmallIntType::class => 'int', - StringType::class => 'string', - TextType::class => 'string', + AsciiStringType::class => ['string'], + BigIntType::class => ['int', 'string'], + BooleanType::class => ['bool'], + DecimalType::class => ['string'], + FloatType::class => ['float'], + GuidType::class => ['string'], + IntegerType::class => ['int'], + JsonType::class => ['array'], + SimpleArrayType::class => ['array'], + SmallIntType::class => ['int'], + StringType::class => ['string'], + TextType::class => ['string'], ]; public function __construct(EntityManagerInterface $em, bool $validatePropertyTypes = true) @@ -390,21 +390,21 @@ function (array $fieldMapping) use ($class): ?string { $propertyType = $propertyType->getName(); // If the property type is the same as the metadata field type, we are ok - if ($propertyType === $metadataFieldType) { + if (in_array($propertyType, $metadataFieldType, true)) { return null; } if (is_a($propertyType, BackedEnum::class, true)) { $backingType = (string) (new ReflectionEnum($propertyType))->getBackingType(); - if ($metadataFieldType !== $backingType) { + if (! in_array($backingType, $metadataFieldType, true)) { return sprintf( "The field '%s#%s' has the property type '%s' with a backing type of '%s' that differs from the metadata field type '%s'.", $class->name, $fieldName, $propertyType, $backingType, - $metadataFieldType + implode('|', $metadataFieldType) ); } @@ -429,7 +429,7 @@ function (array $fieldMapping) use ($class): ?string { ) { $backingType = (string) (new ReflectionEnum($fieldMapping['enumType']))->getBackingType(); - if ($metadataFieldType === $backingType) { + if (in_array($backingType, $metadataFieldType, true)) { return null; } @@ -439,7 +439,7 @@ function (array $fieldMapping) use ($class): ?string { $fieldName, $fieldMapping['enumType'], $backingType, - $metadataFieldType + implode('|', $metadataFieldType) ); } @@ -455,7 +455,7 @@ function (array $fieldMapping) use ($class): ?string { $class->name, $fieldName, $propertyType, - $metadataFieldType, + implode('|', $metadataFieldType), $fieldMapping['type'] ); }, @@ -468,8 +468,10 @@ function (array $fieldMapping) use ($class): ?string { /** * The exact DBAL type must be used (no subclasses), since consumers of doctrine/orm may have their own * customization around field types. + * + * @return list|null */ - private function findBuiltInType(Type $type): ?string + private function findBuiltInType(Type $type): ?array { $typeName = get_class($type); diff --git a/tests/Tests/Models/BigIntegers/BigIntegers.php b/tests/Tests/Models/BigIntegers/BigIntegers.php new file mode 100644 index 00000000000..16b6f58dd0e --- /dev/null +++ b/tests/Tests/Models/BigIntegers/BigIntegers.php @@ -0,0 +1,27 @@ +em->getClassMetadata(BigIntegers::class); + + self::assertSame( + ['The field \'Doctrine\Tests\Models\BigIntegers\BigIntegers#three\' has the property type \'float\' that differs from the metadata field type \'int|string\' returned by the \'bigint\' DBAL type.'], + $this->validator->validateClass($class) + ); + } } /** @MappedSuperclass */ From 9c22814cfa5b9e45a81d318770e2d712470c4770 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 15 Apr 2024 16:03:33 +0200 Subject: [PATCH 11/11] Revert "Merge pull request #11399 from ThomasLandauer/issue-11377" (#11415) This reverts commit cbb6c897de4dd4e215c7005505b381c5927e5f4a, reversing changes made to 9c560713925ac5859342e6ff370c4c997acf2fd4. --- src/Tools/SchemaValidator.php | 29 +------ tests/Tests/ORM/Tools/SchemaValidatorTest.php | 81 ------------------- 2 files changed, 3 insertions(+), 107 deletions(-) diff --git a/src/Tools/SchemaValidator.php b/src/Tools/SchemaValidator.php index 7b4e6176a41..6ebe991876c 100644 --- a/src/Tools/SchemaValidator.php +++ b/src/Tools/SchemaValidator.php @@ -39,7 +39,6 @@ use function in_array; use function interface_exists; use function is_a; -use function method_exists; use function sprintf; /** @@ -50,29 +49,11 @@ class SchemaValidator { /** - * Map built-in Doctrine DBAL 3 types to PHP types - */ - private const BUILTIN_TYPES_MAP_DBAL3 = [ - AsciiStringType::class => 'string', - BigIntType::class => 'string', - BooleanType::class => 'bool', - DecimalType::class => 'string', - FloatType::class => 'float', - GuidType::class => 'string', - IntegerType::class => 'int', - JsonType::class => 'array', - SimpleArrayType::class => 'array', - SmallIntType::class => 'int', - StringType::class => 'string', - TextType::class => 'string', - ]; - - /** - * Map built-in Doctrine DBAL 4+ types to PHP types + * It maps built-in Doctrine types to PHP types */ private const BUILTIN_TYPES_MAP = [ AsciiStringType::class => 'string', - BigIntType::class => 'string|int', + BigIntType::class => 'string', BooleanType::class => 'bool', DecimalType::class => 'string', FloatType::class => 'float', @@ -455,10 +436,6 @@ private function findBuiltInType(Type $type): string|null { $typeName = $type::class; - if (method_exists(BigIntType::class, 'getName')) { // DBAL 3 - return self::BUILTIN_TYPES_MAP_DBAL3[$typeName] ?? null; - } else { // DBAL 4+ - return self::BUILTIN_TYPES_MAP[$typeName] ?? null; - } + return self::BUILTIN_TYPES_MAP[$typeName] ?? null; } } diff --git a/tests/Tests/ORM/Tools/SchemaValidatorTest.php b/tests/Tests/ORM/Tools/SchemaValidatorTest.php index a153e4e77b6..b7141b0d99d 100644 --- a/tests/Tests/ORM/Tools/SchemaValidatorTest.php +++ b/tests/Tests/ORM/Tools/SchemaValidatorTest.php @@ -6,8 +6,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; -use Doctrine\DBAL\Types\BigIntType; -use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\DiscriminatorMap; @@ -32,8 +30,6 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; -use function method_exists; - class SchemaValidatorTest extends OrmTestCase { private EntityManagerInterface|null $em = null; @@ -232,47 +228,6 @@ public function testInvalidAssociationTowardsMappedSuperclass(): void $ce, ); } - - public function testBigintMappedToStringInt(): void - { - $class = $this->em->getClassMetadata(BigintMappedToStringInt::class); - $ce = $this->validator->validateClass($class); - - $this->assertEquals([], $ce); // Same for DBAL 3 and 4+ - } - - public function testBigintMappedToInt(): void - { - $class = $this->em->getClassMetadata(BigintMappedToInt::class); - $ce = $this->validator->validateClass($class); - - if (method_exists(BigIntType::class, 'getName')) { // DBAL 3 - $this->assertEquals( - ["The field 'Doctrine\Tests\ORM\Tools\BigintMappedToInt#bigint' has the property type 'int' that differs from the metadata field type 'string' returned by the 'bigint' DBAL type."], - $ce, - ); - } else { // DBAL 4+ - $this->assertEquals( - ["The field 'Doctrine\Tests\ORM\Tools\BigintMappedToInt#bigint' has the property type 'int' that differs from the metadata field type 'string|int' returned by the 'bigint' DBAL type."], - $ce, - ); - } - } - - public function testBigintMappedToString(): void - { - $class = $this->em->getClassMetadata(BigintMappedToString::class); - $ce = $this->validator->validateClass($class); - - if (method_exists(BigIntType::class, 'getName')) { // DBAL 3 - $this->assertEquals([], $ce); - } else { // DBAL 4+ - $this->assertEquals( - ["The field 'Doctrine\Tests\ORM\Tools\BigintMappedToString#bigint' has the property type 'string' that differs from the metadata field type 'string|int' returned by the 'bigint' DBAL type."], - $ce, - ); - } - } } #[MappedSuperclass] @@ -592,39 +547,3 @@ class InvalidMappedSuperClass #[ManyToMany(targetEntity: 'InvalidMappedSuperClass', mappedBy: 'invalid')] private $selfWhatever; } - -#[Entity] -class BigintMappedToStringInt -{ - #[Id] - #[Column] - #[GeneratedValue] - private int $id; - - #[Column(type: Types::BIGINT)] - private string|int $bigint; -} - -#[Entity] -class BigintMappedToInt -{ - #[Id] - #[Column] - #[GeneratedValue] - private int $id; - - #[Column(type: Types::BIGINT)] - private int $bigint; -} - -#[Entity] -class BigintMappedToString -{ - #[Id] - #[Column] - #[GeneratedValue] - private int $id; - - #[Column(type: Types::BIGINT)] - private string $bigint; -}