mirror of
https://github.com/doctrine/orm.git
synced 2026-03-25 15:32:18 +01:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f79d166a4e | ||
|
|
9c22814cfa | ||
|
|
b274893486 | ||
|
|
e0e55dc9c5 | ||
|
|
010b1e0886 | ||
|
|
93eb8a1bcb | ||
|
|
1464827220 | ||
|
|
8709fb38b0 | ||
|
|
cbb6c897de | ||
|
|
e9e60f2fbc | ||
|
|
5f3c1dbab8 | ||
|
|
753bc16c0b | ||
|
|
6090141e0b | ||
|
|
e4a6c041b5 | ||
|
|
c54c557e02 | ||
|
|
46d0865339 | ||
|
|
9c56071392 | ||
|
|
0a1988b349 | ||
|
|
1a5a4c674a | ||
|
|
95795c87a8 | ||
|
|
db6e702088 | ||
|
|
4175edf311 | ||
|
|
67ac5a82da | ||
|
|
e384978e0b | ||
|
|
5ccbc201bf | ||
|
|
d15624f72f | ||
|
|
9d1a4973ae | ||
|
|
55c4845d57 | ||
|
|
a38f473a92 | ||
|
|
40a0964f06 | ||
|
|
08a9e60ed0 | ||
|
|
3e3c023c95 | ||
|
|
5e6d5c06a9 | ||
|
|
1622b7877d | ||
|
|
80aae2796d | ||
|
|
528ef40fc4 | ||
|
|
4b4b9b7b6f | ||
|
|
ae842259f5 | ||
|
|
7178b9d6b7 | ||
|
|
8a14eee67a | ||
|
|
c5315f86fb | ||
|
|
5820bb8f49 | ||
|
|
820a0da4c1 | ||
|
|
fcd02b1ee2 | ||
|
|
b0d07ffaba | ||
|
|
196d3a6996 | ||
|
|
abcad6fa45 | ||
|
|
1b6cf58a1a | ||
|
|
6501890ab5 | ||
|
|
e399d21fb3 | ||
|
|
16f355f0cc | ||
|
|
94d45a036f | ||
|
|
9acca2252f | ||
|
|
8d4718f875 | ||
|
|
e5e3166747 |
@@ -94,42 +94,6 @@
|
||||
"branchName": "2.10.x",
|
||||
"slug": "2.10",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.9",
|
||||
"branchName": "2.9.x",
|
||||
"slug": "2.9",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.8",
|
||||
"branchName": "2.8.x",
|
||||
"slug": "2.8",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.7",
|
||||
"branchName": "2.7",
|
||||
"slug": "2.7",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.6",
|
||||
"branchName": "2.6",
|
||||
"slug": "2.6",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.5",
|
||||
"branchName": "2.5",
|
||||
"slug": "2.5",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.4",
|
||||
"branchName": "2.4",
|
||||
"slug": "2.4",
|
||||
"maintained": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
5
.github/workflows/documentation.yml
vendored
5
.github/workflows/documentation.yml
vendored
@@ -40,5 +40,10 @@ jobs:
|
||||
with:
|
||||
dependency-versions: "highest"
|
||||
|
||||
- name: "Add orphan metadata where needed"
|
||||
run: |
|
||||
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
|
||||
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/reference/installation.rst)" > docs/en/reference/installation.rst
|
||||
|
||||
- name: "Run guides-cli"
|
||||
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"doctrine/persistence": "^3.3.1",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/var-exporter": "~6.2.13 || ^6.3.2 || ^7.0"
|
||||
"symfony/var-exporter": "^6.3.9 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12.0",
|
||||
|
||||
4
docs/.gitignore
vendored
4
docs/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
en/_exts/configurationblock.pyc
|
||||
build
|
||||
en/_build
|
||||
.idea
|
||||
3
docs/.gitmodules
vendored
3
docs/.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "en/_theme"]
|
||||
path = en/_theme
|
||||
url = https://github.com/doctrine/doctrine-sphinx-theme.git
|
||||
@@ -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
|
||||
|
||||
@@ -228,50 +228,12 @@ and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
|
||||
Doctrine Mapping Types
|
||||
----------------------
|
||||
|
||||
The ``type`` option used in the ``@Column`` accepts any of the existing
|
||||
Doctrine types or even your own custom types. A Doctrine type defines
|
||||
The ``type`` option used in the ``@Column`` accepts any of the
|
||||
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
|
||||
or :doc:`your own custom mapping types
|
||||
<../cookbook/custom-mapping-types>`. A Doctrine type defines
|
||||
the conversion between PHP and SQL types, independent from the database vendor
|
||||
you are using. All Mapping Types that ship with Doctrine are fully portable
|
||||
between the supported database systems.
|
||||
|
||||
As an example, the Doctrine Mapping Type ``string`` defines the
|
||||
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
|
||||
depending on the RDBMS brand). Here is a quick overview of the
|
||||
built-in mapping types:
|
||||
|
||||
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
|
||||
- ``integer``: Type that maps a SQL INT to a PHP integer.
|
||||
- ``smallint``: Type that maps a database SMALLINT to a PHP
|
||||
integer.
|
||||
- ``bigint``: Type that maps a database BIGINT to a PHP string.
|
||||
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
|
||||
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
|
||||
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
|
||||
object.
|
||||
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
|
||||
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
|
||||
DateTime object.
|
||||
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
|
||||
DateTime object with timezone.
|
||||
- ``text``: Type that maps a SQL CLOB to a PHP string.
|
||||
- ``object``: Type that maps a SQL CLOB to a PHP object using
|
||||
``serialize()`` and ``unserialize()``
|
||||
- ``array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``serialize()`` and ``unserialize()``
|
||||
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
|
||||
Only use this type if you are sure that your values cannot contain a ",".
|
||||
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``json_encode()`` and ``json_decode()``
|
||||
- ``float``: Type that maps a SQL Float (Double Precision) to a
|
||||
PHP double. *IMPORTANT*: Works only with locale settings that use
|
||||
decimal points as separator.
|
||||
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
|
||||
varchar but uses a specific type if the platform supports it.
|
||||
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
|
||||
|
||||
A cookbook article shows how to define :doc:`your own custom mapping types
|
||||
<../cookbook/custom-mapping-types>`.
|
||||
you are using.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -976,7 +976,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:
|
||||
@@ -993,58 +993,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 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".
|
||||
|
||||
``getResult()``
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- ``Query#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).
|
||||
- ``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``
|
||||
is thrown. The pure/mixed distinction does not apply.
|
||||
- ``Query#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.
|
||||
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):
|
||||
|
||||
.. note::
|
||||
.. code-block:: php
|
||||
|
||||
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.
|
||||
<?php
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
|
||||
$query = $em->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.
|
||||
|
||||
- ``Query#getScalarResult()``: 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
|
||||
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.
|
||||
- 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.
|
||||
|
||||
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.
|
||||
``getArrayResult()``
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The use of the methods mentioned earlier is generally preferred as
|
||||
it leads to more concise code.
|
||||
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
|
||||
|
||||
<?php
|
||||
$users = $query->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
|
||||
|
||||
<?php
|
||||
$users = $query->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
|
||||
|
||||
<?php
|
||||
$query = $em->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
|
||||
|
||||
<?php
|
||||
$query = $em->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
|
||||
|
||||
<?php
|
||||
namespace MyProject\Hydrators;
|
||||
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
|
||||
class CustomHydrator extends AbstractHydrator
|
||||
{
|
||||
protected function _hydrateAll()
|
||||
{
|
||||
return $this->_stmt->fetchAllAssociative();
|
||||
}
|
||||
}
|
||||
|
||||
Next you just need to add the class to the ORM configuration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator');
|
||||
|
||||
Now the hydrator is ready to be used in your queries:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$results = $query->getResult('CustomHydrator');
|
||||
|
||||
Pure and Mixed Results
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -1148,165 +1236,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
|
||||
|
||||
<?php
|
||||
$query = $em->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
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$users = $query->getResult(Query::HYDRATE_ARRAY);
|
||||
|
||||
You can use the ``getArrayResult()`` shortcut as well:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$users = $query->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
|
||||
|
||||
<?php
|
||||
$query = $em->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
|
||||
|
||||
<?php
|
||||
$query = $em->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
|
||||
|
||||
<?php
|
||||
$numArticles = $query->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
|
||||
|
||||
<?php
|
||||
$query = $em->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
|
||||
|
||||
<?php
|
||||
$ids = $query->getSingleColumnResult();
|
||||
|
||||
Custom Hydration Modes
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can easily add your own custom hydration modes by first
|
||||
creating a class which extends ``AbstractHydrator``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Hydrators;
|
||||
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
|
||||
class CustomHydrator extends AbstractHydrator
|
||||
{
|
||||
protected function _hydrateAll()
|
||||
{
|
||||
return $this->_stmt->fetchAllAssociative();
|
||||
}
|
||||
}
|
||||
|
||||
Next you just need to add the class to the ORM configuration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator');
|
||||
|
||||
Now the hydrator is ready to be used in your queries:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$results = $query->getResult('CustomHydrator');
|
||||
|
||||
Iterating Large Result Sets
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
:orphan:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
:orphan:
|
||||
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Tutorials
|
||||
|
||||
@@ -27,7 +27,7 @@ What is Doctrine?
|
||||
-----------------
|
||||
|
||||
Doctrine ORM is an `object-relational mapper (ORM) <https://en.wikipedia.org/wiki/Object-relational_mapping>`_
|
||||
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.
|
||||
|
||||
|
||||
@@ -115,11 +115,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/EntityManagerInterface.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\\<int, T of object\\>&Doctrine\\\\Common\\\\Collections\\\\Selectable\\<int, T of object\\> but returns Doctrine\\\\ORM\\\\LazyCriteriaCollection\\<\\(int\\|string\\), object\\>\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
-
|
||||
|
||||
@@ -154,14 +154,6 @@
|
||||
<code><![CDATA[$className]]></code>
|
||||
</ArgumentTypeCoercion>
|
||||
</file>
|
||||
<file src="src/Decorator/EntityManagerDecorator.php">
|
||||
<InvalidReturnStatement>
|
||||
<code><![CDATA[$this->wrapped->getClassMetadata($className)]]></code>
|
||||
</InvalidReturnStatement>
|
||||
<InvalidReturnType>
|
||||
<code><![CDATA[ClassMetadata]]></code>
|
||||
</InvalidReturnType>
|
||||
</file>
|
||||
<file src="src/EntityManager.php">
|
||||
<ArgumentTypeCoercion>
|
||||
<code><![CDATA[$className]]></code>
|
||||
@@ -174,11 +166,9 @@
|
||||
<code><![CDATA[$persister->load($sortedId, null, null, [], $lockMode)]]></code>
|
||||
<code><![CDATA[$persister->loadById($sortedId)]]></code>
|
||||
<code><![CDATA[$this->metadataFactory]]></code>
|
||||
<code><![CDATA[$this->metadataFactory->getMetadataFor($className)]]></code>
|
||||
</InvalidReturnStatement>
|
||||
<InvalidReturnType>
|
||||
<code><![CDATA[ClassMetadataFactory]]></code>
|
||||
<code><![CDATA[Mapping\ClassMetadata]]></code>
|
||||
</InvalidReturnType>
|
||||
<PossiblyNullArgument>
|
||||
<code><![CDATA[$config->getProxyDir()]]></code>
|
||||
@@ -251,9 +241,6 @@
|
||||
</UnsupportedReferenceUsage>
|
||||
</file>
|
||||
<file src="src/Internal/Hydration/ObjectHydrator.php">
|
||||
<InvalidArgument>
|
||||
<code><![CDATA[$element]]></code>
|
||||
</InvalidArgument>
|
||||
<PossiblyFalseArgument>
|
||||
<code><![CDATA[$index]]></code>
|
||||
</PossiblyFalseArgument>
|
||||
@@ -298,6 +285,10 @@
|
||||
<code><![CDATA[$this->columnNames]]></code>
|
||||
</DeprecatedProperty>
|
||||
<InvalidArgument>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
<code><![CDATA[$mapping]]></code>
|
||||
@@ -760,13 +751,8 @@
|
||||
<code><![CDATA[$autoGenerate > 4]]></code>
|
||||
</TypeDoesNotContainType>
|
||||
<UndefinedMethod>
|
||||
<code><![CDATA[self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
|
||||
$initializer($object, $proxy);
|
||||
}, $skippedProperties)]]></code>
|
||||
<code><![CDATA[self::createLazyGhost($initializer, $skippedProperties)]]></code>
|
||||
</UndefinedMethod>
|
||||
<UndefinedVariable>
|
||||
<code><![CDATA[$proxy]]></code>
|
||||
</UndefinedVariable>
|
||||
<UnresolvableInclude>
|
||||
<code><![CDATA[require $fileName]]></code>
|
||||
</UnresolvableInclude>
|
||||
|
||||
@@ -234,7 +234,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
*
|
||||
* @psalm-param string|class-string<T> $className
|
||||
*
|
||||
* @psalm-return Mapping\ClassMetadata<T>
|
||||
* @psalm-return ($className is class-string<T> ? Mapping\ClassMetadata<T> : Mapping\ClassMetadata<object>)
|
||||
*
|
||||
* @psalm-template T of object
|
||||
*/
|
||||
|
||||
@@ -64,11 +64,11 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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(mixed $element): bool
|
||||
{
|
||||
|
||||
@@ -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 @@ abstract class AssociationMapping implements ArrayAccess
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<mixed>|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
|
||||
{
|
||||
|
||||
@@ -1152,7 +1152,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
* fieldName?: string,
|
||||
* columnName?: string,
|
||||
* id?: bool,
|
||||
* generated?: int,
|
||||
* generated?: self::GENERATED_*,
|
||||
* enumType?: class-string,
|
||||
* } $mapping The field mapping to validate & complete.
|
||||
*
|
||||
|
||||
@@ -49,10 +49,12 @@ final class EmbeddedClassMapping implements ArrayAccess
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
||||
@@ -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<BackedEnum>|null */
|
||||
public string|null $enumType = null;
|
||||
@@ -83,7 +83,34 @@ final class FieldMapping implements ArrayAccess
|
||||
) {
|
||||
}
|
||||
|
||||
/** @param array{type: string, fieldName: string, columnName: string} $mappingArray */
|
||||
/**
|
||||
* @param array<string, mixed> $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<string, mixed>|null,
|
||||
* version?: bool|null,
|
||||
* default?: string|int|null,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): self
|
||||
{
|
||||
$mapping = new self(
|
||||
|
||||
@@ -31,7 +31,17 @@ final class JoinColumnMapping implements ArrayAccess
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $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<string, mixed>|null,
|
||||
* } $mappingArray
|
||||
*/
|
||||
public static function fromMappingArray(array $mappingArray): self
|
||||
{
|
||||
|
||||
@@ -35,10 +35,10 @@ final class JoinTableMapping implements ArrayAccess
|
||||
* @param mixed[] $mappingArray
|
||||
* @psalm-param array{
|
||||
* name: string,
|
||||
* quoted?: bool,
|
||||
* quoted?: bool|null,
|
||||
* joinColumns?: mixed[],
|
||||
* inverseJoinColumns?: mixed[],
|
||||
* schema?: string,
|
||||
* schema?: string|null,
|
||||
* options?: array<string, mixed>
|
||||
* } $mappingArray
|
||||
*/
|
||||
|
||||
@@ -41,9 +41,21 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
|
||||
* 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<mixed>|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
|
||||
{
|
||||
|
||||
@@ -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<mixed>|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 @@ 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<mixed>|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
|
||||
{
|
||||
|
||||
@@ -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<mixed>|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,
|
||||
|
||||
@@ -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<mixed>|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 @@ 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<mixed>|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,
|
||||
|
||||
@@ -350,11 +350,6 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
return parent::containsKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @template TMaybeContained
|
||||
*/
|
||||
public function contains(mixed $element): bool
|
||||
{
|
||||
if (! $this->initialized && $this->getMapping()->fetch === ClassMetadata::FETCH_EXTRA_LAZY) {
|
||||
|
||||
@@ -216,11 +216,11 @@ EOPHP;
|
||||
*/
|
||||
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
|
||||
{
|
||||
return static function (InternalProxy $proxy, InternalProxy $original) use ($entityPersister, $classMetadata, $identifierFlattener): void {
|
||||
$identifier = $classMetadata->getIdentifierValues($original);
|
||||
$entity = $entityPersister->loadById($identifier, $original);
|
||||
return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
|
||||
$identifier = $classMetadata->getIdentifierValues($proxy);
|
||||
$original = $entityPersister->loadById($identifier);
|
||||
|
||||
if ($entity === null) {
|
||||
if ($original === null) {
|
||||
throw EntityNotFoundException::fromClassNameAndIdentifier(
|
||||
$classMetadata->getName(),
|
||||
$identifierFlattener->flattenIdentifier($classMetadata, $identifier),
|
||||
@@ -234,11 +234,11 @@ EOPHP;
|
||||
$class = $entityPersister->getClassMetadata();
|
||||
|
||||
foreach ($class->getReflectionProperties() as $property) {
|
||||
if (! $property || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
|
||||
if (! $property || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$property->setValue($proxy, $property->getValue($entity));
|
||||
$property->setValue($proxy, $property->getValue($original));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -283,9 +283,7 @@ EOPHP;
|
||||
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
|
||||
|
||||
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
|
||||
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
|
||||
$initializer($object, $proxy);
|
||||
}, $skippedProperties);
|
||||
$proxy = self::createLazyGhost($initializer, $skippedProperties);
|
||||
|
||||
foreach ($identifierFields as $idField => $reflector) {
|
||||
if (! isset($identifier[$idField])) {
|
||||
|
||||
@@ -52,18 +52,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(
|
||||
@@ -353,21 +353,21 @@ class SchemaValidator
|
||||
$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),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -392,7 +392,7 @@ class SchemaValidator
|
||||
) {
|
||||
$backingType = (string) (new ReflectionEnum($fieldMapping->enumType))->getBackingType();
|
||||
|
||||
if ($metadataFieldType === $backingType) {
|
||||
if (in_array($backingType, $metadataFieldType, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ class SchemaValidator
|
||||
$fieldName,
|
||||
$fieldMapping->enumType,
|
||||
$backingType,
|
||||
$metadataFieldType,
|
||||
implode('|', $metadataFieldType),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ class SchemaValidator
|
||||
$class->name,
|
||||
$fieldName,
|
||||
$propertyType,
|
||||
$metadataFieldType,
|
||||
implode('|', $metadataFieldType),
|
||||
$fieldMapping->type,
|
||||
);
|
||||
},
|
||||
@@ -431,8 +431,10 @@ class SchemaValidator
|
||||
/**
|
||||
* The exact DBAL type must be used (no subclasses), since consumers of doctrine/orm may have their own
|
||||
* customization around field types.
|
||||
*
|
||||
* @return list<string>|null
|
||||
*/
|
||||
private function findBuiltInType(Type $type): string|null
|
||||
private function findBuiltInType(Type $type): array|null
|
||||
{
|
||||
$typeName = $type::class;
|
||||
|
||||
|
||||
@@ -2581,9 +2581,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
|
||||
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
|
||||
if (! $isIteration && $assoc->isOneToMany()) {
|
||||
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! $assoc->isIndexed()) {
|
||||
$this->scheduleCollectionForBatchLoading($pColl, $class);
|
||||
} elseif (($isIteration && $assoc->isOneToMany()) || $assoc->isManyToMany()) {
|
||||
} else {
|
||||
$this->loadCollection($pColl);
|
||||
$pColl->takeSnapshot();
|
||||
}
|
||||
@@ -2662,7 +2662,19 @@ class UnitOfWork implements PropertyChangedListener
|
||||
foreach ($found as $targetValue) {
|
||||
$sourceEntity = $targetProperty->getValue($targetValue);
|
||||
|
||||
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
|
||||
if ($sourceEntity === null && isset($targetClass->associationMappings[$mappedBy]->joinColumns)) {
|
||||
// case where the hydration $targetValue itself has not yet fully completed, for example
|
||||
// in case a bi-directional association is being hydrated and deferring eager loading is
|
||||
// not possible due to subclassing.
|
||||
$data = $this->getOriginalEntityData($targetValue);
|
||||
$id = [];
|
||||
foreach ($targetClass->associationMappings[$mappedBy]->joinColumns as $joinColumn) {
|
||||
$id[] = $data[$joinColumn->name];
|
||||
}
|
||||
} else {
|
||||
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
|
||||
}
|
||||
|
||||
$idHash = implode(' ', $id);
|
||||
|
||||
if ($mapping->indexBy !== null) {
|
||||
|
||||
@@ -4,8 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance\Mock;
|
||||
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
|
||||
|
||||
/**
|
||||
@@ -13,22 +12,14 @@ use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
|
||||
*/
|
||||
class NonLoadingPersister extends BasicEntityPersister
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
public function __construct(
|
||||
ClassMetadata $class,
|
||||
) {
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function load(
|
||||
array $criteria,
|
||||
object|null $entity = null,
|
||||
AssociationMapping|null $assoc = null,
|
||||
array $hints = [],
|
||||
LockMode|int|null $lockMode = null,
|
||||
int|null $limit = null,
|
||||
array|null $orderBy = null,
|
||||
): object|null {
|
||||
return $entity;
|
||||
public function loadById(array $identifier, object|null $entity = null): object|null
|
||||
{
|
||||
return $entity ?? new ($this->class->name)();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class NonProxyLoadingEntityManager implements EntityManagerInterface
|
||||
|
||||
public function getUnitOfWork(): UnitOfWork
|
||||
{
|
||||
return new NonProxyLoadingUnitOfWork();
|
||||
return new NonProxyLoadingUnitOfWork($this);
|
||||
}
|
||||
|
||||
public function getCache(): Cache|null
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance\Mock;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
|
||||
/**
|
||||
@@ -11,15 +12,17 @@ use Doctrine\ORM\UnitOfWork;
|
||||
*/
|
||||
class NonProxyLoadingUnitOfWork extends UnitOfWork
|
||||
{
|
||||
private NonLoadingPersister $entityPersister;
|
||||
/** @var array<class-string, NonLoadingPersister> */
|
||||
private array $entityPersisters = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->entityPersister = new NonLoadingPersister();
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEntityPersister(string $entityName): NonLoadingPersister
|
||||
{
|
||||
return $this->entityPersister;
|
||||
return $this->entityPersisters[$entityName]
|
||||
??= new NonLoadingPersister($this->entityManager->getClassMetadata($entityName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\AbstractFetchEager;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'abstract_fetch_eager_remote_control')]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
|
||||
#[ORM\DiscriminatorMap(['mobile' => 'MobileRemoteControl'])]
|
||||
abstract class AbstractRemoteControl
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
public int $id;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
public string $name;
|
||||
|
||||
/** @var Collection<User> */
|
||||
#[ORM\OneToMany(targetEntity: User::class, mappedBy: 'remoteControl', fetch: 'EAGER')]
|
||||
public Collection $users;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->users = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\AbstractFetchEager;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class MobileRemoteControl extends AbstractRemoteControl
|
||||
{
|
||||
}
|
||||
26
tests/Tests/Models/AbstractFetchEager/User.php
Normal file
26
tests/Tests/Models/AbstractFetchEager/User.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\AbstractFetchEager;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'abstract_fetch_eager_user')]
|
||||
class User
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
public int $id;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: AbstractRemoteControl::class, inversedBy: 'users')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
public AbstractRemoteControl $remoteControl;
|
||||
|
||||
public function __construct(AbstractRemoteControl $control)
|
||||
{
|
||||
$this->remoteControl = $control;
|
||||
}
|
||||
}
|
||||
26
tests/Tests/Models/BigIntegers/BigIntegers.php
Normal file
26
tests/Tests/Models/BigIntegers/BigIntegers.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\BigIntegers;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class BigIntegers
|
||||
{
|
||||
#[ORM\Column]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
public int|null $id = null;
|
||||
|
||||
#[ORM\Column(type: Types::BIGINT)]
|
||||
public int $one = 1;
|
||||
|
||||
#[ORM\Column(type: Types::BIGINT)]
|
||||
public string $two = '2';
|
||||
|
||||
#[ORM\Column(type: Types::BIGINT)]
|
||||
public float $three = 3.0;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'eager_composite_join_root')]
|
||||
class RootEntity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer', nullable: false)]
|
||||
private int|null $id = null;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'string', nullable: false, name: 'other_key', length: 42)]
|
||||
private string $otherKey;
|
||||
|
||||
/** @var Collection<int, SecondLevel> */
|
||||
#[ORM\OneToMany(mappedBy: 'root', targetEntity: SecondLevel::class, fetch: 'EAGER')]
|
||||
private Collection $secondLevel;
|
||||
|
||||
public function __construct(int $id, string $other)
|
||||
{
|
||||
$this->otherKey = $other;
|
||||
$this->secondLevel = new ArrayCollection();
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getOtherKey(): string
|
||||
{
|
||||
return $this->otherKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'eager_composite_join_second_level')]
|
||||
#[ORM\Index(name: 'root_other_key_idx', columns: ['root_other_key', 'root_id'])]
|
||||
class SecondLevel
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer', nullable: false)]
|
||||
private int|null $upperId;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'string', nullable: false, name: 'other_key')]
|
||||
private string $otherKey;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: RootEntity::class, inversedBy: 'secondLevel')]
|
||||
#[ORM\JoinColumn(name: 'root_id', referencedColumnName: 'id')]
|
||||
#[ORM\JoinColumn(name: 'root_other_key', referencedColumnName: 'other_key')]
|
||||
private RootEntity $root;
|
||||
|
||||
public function __construct(RootEntity $upper)
|
||||
{
|
||||
$this->upperId = $upper->getId();
|
||||
$this->otherKey = $upper->getOtherKey();
|
||||
$this->root = $upper;
|
||||
}
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
37
tests/Tests/ORM/Functional/AbstractFetchEagerTest.php
Normal file
37
tests/Tests/ORM/Functional/AbstractFetchEagerTest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\Models\AbstractFetchEager\AbstractRemoteControl;
|
||||
use Doctrine\Tests\Models\AbstractFetchEager\MobileRemoteControl;
|
||||
use Doctrine\Tests\Models\AbstractFetchEager\User;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
final class AbstractFetchEagerTest extends OrmFunctionalTestCase
|
||||
{
|
||||
public function testWithAbstractFetchEager(): void
|
||||
{
|
||||
$this->createSchemaForModels(
|
||||
AbstractRemoteControl::class,
|
||||
User::class,
|
||||
);
|
||||
|
||||
$control = new MobileRemoteControl('smart');
|
||||
$user = new User($control);
|
||||
|
||||
$entityManage = $this->getEntityManager();
|
||||
|
||||
$entityManage->persist($control);
|
||||
$entityManage->persist($user);
|
||||
$entityManage->flush();
|
||||
$entityManage->clear();
|
||||
|
||||
$user = $entityManage->find(User::class, $user->id);
|
||||
|
||||
self::assertNotNull($user);
|
||||
self::assertEquals('smart', $user->remoteControl->name);
|
||||
self::assertTrue($user->remoteControl->users->contains($user));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\RootEntity;
|
||||
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevel;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCase
|
||||
{
|
||||
/** @ticket 11154 */
|
||||
public function testItDoesNotThrowAnExceptionWhenTriggeringALoad(): void
|
||||
{
|
||||
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class]);
|
||||
|
||||
$a1 = new RootEntity(1, 'A');
|
||||
|
||||
$this->_em->persist($a1);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
self::assertCount(1, $this->_em->getRepository(RootEntity::class)->findAll());
|
||||
}
|
||||
}
|
||||
33
tests/Tests/ORM/Functional/Ticket/GH11149/EagerProduct.php
Normal file
33
tests/Tests/ORM/Functional/Ticket/GH11149/EagerProduct.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('gh11149_eager_product')]
|
||||
class EagerProduct
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
public int $id;
|
||||
|
||||
/** @var Collection<string, EagerProductTranslation> */
|
||||
#[ORM\OneToMany(
|
||||
targetEntity: EagerProductTranslation::class,
|
||||
mappedBy: 'product',
|
||||
fetch: 'EAGER',
|
||||
indexBy: 'locale_code',
|
||||
)]
|
||||
public Collection $translations;
|
||||
|
||||
public function __construct(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->translations = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('gh11149_eager_product_translation')]
|
||||
class EagerProductTranslation
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'translations')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
public EagerProduct $product;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(name: 'locale_code', referencedColumnName: 'code', nullable: false)]
|
||||
public Locale $locale;
|
||||
|
||||
public function __construct(int $id, EagerProduct $product, Locale $locale)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->product = $product;
|
||||
$this->locale = $locale;
|
||||
}
|
||||
}
|
||||
47
tests/Tests/ORM/Functional/Ticket/GH11149/GH11149Test.php
Normal file
47
tests/Tests/ORM/Functional/Ticket/GH11149/GH11149Test.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
|
||||
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH11149Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
Locale::class,
|
||||
EagerProduct::class,
|
||||
EagerProductTranslation::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testFetchEagerModeWithIndexBy(): void
|
||||
{
|
||||
// Load entities into database
|
||||
$this->_em->persist($product = new EagerProduct(11149));
|
||||
$this->_em->persist($locale = new Locale('fr_FR'));
|
||||
$this->_em->persist(new EagerProductTranslation(11149, $product, $locale));
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
// Fetch entity from database
|
||||
$product = $this->_em->find(EagerProduct::class, 11149);
|
||||
|
||||
// Assert associated entity is loaded eagerly
|
||||
static::assertInstanceOf(EagerProduct::class, $product);
|
||||
static::assertInstanceOf(PersistentCollection::class, $product->translations);
|
||||
static::assertTrue($product->translations->isInitialized());
|
||||
static::assertCount(1, $product->translations);
|
||||
|
||||
// Assert associated entity is indexed by given property
|
||||
$translation = $product->translations->get('fr_FR');
|
||||
static::assertInstanceOf(EagerProductTranslation::class, $translation);
|
||||
static::assertNotInstanceOf(Proxy::class, $translation);
|
||||
}
|
||||
}
|
||||
21
tests/Tests/ORM/Functional/Ticket/GH11149/Locale.php
Normal file
21
tests/Tests/ORM/Functional/Ticket/GH11149/Locale.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('gh11149_locale')]
|
||||
class Locale
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(length: 5)]
|
||||
public string $code;
|
||||
|
||||
public function __construct(string $code)
|
||||
{
|
||||
$this->code = $code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
|
||||
#[Entity]
|
||||
class GH11386EntityCart
|
||||
{
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
#[Column]
|
||||
private int|null $id = null;
|
||||
|
||||
#[Column]
|
||||
private int|null $amount = null;
|
||||
|
||||
#[OneToOne(inversedBy: 'cart', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
private GH11386EntityCustomer|null $customer = null;
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getAmount(): int|null
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
public function setAmount(int $amount): static
|
||||
{
|
||||
$this->amount = $amount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCustomer(): GH11386EntityCustomer|null
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
public function setCustomer(GH11386EntityCustomer|null $customer): self
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
|
||||
#[Entity]
|
||||
class GH11386EntityCustomer
|
||||
{
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
#[Column]
|
||||
private int|null $id = null;
|
||||
|
||||
#[Column]
|
||||
private string|null $name = null;
|
||||
|
||||
#[Column(type: 'smallint', nullable: true, enumType: GH11386EnumType::class, options: ['unsigned' => true])]
|
||||
private GH11386EnumType|null $type = null;
|
||||
|
||||
#[OneToOne(mappedBy: 'customer')]
|
||||
private GH11386EntityCart|null $cart = null;
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string|null
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): GH11386EnumType|null
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(GH11386EnumType $type): static
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCart(): GH11386EntityCart|null
|
||||
{
|
||||
return $this->cart;
|
||||
}
|
||||
|
||||
public function setCart(GH11386EntityCart|null $cart): self
|
||||
{
|
||||
// unset the owning side of the relation if necessary
|
||||
if ($cart === null && $this->cart !== null) {
|
||||
$this->cart->setCustomer(null);
|
||||
}
|
||||
|
||||
// set the owning side of the relation if necessary
|
||||
if ($cart !== null && $cart->getCustomer() !== $this) {
|
||||
$cart->setCustomer($this);
|
||||
}
|
||||
|
||||
$this->cart = $cart;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
|
||||
|
||||
enum GH11386EnumType: int
|
||||
{
|
||||
case MALE = 1;
|
||||
case FEMALE = 2;
|
||||
}
|
||||
39
tests/Tests/ORM/Functional/Ticket/GH11386/GH11386Test.php
Normal file
39
tests/Tests/ORM/Functional/Ticket/GH11386/GH11386Test.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
final class GH11386Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(
|
||||
GH11386EntityCart::class,
|
||||
GH11386EntityCustomer::class,
|
||||
);
|
||||
}
|
||||
|
||||
public function testInitializeClonedProxy(): void
|
||||
{
|
||||
$cart = new GH11386EntityCart();
|
||||
$cart->setAmount(1000);
|
||||
|
||||
$customer = new GH11386EntityCustomer();
|
||||
$customer->setName('John Doe')
|
||||
->setType(GH11386EnumType::MALE)
|
||||
->setCart($cart);
|
||||
|
||||
$this->_em->persist($cart);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$cart = $this->_em->find(GH11386EntityCart::class, 1);
|
||||
$customer = clone $cart->getCustomer();
|
||||
self::assertEquals('John Doe', $customer->getName());
|
||||
}
|
||||
}
|
||||
@@ -62,9 +62,8 @@ class ProxyFactoryTest extends OrmTestCase
|
||||
public function testReferenceProxyDelegatesLoadingToThePersister(): void
|
||||
{
|
||||
$identifier = ['id' => 42];
|
||||
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
|
||||
$persister = $this->getMockBuilder(BasicEntityPersister::class)
|
||||
->onlyMethods(['load'])
|
||||
->onlyMethods(['loadById'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
@@ -74,8 +73,8 @@ class ProxyFactoryTest extends OrmTestCase
|
||||
|
||||
$persister
|
||||
->expects(self::atLeastOnce())
|
||||
->method('load')
|
||||
->with(self::equalTo($identifier), self::isInstanceOf($proxyClass))
|
||||
->method('loadById')
|
||||
->with(self::equalTo($identifier))
|
||||
->will(self::returnValue($proxy));
|
||||
|
||||
$proxy->getDescription();
|
||||
|
||||
@@ -25,6 +25,7 @@ use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\ORM\Mapping\OrderBy;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
use Doctrine\ORM\Tools\SchemaValidator;
|
||||
use Doctrine\Tests\Models\BigIntegers\BigIntegers;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceCart;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
@@ -228,6 +229,16 @@ class SchemaValidatorTest extends OrmTestCase
|
||||
$ce,
|
||||
);
|
||||
}
|
||||
|
||||
public function testBigIntProperty(): void
|
||||
{
|
||||
$class = $this->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]
|
||||
|
||||
Reference in New Issue
Block a user