mirror of
https://github.com/doctrine/orm.git
synced 2026-03-30 02:42:18 +02:00
Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b52ef5a100 | ||
|
|
ef783f7049 | ||
|
|
435d624d33 | ||
|
|
53775fe086 | ||
|
|
59f1679fed | ||
|
|
390d081fca | ||
|
|
37d1d57900 | ||
|
|
d7a537c941 | ||
|
|
cfe73cd74f | ||
|
|
d0e1da8c51 | ||
|
|
7fbe663ea0 | ||
|
|
409f2f5d82 | ||
|
|
3d8b672771 | ||
|
|
17650a6100 | ||
|
|
1588ca7e1f | ||
|
|
0de17319d3 | ||
|
|
681ff32e76 | ||
|
|
caee6c8685 | ||
|
|
c67a515cc2 | ||
|
|
24892779f7 | ||
|
|
39d2113549 | ||
|
|
65522d9775 | ||
|
|
50eecf698c | ||
|
|
20ab78e3c1 | ||
|
|
613ffe9bbd | ||
|
|
61ff45f98e | ||
|
|
a8aa475d09 | ||
|
|
a4215cfa59 | ||
|
|
a4ac9a721f | ||
|
|
447183e235 | ||
|
|
642e543b4b | ||
|
|
80503c4837 | ||
|
|
3577064f8c | ||
|
|
b6663733c0 | ||
|
|
b9d6834213 | ||
|
|
eafc4c5a0c | ||
|
|
ecf80b47a0 | ||
|
|
5499555862 | ||
|
|
70df74f65f | ||
|
|
74415becce | ||
|
|
3a56cf8ad9 | ||
|
|
6b7f53f0f3 | ||
|
|
e51666e8be | ||
|
|
6e56bcd75f | ||
|
|
48bfef1f7a | ||
|
|
e8f91434a7 | ||
|
|
7e26d82790 | ||
|
|
869b70e4db | ||
|
|
33904cb9c1 | ||
|
|
a42191eecf | ||
|
|
3fbf163d34 | ||
|
|
c777aa62b6 | ||
|
|
6296bd4e1d | ||
|
|
5a236c19f5 | ||
|
|
4f8a1f92a3 | ||
|
|
0b5be00374 | ||
|
|
145cc782ff | ||
|
|
9712506be8 | ||
|
|
bd9ead11c5 | ||
|
|
a98ebf7344 | ||
|
|
c721ab63ee | ||
|
|
2820438afc | ||
|
|
180cfcc3e3 | ||
|
|
52d806a34a | ||
|
|
49a8f2ec96 | ||
|
|
7f5f4629e5 | ||
|
|
d91e0b3867 | ||
|
|
b537758b32 | ||
|
|
2ba6e473de | ||
|
|
de97061d65 | ||
|
|
624ee78081 | ||
|
|
e003bb2bb4 | ||
|
|
5c5f310646 | ||
|
|
c10433e512 | ||
|
|
580c530041 | ||
|
|
4d461afbd6 | ||
|
|
536e31f343 | ||
|
|
c6eb4df25e | ||
|
|
aae00e3987 | ||
|
|
b56800b15c | ||
|
|
be461be36b | ||
|
|
85171a9490 | ||
|
|
f5b9f2052a | ||
|
|
3d652997d1 | ||
|
|
10393dca68 | ||
|
|
597bfaea03 | ||
|
|
98b8ced814 | ||
|
|
efaee8ce85 | ||
|
|
6e93f5bb72 | ||
|
|
a41f5673bc | ||
|
|
ca436f0bae | ||
|
|
d8212e8dd6 | ||
|
|
12eb9f42dc | ||
|
|
23af164d7a | ||
|
|
960a437d46 | ||
|
|
237bebe2ed | ||
|
|
fc3dca772e | ||
|
|
ee64d31f48 | ||
|
|
493ff74a0d | ||
|
|
78c7000962 | ||
|
|
6a05e01298 | ||
|
|
7de3434733 | ||
|
|
74e6189f3e | ||
|
|
2e7a3affba | ||
|
|
505ec21f97 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,5 @@ lib/Doctrine/DBAL
|
||||
.idea
|
||||
*.iml
|
||||
vendor/
|
||||
composer.lock
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
|
||||
33
.travis.yml
33
.travis.yml
@@ -6,6 +6,7 @@ php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4snapshot
|
||||
- nightly
|
||||
|
||||
env:
|
||||
@@ -32,22 +33,31 @@ jobs:
|
||||
mariadb: 10.1
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.1
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.2
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: nightly
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
@@ -77,7 +87,7 @@ jobs:
|
||||
|
||||
- stage: Code Quality
|
||||
env: DB=none STATIC_ANALYSIS
|
||||
install: travis_retry composer update --prefer-dist --prefer-stable
|
||||
install: travis_retry composer install --prefer-dist
|
||||
before_script:
|
||||
- echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
- echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
@@ -90,13 +100,32 @@ jobs:
|
||||
script: php phpbench.phar run -l dots --report=default
|
||||
|
||||
- stage: Code Quality
|
||||
if: NOT type = pull_request
|
||||
env: DB=none CODING_STANDARDS
|
||||
php: nightly
|
||||
php: 7.1
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script:
|
||||
- ./vendor/bin/phpcs
|
||||
|
||||
- stage: Code Quality
|
||||
if: type = pull_request
|
||||
env: DB=none PULL_REQUEST_CODING_STANDARDS
|
||||
php: 7.1
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script:
|
||||
- |
|
||||
if [ $TRAVIS_BRANCH != "master" ]; then
|
||||
git remote set-branches --add origin $TRAVIS_BRANCH;
|
||||
git fetch origin $TRAVIS_BRANCH;
|
||||
fi
|
||||
- git merge-base origin/$TRAVIS_BRANCH $TRAVIS_PULL_REQUEST_SHA || git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge --unshallow
|
||||
- wget https://github.com/diff-sniffer/git/releases/download/0.2.0/git-phpcs.phar
|
||||
- php git-phpcs.phar origin/$TRAVIS_BRANCH...$TRAVIS_PULL_REQUEST_SHA
|
||||
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
- stage: Code Quality
|
||||
env: DB=none CODING_STANDARDS
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
||||
@@ -44,12 +44,12 @@ Please try to add a test for your pull-request.
|
||||
You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project.
|
||||
It will run all the tests with an in memory SQLite database.
|
||||
|
||||
In order to do that, you will need a fresh copy of doctrine2, and you
|
||||
In order to do that, you will need a fresh copy of the ORM, and you
|
||||
will have to run a composer installation in the project:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:doctrine/doctrine2.git
|
||||
cd doctrine2
|
||||
git clone git@github.com:doctrine/orm.git
|
||||
cd orm
|
||||
curl -sS https://getcomposer.org/installer | php --
|
||||
./composer.phar install
|
||||
```
|
||||
@@ -66,7 +66,7 @@ sqlite database.
|
||||
Tips for creating unit tests:
|
||||
|
||||
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
|
||||
See `https://github.com/doctrine/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
See `https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
example.
|
||||
|
||||
## Travis
|
||||
|
||||
16
README.md
16
README.md
@@ -16,11 +16,11 @@ without requiring unnecessary code duplication.
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
|
||||
|
||||
[Master image]: https://img.shields.io/travis/doctrine/doctrine2/master.svg?style=flat-square
|
||||
[Master]: https://travis-ci.org/doctrine/doctrine2
|
||||
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/master.svg?style=flat-square
|
||||
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=master
|
||||
[2.5 image]: https://img.shields.io/travis/doctrine/doctrine2/2.5.svg?style=flat-square
|
||||
[2.5]: https://github.com/doctrine/doctrine2/tree/2.5
|
||||
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/2.5.svg?style=flat-square
|
||||
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=2.5
|
||||
[Master image]: https://img.shields.io/travis/doctrine/orm/master.svg?style=flat-square
|
||||
[Master]: https://travis-ci.org/doctrine/orm
|
||||
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/master.svg?style=flat-square
|
||||
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=master
|
||||
[2.5 image]: https://img.shields.io/travis/doctrine/orm/2.5.svg?style=flat-square
|
||||
[2.5]: https://github.com/doctrine/orm/tree/2.5
|
||||
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.5.svg?style=flat-square
|
||||
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.5
|
||||
|
||||
@@ -11,7 +11,7 @@ Please read the documentation chapter on Security in Doctrine DBAL and ORM to
|
||||
understand the assumptions we make.
|
||||
|
||||
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
|
||||
- [ORM Security Page](https://github.com/doctrine/doctrine2/blob/master/docs/en/reference/security.rst)
|
||||
- [ORM Security Page](https://github.com/doctrine/orm/blob/master/docs/en/reference/security.rst)
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
|
||||
@@ -20,13 +20,13 @@ now has a required parameter `$pathExpr`.
|
||||
|
||||
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
|
||||
the distinction between internal function and user defined DQL was removed.
|
||||
[#6500](https://github.com/doctrine/doctrine2/pull/6500)
|
||||
[#6500](https://github.com/doctrine/orm/pull/6500)
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
|
||||
|
||||
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
|
||||
removed because of the choice to allow users to overwrite internal functions, ie
|
||||
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/doctrine2/pull/6500)
|
||||
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/orm/pull/6500)
|
||||
|
||||
## PHP 7.1 is now required
|
||||
|
||||
@@ -42,7 +42,7 @@ As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Conf
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
|
||||
|
||||
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
|
||||
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/doctrine2/pull/5600).
|
||||
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/orm/pull/5600).
|
||||
|
||||
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
|
||||
|
||||
|
||||
@@ -27,9 +27,8 @@
|
||||
"symfony/console": "~3.0|~4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^1.0",
|
||||
"phpunit/phpunit": "^6.5",
|
||||
"squizlabs/php_codesniffer": "^3.2",
|
||||
"doctrine/coding-standard": "^5.0",
|
||||
"phpunit/phpunit": "^7.5",
|
||||
"symfony/yaml": "~3.4|~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
||||
2804
composer.lock
generated
Normal file
2804
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,711 +0,0 @@
|
||||
What is new in Doctrine ORM 2.5?
|
||||
================================
|
||||
|
||||
This document describes changes between Doctrine ORM 2.4 and 2.5.
|
||||
It contains a description of all the new features and sections about
|
||||
behavioral changes and potential backwards compatibility breaks.
|
||||
Please review this document carefully when updating to Doctrine 2.5.
|
||||
|
||||
First note, that with the ORM 2.5 release we are dropping support
|
||||
for PHP 5.3. We are enforcing this with Composer, servers without
|
||||
at least PHP 5.4 will not allow installing Doctrine 2.5.
|
||||
|
||||
New Features and Improvements
|
||||
-----------------------------
|
||||
|
||||
Events: PostLoad now triggered after associations are loaded
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before Doctrine 2.5 if you had an entity with a ``@PostLoad`` event
|
||||
defined then Doctrine would trigger listeners after the fields were
|
||||
loaded, but before assocations are available.
|
||||
|
||||
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
|
||||
- `Commit #a90629 <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
|
||||
|
||||
Events: Add API to programatically add event listeners to Entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When developing third party libraries or decoupled applications
|
||||
it can be interesting to develop an entity listener without knowing
|
||||
the entities that require this listener.
|
||||
|
||||
You can now attach entity listeners to entities using the
|
||||
``AttachEntityListenersListener`` class, which is listening to the
|
||||
``loadMetadata`` event that is fired once for every entity during
|
||||
metadata generation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\AttachEntityListenersListener;
|
||||
use Doctrine\ORM\Events;
|
||||
|
||||
$listener = new AttachEntityListenersListener();
|
||||
$listener->addEntityListener(
|
||||
'MyProject\Entity\User', 'MyProject\Listener\TimestampableListener',
|
||||
Events::prePersist, 'onPrePersist'
|
||||
);
|
||||
|
||||
$evm->addEventListener(Events::loadClassMetadata, $listener);
|
||||
|
||||
class TimestampableListener
|
||||
{
|
||||
public function onPrePersist($event)
|
||||
{
|
||||
$entity = $event->getEntity();
|
||||
$entity->setCreated(new \DateTime('now'));
|
||||
}
|
||||
}
|
||||
|
||||
Embeddable Objects
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine now supports creating multiple PHP objects from one database table
|
||||
implementing a feature called "Embeddable Objects". Next to an ``@Entity``
|
||||
class you can now define a class that is embeddable into a database table of an
|
||||
entity using the ``@Embeddable`` annotation. Embeddable objects can never be
|
||||
saved, updated or deleted on their own, only as part of an entity (called
|
||||
"root-entity" or "aggregate"). Consequently embeddables don't have a primary
|
||||
key, they are identified only by their values.
|
||||
|
||||
Example of defining and using embeddables classes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/** @Entity */
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @Embedded(class = "Money") */
|
||||
private $price;
|
||||
}
|
||||
|
||||
/** @Embeddable */
|
||||
class Money
|
||||
{
|
||||
/** @Column(type = "decimal") */
|
||||
private $value;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $currency = 'EUR';
|
||||
}
|
||||
|
||||
You can read more on the features of Embeddables objects `in the documentation
|
||||
<http://docs.doctrine-project.org/en/latest/tutorials/embeddables.html>`_.
|
||||
|
||||
This feature was developed by external contributor `Johannes Schmitt
|
||||
<https://twitter.com/schmittjoh>`_
|
||||
|
||||
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
|
||||
- `Pull Request #835 <https://github.com/doctrine/doctrine2/pull/835>`_
|
||||
|
||||
Second-Level-Cache
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since version 2.0 of Doctrine, fetching the same object twice by primary key
|
||||
would result in just one query. This was achieved by the identity map pattern
|
||||
(first-level-cache) that kept entities in memory.
|
||||
|
||||
The newly introduced second-level-cache works a bit differently. Instead
|
||||
of saving objects in memory, it saves them in a fast in-memory cache such
|
||||
as Memcache, Redis, Riak or MongoDB. Additionally it allows saving the result
|
||||
of more complex queries than by primary key. Summarized this feature works
|
||||
like the existing Query result cache, but it is much more powerful.
|
||||
|
||||
As an example lets cache an entity Country that is a relation to the User
|
||||
entity. We always want to display the country, but avoid the additional
|
||||
query to this table.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache(usage="READ_ONLY", region="country_region")
|
||||
*/
|
||||
class Country
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
}
|
||||
|
||||
In this example we have specified a caching region name called
|
||||
``country_region``, which we have to configure now on the EntityManager:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setSecondLevelCacheEnabled();
|
||||
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
$regionConfig = $cacheConfig->getRegionsConfiguration();
|
||||
$regionConfig->setLifetime('country_region', 3600);
|
||||
|
||||
Now Doctrine will first check for the data of any country in the cache
|
||||
instead of the database.
|
||||
|
||||
- `Documentation
|
||||
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
|
||||
- `Pull Request #808 <https://github.com/doctrine/doctrine2/pull/808>`_
|
||||
|
||||
Criteria API: Support for ManyToMany assocations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We introduced support for querying collections using the `Criteria API
|
||||
<http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections>`_
|
||||
in 2.4. This only worked efficently for One-To-Many assocations, not for
|
||||
Many-To-Many. With the start of 2.5 also Many-To-Many associations get queried
|
||||
instead of loading them into memory.
|
||||
|
||||
Criteria API: Add new contains() expression
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to use the Criteria API to check for string contains needle
|
||||
using ``contains()``. This translates to using a ``column LIKE '%needle%'`` SQL
|
||||
condition.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use \Doctrine\Common\Collections\Criteria;
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria::expr()->contains('name', 'Benjamin'));
|
||||
|
||||
$users = $repository->matching($criteria);
|
||||
|
||||
Criteria API: Support for EXTRA_LAZY
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A collection that is marked as ``fetch="EXTRA_LAZY"`` will now return another
|
||||
lazy collection when using ``Collection::matching($criteria)``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class Post
|
||||
{
|
||||
/** @OneToMany(targetEntity="Comment", fetch="EXTRA_LAZY") */
|
||||
private $comments;
|
||||
}
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria->expr()->eq("published", 1));
|
||||
|
||||
$publishedComments = $post->getComments()->matching($criteria);
|
||||
|
||||
echo count($publishedComments);
|
||||
|
||||
The lazy criteria currently supports the ``count()`` and ``contains()``
|
||||
functionality lazily. All other operations of the ``Collection`` interface
|
||||
trigger a full load of the collection.
|
||||
|
||||
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
|
||||
|
||||
- `Pull Request #882 <https://github.com/doctrine/doctrine2/pull/882>`_
|
||||
- `Pull Request #1032 <https://github.com/doctrine/doctrine2/pull/1032>`_
|
||||
|
||||
Mapping: Allow configuring Index flags
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to control the index flags in the DBAL
|
||||
schema abstraction from the ORM using metadata. This was possible
|
||||
only with a schema event listener before.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @Table(name="product", indexes={@Index(columns={"description"},flags={"fulltext"})})
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
private $description;
|
||||
}
|
||||
|
||||
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
|
||||
|
||||
- `Pull Request #973 <https://github.com/doctrine/doctrine2/pull/973>`_
|
||||
|
||||
SQLFilter API: Check if a parameter is set
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can now check in your SQLFilter if a parameter was set. This allows
|
||||
to more easily control which features of a filter to enable or disable.
|
||||
|
||||
Extending on the locale example of the documentation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyLocaleFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||
{
|
||||
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!$this->hasParameter('locale')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $targetTableAlias.'.locale = ' . $this->getParameter('locale');
|
||||
}
|
||||
}
|
||||
|
||||
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
|
||||
|
||||
- `Pull Request #963 <https://github.com/doctrine/doctrine2/pull/963>`_
|
||||
|
||||
|
||||
EXTRA_LAZY Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. Efficient query when using EXTRA_LAZY and containsKey
|
||||
|
||||
When calling ``Collection::containsKey($key)`` on one-to-many and many-to-many
|
||||
collections using ``indexBy`` and ``EXTRA_LAZY`` a query is now executed to check
|
||||
for the existance for the item. Prevoiusly this operation was performed in memory
|
||||
by loading all entities of the collection.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class User
|
||||
{
|
||||
/** @OneToMany(targetEntity="Group", indexBy="id") */
|
||||
private $groups;
|
||||
}
|
||||
|
||||
if ($user->getGroups()->containsKey($groupId)) {
|
||||
echo "User is in group $groupId\n";
|
||||
}
|
||||
|
||||
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
|
||||
|
||||
- `Pull Request #937 <https://github.com/doctrine/doctrine2/pull/937>`_
|
||||
|
||||
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
|
||||
|
||||
This was contributed by `Sander Marechal <https://github.com/sandermarechal>`_.
|
||||
|
||||
Improve efficiency of One-To-Many EAGER
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When marking a one-to-many association with ``fetch="EAGER"`` it will now
|
||||
execute one query less than before and work correctly in combination with
|
||||
``indexBy``.
|
||||
|
||||
Better support for EntityManagerInterface
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Many of the locations where previously only the ``Doctrine\ORM\EntityManager``
|
||||
was allowed are now changed to accept the ``EntityManagerInterface`` that was
|
||||
introduced in 2.4. This allows you to more easily use the decorator pattern
|
||||
to extend the EntityManager if you need. It's still not replaced everywhere,
|
||||
so you still have to be careful.
|
||||
|
||||
DQL Improvements
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
1. It is now possible to add functions to the ``ORDER BY`` clause in DQL statements:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u ORDER BY CONCAT(u.username, u.name)";
|
||||
|
||||
2. Support for functions in ``IS NULL`` expressions:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u.name FROM User u WHERE MAX(u.name) IS NULL";
|
||||
|
||||
3. A ``LIKE`` expression is now suported in ``HAVING`` clause.
|
||||
|
||||
4. Subselects are now supported inside a ``NEW()`` expression:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT new UserDTO(u.name, SELECT count(g.id) FROM Group g WHERE g.id = u.id) FROM User u";
|
||||
|
||||
5. ``MEMBER OF`` expression now allows to filter for more than one result:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u WHERE :groups MEMBER OF u.groups";
|
||||
$query = $entityManager->createQuery($dql);
|
||||
$query->setParameter('groups', array(1, 2, 3));
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
6. Expressions inside ``COUNT()`` now allowed
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT COUNT(DISTINCT CONCAT(u.name, u.lastname)) FROM User u";
|
||||
|
||||
7. Add support for ``HOUR`` in ``DATE_ADD()``/``DATE_SUB()`` functions
|
||||
|
||||
Custom DQL Functions: Add support for factories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Previously custom DQL functions could only be provided with their
|
||||
full-qualified class-name, preventing runtime configuration through
|
||||
dependency injection.
|
||||
|
||||
A simplistic approach has been contributed by `Matthieu Napoli
|
||||
<https://github.com/mnapoli>`_ to pass a callback instead that resolves
|
||||
the function:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
|
||||
$config->addCustomNumericFunction(
|
||||
'IS_PUBLISHED', function($funcName) use ($currentSiteId) {
|
||||
return new IsPublishedFunction($currentSiteId);
|
||||
}
|
||||
);
|
||||
|
||||
Query API: WHERE IN Query using a Collection as parameter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When performing a ``WHERE IN`` query for a collection of entities you can
|
||||
now pass the array collection of entities as a parameter value to the query
|
||||
object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$categories = $rootCategory->getChildren();
|
||||
|
||||
$queryBuilder
|
||||
->select('p')
|
||||
->from('Product', 'p')
|
||||
->where('p.category IN (:categories)')
|
||||
->setParameter('categories', $categories)
|
||||
;
|
||||
|
||||
This feature was contributed by `Michael Perrin
|
||||
<https://github.com/michaelperrin>`_.
|
||||
|
||||
- `Pull Request #590 <https://github.com/doctrine/doctrine2/pull/590>`_
|
||||
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
|
||||
|
||||
Query API: Add support for default Query Hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To configure multiple different features such as custom AST Walker, fetch modes,
|
||||
locking and other features affecting DQL generation we have had a feature
|
||||
called "query hints" since version 2.0.
|
||||
|
||||
It is now possible to add query hints that are always enabled for every Query:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setDefaultQueryHints(
|
||||
'doctrine.customOutputWalker' => 'MyProject\CustomOutputWalker'
|
||||
);
|
||||
|
||||
This feature was contributed by `Artur Eshenbrener
|
||||
<https://github.com/Strate>`_.
|
||||
|
||||
- `Pull Request #863 <https://github.com/doctrine/doctrine2/pull/863>`_
|
||||
|
||||
ResultSetMappingBuilder: Add support for Single-Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before 2.5 the ResultSetMappingBuilder did not work with entities
|
||||
that are using Single-Table-Inheritance. This restriction was lifted
|
||||
by adding the missing support.
|
||||
|
||||
YAML Mapping: Many-To-Many doesnt require join column definition
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Annotations and XML it was not necessary using conventions for naming
|
||||
the many-to-many join column names, in YAML it was not possible however.
|
||||
|
||||
A many-to-many definition in YAML is now possible using this minimal
|
||||
definition:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
manyToMany:
|
||||
groups:
|
||||
targetEntity: Group
|
||||
joinTable:
|
||||
name: users_groups
|
||||
|
||||
Schema Validator Command: Allow to skip sub-checks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Schema Validator command executes two independent checks
|
||||
for validity of the mappings and if the schema is synchronized
|
||||
correctly. It is now possible to skip any of the two steps
|
||||
when executing the command:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:validate-schema --skip-mapping
|
||||
$ php vendor/bin/doctrine orm:validate-schema --skip-sync
|
||||
|
||||
This allows you to write more specialized continuous integration and automation
|
||||
checks. When no changes are found the command returns the exit code 0
|
||||
and 1, 2 or 3 when failing because of mapping, sync or both.
|
||||
|
||||
EntityGenerator Command: Avoid backups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When calling the EntityGenerator for an existing entity, Doctrine would
|
||||
create a backup file every time to avoid losing changes to the code. You
|
||||
can now skip generating the backup file by passing the ``--no-backup``
|
||||
flag:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:generate-entities src/ --no-backup
|
||||
|
||||
Support for Objects as Identifiers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to use Objects as identifiers for Entities
|
||||
as long as they implement the magic method ``__toString()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class UserId
|
||||
{
|
||||
private $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string)$this->value;
|
||||
}
|
||||
}
|
||||
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="userid") */
|
||||
private $id;
|
||||
|
||||
public function __construct(UserId $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
class UserIdType extends \Doctrine\DBAL\Types\Type
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Doctrine\DBAL\Types\Type::addType('userid', 'MyProject\UserIdType');
|
||||
|
||||
Behavioral Changes (BC Breaks)
|
||||
------------------------------
|
||||
|
||||
NamingStrategy interface changed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``Doctrine\ORM\Mapping\NamingStrategyInterface`` changed slightly
|
||||
to pass the Class Name of the entity into the join column name generation:
|
||||
|
||||
::
|
||||
|
||||
- function joinColumnName($propertyName);
|
||||
+ function joinColumnName($propertyName, $className = null);
|
||||
|
||||
It also received a new method for supporting embeddables:
|
||||
|
||||
::
|
||||
|
||||
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName);
|
||||
|
||||
Minor BC BREAK: EntityManagerInterface instead of EntityManager in type-hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
an ``EntityManagerInterface`` instead.
|
||||
If you are extending any of the following classes, then you need to check following
|
||||
signatures:
|
||||
|
||||
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
|
||||
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
|
||||
|
||||
Minor BC BREAK: Custom Hydrators API change
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, ``AbstractHydrator`` does not enforce the usage of cache as part of
|
||||
API, and now provides you a clean API for column information through the method
|
||||
``hydrateColumnInfo($column)``.
|
||||
Cache variable being passed around by reference is no longer needed since
|
||||
Hydrators are per query instantiated since Doctrine 2.4.
|
||||
|
||||
- `DDC-3060 <http://doctrine-project.org/jira/browse/DDC-3060>`_
|
||||
|
||||
Minor BC BREAK: All non-transient classes in an inheritance must be part of the inheritance map
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, classes, if you define an inheritance map for an inheritance tree, you are required
|
||||
to map all non-transient classes in that inheritance, including the root of the inheritance.
|
||||
|
||||
So far, the root of the inheritance was allowed to be skipped in the inheritance map: this is
|
||||
not possible anymore, and if you don't plan to persist instances of that class, then you should
|
||||
either:
|
||||
|
||||
- make that class as ``abstract``
|
||||
- add that class to your inheritance map
|
||||
|
||||
If you fail to do so, then a ``Doctrine\ORM\Mapping\MappingException`` will be thrown.
|
||||
|
||||
|
||||
- `DDC-3300 <http://doctrine-project.org/jira/browse/DDC-3300>`_
|
||||
- `DDC-3503 <http://doctrine-project.org/jira/browse/DDC-3503>`_
|
||||
|
||||
Minor BC BREAK: Entity based EntityManager#clear() calls follow cascade detach
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever ``EntityManager#clear()`` method gets called with a given entity class
|
||||
name, until 2.4, it was only detaching the specific requested entity.
|
||||
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
|
||||
memory management since associations will be garbage collected, optimizing
|
||||
resources consumption on long running jobs.
|
||||
|
||||
Updates on entities scheduled for deletion are no longer processed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
|
||||
produce an ``UPDATE`` statement to be executed right before the ``DELETE`` statement. The entity in question
|
||||
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
|
||||
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
|
||||
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
|
||||
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
|
||||
calculation logic is optimized away.
|
||||
|
||||
Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
|
||||
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
|
||||
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
|
||||
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
|
||||
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
|
||||
instead of the default READ COMMITTED transaction isolation level.
|
||||
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
|
||||
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
|
||||
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
|
||||
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
|
||||
- ``Doctrine\ORM\EntityManager#find()``
|
||||
- ``Doctrine\ORM\EntityRepository#find()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
|
||||
|
||||
You should update signatures for these methods if you have subclassed one of the above classes.
|
||||
Please also check the calling code of these methods in your application and update if necessary.
|
||||
|
||||
.. note::
|
||||
|
||||
This in fact is really a minor BC BREAK and should not have any affect on database vendors
|
||||
other than SQL Server because it is the only one that supports and therefore cares about
|
||||
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
|
||||
|
||||
Minor BC BREAK: __clone method not called anymore when entities are instantiated via metadata API
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of PHP 5.6, instantiation of new entities is deferred to the
|
||||
`doctrine/instantiator <https://github.com/doctrine/instantiator>`_ library, which will avoid calling ``__clone``
|
||||
or any public API on instantiated objects.
|
||||
|
||||
BC BREAK: DefaultRepositoryFactory is now final
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please implement the ``Doctrine\ORM\Repository\RepositoryFactory`` interface instead of extending
|
||||
the ``Doctrine\ORM\Repository\DefaultRepositoryFactory``.
|
||||
|
||||
BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When executing DQL queries with new object expressions, instead of returning
|
||||
DTOs numerically indexes, it will now respect user provided aliases. Consider
|
||||
the following query:
|
||||
|
||||
::
|
||||
|
||||
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId
|
||||
FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
|
||||
|
||||
Previously, your result would be similar to this:
|
||||
|
||||
::
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
0=>{UserDTO object},
|
||||
1=>{AddressDTO object},
|
||||
2=>{u.id scalar},
|
||||
3=>{u.name scalar},
|
||||
4=>{a.street scalar},
|
||||
5=>{a.postalCode scalar},
|
||||
'addressId'=>{a.id scalar},
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
From now on, the resultset will look like this:
|
||||
|
||||
::
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
'user'=>{UserDTO object},
|
||||
'address'=>{AddressDTO object},
|
||||
'addressId'=>{a.id scalar}
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
@@ -11,9 +11,9 @@ There are several ways to achieve this: converting the value inside the Type
|
||||
class, converting the value on the database-level or a combination of both.
|
||||
|
||||
This article describes the third way by implementing the MySQL specific column
|
||||
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
|
||||
type `Point <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
|
||||
|
||||
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
|
||||
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
|
||||
of MySQL and enables you to store a single location in a coordinate space by
|
||||
using x and y coordinates. You can use the Point type to store a
|
||||
longitude/latitude pair to represent a geographic location.
|
||||
@@ -192,9 +192,9 @@ object into a string representation before saving to the database (in the
|
||||
``convertToDatabaseValue`` method) and back into an object after fetching the
|
||||
value from the database (in the ``convertToPHPValue`` method).
|
||||
|
||||
The format of the string representation format is called `Well-known text (WKT)
|
||||
<http://en.wikipedia.org/wiki/Well-known_text>`_. The advantage of this format
|
||||
is, that it is both human readable and parsable by MySQL.
|
||||
The format of the string representation format is called
|
||||
`Well-known text (WKT) <http://en.wikipedia.org/wiki/Well-known_text>`_.
|
||||
The advantage of this format is, that it is both human readable and parsable by MySQL.
|
||||
|
||||
Internally, MySQL stores geometry values in a binary format that is not
|
||||
identical to the WKT format. So, we need to let MySQL transform the WKT
|
||||
@@ -204,8 +204,8 @@ This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL``
|
||||
methods come into play.
|
||||
|
||||
This methods wrap a sql expression (the WKT representation of the Point) into
|
||||
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
|
||||
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
|
||||
MySQL functions `ST_PointFromText <https://dev.mysql.com/doc/refman/8.0/en/gis-wkt-functions.html#function_st-pointfromtext>`_
|
||||
and `ST_AsText <https://dev.mysql.com/doc/refman/8.0/en/gis-format-conversion-functions.html#function_st-astext>`_
|
||||
which convert WKT strings to and from the internal format of MySQL.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -21,7 +21,7 @@ the :doc:`Native Query <../reference/native-sql>` chapter.
|
||||
The DQL Parser has hooks to register functions that can then be
|
||||
used in your DQL queries and transformed into SQL, allowing to
|
||||
extend Doctrines Query capabilities to the vendors strength. This
|
||||
post explains the Used-Defined Functions API (UDF) of the Dql
|
||||
post explains the User-Defined Functions API (UDF) of the Dql
|
||||
Parser and shows some examples to give you some hints how you would
|
||||
extend DQL.
|
||||
|
||||
@@ -70,7 +70,7 @@ methods, which are quite handy in my opinion:
|
||||
Date Diff
|
||||
---------
|
||||
|
||||
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
|
||||
`Mysql's DateDiff function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff>`_
|
||||
takes two dates as argument and calculates the difference in days
|
||||
with ``date1-date2``.
|
||||
|
||||
@@ -164,7 +164,7 @@ Date Add
|
||||
|
||||
Often useful it the ability to do some simple date calculations in
|
||||
your DQL query using
|
||||
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
|
||||
`MySql's DATE_ADD function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add>`_.
|
||||
|
||||
I'll skip the blah and show the code for this function:
|
||||
|
||||
@@ -246,6 +246,6 @@ vendor sql functions and extend the DQL languages scope.
|
||||
|
||||
Code for this Extension to DQL and other Doctrine Extensions can be
|
||||
found
|
||||
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||
`in the GitHub DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ However, it is quite easy to make use of these methods in a safe
|
||||
way by guarding the custom wakeup or clone code with an entity
|
||||
identity check, as demonstrated in the following sections.
|
||||
|
||||
Safely implementing \_\_wakeup
|
||||
------------------------------
|
||||
Safely implementing __wakeup
|
||||
----------------------------
|
||||
|
||||
To safely implement ``__wakeup``, simply enclose your
|
||||
implementation code in an identity check as follows:
|
||||
@@ -37,8 +37,8 @@ implementation code in an identity check as follows:
|
||||
//...
|
||||
}
|
||||
|
||||
Safely implementing \_\_clone
|
||||
-----------------------------
|
||||
Safely implementing __clone
|
||||
---------------------------
|
||||
|
||||
Safely implementing ``__clone`` is pretty much the same:
|
||||
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
Integrating with CodeIgniter
|
||||
============================
|
||||
|
||||
This is recipe for using Doctrine 2 in your
|
||||
`CodeIgniter <http://www.codeigniter.com>`_ framework.
|
||||
|
||||
.. note::
|
||||
|
||||
This might not work for all CodeIgniter versions and may require
|
||||
slight adjustments.
|
||||
|
||||
|
||||
Here is how to set it up:
|
||||
|
||||
Make a CodeIgniter library that is both a wrapper and a bootstrap
|
||||
for Doctrine 2.
|
||||
|
||||
Setting up the file structure
|
||||
-----------------------------
|
||||
|
||||
Here are the steps:
|
||||
|
||||
|
||||
- Add a php file to your system/application/libraries folder
|
||||
called Doctrine.php. This is going to be your wrapper/bootstrap for
|
||||
the D2 entity manager.
|
||||
- Put the Doctrine folder (the one that contains Common, DBAL, and
|
||||
ORM) inside that same libraries folder.
|
||||
- Your system/application/libraries folder now looks like this:
|
||||
|
||||
system/applications/libraries -Doctrine -Doctrine.php -index.html
|
||||
|
||||
- If you want, open your config/autoload.php file and autoload
|
||||
your Doctrine library.
|
||||
|
||||
<?php $autoload['libraries'] = array('doctrine');
|
||||
|
||||
|
||||
Creating your Doctrine CodeIgniter library
|
||||
------------------------------------------
|
||||
|
||||
Now, here is what your Doctrine.php file should look like.
|
||||
Customize it to your needs.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\ClassLoader,
|
||||
Doctrine\ORM\Configuration,
|
||||
Doctrine\ORM\EntityManager,
|
||||
Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\DBAL\Logging\EchoSQLLogger;
|
||||
|
||||
class Doctrine {
|
||||
|
||||
public $em = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// load database configuration from CodeIgniter
|
||||
require_once APPPATH.'config/database.php';
|
||||
|
||||
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
|
||||
// if you want to.
|
||||
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
|
||||
|
||||
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
|
||||
$doctrineClassLoader->register();
|
||||
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
|
||||
$entitiesClassLoader->register();
|
||||
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
|
||||
$proxiesClassLoader->register();
|
||||
|
||||
// Set up caches
|
||||
$config = new Configuration;
|
||||
$cache = new ArrayCache;
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
|
||||
$config->setQueryCacheImpl($cache);
|
||||
|
||||
// Proxy configuration
|
||||
$config->setProxyDir(APPPATH.'/models/proxies');
|
||||
$config->setProxyNamespace('Proxies');
|
||||
|
||||
// Set up logger
|
||||
$logger = new EchoSQLLogger;
|
||||
$config->setSQLLogger($logger);
|
||||
|
||||
$config->setAutoGenerateProxyClasses( TRUE );
|
||||
|
||||
// Database connection information
|
||||
$connectionOptions = array(
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => $db['default']['username'],
|
||||
'password' => $db['default']['password'],
|
||||
'host' => $db['default']['hostname'],
|
||||
'dbname' => $db['default']['database']
|
||||
);
|
||||
|
||||
// Create EntityManager
|
||||
$this->em = EntityManager::create($connectionOptions, $config);
|
||||
}
|
||||
}
|
||||
|
||||
Please note that this is a development configuration; for a
|
||||
production system you'll want to use a real caching system like
|
||||
APC, get rid of EchoSqlLogger, and turn off
|
||||
autoGenerateProxyClasses.
|
||||
|
||||
For more details, consult the
|
||||
`Doctrine 2 Configuration documentation <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html>`_.
|
||||
|
||||
Now to use it
|
||||
-------------
|
||||
|
||||
Whenever you need a reference to the entity manager inside one of
|
||||
your controllers, views, or models you can do this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em = $this->doctrine->em;
|
||||
|
||||
That's all there is to it. Once you get the reference to your
|
||||
EntityManager do your Doctrine 2.0 voodoo as normal.
|
||||
|
||||
Note: If you do not choose to autoload the Doctrine library, you
|
||||
will need to put this line before you get a reference to it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$this->load->library('doctrine');
|
||||
|
||||
Good luck!
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Strategy-Pattern
|
||||
|
||||
This recipe will give you a short introduction on how to design
|
||||
similar entities without using expensive (i.e. slow) inheritance
|
||||
but with not more than \* the well-known strategy pattern \* event
|
||||
but with not more than *the well-known strategy pattern* event
|
||||
listeners
|
||||
|
||||
Scenario / Problem
|
||||
|
||||
@@ -134,4 +134,4 @@ instances. This was already discussed in the previous blog post on
|
||||
the Versionable extension, which requires another type of event
|
||||
called "onFlush".
|
||||
|
||||
Further readings: :doc:`Lifecycle Events <../reference/events>`
|
||||
Further readings: :ref:`reference-events-lifecycle-events`
|
||||
|
||||
@@ -13,11 +13,11 @@ If this documentation is not helping to answer questions you have about
|
||||
Doctrine ORM don't panic. You can get help from different sources:
|
||||
|
||||
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
|
||||
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
|
||||
- Internet Relay Chat (IRC) in #doctrine on Freenode
|
||||
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
|
||||
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
|
||||
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
|
||||
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
|
||||
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
|
||||
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
|
||||
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
|
||||
|
||||
If you need more structure over the different topics you can browse the :doc:`table
|
||||
of contents <toc>`.
|
||||
@@ -72,9 +72,9 @@ Advanced Topics
|
||||
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
|
||||
* :doc:`Filters <reference/filters>`
|
||||
* :doc:`NamingStrategy <reference/namingstrategy>`
|
||||
* :doc:`Improving Performance <reference/improving-performance>`
|
||||
* :doc:`Caching <reference/caching>`
|
||||
* :doc:`Partial Objects <reference/partial-objects>`
|
||||
* :doc:`Improving Performance <reference/improving-performance>`
|
||||
* :doc:`Caching <reference/caching>`
|
||||
* :doc:`Partial Objects <reference/partial-objects>`
|
||||
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
|
||||
* :doc:`Best Practices <reference/best-practices>`
|
||||
* :doc:`Metadata Drivers <reference/metadata-drivers>`
|
||||
@@ -95,7 +95,7 @@ Tutorials
|
||||
Changelogs
|
||||
----------
|
||||
|
||||
* :doc:`Migration to 2.5 <changelog/migration_2_5>`
|
||||
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
@@ -103,7 +103,7 @@ Cookbook
|
||||
* **Patterns**:
|
||||
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
|
||||
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
|
||||
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
|
||||
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
|
||||
|
||||
* **DQL Extension Points**:
|
||||
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
|
||||
@@ -118,9 +118,6 @@ Cookbook
|
||||
:doc:`Entities in the Session <cookbook/entities-in-session>` |
|
||||
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
|
||||
|
||||
* **Integration into Frameworks/Libraries**
|
||||
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
|
||||
|
||||
* **Hidden Gems**
|
||||
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ instance of ``Doctrine\DBAL\Connection``. If an array is passed it
|
||||
is directly passed along to the DBAL Factory
|
||||
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
|
||||
configuration is explained in the
|
||||
`DBAL section <./../../../../../projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
|
||||
|
||||
Proxy Objects
|
||||
-------------
|
||||
|
||||
@@ -181,7 +181,7 @@ Examples:
|
||||
protected $initials;
|
||||
|
||||
/**
|
||||
* @Column(type="integer", name="login_count" nullable=false, options={"unsigned":true, "default":0})
|
||||
* @Column(type="integer", name="login_count", nullable=false, options={"unsigned":true, "default":0})
|
||||
*/
|
||||
protected $loginCount;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ well.
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Doctrine 2 requires a minimum of PHP 5.4. For greatly improved
|
||||
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
|
||||
performance it is also recommended that you use APC with PHP.
|
||||
|
||||
Doctrine 2 Packages
|
||||
|
||||
@@ -270,7 +270,7 @@ A cookbook article shows how to define :doc:`your own custom mapping types
|
||||
.. warning::
|
||||
|
||||
All Date types assume that you are exclusively using the default timezone
|
||||
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
set by `date_default_timezone_set() <http://php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
or by the php.ini configuration ``date.timezone``. Working with
|
||||
different timezones will cause troubles and unexpected behavior.
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@ abstract protected methods that each of the drivers must
|
||||
implement:
|
||||
|
||||
|
||||
- \_doFetch($id)
|
||||
- \_doContains($id)
|
||||
- \_doSave($id, $data, $lifeTime = false)
|
||||
- \_doDelete($id)
|
||||
- doFetch($id)
|
||||
- doContains($id)
|
||||
- doSave($id, $data, $lifeTime = false)
|
||||
- doDelete($id)
|
||||
|
||||
The public methods ``fetch()``, ``contains()`` etc. use the
|
||||
above protected methods which are implemented by the drivers. The
|
||||
@@ -43,7 +43,7 @@ these methods.
|
||||
|
||||
This documentation does not cover every single cache driver included
|
||||
with Doctrine. For an up-to-date-list, see the
|
||||
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`.
|
||||
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`_.
|
||||
|
||||
APC
|
||||
~~~
|
||||
@@ -282,6 +282,8 @@ You can set the namespace a cache driver should use by using the
|
||||
<?php
|
||||
$cacheDriver->setNamespace('my_namespace_');
|
||||
|
||||
.. _integrating-with-the-orm:
|
||||
|
||||
Integrating with the ORM
|
||||
------------------------
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Installation and Configuration
|
||||
==============================
|
||||
|
||||
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
|
||||
older versions we still have `PEAR packages
|
||||
<http://pear.doctrine-project.org>`_.
|
||||
Doctrine can be installed with `Composer <https://getcomposer.org>`_.
|
||||
|
||||
Define the following requirement in your ``composer.json`` file:
|
||||
|
||||
@@ -16,8 +14,7 @@ Define the following requirement in your ``composer.json`` file:
|
||||
}
|
||||
|
||||
Then call ``composer install`` from your command line. If you don't know
|
||||
how Composer works, check out their `Getting Started
|
||||
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
|
||||
how Composer works, check out their `Getting Started <https://getcomposer.org/doc/00-intro.md>`_ to set up.
|
||||
|
||||
Class loading
|
||||
-------------
|
||||
@@ -93,8 +90,7 @@ Inside the ``Setup`` methods several assumptions are made:
|
||||
- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
|
||||
- If third argument `$proxyDir` is not set, use the systems temporary directory.
|
||||
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced
|
||||
Configuration <reference/advanced-configuration>` section.
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -103,15 +103,15 @@ their inclusion in the SELECT clause.
|
||||
|
||||
In this case, the result will be an array of arrays. In the example
|
||||
above, each element of the result array would be an array of the
|
||||
scalar name and address values.
|
||||
scalar name and address values.
|
||||
|
||||
You can select scalars from any entity in the query.
|
||||
You can select scalars from any entity in the query.
|
||||
|
||||
**Mixed**
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
``SELECT u, p.quantity FROM Users u...``
|
||||
SELECT u, p.quantity FROM Users u...
|
||||
|
||||
Here, the result will again be an array of arrays, with each element
|
||||
being an array made up of a User object and the scalar value
|
||||
@@ -691,8 +691,8 @@ clauses:
|
||||
- TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim
|
||||
the string by the given trim char, defaults to whitespaces.
|
||||
- UPPER(str) - Return the upper-case of the given string.
|
||||
- DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)
|
||||
- DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH)
|
||||
- DATE_ADD(date, value, unit) - Add the given time to a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
|
||||
- DATE_SUB(date, value, unit) - Subtract the given time from a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
|
||||
- DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
|
||||
|
||||
Arithmetic operators
|
||||
@@ -1172,7 +1172,7 @@ why we are listing as many of the assumptions here for reference:
|
||||
- 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.
|
||||
previous object is still an unloaded proxy.
|
||||
|
||||
This list might be incomplete.
|
||||
|
||||
@@ -1452,10 +1452,10 @@ Given that there are 10 users and corresponding addresses in the database the ex
|
||||
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
|
||||
.. note::
|
||||
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
|
||||
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
|
||||
all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
|
||||
query per association can be executed to fetch all the referred-to entities (``address``).
|
||||
|
||||
|
||||
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
|
||||
loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
|
||||
a one-by-one basis once they are accessed.
|
||||
|
||||
@@ -548,8 +548,9 @@ preFlush
|
||||
~~~~~~~~
|
||||
|
||||
``preFlush`` is called at ``EntityManager#flush()`` before
|
||||
anything else. ``EntityManager#flush()`` can be called safely
|
||||
inside its listeners.
|
||||
anything else. ``EntityManager#flush()`` should not be called inside
|
||||
its listeners, since `preFlush` event is dispatched in it, which would
|
||||
result in infinite loop.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Improving Performance
|
||||
Bytecode Cache
|
||||
--------------
|
||||
|
||||
It is highly recommended to make use of a bytecode cache like APC.
|
||||
It is highly recommended to make use of a bytecode cache like OPcache.
|
||||
A bytecode cache removes the need for parsing PHP code on every
|
||||
request and can greatly improve performance.
|
||||
|
||||
@@ -26,6 +26,8 @@ Doctrine will need to load your mapping information on every single
|
||||
request and has to parse each DQL query on every single request.
|
||||
This is a waste of resources.
|
||||
|
||||
See :ref:`integrating-with-the-orm`
|
||||
|
||||
Alternative Query Result Formats
|
||||
--------------------------------
|
||||
|
||||
@@ -42,6 +44,8 @@ for updates, which means when you call flush on the EntityManager these entities
|
||||
even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
|
||||
ones, they are just not considered for updates.
|
||||
|
||||
See :ref:`annref_entity`
|
||||
|
||||
Extra-Lazy Collections
|
||||
----------------------
|
||||
|
||||
@@ -52,7 +56,7 @@ for more information on how this fetch mode works.
|
||||
Temporarily change fetch mode in DQL
|
||||
------------------------------------
|
||||
|
||||
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
|
||||
See :ref:`dql-temporarily-change-fetch-mode`
|
||||
|
||||
|
||||
Apply Best Practices
|
||||
@@ -61,6 +65,7 @@ Apply Best Practices
|
||||
A lot of the points mentioned in the Best Practices chapter will
|
||||
also positively affect the performance of Doctrine.
|
||||
|
||||
See :doc:`Best Practices <reference/best-practices>`
|
||||
|
||||
Change Tracking policies
|
||||
------------------------
|
||||
|
||||
@@ -174,7 +174,7 @@ SQL Schema considerations
|
||||
For Single-Table-Inheritance to work in scenarios where you are
|
||||
using either a legacy database schema or a self-written database
|
||||
schema you have to make sure that all columns that are not in the
|
||||
root entity but in any of the different sub-entities has to allows
|
||||
root entity but in any of the different sub-entities has to allow
|
||||
null values. Columns that have NOT NULL constraints have to be on
|
||||
the root entity of the single-table inheritance hierarchy.
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
The installation chapter has moved to `Installation and Configuration
|
||||
<reference/configuration>`_.
|
||||
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.
|
||||
|
||||
@@ -63,7 +63,7 @@ Where the ``attribute_name`` column contains the key and
|
||||
``$attributes``.
|
||||
|
||||
The feature request for persistence of primitive value arrays
|
||||
`is described in the DDC-298 ticket <https://github.com/doctrine/doctrine2/issues/3743>`_.
|
||||
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
|
||||
|
||||
Cascade Merge with Bi-directional Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -71,8 +71,8 @@ Cascade Merge with Bi-directional Associations
|
||||
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
|
||||
Make sure to study the behavior of cascade merge if you are using it:
|
||||
|
||||
- `DDC-875 <https://github.com/doctrine/doctrine2/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <https://github.com/doctrine/doctrine2/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
- `DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
|
||||
Custom Persisters
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@@ -83,8 +83,8 @@ Currently there is no way to overwrite the persister implementation
|
||||
for a given entity, however there are several use-cases that can
|
||||
benefit from custom persister implementations:
|
||||
|
||||
- `Add Upsert Support <https://github.com/doctrine/doctrine2/issues/5178>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/doctrine2/issues/4946>`_
|
||||
- `Add Upsert Support <https://github.com/doctrine/orm/issues/5178>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/orm/issues/4946>`_
|
||||
|
||||
Persist Keys of Collections
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -94,7 +94,7 @@ PHP Arrays are ordered hash-maps and so should be the
|
||||
evaluate a feature that optionally persists and hydrates the keys
|
||||
of a Collection instance.
|
||||
|
||||
`Ticket DDC-213 <https://github.com/doctrine/doctrine2/issues/2817>`_
|
||||
`Ticket DDC-213 <https://github.com/doctrine/orm/issues/2817>`_
|
||||
|
||||
Mapping many tables to one entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -116,7 +116,6 @@ blog posts we have written on this topics:
|
||||
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
|
||||
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
|
||||
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
|
||||
- `Doctrator <https://github.com/pablodip/doctrator`>_
|
||||
|
||||
Doctrine 2 has enough hooks and extension points so that **you** can
|
||||
add whatever you want on top of it. None of this will ever become
|
||||
@@ -144,8 +143,7 @@ backwards compatibility issues or where no simple fix exists (yet).
|
||||
We don't plan to add every bug in the tracker there, just those
|
||||
issues that can potentially cause nightmares or pain of any sort.
|
||||
|
||||
See bugs, improvement and feature requests on `Github issues
|
||||
<https://github.com/doctrine/doctrine2/issues>`_.
|
||||
See bugs, improvement and feature requests on `Github issues <https://github.com/doctrine/orm/issues>`_.
|
||||
|
||||
Identifier Quoting and Legacy Databases
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -92,7 +92,7 @@ a mapping from DQL alias (key) to SQL alias (value)
|
||||
|
||||
<?php
|
||||
|
||||
$selectClause = $builder->generateSelectClause(array(
|
||||
$selectClause = $rsm->generateSelectClause(array(
|
||||
'u' => 't1',
|
||||
'g' => 't2'
|
||||
));
|
||||
|
||||
@@ -502,7 +502,7 @@ complete list of supported helper methods available:
|
||||
Adding a Criteria to a Query
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also add a :ref:`Criteria <filtering-collections>` to a QueryBuilder by
|
||||
You can also add a :ref:`filtering-collections` to a QueryBuilder by
|
||||
using ``addCriteria``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -97,7 +97,7 @@ Defines a contract for accessing a particular region.
|
||||
|
||||
Defines a contract for accessing a particular cache region.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html>`_.
|
||||
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Region.html>`_.
|
||||
|
||||
Concurrent cache region
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -111,7 +111,7 @@ If you want to use an ``READ_WRITE`` cache, you should consider providing your o
|
||||
|
||||
Defines contract for concurrently managed data region.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html>`_.
|
||||
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
|
||||
|
||||
Timestamp region
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -120,7 +120,7 @@ Timestamp region
|
||||
|
||||
Tracks the timestamps of the most recent updates to particular entity.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
|
||||
|
||||
.. _reference-second-level-cache-mode:
|
||||
|
||||
@@ -209,7 +209,7 @@ It allows you to provide a specific implementation of the following components :
|
||||
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
|
||||
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
|
||||
|
||||
Region Lifetime
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -270,7 +270,7 @@ By providing a cache logger you should be able to get information about all cach
|
||||
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
|
||||
and collect all information you want.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
|
||||
|
||||
|
||||
Entity cache definition
|
||||
|
||||
@@ -10,7 +10,7 @@ we cannot protect you from SQL injection.
|
||||
Please also read the documentation chapter on Security in Doctrine DBAL. This
|
||||
page only handles Security issues in the ORM.
|
||||
|
||||
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
|
||||
- `DBAL Security Page <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
@@ -119,7 +119,7 @@ entity might look like this:
|
||||
}
|
||||
|
||||
Now the possiblity of mass-asignment exists on this entity and can
|
||||
be exploitet by attackers to set the "isAdmin" flag to true on any
|
||||
be exploited by attackers to set the "isAdmin" flag to true on any
|
||||
object when you pass the whole request data to this method like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -716,6 +716,7 @@ methods:
|
||||
* ``in($field, array $values)``
|
||||
* ``notIn($field, array $values)``
|
||||
* ``contains($field, $value)``
|
||||
* ``memberOf($value, $field)``
|
||||
* ``startsWith($field, $value)``
|
||||
* ``endsWith($field, $value)``
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ as follows:
|
||||
persist operation. However, the persist operation is cascaded to
|
||||
entities referenced by X, if the relationships from X to these
|
||||
other entities are mapped with cascade=PERSIST or cascade=ALL (see
|
||||
":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
":ref:`transitive-persistence`").
|
||||
- If X is a removed entity, it becomes managed.
|
||||
- If X is a detached entity, an exception will be thrown on
|
||||
flush.
|
||||
@@ -286,12 +286,12 @@ as follows:
|
||||
- If X is a new entity, it is ignored by the remove operation.
|
||||
However, the remove operation is cascaded to entities referenced by
|
||||
X, if the relationship from X to these other entities is mapped
|
||||
with cascade=REMOVE or cascade=ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
with cascade=REMOVE or cascade=ALL (see ":ref:`transitive-persistence`").
|
||||
- If X is a managed entity, the remove operation causes it to
|
||||
become removed. The remove operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=REMOVE or cascade=ALL (see
|
||||
":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
":ref:`transitive-persistence`").
|
||||
- If X is a detached entity, an InvalidArgumentException will be
|
||||
thrown.
|
||||
- If X is a removed entity, it is ignored by the remove operation.
|
||||
@@ -357,14 +357,14 @@ as follows:
|
||||
become detached. The detach operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=DETACH or cascade=ALL (see
|
||||
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
|
||||
":ref:`transitive-persistence`"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
- If X is a new or detached entity, it is ignored by the detach
|
||||
operation.
|
||||
- If X is a removed entity, the detach operation is cascaded to
|
||||
entities referenced by X, if the relationships from X to these
|
||||
other entities is mapped with cascade=DETACH or cascade=ALL (see
|
||||
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
|
||||
":ref:`transitive-persistence`"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
|
||||
There are several situations in which an entity is detached
|
||||
@@ -423,7 +423,7 @@ as follows:
|
||||
- If X is a managed entity, it is ignored by the merge operation,
|
||||
however, the merge operation is cascaded to entities referenced by
|
||||
relationships from X if these relationships have been mapped with
|
||||
the cascade element value MERGE or ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
the cascade element value MERGE or ALL (see ":ref:`transitive-persistence`").
|
||||
- For all entities Y referenced by relationships from X having the
|
||||
cascade element value MERGE or ALL, Y is merged recursively as Y'.
|
||||
For all such Y referenced by X, X' is set to reference Y'. (Note
|
||||
|
||||
83
docs/en/sidebar.rst
Normal file
83
docs/en/sidebar.rst
Normal file
@@ -0,0 +1,83 @@
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Tutorials
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
|
||||
tutorials/getting-started
|
||||
tutorials/getting-started-database
|
||||
tutorials/getting-started-models
|
||||
tutorials/working-with-indexed-associations
|
||||
tutorials/extra-lazy-associations
|
||||
tutorials/composite-primary-keys
|
||||
tutorials/ordered-associations
|
||||
tutorials/override-field-association-mappings-in-subclasses
|
||||
tutorials/pagination
|
||||
tutorials/embeddables
|
||||
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Reference
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
|
||||
reference/architecture
|
||||
reference/configuration
|
||||
reference/faq
|
||||
reference/basic-mapping
|
||||
reference/association-mapping
|
||||
reference/inheritance-mapping
|
||||
reference/working-with-objects
|
||||
reference/working-with-associations
|
||||
reference/events
|
||||
reference/unitofwork
|
||||
reference/unitofwork-associations
|
||||
reference/transactions-and-concurrency
|
||||
reference/batch-processing
|
||||
reference/dql-doctrine-query-language
|
||||
reference/query-builder
|
||||
reference/native-sql
|
||||
reference/change-tracking-policies
|
||||
reference/partial-objects
|
||||
reference/xml-mapping
|
||||
reference/yaml-mapping
|
||||
reference/annotations-reference
|
||||
reference/php-mapping
|
||||
reference/caching
|
||||
reference/improving-performance
|
||||
reference/tools
|
||||
reference/metadata-drivers
|
||||
reference/best-practices
|
||||
reference/limitations-and-known-issues
|
||||
tutorials/pagination
|
||||
reference/filters
|
||||
reference/namingstrategy
|
||||
reference/advanced-configuration
|
||||
reference/second-level-cache
|
||||
reference/security
|
||||
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Cookbook
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
|
||||
cookbook/aggregate-fields
|
||||
cookbook/custom-mapping-types
|
||||
cookbook/decorator-pattern
|
||||
cookbook/dql-custom-walkers
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
cookbook/validation-of-entities
|
||||
cookbook/working-with-datetime
|
||||
cookbook/mysql-enums
|
||||
cookbook/advanced-field-value-conversion-using-custom-mapping-types
|
||||
cookbook/entities-in-session
|
||||
@@ -75,7 +75,6 @@ Cookbook
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/integrating-with-codeigniter
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
|
||||
@@ -7,7 +7,7 @@ Getting Started: Database First
|
||||
start with developing Objects and then map them onto your database. When
|
||||
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a :doc:`Database First <getting-started-database>`, you already have a database schema
|
||||
When you have a Database First, you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -5,7 +5,7 @@ Getting Started: Model First
|
||||
|
||||
When you :doc:`Code First <getting-started>`, you
|
||||
start with developing Objects and then map them onto your database. When
|
||||
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
|
||||
you Model First, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a :doc:`Database First <getting-started-database>`, then you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
@@ -19,7 +19,7 @@ installed:
|
||||
|
||||
- PHP (latest stable version)
|
||||
- Composer Package Manager (`Install Composer
|
||||
<http://getcomposer.org/doc/00-intro.md>`_)
|
||||
<https://getcomposer.org/doc/00-intro.md>`_)
|
||||
|
||||
The code of this tutorial is `available on Github <https://github.com/doctrine/doctrine2-orm-tutorial>`_.
|
||||
|
||||
@@ -31,9 +31,8 @@ The code of this tutorial is `available on Github <https://github.com/doctrine/d
|
||||
What is Doctrine?
|
||||
-----------------
|
||||
|
||||
Doctrine 2 is an `object-relational mapper (ORM)
|
||||
<http://en.wikipedia.org/wiki/Object-relational_mapping>`_ for PHP 5.4+ that
|
||||
provides transparent persistence for PHP objects. It uses the Data Mapper
|
||||
Doctrine 2 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
|
||||
pattern at the heart, aiming for a complete separation of your domain/business
|
||||
logic from the persistence in a relational database management system.
|
||||
|
||||
@@ -62,7 +61,7 @@ An Example Model: Bug Tracker
|
||||
|
||||
For this Getting Started Guide for Doctrine we will implement the
|
||||
Bug Tracker domain model from the
|
||||
`Zend\_Db\_Table <http://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
|
||||
`Zend_Db_Table <https://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
|
||||
documentation. Reading their documentation we can extract the
|
||||
requirements:
|
||||
|
||||
@@ -139,7 +138,10 @@ step:
|
||||
|
||||
// Create a simple "default" Doctrine ORM configuration for Annotations
|
||||
$isDevMode = true;
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
|
||||
$proxyDir = null;
|
||||
$cache = null;
|
||||
$useSimpleAnnotationReader = false;
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
|
||||
// or if you prefer yaml or XML
|
||||
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
|
||||
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
|
||||
@@ -157,6 +159,10 @@ step:
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. note::
|
||||
It is recommended not to use the SimpleAnnotationReader because its
|
||||
usage will be removed for version 3.0.
|
||||
|
||||
The ``require_once`` statement sets up the class autoloading for Doctrine and
|
||||
its dependencies using Composer's autoloader.
|
||||
|
||||
@@ -169,7 +175,7 @@ read up on the configuration details in the
|
||||
The third block shows the configuration options required to connect to
|
||||
a database. In this case, we'll use a file-based SQLite database. All the
|
||||
configuration options for all the shipped drivers are given in the
|
||||
`DBAL Configuration section of the manual <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
|
||||
`DBAL Configuration section of the manual <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/>`_.
|
||||
|
||||
The last block shows how the ``EntityManager`` is obtained from a
|
||||
factory method.
|
||||
@@ -284,14 +290,24 @@ but you only need to choose one.
|
||||
|
||||
<?php
|
||||
// src/Product.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity @Table(name="products")
|
||||
**/
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="products")
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue **/
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
/** @Column(type="string") **/
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
// .. (other code)
|
||||
@@ -468,28 +484,37 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity(repositoryClass="BugRepository") @Table(name="bugs")
|
||||
* @ORM\Entity(repositoryClass="BugRepository")
|
||||
* @ORM\Table(name="bugs")
|
||||
*/
|
||||
class Bug
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @Column(type="datetime")
|
||||
* @ORM\Column(type="datetime")
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $created;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $status;
|
||||
@@ -534,18 +559,25 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
|
||||
<?php
|
||||
// src/User.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity @Table(name="users")
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="users")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
@@ -775,7 +807,7 @@ the database that points from Bugs to Products.
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
protected $products = null;
|
||||
protected $products;
|
||||
|
||||
public function assignToProduct(Product $product)
|
||||
{
|
||||
@@ -797,41 +829,50 @@ the ``Product`` before:
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity @Table(name="bugs")
|
||||
**/
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="bugs")
|
||||
*/
|
||||
class Bug
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
**/
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
**/
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @Column(type="datetime")
|
||||
**/
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
protected $created;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
**/
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
|
||||
**/
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
|
||||
*/
|
||||
protected $engineer;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
|
||||
**/
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
|
||||
*/
|
||||
protected $reporter;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Product")
|
||||
**/
|
||||
* @ORM\ManyToMany(targetEntity="Product")
|
||||
*/
|
||||
protected $products;
|
||||
|
||||
// ... (other code)
|
||||
@@ -925,34 +966,40 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
|
||||
<?php
|
||||
// src/User.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity @Table(name="users")
|
||||
**/
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="users")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @var int
|
||||
**/
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
**/
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
**/
|
||||
protected $reportedBugs = null;
|
||||
*/
|
||||
protected $reportedBugs;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
**/
|
||||
protected $assignedBugs = null;
|
||||
*/
|
||||
protected $assignedBugs;
|
||||
|
||||
// .. (other code)
|
||||
}
|
||||
@@ -1490,9 +1537,12 @@ we have to adjust the metadata slightly.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity(repositoryClass="BugRepository")
|
||||
* @Table(name="bugs")
|
||||
* @ORM\Entity(repositoryClass="BugRepository")
|
||||
* @ORM\Table(name="bugs")
|
||||
**/
|
||||
class Bug
|
||||
{
|
||||
|
||||
@@ -5,8 +5,8 @@ There are use-cases when you'll want to sort collections when they are
|
||||
retrieved from the database. In userland you do this as long as you
|
||||
haven't initially saved an entity with its associations into the
|
||||
database. To retrieve a sorted collection from the database you can
|
||||
use the ``@OrderBy`` annotation with an collection that specifies
|
||||
an DQL snippet that is appended to all queries with this
|
||||
use the ``@OrderBy`` annotation with a collection that specifies
|
||||
a DQL snippet that is appended to all queries with this
|
||||
collection.
|
||||
|
||||
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
|
||||
@@ -64,7 +64,7 @@ positional statement. Multiple Fields are separated by a comma (,).
|
||||
The referenced field names have to exist on the ``targetEntity``
|
||||
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
|
||||
|
||||
The semantics of this feature can be described as follows.
|
||||
The semantics of this feature can be described as follows:
|
||||
|
||||
|
||||
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
|
||||
@@ -73,7 +73,7 @@ The semantics of this feature can be described as follows.
|
||||
- All collections of the ordered type are always retrieved in an
|
||||
ordered fashion.
|
||||
- To keep the database impact low, these implicit ORDER BY items
|
||||
are only added to an DQL Query if the collection is fetch joined in
|
||||
are only added to a DQL Query if the collection is fetch joined in
|
||||
the DQL query.
|
||||
|
||||
Given our previously defined example, the following would not add
|
||||
|
||||
@@ -72,7 +72,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* The parameter map of this query.
|
||||
*
|
||||
* @var \Doctrine\Common\Collections\ArrayCollection
|
||||
* @var ArrayCollection|Parameter[]
|
||||
*/
|
||||
protected $parameters;
|
||||
|
||||
@@ -306,7 +306,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Get all defined parameters.
|
||||
*
|
||||
* @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
|
||||
* @return ArrayCollection The defined query parameters.
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
@@ -336,7 +336,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Sets a collection of query parameters.
|
||||
*
|
||||
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
|
||||
* @param ArrayCollection|mixed[] $parameters
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -29,6 +30,7 @@ use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query;
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* Default query cache implementation.
|
||||
@@ -106,25 +108,27 @@ class DefaultQueryCache implements QueryCache
|
||||
|
||||
$result = [];
|
||||
$entityName = reset($rsm->aliasMap);
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$hasRelation = ! empty($rsm->relationMap);
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
assert($persister instanceof CachedEntityPersister);
|
||||
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
|
||||
$cm = $this->em->getClassMetadata($entityName);
|
||||
|
||||
$generateKeys = function (array $entry) use ($cm): EntityCacheKey {
|
||||
$generateKeys = static function (array $entry) use ($cm) : EntityCacheKey {
|
||||
return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
|
||||
};
|
||||
|
||||
$cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
|
||||
$entries = $region->getMultiple($cacheKeys);
|
||||
$entries = $region->getMultiple($cacheKeys) ?? [];
|
||||
|
||||
// @TODO - move to cache hydration component
|
||||
foreach ($cacheEntry->result as $index => $entry) {
|
||||
$entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null;
|
||||
$entityEntry = $entries[$index] ?? null;
|
||||
|
||||
if ($entityEntry === null) {
|
||||
if (! $entityEntry instanceof EntityCacheEntry) {
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
|
||||
}
|
||||
@@ -145,9 +149,11 @@ class DefaultQueryCache implements QueryCache
|
||||
$data = $entityEntry->data;
|
||||
|
||||
foreach ($entry['associations'] as $name => $assoc) {
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
assert($assocPersister instanceof CachedEntityPersister);
|
||||
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
|
||||
@@ -267,7 +273,7 @@ class DefaultQueryCache implements QueryCache
|
||||
$rootAlias = key($rsm->aliasMap);
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
if (! $persister instanceof CachedEntityPersister) {
|
||||
throw CacheException::nonCacheableEntity($entityName);
|
||||
}
|
||||
|
||||
|
||||
@@ -452,12 +452,14 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$class = $this->metadataFactory->getMetadataFor($cacheEntry->class);
|
||||
}
|
||||
|
||||
if (($entity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) {
|
||||
$cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity);
|
||||
|
||||
if ($cachedEntity !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->entityCacheHit($this->regionName, $cacheKey);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
return $cachedEntity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
namespace Doctrine\ORM\Cache\Region;
|
||||
|
||||
use Doctrine\Common\Cache\MultiGetCache;
|
||||
use Doctrine\ORM\Cache\CacheEntry;
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
|
||||
/**
|
||||
@@ -67,7 +68,12 @@ class DefaultMultiGetRegion extends DefaultRegion
|
||||
}
|
||||
|
||||
$returnableItems = [];
|
||||
|
||||
foreach ($keysToRetrieve as $index => $key) {
|
||||
if (! $items[$key] instanceof CacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnableItems[$index] = $items[$key];
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,13 @@ class DefaultRegion implements Region
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
return $this->cache->fetch($this->getCacheEntryKey($key)) ?: null;
|
||||
$entry = $this->cache->fetch($this->getCacheEntryKey($key));
|
||||
|
||||
if (! $entry instanceof CacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +114,7 @@ class DefaultRegion implements Region
|
||||
$entryKey = $this->getCacheEntryKey($key);
|
||||
$entryValue = $this->cache->fetch($entryKey);
|
||||
|
||||
if ($entryValue === false) {
|
||||
if (! $entryValue instanceof CacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,9 +57,6 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>EntityRepository</tt>.
|
||||
*
|
||||
* @param EntityManager $em The EntityManager to use.
|
||||
* @param Mapping\ClassMetadata $class The class descriptor.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
|
||||
{
|
||||
|
||||
@@ -703,6 +703,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
|
||||
case ClassMetadata::GENERATOR_TYPE_CUSTOM:
|
||||
$definition = $class->customGeneratorDefinition;
|
||||
if ($definition === null) {
|
||||
throw new ORMException("Can't instantiate custom generator : no custom generator definition");
|
||||
}
|
||||
if ( ! class_exists($definition['class'])) {
|
||||
throw new ORMException("Can't instantiate custom generator : " .
|
||||
$definition['class']);
|
||||
|
||||
@@ -241,9 +241,9 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @todo Merge with tableGeneratorDefinition into generic generatorDefinition
|
||||
*
|
||||
* @var array<string, string>|null
|
||||
*/
|
||||
public $customGeneratorDefinition;
|
||||
|
||||
@@ -2017,7 +2017,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return \Doctrine\DBAL\Types\Type|string|null
|
||||
* @return string|null
|
||||
*
|
||||
* @todo 3.0 Remove this. PersisterHelper should fix it somehow
|
||||
*/
|
||||
@@ -2033,7 +2033,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return \Doctrine\DBAL\Types\Type|string|null
|
||||
* @return string|null
|
||||
*
|
||||
* @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
|
||||
* that is derived by a referenced field on a different entity.
|
||||
|
||||
@@ -25,6 +25,8 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use function array_merge;
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* A PersistentCollection represents a collection of elements that have persistent state.
|
||||
@@ -447,7 +449,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
|
||||
if (! $this->initialized && $this->association !== null && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
|
||||
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
|
||||
|
||||
return $persister->count($this) + ($this->isDirty ? $this->collection->count() : 0);
|
||||
@@ -565,7 +567,9 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
if ($this->association['isOwningSide'] && $this->owner) {
|
||||
$this->changed();
|
||||
|
||||
$uow->scheduleCollectionDeletion($this);
|
||||
if (! $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingDeferredExplicit()) {
|
||||
$uow->scheduleCollectionDeletion($this);
|
||||
}
|
||||
|
||||
$this->takeSnapshot();
|
||||
}
|
||||
@@ -668,6 +672,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
|
||||
$criteria = clone $criteria;
|
||||
$criteria->where($expression);
|
||||
$criteria->orderBy(array_merge($this->association['orderBy'] ?? [], $criteria->getOrderings()));
|
||||
|
||||
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function reset;
|
||||
|
||||
/**
|
||||
@@ -569,29 +571,12 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$self = $this;
|
||||
$class = $this->class;
|
||||
$identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
$tableName = $this->quoteStrategy->getTableName($class, $this->platform);
|
||||
$idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform);
|
||||
$id = array_combine($idColumns, $identifier);
|
||||
$types = array_map(function ($identifier) use ($class, $self) {
|
||||
if (isset($class->fieldMappings[$identifier])) {
|
||||
return $class->fieldMappings[$identifier]['type'];
|
||||
}
|
||||
|
||||
$targetMapping = $self->em->getClassMetadata($class->associationMappings[$identifier]['targetEntity']);
|
||||
|
||||
if (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])) {
|
||||
return $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type'];
|
||||
}
|
||||
|
||||
if (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])) {
|
||||
return $targetMapping->associationMappings[$targetMapping->identifier[0]]['type'];
|
||||
}
|
||||
|
||||
throw ORMException::unrecognizedField($targetMapping->identifier[0]);
|
||||
}, $class->identifier);
|
||||
$types = $this->getClassIdentifiersTypes($class);
|
||||
|
||||
$this->deleteJoinTableRecords($identifier);
|
||||
|
||||
@@ -2089,4 +2074,22 @@ class BasicEntityPersister implements EntityPersister
|
||||
|
||||
$this->currentPersisterContext = $this->limitsHandlingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getClassIdentifiersTypes(ClassMetadata $class) : array
|
||||
{
|
||||
$entityManager = $this->em;
|
||||
|
||||
return array_map(
|
||||
static function ($fieldName) use ($class, $entityManager) : string {
|
||||
$types = PersisterHelper::getTypeOfField($fieldName, $class, $entityManager);
|
||||
assert(isset($types[0]));
|
||||
|
||||
return $types[0];
|
||||
},
|
||||
$class->identifier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,22 +278,25 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
// If the database platform supports FKs, just
|
||||
// delete the row from the root table. Cascades do the rest.
|
||||
if ($this->platform->supportsForeignKeyConstraints()) {
|
||||
$rootClass = $this->em->getClassMetadata($this->class->rootEntityName);
|
||||
$rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform);
|
||||
$rootClass = $this->em->getClassMetadata($this->class->rootEntityName);
|
||||
$rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform);
|
||||
$rootTypes = $this->getClassIdentifiersTypes($rootClass);
|
||||
|
||||
return (bool) $this->conn->delete($rootTable, $id);
|
||||
return (bool) $this->conn->delete($rootTable, $id, $rootTypes);
|
||||
}
|
||||
|
||||
// Delete from all tables individually, starting from this class' table up to the root table.
|
||||
$rootTable = $this->quoteStrategy->getTableName($this->class, $this->platform);
|
||||
$rootTypes = $this->getClassIdentifiersTypes($this->class);
|
||||
|
||||
$affectedRows = $this->conn->delete($rootTable, $id);
|
||||
$affectedRows = $this->conn->delete($rootTable, $id, $rootTypes);
|
||||
|
||||
foreach ($this->class->parentClasses as $parentClass) {
|
||||
$parentMetadata = $this->em->getClassMetadata($parentClass);
|
||||
$parentTable = $this->quoteStrategy->getTableName($parentMetadata, $this->platform);
|
||||
$parentTypes = $this->getClassIdentifiersTypes($parentMetadata);
|
||||
|
||||
$this->conn->delete($parentTable, $id);
|
||||
$this->conn->delete($parentTable, $id, $parentTypes);
|
||||
}
|
||||
|
||||
return (bool) $affectedRows;
|
||||
|
||||
@@ -19,15 +19,18 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Query\ParameterTypeInferer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\ParserResult;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\ParameterTypeInferer;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
|
||||
use function array_keys;
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* A Query object represents a DQL query.
|
||||
@@ -387,26 +390,13 @@ final class Query extends AbstractQuery
|
||||
$types = [];
|
||||
|
||||
foreach ($this->parameters as $parameter) {
|
||||
$key = $parameter->getName();
|
||||
$value = $parameter->getValue();
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$key = $parameter->getName();
|
||||
|
||||
if ( ! isset($paramMappings[$key])) {
|
||||
throw QueryException::unknownParameter($key);
|
||||
}
|
||||
|
||||
if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
|
||||
$value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
|
||||
}
|
||||
|
||||
if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
|
||||
$value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
|
||||
}
|
||||
|
||||
$value = $this->processParameterValue($value);
|
||||
$type = ($parameter->getValue() === $value)
|
||||
? $parameter->getType()
|
||||
: ParameterTypeInferer::inferType($value);
|
||||
[$value, $type] = $this->resolveParameterValue($parameter);
|
||||
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$types[$position] = $type;
|
||||
@@ -439,6 +429,38 @@ final class Query extends AbstractQuery
|
||||
return [$sqlParams, $types];
|
||||
}
|
||||
|
||||
/** @return mixed[] tuple of (value, type) */
|
||||
private function resolveParameterValue(Parameter $parameter) : array
|
||||
{
|
||||
if ($parameter->typeWasSpecified()) {
|
||||
return [$parameter->getValue(), $parameter->getType()];
|
||||
}
|
||||
|
||||
$key = $parameter->getName();
|
||||
$originalValue = $parameter->getValue();
|
||||
$value = $originalValue;
|
||||
$rsm = $this->getResultSetMapping();
|
||||
|
||||
assert($rsm !== null);
|
||||
|
||||
if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) {
|
||||
$value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
|
||||
}
|
||||
|
||||
if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) {
|
||||
$value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
|
||||
}
|
||||
|
||||
$processedValue = $this->processParameterValue($value);
|
||||
|
||||
return [
|
||||
$processedValue,
|
||||
$originalValue === $processedValue
|
||||
? $parameter->getType()
|
||||
: ParameterTypeInferer::inferType($processedValue),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a cache driver to be used for caching queries.
|
||||
*
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Defines a Query Parameter.
|
||||
*
|
||||
@@ -49,6 +51,13 @@ class Parameter
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* Whether the parameter type was explicitly specified or not
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $typeSpecified;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -58,7 +67,8 @@ class Parameter
|
||||
*/
|
||||
public function __construct($name, $value, $type = null)
|
||||
{
|
||||
$this->name = trim($name, ':');
|
||||
$this->name = trim($name, ':');
|
||||
$this->typeSpecified = $type !== null;
|
||||
|
||||
$this->setValue($value, $type);
|
||||
}
|
||||
@@ -104,4 +114,9 @@ class Parameter
|
||||
$this->value = $value;
|
||||
$this->type = $type ?: ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
|
||||
public function typeWasSpecified() : bool
|
||||
{
|
||||
return $this->typeSpecified;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\AST\Functions;
|
||||
use function in_array;
|
||||
use function strpos;
|
||||
|
||||
/**
|
||||
@@ -469,7 +470,7 @@ class Parser
|
||||
public function semanticalError($message = '', $token = null)
|
||||
{
|
||||
if ($token === null) {
|
||||
$token = $this->lexer->lookahead;
|
||||
$token = $this->lexer->lookahead ?? ['position' => null];
|
||||
}
|
||||
|
||||
// Minimum exposed chars ahead of token
|
||||
@@ -536,7 +537,7 @@ class Parser
|
||||
*/
|
||||
private function isMathOperator($token)
|
||||
{
|
||||
return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
|
||||
return $token !== null && in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,7 +552,7 @@ class Parser
|
||||
|
||||
$this->lexer->resetPeek();
|
||||
|
||||
return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
|
||||
return $lookaheadType >= Lexer::T_IDENTIFIER && $peek !== null && $peek['type'] === Lexer::T_OPEN_PARENTHESIS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -842,7 +843,7 @@ class Parser
|
||||
|
||||
$this->lexer->moveNext();
|
||||
|
||||
switch ($this->lexer->lookahead['type']) {
|
||||
switch ($this->lexer->lookahead['type'] ?? null) {
|
||||
case Lexer::T_SELECT:
|
||||
$statement = $this->SelectStatement();
|
||||
break;
|
||||
@@ -1464,7 +1465,7 @@ class Parser
|
||||
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
|
||||
$glimpse = $this->lexer->glimpse();
|
||||
|
||||
if ($glimpse['type'] === Lexer::T_DOT) {
|
||||
if ($glimpse !== null && $glimpse['type'] === Lexer::T_DOT) {
|
||||
return $this->SingleValuedPathExpression();
|
||||
}
|
||||
|
||||
@@ -1508,7 +1509,7 @@ class Parser
|
||||
$expr = $this->SimpleArithmeticExpression();
|
||||
break;
|
||||
|
||||
case ($glimpse['type'] === Lexer::T_DOT):
|
||||
case $glimpse !== null && $glimpse['type'] === Lexer::T_DOT:
|
||||
$expr = $this->SingleValuedPathExpression();
|
||||
break;
|
||||
|
||||
@@ -2479,9 +2480,11 @@ class Parser
|
||||
// Peek beyond the matching closing parenthesis ')'
|
||||
$peek = $this->peekBeyondClosingParenthesis();
|
||||
|
||||
if (in_array($peek['value'], ["=", "<", "<=", "<>", ">", ">=", "!="]) ||
|
||||
if ($peek !== null && (
|
||||
in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!=']) ||
|
||||
in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
|
||||
$this->isMathOperator($peek)) {
|
||||
$this->isMathOperator($peek)
|
||||
)) {
|
||||
$condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
|
||||
|
||||
return $condPrimary;
|
||||
@@ -2833,11 +2836,11 @@ class Parser
|
||||
case Lexer::T_IDENTIFIER:
|
||||
$peek = $this->lexer->glimpse();
|
||||
|
||||
if ($peek['value'] == '(') {
|
||||
if ($peek !== null && $peek['value'] === '(') {
|
||||
return $this->FunctionDeclaration();
|
||||
}
|
||||
|
||||
if ($peek['value'] == '.') {
|
||||
if ($peek !== null && $peek['value'] === '.') {
|
||||
return $this->SingleValuedPathExpression();
|
||||
}
|
||||
|
||||
@@ -2853,7 +2856,7 @@ class Parser
|
||||
default:
|
||||
$peek = $this->lexer->glimpse();
|
||||
|
||||
if ($peek['value'] == '(') {
|
||||
if ($peek !== null && $peek['value'] === '(') {
|
||||
return $this->FunctionDeclaration();
|
||||
}
|
||||
|
||||
@@ -3192,7 +3195,7 @@ class Parser
|
||||
|
||||
$escapeChar = null;
|
||||
|
||||
if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
|
||||
if ($this->lexer->lookahead !== null && $this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
|
||||
$this->match(Lexer::T_ESCAPE);
|
||||
$this->match(Lexer::T_STRING);
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ class QueryExpressionVisitor extends ExpressionVisitor
|
||||
$this->parameters[] = $parameter;
|
||||
|
||||
return $this->expr->like($field, $placeholder);
|
||||
case Comparison::MEMBER_OF:
|
||||
return $this->expr->isMemberOf($comparison->getField(), $comparison->getValue()->getValue());
|
||||
case Comparison::STARTS_WITH:
|
||||
$parameter->setValue($parameter->getValue() . '%', $parameter->getType());
|
||||
$this->parameters[] = $parameter;
|
||||
|
||||
@@ -584,4 +584,3 @@ class ResultSetMapping
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,11 +48,12 @@ class YamlExporter extends AbstractExporter
|
||||
} else {
|
||||
$array['type'] = 'entity';
|
||||
}
|
||||
$metadataTable = $metadata->table ?? ['name' => null];
|
||||
|
||||
$array['table'] = $metadata->table['name'];
|
||||
$array['table'] = $metadataTable['name'];
|
||||
|
||||
if (isset($metadata->table['schema'])) {
|
||||
$array['schema'] = $metadata->table['schema'];
|
||||
if (isset($metadataTable['schema'])) {
|
||||
$array['schema'] = $metadataTable['schema'];
|
||||
}
|
||||
|
||||
$inheritanceType = $metadata->inheritanceType;
|
||||
@@ -73,20 +74,20 @@ class YamlExporter extends AbstractExporter
|
||||
$array['changeTrackingPolicy'] = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy);
|
||||
}
|
||||
|
||||
if (isset($metadata->table['indexes'])) {
|
||||
$array['indexes'] = $metadata->table['indexes'];
|
||||
if (isset($metadataTable['indexes'])) {
|
||||
$array['indexes'] = $metadataTable['indexes'];
|
||||
}
|
||||
|
||||
if ($metadata->customRepositoryClassName) {
|
||||
$array['repositoryClass'] = $metadata->customRepositoryClassName;
|
||||
}
|
||||
|
||||
if (isset($metadata->table['uniqueConstraints'])) {
|
||||
$array['uniqueConstraints'] = $metadata->table['uniqueConstraints'];
|
||||
if (isset($metadataTable['uniqueConstraints'])) {
|
||||
$array['uniqueConstraints'] = $metadataTable['uniqueConstraints'];
|
||||
}
|
||||
|
||||
if (isset($metadata->table['options'])) {
|
||||
$array['options'] = $metadata->table['options'];
|
||||
if (isset($metadataTable['options'])) {
|
||||
$array['options'] = $metadataTable['options'];
|
||||
}
|
||||
|
||||
$fieldMappings = $metadata->fieldMappings;
|
||||
|
||||
@@ -19,11 +19,12 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Pagination;
|
||||
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use function array_map;
|
||||
|
||||
/**
|
||||
* The paginator can handle various complex scenarios with DQL.
|
||||
@@ -150,14 +151,16 @@ class Paginator implements \Countable, \IteratorAggregate
|
||||
|
||||
$subQuery->setFirstResult($offset)->setMaxResults($length);
|
||||
|
||||
$ids = array_map('current', $subQuery->getScalarResult());
|
||||
$foundIdRows = $subQuery->getScalarResult();
|
||||
|
||||
$whereInQuery = $this->cloneQuery($this->query);
|
||||
// don't do this for an empty id array
|
||||
if (count($ids) === 0) {
|
||||
if ($foundIdRows === []) {
|
||||
return new \ArrayIterator([]);
|
||||
}
|
||||
|
||||
$whereInQuery = $this->cloneQuery($this->query);
|
||||
$ids = array_map('current', $foundIdRows);
|
||||
|
||||
$this->appendTreeWalker($whereInQuery, WhereInWalker::class);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
|
||||
$whereInQuery->setFirstResult(null)->setMaxResults(null);
|
||||
|
||||
@@ -19,19 +19,24 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Pagination;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\AST\ArithmeticExpression;
|
||||
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
|
||||
use Doctrine\ORM\Query\TreeWalkerAdapter;
|
||||
use Doctrine\ORM\Query\AST\SelectStatement;
|
||||
use Doctrine\ORM\Query\AST\PathExpression;
|
||||
use Doctrine\ORM\Query\AST\InExpression;
|
||||
use Doctrine\ORM\Query\AST\NullComparisonExpression;
|
||||
use Doctrine\ORM\Query\AST\InputParameter;
|
||||
use Doctrine\ORM\Query\AST\ConditionalPrimary;
|
||||
use Doctrine\ORM\Query\AST\ConditionalTerm;
|
||||
use Doctrine\ORM\Query\AST\ConditionalExpression;
|
||||
use Doctrine\ORM\Query\AST\ConditionalFactor;
|
||||
use Doctrine\ORM\Query\AST\ConditionalPrimary;
|
||||
use Doctrine\ORM\Query\AST\ConditionalTerm;
|
||||
use Doctrine\ORM\Query\AST\InExpression;
|
||||
use Doctrine\ORM\Query\AST\InputParameter;
|
||||
use Doctrine\ORM\Query\AST\NullComparisonExpression;
|
||||
use Doctrine\ORM\Query\AST\PathExpression;
|
||||
use Doctrine\ORM\Query\AST\SelectStatement;
|
||||
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
|
||||
use Doctrine\ORM\Query\AST\WhereClause;
|
||||
use Doctrine\ORM\Query\TreeWalkerAdapter;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use function array_map;
|
||||
use function assert;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Replaces the whereClause of the AST with a WHERE id IN (:foo_1, :foo_2) equivalent.
|
||||
@@ -83,6 +88,7 @@ class WhereInWalker extends TreeWalkerAdapter
|
||||
|
||||
$fromRoot = reset($from);
|
||||
$rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
|
||||
/** @var ClassMetadata $rootClass */
|
||||
$rootClass = $queryComponents[$rootAlias]['metadata'];
|
||||
$identifierFieldName = $rootClass->getSingleIdentifierFieldName();
|
||||
|
||||
@@ -104,6 +110,14 @@ class WhereInWalker extends TreeWalkerAdapter
|
||||
$expression = new InExpression($arithmeticExpression);
|
||||
$expression->literals[] = new InputParameter(":" . self::PAGINATOR_ID_ALIAS);
|
||||
|
||||
$this->convertWhereInIdentifiersToDatabaseValue(
|
||||
PersisterHelper::getTypeOfField(
|
||||
$identifierFieldName,
|
||||
$rootClass,
|
||||
$this->_getQuery()
|
||||
->getEntityManager()
|
||||
)[0]
|
||||
);
|
||||
} else {
|
||||
$expression = new NullComparisonExpression($pathExpression);
|
||||
$expression->not = false;
|
||||
@@ -147,4 +161,24 @@ class WhereInWalker extends TreeWalkerAdapter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function convertWhereInIdentifiersToDatabaseValue(string $type) : void
|
||||
{
|
||||
$query = $this->_getQuery();
|
||||
$identifiersParameter = $query->getParameter(self::PAGINATOR_ID_ALIAS);
|
||||
|
||||
assert($identifiersParameter !== null);
|
||||
|
||||
$identifiers = $identifiersParameter->getValue();
|
||||
|
||||
assert(is_array($identifiers));
|
||||
|
||||
$connection = $this->_getQuery()
|
||||
->getEntityManager()
|
||||
->getConnection();
|
||||
|
||||
$query->setParameter(self::PAGINATOR_ID_ALIAS, array_map(static function ($id) use ($connection, $type) {
|
||||
return $connection->convertToDatabaseValue($id, $type);
|
||||
}, $identifiers));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
/**
|
||||
* Hint used to collect all primary keys of associated entities during hydration
|
||||
* and execute it in a dedicated query afterwards
|
||||
* @see https://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html?highlight=eager#temporarily-change-fetch-mode-in-dql
|
||||
* @see https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql
|
||||
*/
|
||||
const HINT_DEFEREAGERLOAD = 'deferEagerLoad';
|
||||
|
||||
@@ -356,6 +356,8 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$this->dispatchOnFlushEvent();
|
||||
$this->dispatchPostFlushEvent();
|
||||
|
||||
$this->postCommitCleanup($entity);
|
||||
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class PersisterHelper
|
||||
* @param ClassMetadata $class
|
||||
* @param EntityManagerInterface $em
|
||||
*
|
||||
* @return array
|
||||
* @return array<int, string>
|
||||
*
|
||||
* @throws QueryException
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,7 @@ class Version
|
||||
/**
|
||||
* Current Doctrine Version
|
||||
*/
|
||||
const VERSION = '2.6.3';
|
||||
const VERSION = '2.6.4-DEV';
|
||||
|
||||
/**
|
||||
* Compares a Doctrine version with the current one.
|
||||
|
||||
144
phpcs.xml.dist
144
phpcs.xml.dist
@@ -4,10 +4,10 @@
|
||||
<arg name="extensions" value="php"/>
|
||||
<arg name="parallel" value="80"/>
|
||||
<arg name="cache" value=".phpcs-cache"/>
|
||||
<arg name="colors" />
|
||||
<arg name="colors"/>
|
||||
|
||||
<!-- Ignore warnings and show progress of the run -->
|
||||
<arg value="np"/>
|
||||
<!-- Ignore warnings, show progress of the run and show sniff names -->
|
||||
<arg value="nps"/>
|
||||
|
||||
<file>lib</file>
|
||||
<file>tests</file>
|
||||
@@ -15,10 +15,144 @@
|
||||
|
||||
<exclude-pattern>*/tests/Doctrine/Tests/Proxies/__CG__/*</exclude-pattern>
|
||||
|
||||
<rule ref="Doctrine" />
|
||||
<rule ref="Doctrine">
|
||||
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint"/>
|
||||
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint"/>
|
||||
<exclude name="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException"/>
|
||||
<exclude name="SlevomatCodingStandard.ControlStructures.EarlyExit"/>
|
||||
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"/>
|
||||
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming"/>
|
||||
</rule>
|
||||
|
||||
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
</rule>
|
||||
</ruleset>
|
||||
|
||||
<rule ref="Squiz.Classes.ClassFileName.NoMatch">
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase">
|
||||
<exclude-pattern>lib/Doctrine/ORM/Events.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Tools/ToolEvents.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversablePropertyTypeHintSpecification">
|
||||
<exclude-pattern>lib/Doctrine/ORM/Annotation/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedNotEqualOperator">
|
||||
<exclude-pattern>lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
|
||||
<exclude-pattern>lib/Doctrine/ORM/Query/Parser.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName">
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/AssociationOverride.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/AssociationOverrides.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/AttributeOverride.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/AttributeOverrides.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Cache.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/ChangeTrackingPolicy.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Column.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/ColumnResult.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/CustomIdGenerator.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/DiscriminatorMap.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Embeddable.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Embedded.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Entity.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/EntityListeners.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/EntityResult.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/FieldResult.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/GeneratedValue.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/HasLifecycleCallbacks.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Id.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Index.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/InheritanceType.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/JoinColumn.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/JoinColumns.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/JoinTable.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/ManyToMany.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/ManyToOne.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/MappedSuperclass.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/NamedNativeQueries.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/NamedNativeQuery.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/NamedQueries.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/NamedQuery.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/OneToMany.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/OneToOne.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/OrderBy.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/PostLoad.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/PostPersist.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/PostRemove.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/PostUpdate.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/PreFlush.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/PrePersist.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/PreRemove.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/PreUpdate.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/SequenceGenerator.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Table.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/UniqueConstraint.php</exclude-pattern>
|
||||
<exclude-pattern>lib/Doctrine/ORM/Mapping/Version.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.Commenting.EmptyComment">
|
||||
<exclude-pattern>lib/Doctrine/ORM/Cache/DefaultQueryCache.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming">
|
||||
<exclude-pattern>lib/Doctrine/ORM/EntityManagerInterface.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingPropertyTypeHint">
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedProperty">
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversableParameterTypeHintSpecification">
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.Classes.UnusedPrivateElements.WriteOnlyProperty">
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversablePropertyTypeHintSpecification">
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversableReturnTypeHintSpecification">
|
||||
<exclude-pattern>*/tests/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- intentionally without namespace -->
|
||||
<rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
|
||||
<exclude-pattern>tests/Doctrine/Tests/Models/Global/GlobalNamespaceModel.php</exclude-pattern>
|
||||
<exclude-pattern>tests/Doctrine/Tests/Models/DDC3231/DDC3231User1NoNamespace.php</exclude-pattern>
|
||||
<exclude-pattern>tests/Doctrine/Tests/Models/DDC3231/DDC3231User2NoNamespace.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- file with multiple namespaces confuses the sniff -->
|
||||
<rule ref="PSR2.Namespaces.UseDeclaration.UseAfterNamespace">
|
||||
<exclude-pattern>tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2084Test.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- file with multiple namespaces confuses the sniff -->
|
||||
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses.IncorrectlyOrderedUses">
|
||||
<exclude-pattern>tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2084Test.php</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- intentionally empty blocks -->
|
||||
<rule ref="Generic.CodeAnalysis.EmptyStatement.DetectedForeach">
|
||||
<exclude-pattern>tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php</exclude-pattern>
|
||||
<exclude-pattern>tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php</exclude-pattern>
|
||||
</rule>
|
||||
</ruleset>
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
failOnRisky="true"
|
||||
bootstrap="./tests/Doctrine/Tests/TestInit.php"
|
||||
>
|
||||
|
||||
<php>
|
||||
<env name="COLUMNS" value="120"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Doctrine ORM Test Suite">
|
||||
<directory>./tests/Doctrine/Tests/ORM</directory>
|
||||
@@ -54,6 +49,8 @@
|
||||
<var name="tmpdb_password" value="" />
|
||||
<var name="tmpdb_name" value="doctrine_tests_tmp" />
|
||||
<var name="tmpdb_port" value="3306"/>
|
||||
|
||||
<env name="COLUMNS" value="120"/>
|
||||
</php>
|
||||
|
||||
</phpunit>
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance;
|
||||
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Cache\ArrayStatement;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\PDOSqlite\Driver;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use function array_map;
|
||||
use function realpath;
|
||||
|
||||
final class EntityManagerFactory
|
||||
{
|
||||
@@ -20,7 +28,7 @@ final class EntityManagerFactory
|
||||
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
|
||||
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver([
|
||||
realpath(__DIR__ . '/Models/Cache'),
|
||||
realpath(__DIR__ . '/Models/GeoNames')
|
||||
realpath(__DIR__ . '/Models/GeoNames'),
|
||||
], true));
|
||||
|
||||
$entityManager = EntityManager::create(
|
||||
@@ -36,4 +44,30 @@ final class EntityManagerFactory
|
||||
|
||||
return $entityManager;
|
||||
}
|
||||
|
||||
public static function makeEntityManagerWithNoResultsConnection() : EntityManagerInterface
|
||||
{
|
||||
$config = new Configuration();
|
||||
|
||||
$config->setProxyDir(__DIR__ . '/../Tests/Proxies');
|
||||
$config->setProxyNamespace('Doctrine\Tests\Proxies');
|
||||
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
|
||||
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver([
|
||||
realpath(__DIR__ . '/Models/Cache'),
|
||||
realpath(__DIR__ . '/Models/Generic'),
|
||||
realpath(__DIR__ . '/Models/GeoNames'),
|
||||
], true));
|
||||
|
||||
// A connection that doesn't really do anything
|
||||
$connection = new class ([], new Driver(), null, new EventManager()) extends Connection
|
||||
{
|
||||
/** {@inheritdoc} */
|
||||
public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
|
||||
{
|
||||
return new ArrayStatement([]);
|
||||
}
|
||||
};
|
||||
|
||||
return EntityManager::create($connection, $config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance\Query;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\DBAL\Types\DateTimeType;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
|
||||
use function range;
|
||||
|
||||
/**
|
||||
* @BeforeMethods({"init"})
|
||||
*/
|
||||
final class QueryBoundParameterProcessingBench
|
||||
{
|
||||
/** @var Query */
|
||||
private $parsedQueryWithInferredParameterType;
|
||||
|
||||
/** @var Query */
|
||||
private $parsedQueryWithDeclaredParameterType;
|
||||
|
||||
public function init() : void
|
||||
{
|
||||
$entityManager = EntityManagerFactory::makeEntityManagerWithNoResultsConnection();
|
||||
|
||||
// Note: binding a lot of parameters because DQL operations are noisy due to hydrators and other components
|
||||
// kicking in, so we make the parameter operations more noticeable.
|
||||
$dql = <<<'DQL'
|
||||
SELECT e
|
||||
FROM Doctrine\Tests\Models\Generic\DateTimeModel e
|
||||
WHERE
|
||||
e.datetime = :parameter1
|
||||
OR
|
||||
e.datetime = :parameter2
|
||||
OR
|
||||
e.datetime = :parameter3
|
||||
OR
|
||||
e.datetime = :parameter4
|
||||
OR
|
||||
e.datetime = :parameter5
|
||||
OR
|
||||
e.datetime = :parameter6
|
||||
OR
|
||||
e.datetime = :parameter7
|
||||
OR
|
||||
e.datetime = :parameter8
|
||||
OR
|
||||
e.datetime = :parameter9
|
||||
OR
|
||||
e.datetime = :parameter10
|
||||
DQL;
|
||||
|
||||
$this->parsedQueryWithInferredParameterType = $entityManager->createQuery($dql);
|
||||
$this->parsedQueryWithDeclaredParameterType = $entityManager->createQuery($dql);
|
||||
|
||||
foreach (range(1, 10) as $index) {
|
||||
$this->parsedQueryWithInferredParameterType->setParameter('parameter' . $index, new DateTime());
|
||||
$this->parsedQueryWithDeclaredParameterType->setParameter('parameter' . $index, new DateTime(), DateTimeType::DATETIME);
|
||||
}
|
||||
|
||||
// Force parsing upfront - we don't benchmark that bit in this scenario
|
||||
$this->parsedQueryWithInferredParameterType->getSQL();
|
||||
$this->parsedQueryWithDeclaredParameterType->getSQL();
|
||||
}
|
||||
|
||||
public function benchExecuteParsedQueryWithInferredParameterType() : void
|
||||
{
|
||||
$this->parsedQueryWithInferredParameterType->execute();
|
||||
}
|
||||
|
||||
public function benchExecuteParsedQueryWithDeclaredParameterType() : void
|
||||
{
|
||||
$this->parsedQueryWithDeclaredParameterType->execute();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\ValueConversionType;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\ValueConversionType;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="vct_owning_manytoone_foreignkey")
|
||||
*/
|
||||
class OwningManyToOneIdForeignKeyEntity
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @ManyToOne(targetEntity=AuxiliaryEntity::class, inversedBy="associatedEntities")
|
||||
* @JoinColumn(name="associated_id", referencedColumnName="id4")
|
||||
*/
|
||||
public $associatedEntity;
|
||||
}
|
||||
@@ -354,6 +354,34 @@ class DefaultQueryCacheTest extends OrmTestCase
|
||||
$this->assertEquals('Bar', $result[1]->getName());
|
||||
}
|
||||
|
||||
public function testGetWithAssociationCacheMiss() : void
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$key = new QueryCacheKey('query.key1', 0);
|
||||
$entry = new QueryCacheEntry(
|
||||
[
|
||||
['identifier' => ['id' => 1]],
|
||||
['identifier' => ['id' => 2]],
|
||||
]
|
||||
);
|
||||
|
||||
$this->region->addReturn('get', $entry);
|
||||
|
||||
$this->region->addReturn(
|
||||
'getMultiple',
|
||||
[
|
||||
new EntityCacheEntry(Country::class, ['id' => 1, 'name' => 'Foo']),
|
||||
false,
|
||||
]
|
||||
);
|
||||
|
||||
$rsm->addRootEntityFromClassMetadata(Country::class, 'c');
|
||||
|
||||
$result = $this->queryCache->get($key, $rsm);
|
||||
|
||||
self::assertNull($result);
|
||||
}
|
||||
|
||||
public function testCancelPutResultIfEntityPutFails()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
@@ -95,6 +95,32 @@ class DefaultRegionTest extends AbstractRegionTest
|
||||
$this->assertEquals($value1, $actual[0]);
|
||||
$this->assertEquals($value2, $actual[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @group GH7266
|
||||
*/
|
||||
public function corruptedDataDoesNotLeakIntoApplicationWhenGettingSingleEntry() : void
|
||||
{
|
||||
$key1 = new CacheKeyMock('key.1');
|
||||
$this->cache->save($this->region->getName() . '_' . $key1->hash, 'a-very-invalid-value');
|
||||
|
||||
self::assertTrue($this->region->contains($key1));
|
||||
self::assertNull($this->region->get($key1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @group GH7266
|
||||
*/
|
||||
public function corruptedDataDoesNotLeakIntoApplicationWhenGettingMultipleEntries() : void
|
||||
{
|
||||
$key1 = new CacheKeyMock('key.1');
|
||||
$this->cache->save($this->region->getName() . '_' . $key1->hash, 'a-very-invalid-value');
|
||||
|
||||
self::assertTrue($this->region->contains($key1));
|
||||
self::assertNull($this->region->getMultiple(new CollectionCacheEntry([$key1])));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,4 +39,17 @@ class MultiGetRegionTest extends AbstractRegionTest
|
||||
$this->assertEquals($value1, $actual[0]);
|
||||
$this->assertEquals($value2, $actual[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @group GH7266
|
||||
*/
|
||||
public function corruptedDataDoesNotLeakIntoApplication() : void
|
||||
{
|
||||
$key1 = new CacheKeyMock('key.1');
|
||||
$this->cache->save($this->region->getName() . '_' . $key1->hash, 'a-very-invalid-value');
|
||||
|
||||
self::assertTrue($this->region->contains($key1));
|
||||
self::assertNull($this->region->getMultiple(new CollectionCacheEntry([$key1])));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,52 +35,6 @@ abstract class AbstractEntityPersisterTest extends OrmTestCase
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $regionMockMethods = [
|
||||
'getName',
|
||||
'contains',
|
||||
'get',
|
||||
'getMultiple',
|
||||
'put',
|
||||
'evict',
|
||||
'evictAll'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $entityPersisterMockMethods = [
|
||||
'getClassMetadata',
|
||||
'getResultSetMapping',
|
||||
'getInserts',
|
||||
'getInsertSQL',
|
||||
'getSelectSQL',
|
||||
'getCountSQL',
|
||||
'expandParameters',
|
||||
'expandCriteriaParameters',
|
||||
'getSelectConditionStatementSQL',
|
||||
'addInsert',
|
||||
'executeInserts',
|
||||
'update',
|
||||
'delete',
|
||||
'getOwningTable',
|
||||
'load',
|
||||
'loadById',
|
||||
'loadOneToOneEntity',
|
||||
'count',
|
||||
'refresh',
|
||||
'loadCriteria',
|
||||
'loadAll',
|
||||
'getManyToManyCollection',
|
||||
'loadManyToManyCollection',
|
||||
'loadOneToManyCollection',
|
||||
'lock',
|
||||
'getOneToManyCollection',
|
||||
'exists'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister
|
||||
@@ -97,11 +51,9 @@ abstract class AbstractEntityPersisterTest extends OrmTestCase
|
||||
$this->enableSecondLevelCache();
|
||||
parent::setUp();
|
||||
|
||||
$this->em = $this->_getTestEntityManager();
|
||||
$this->region = $this->createRegion();
|
||||
$this->entityPersister = $this->getMockBuilder(EntityPersister::class)
|
||||
->setMethods($this->entityPersisterMockMethods)
|
||||
->getMock();
|
||||
$this->em = $this->_getTestEntityManager();
|
||||
$this->region = $this->createRegion();
|
||||
$this->entityPersister = $this->createMock(EntityPersister::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,9 +61,7 @@ abstract class AbstractEntityPersisterTest extends OrmTestCase
|
||||
*/
|
||||
protected function createRegion()
|
||||
{
|
||||
return $this->getMockBuilder(Region::class)
|
||||
->setMethods($this->regionMockMethods)
|
||||
->getMock();
|
||||
return $this->createMock(Region::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,18 +17,6 @@ use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
|
||||
*/
|
||||
class ReadWriteCachedEntityPersisterTest extends AbstractEntityPersisterTest
|
||||
{
|
||||
protected $regionMockMethods = [
|
||||
'getName',
|
||||
'contains',
|
||||
'get',
|
||||
'getMultiple',
|
||||
'put',
|
||||
'evict',
|
||||
'evictAll',
|
||||
'lock',
|
||||
'unlock',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -42,9 +30,7 @@ class ReadWriteCachedEntityPersisterTest extends AbstractEntityPersisterTest
|
||||
*/
|
||||
protected function createRegion()
|
||||
{
|
||||
return $this->getMockBuilder(ConcurrentRegion::class)
|
||||
->setConstructorArgs($this->regionMockMethods)
|
||||
->getMock();
|
||||
return $this->createMock(ConcurrentRegion::class);
|
||||
}
|
||||
|
||||
public function testDeleteShouldLockItem()
|
||||
|
||||
114
tests/Doctrine/Tests/ORM/Functional/GH5988Test.php
Normal file
114
tests/Doctrine/Tests/ORM/Functional/GH5988Test.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type as DBALType;
|
||||
use Doctrine\Tests\DbalTypes\CustomIdObject;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use function str_replace;
|
||||
|
||||
/**
|
||||
* Functional tests for the Class Table Inheritance mapping strategy with custom id object types.
|
||||
*
|
||||
* @group GH5988
|
||||
*/
|
||||
final class GH5988Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (! DBALType::hasType(GH5988CustomIdObjectHashType::class)) {
|
||||
DBALType::addType(GH5988CustomIdObjectHashType::class, GH5988CustomIdObjectHashType::class);
|
||||
}
|
||||
|
||||
$this->setUpEntitySchema([GH5988CustomIdObjectTypeParent::class, GH5988CustomIdObjectTypeChild::class]);
|
||||
}
|
||||
|
||||
public function testDelete()
|
||||
{
|
||||
$object = new GH5988CustomIdObjectTypeChild(new CustomIdObject('foo'), 'Test');
|
||||
|
||||
$this->_em->persist($object);
|
||||
$this->_em->flush();
|
||||
|
||||
$id = $object->id;
|
||||
|
||||
$object2 = $this->_em->find(GH5988CustomIdObjectTypeChild::class, $id);
|
||||
|
||||
$this->_em->remove($object2);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertNull($this->_em->find(GH5988CustomIdObjectTypeChild::class, $id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class GH5988CustomIdObjectHashType extends DBALType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
return $value->id . '_test';
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
return new CustomIdObject(str_replace('_test', '', $value));
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return self::class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table
|
||||
* @InheritanceType("JOINED")
|
||||
* @DiscriminatorColumn(name="type", type="string")
|
||||
* @DiscriminatorMap({"child" = GH5988CustomIdObjectTypeChild::class})
|
||||
*/
|
||||
abstract class GH5988CustomIdObjectTypeParent
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="Doctrine\Tests\ORM\Functional\GH5988CustomIdObjectHashType")
|
||||
* @var CustomIdObject
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table
|
||||
*/
|
||||
class GH5988CustomIdObjectTypeChild extends GH5988CustomIdObjectTypeParent
|
||||
{
|
||||
/** @var string */
|
||||
public $name;
|
||||
|
||||
public function __construct(CustomIdObject $id, string $name)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,7 @@ DQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/doctrine/doctrine2/issues/6568
|
||||
* https://github.com/doctrine/orm/issues/6568
|
||||
*/
|
||||
public function testPostLoadIsInvokedOnFetchJoinedEntities()
|
||||
{
|
||||
|
||||
48
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7629Test.php
Normal file
48
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7629Test.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH7629Test extends OrmFunctionalTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
GH7629Entity::class,
|
||||
]);
|
||||
|
||||
$this->_em->persist(new GH7629Entity());
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
public function testClearScheduledForSynchronizationWhenCommitEmpty(): void
|
||||
{
|
||||
$entity = $this->_em->find(GH7629Entity::class, 1);
|
||||
|
||||
$this->_em->persist($entity);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForDirtyCheck($entity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
|
||||
*/
|
||||
class GH7629Entity
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
172
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7735Test.php
Normal file
172
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7735Test.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use function assert;
|
||||
|
||||
final class GH7735Test extends OrmFunctionalTestCase
|
||||
{
|
||||
public function setUp() : void
|
||||
{
|
||||
$this->enableSecondLevelCache();
|
||||
parent::setUp();
|
||||
|
||||
$this->_schemaTool->createSchema(
|
||||
[
|
||||
$this->_em->getClassMetadata(GH7735Car::class),
|
||||
$this->_em->getClassMetadata(GH7735Power::class),
|
||||
$this->_em->getClassMetadata(GH7735Engine::class),
|
||||
]
|
||||
);
|
||||
|
||||
$this->_em->persist(new GH7735Car(1, new GH7735Engine(1, 'turbo', new GH7735Power(1))));
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @group GH7735
|
||||
*/
|
||||
public function findByReturnsCachedEntity() : void
|
||||
{
|
||||
$this->_em->getCache()->evictEntityRegion(GH7735Power::class);
|
||||
|
||||
$car = $this->_em->find(GH7735Car::class, 1);
|
||||
assert($car instanceof GH7735Car);
|
||||
|
||||
self::assertSame('turbo', $car->getEngine()->getModel());
|
||||
self::assertSame(1, $car->getEngine()->getPower()->getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity @Cache(usage="READ_ONLY")
|
||||
*/
|
||||
class GH7735Car
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity=GH7735Engine::class, cascade={"all"})
|
||||
* @JoinColumn(nullable=false)
|
||||
* @Cache("READ_ONLY")
|
||||
* @var GH7735Engine
|
||||
*/
|
||||
private $engine;
|
||||
|
||||
public function __construct(int $id, GH7735Engine $engine)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->engine = $engine;
|
||||
}
|
||||
|
||||
public function getId() : int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEngine() : GH7735Engine
|
||||
{
|
||||
return $this->engine;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache(usage="READ_ONLY")
|
||||
*/
|
||||
class GH7735Engine
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @OneToOne(targetEntity=GH7735Power::class, mappedBy="engine", cascade={"all"})
|
||||
* @Cache("READ_ONLY")
|
||||
* @var GH7735Power
|
||||
*/
|
||||
private $power;
|
||||
|
||||
/**
|
||||
* @Column
|
||||
* @var string
|
||||
*/
|
||||
private $model;
|
||||
|
||||
public function __construct(int $id, string $model, GH7735Power $power)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->model = $model;
|
||||
$this->power = $power;
|
||||
|
||||
$power->setEngine($this);
|
||||
}
|
||||
|
||||
public function getId() : int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getPower() : GH7735Power
|
||||
{
|
||||
return $this->power;
|
||||
}
|
||||
|
||||
public function getModel() : string
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache(usage="READ_ONLY")
|
||||
*/
|
||||
class GH7735Power
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @OneToOne(targetEntity=GH7735Engine::class, inversedBy="power")
|
||||
* @Cache("READ_ONLY")
|
||||
* @var GH7735Engine
|
||||
*/
|
||||
private $engine;
|
||||
|
||||
public function __construct(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId() : int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setEngine(GH7735Engine $engine) : void
|
||||
{
|
||||
$this->engine = $engine;
|
||||
}
|
||||
|
||||
public function getEngine() : GH7735Engine
|
||||
{
|
||||
return $this->engine;
|
||||
}
|
||||
}
|
||||
100
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7737Test.php
Normal file
100
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7737Test.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
/**
|
||||
* @group GH7737
|
||||
*/
|
||||
class GH7737Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([GH7737Group::class, GH7737Person::class]);
|
||||
|
||||
$group1 = new GH7737Group(1, 'Test 1');
|
||||
$person = new GH7737Person(1);
|
||||
$person->groups->add($group1);
|
||||
|
||||
$this->_em->persist($person);
|
||||
$this->_em->persist($group1);
|
||||
$this->_em->persist(new GH7737Group(2, 'Test 2'));
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function memberOfCriteriaShouldBeCompatibleWithQueryBuilder() : void
|
||||
{
|
||||
$query = $this->_em->createQueryBuilder()
|
||||
->select('person')
|
||||
->from(GH7737Person::class, 'person')
|
||||
->addCriteria(Criteria::create()->where(Criteria::expr()->memberOf(':group', 'person.groups')))
|
||||
->getQuery();
|
||||
|
||||
$group1 = $this->_em->find(GH7737Group::class, 1);
|
||||
$matching = $query->setParameter('group', $group1)->getOneOrNullResult();
|
||||
|
||||
self::assertInstanceOf(GH7737Person::class, $matching);
|
||||
self::assertSame(1, $matching->id);
|
||||
|
||||
$group2 = $this->_em->find(GH7737Group::class, 2);
|
||||
$notMatching = $query->setParameter('group', $group2)->getOneOrNullResult();
|
||||
|
||||
self::assertNull($notMatching);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class GH7737Group
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/** @Column */
|
||||
public $name;
|
||||
|
||||
public function __construct(int $id, string $name)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class GH7737Person
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity=GH7737Group::class)
|
||||
* @JoinTable(inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id", unique=true)})
|
||||
*/
|
||||
public $groups;
|
||||
|
||||
public function __construct(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->groups = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
91
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7761Test.php
Normal file
91
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7761Test.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
final class GH7761Test extends OrmFunctionalTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([
|
||||
GH7761Entity::class,
|
||||
GH7761ChildEntity::class,
|
||||
]);
|
||||
|
||||
$parent = new GH7761Entity();
|
||||
$child = new GH7761ChildEntity();
|
||||
$parent->children->add($child);
|
||||
|
||||
$this->_em->persist($parent);
|
||||
$this->_em->persist($child);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
public function testCollectionClearDoesNotClearIfNotPersisted() : void
|
||||
{
|
||||
/** @var GH7761Entity $entity */
|
||||
$entity = $this->_em->find(GH7761Entity::class, 1);
|
||||
$entity->children->clear();
|
||||
$this->_em->persist(new GH7761Entity());
|
||||
$this->_em->flush();
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$entity = $this->_em->find(GH7761Entity::class, 1);
|
||||
self::assertCount(1, $entity->children);
|
||||
|
||||
$this->_em->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
|
||||
*/
|
||||
class GH7761Entity
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Doctrine\Tests\ORM\Functional\Ticket\GH7761ChildEntity", cascade={"all"})
|
||||
* @JoinTable(name="gh7761_to_child",
|
||||
* joinColumns={@JoinColumn(name="entity_id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="child_id")}
|
||||
* )
|
||||
*/
|
||||
public $children;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
|
||||
*/
|
||||
class GH7761ChildEntity
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
110
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7767Test.php
Normal file
110
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7767Test.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* @group GH7767
|
||||
*/
|
||||
class GH7767Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpEntitySchema([GH7767ParentEntity::class, GH7767ChildEntity::class]);
|
||||
|
||||
$parent = new GH7767ParentEntity();
|
||||
$parent->addChild(200);
|
||||
$parent->addChild(100);
|
||||
$parent->addChild(300);
|
||||
|
||||
$this->_em->persist($parent);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
public function testMatchingRespectsCollectionOrdering() : void
|
||||
{
|
||||
$parent = $this->_em->find(GH7767ParentEntity::class, 1);
|
||||
assert($parent instanceof GH7767ParentEntity);
|
||||
|
||||
$children = $parent->getChildren()->matching(Criteria::create());
|
||||
|
||||
self::assertEquals(100, $children[0]->position);
|
||||
self::assertEquals(200, $children[1]->position);
|
||||
self::assertEquals(300, $children[2]->position);
|
||||
}
|
||||
|
||||
public function testMatchingOverrulesCollectionOrdering() : void
|
||||
{
|
||||
$parent = $this->_em->find(GH7767ParentEntity::class, 1);
|
||||
assert($parent instanceof GH7767ParentEntity);
|
||||
|
||||
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC']));
|
||||
|
||||
self::assertEquals(300, $children[0]->position);
|
||||
self::assertEquals(200, $children[1]->position);
|
||||
self::assertEquals(100, $children[2]->position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class GH7767ParentEntity
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity=GH7767ChildEntity::class, mappedBy="parent", fetch="EXTRA_LAZY", cascade={"persist"})
|
||||
* @OrderBy({"position" = "ASC"})
|
||||
*/
|
||||
private $children;
|
||||
|
||||
public function addChild(int $position) : void
|
||||
{
|
||||
$this->children[] = new GH7767ChildEntity($this, $position);
|
||||
}
|
||||
|
||||
public function getChildren() : PersistentCollection
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class GH7767ChildEntity
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/** @Column(type="integer") */
|
||||
public $position;
|
||||
|
||||
/** @ManyToOne(targetEntity=GH7767ParentEntity::class, inversedBy="children") */
|
||||
private $parent;
|
||||
|
||||
public function __construct(GH7767ParentEntity $parent, int $position)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->position = $position;
|
||||
}
|
||||
}
|
||||
159
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7820Test.php
Normal file
159
tests/Doctrine/Tests/ORM/Functional/Ticket/GH7820Test.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\StringType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use function array_map;
|
||||
use function is_string;
|
||||
use function iterator_to_array;
|
||||
|
||||
/**
|
||||
* @group GH7820
|
||||
*
|
||||
* When using a {@see \Doctrine\ORM\Tools\Pagination\Paginator} to iterate over a query
|
||||
* that has entities with a custom DBAL type used in the identifier, then `$id->__toString()`
|
||||
* is used implicitly by {@see \PDOStatement::bindValue()}, instead of being converted by the
|
||||
* expected {@see \Doctrine\DBAL\Types\Type::convertToDatabaseValue()}.
|
||||
*
|
||||
* In order to reproduce this, you must have identifiers implementing
|
||||
* `#__toString()` (to allow {@see \Doctrine\ORM\UnitOfWork} to hash them) and other accessors
|
||||
* that are used by the custom DBAL type during DB/PHP conversions.
|
||||
*
|
||||
* If `#__toString()` and the DBAL type conversions are asymmetric, then the paginator will fail
|
||||
* to find records.
|
||||
*
|
||||
* Tricky situation, but this very much affects `ramsey/uuid-doctrine` and anyone relying on (for
|
||||
* example) the {@see \Ramsey\Uuid\Doctrine\UuidBinaryType} type.
|
||||
*/
|
||||
class GH7820Test extends OrmFunctionalTestCase
|
||||
{
|
||||
private const SONG = [
|
||||
'What is this song all about?',
|
||||
'Can\'t figure any lyrics out',
|
||||
'How do the words to it go?',
|
||||
'I wish you\'d tell me, I don\'t know',
|
||||
'Don\'t know, don\'t know, don\'t know, I don\'t know!',
|
||||
'Don\'t know, don\'t know, don\'t know...',
|
||||
];
|
||||
|
||||
protected function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (! Type::hasType(GH7820LineTextType::class)) {
|
||||
Type::addType(GH7820LineTextType::class, GH7820LineTextType::class);
|
||||
}
|
||||
|
||||
$this->setUpEntitySchema([GH7820Line::class]);
|
||||
|
||||
foreach (self::SONG as $index => $line) {
|
||||
$this->_em->persist(new GH7820Line(GH7820LineText::fromText($line), $index));
|
||||
}
|
||||
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
public function testWillFindSongsInPaginator() : void
|
||||
{
|
||||
$query = $this->_em->getRepository(GH7820Line::class)
|
||||
->createQueryBuilder('l')
|
||||
->orderBy('l.lineNumber', Criteria::ASC);
|
||||
|
||||
self::assertSame(
|
||||
self::SONG,
|
||||
array_map(static function (GH7820Line $line) : string {
|
||||
return $line->toString();
|
||||
}, iterator_to_array(new Paginator($query)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class GH7820Line
|
||||
{
|
||||
/**
|
||||
* @var GH7820LineText
|
||||
* @Id()
|
||||
* @Column(type="Doctrine\Tests\ORM\Functional\Ticket\GH7820LineTextType")
|
||||
*/
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $lineNumber;
|
||||
|
||||
public function __construct(GH7820LineText $text, int $index)
|
||||
{
|
||||
$this->text = $text;
|
||||
$this->lineNumber = $index;
|
||||
}
|
||||
|
||||
public function toString() : string
|
||||
{
|
||||
return $this->text->getText();
|
||||
}
|
||||
}
|
||||
|
||||
final class GH7820LineText
|
||||
{
|
||||
/** @var string */
|
||||
private $text;
|
||||
|
||||
private function __construct(string $text)
|
||||
{
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
public static function fromText(string $text) : self
|
||||
{
|
||||
return new self($text);
|
||||
}
|
||||
|
||||
public function getText() : string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return 'Line: ' . $this->text;
|
||||
}
|
||||
}
|
||||
|
||||
final class GH7820LineTextType extends StringType
|
||||
{
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
$text = parent::convertToPHPValue($value, $platform);
|
||||
|
||||
if (! is_string($text)) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return GH7820LineText::fromText($text);
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if (! $value instanceof GH7820LineText) {
|
||||
return parent::convertToDatabaseValue($value, $platform);
|
||||
}
|
||||
|
||||
return parent::convertToDatabaseValue($value->getText(), $platform);
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function getName() : string
|
||||
{
|
||||
return self::class;
|
||||
}
|
||||
}
|
||||
@@ -269,6 +269,7 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase
|
||||
$this->assertEquals(CmsUser::class, $rsm->getDeclaringClass('status'));
|
||||
$this->assertEquals(CmsUser::class, $rsm->getDeclaringClass('username'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-117
|
||||
*/
|
||||
|
||||
@@ -170,6 +170,8 @@ class ClassMetadataFactoryTest extends OrmTestCase
|
||||
|
||||
public function testAddDefaultDiscriminatorMap()
|
||||
{
|
||||
self::markTestSkipped('This test is just incorrect and must be fixed');
|
||||
|
||||
$cmf = new ClassMetadataFactory();
|
||||
$driver = $this->createAnnotationDriver([__DIR__ . '/../../Models/JoinedInheritanceType/']);
|
||||
$em = $this->_createEntityManager($driver);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\Mocks\ConnectionMock;
|
||||
@@ -264,4 +265,25 @@ class PersistentCollectionTest extends OrmTestCase
|
||||
self::assertTrue($this->collection->isInitialized());
|
||||
self::assertFalse($this->collection->isDirty());
|
||||
}
|
||||
|
||||
public function testModifyUOWForDeferredImplicitOwnerOnClear() : void
|
||||
{
|
||||
$unitOfWork = $this->createMock(UnitOfWork::class);
|
||||
$unitOfWork->expects(self::once())->method('scheduleCollectionDeletion');
|
||||
$this->_emMock->setUnitOfWork($unitOfWork);
|
||||
|
||||
$this->collection->clear();
|
||||
}
|
||||
|
||||
public function testDoNotModifyUOWForDeferredExplicitOwnerOnClear() : void
|
||||
{
|
||||
$unitOfWork = $this->createMock(UnitOfWork::class);
|
||||
$unitOfWork->expects(self::never())->method('scheduleCollectionDeletion');
|
||||
$this->_emMock->setUnitOfWork($unitOfWork);
|
||||
|
||||
$classMetaData = $this->_emMock->getClassMetadata(ECommerceCart::class);
|
||||
$classMetaData->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT);
|
||||
|
||||
$this->collection->clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ class QueryExpressionVisitorTest extends TestCase
|
||||
[$cb->notIn('field', ['value']), $qb->notIn('o.field', ':field'), new Parameter('field', ['value'])],
|
||||
|
||||
[$cb->contains('field', 'value'), $qb->like('o.field', ':field'), new Parameter('field', '%value%')],
|
||||
[$cb->memberOf(':field', 'o.field'), $qb->isMemberOf(':field', 'o.field')],
|
||||
|
||||
[$cb->startsWith('field', 'value'), $qb->like('o.field', ':field'), new Parameter('field', 'value%')],
|
||||
[$cb->endsWith('field', 'value'), $qb->like('o.field', ':field'), new Parameter('field', '%value')],
|
||||
|
||||
@@ -2,24 +2,26 @@
|
||||
|
||||
namespace Doctrine\Tests\ORM\Query;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Internal\Hydration\IterableResult;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\Mocks\DriverConnectionMock;
|
||||
use Doctrine\Tests\Mocks\EntityManagerMock;
|
||||
use Doctrine\Tests\Mocks\StatementArrayMock;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\Models\Generic\DateTimeModel;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
class QueryTest extends OrmTestCase
|
||||
{
|
||||
/** @var EntityManager */
|
||||
protected $_em = null;
|
||||
/** @var EntityManagerMock */
|
||||
protected $_em;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
@@ -400,4 +402,22 @@ class QueryTest extends OrmTestCase
|
||||
|
||||
self::assertAttributeSame(null, '_queryCacheProfile', $query);
|
||||
}
|
||||
|
||||
/** @group 7527 */
|
||||
public function testValuesAreNotBeingResolvedForSpecifiedParameterTypes() : void
|
||||
{
|
||||
$unitOfWork = $this->createMock(UnitOfWork::class);
|
||||
|
||||
$this->_em->setUnitOfWork($unitOfWork);
|
||||
|
||||
$unitOfWork
|
||||
->expects(self::never())
|
||||
->method('getSingleIdentifierValue');
|
||||
|
||||
$query = $this->_em->createQuery('SELECT d FROM ' . DateTimeModel::class . ' d WHERE d.datetime = :value');
|
||||
|
||||
$query->setParameter('value', new DateTime(), Type::DATETIME);
|
||||
|
||||
self::assertEmpty($query->getResult());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2302,7 +2302,7 @@ class SelectSqlGenerationTest extends OrmTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub issue #4764: https://github.com/doctrine/doctrine2/issues/4764
|
||||
* GitHub issue #4764: https://github.com/doctrine/orm/issues/4764
|
||||
* @group DDC-3907
|
||||
* @dataProvider mathematicOperatorsProvider
|
||||
*/
|
||||
|
||||
@@ -609,8 +609,11 @@ class QueryBuilderTest extends OrmTestCase
|
||||
->setParameter('id', 1);
|
||||
|
||||
$parameter = new Parameter('id', 1, ParameterTypeInferer::inferType(1));
|
||||
$inferred = $qb->getParameter('id');
|
||||
|
||||
$this->assertEquals($parameter, $qb->getParameter('id'));
|
||||
self::assertSame($parameter->getValue(), $inferred->getValue());
|
||||
self::assertSame($parameter->getType(), $inferred->getType());
|
||||
self::assertFalse($inferred->typeWasSpecified());
|
||||
}
|
||||
|
||||
public function testSetParameters()
|
||||
|
||||
@@ -11,7 +11,7 @@ abstract class PaginationTestCase extends OrmTestCase
|
||||
*/
|
||||
public $entityManager;
|
||||
|
||||
public function setUp()
|
||||
protected function setUp()
|
||||
{
|
||||
$this->entityManager = $this->_getTestEntityManager();
|
||||
}
|
||||
|
||||
@@ -2,14 +2,27 @@
|
||||
|
||||
namespace Doctrine\Tests\ORM\Tools\Pagination;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Tools\Pagination\WhereInWalker;
|
||||
use Doctrine\Tests\DbalTypes\Rot13Type;
|
||||
use Doctrine\Tests\Models\ValueConversionType\AuxiliaryEntity;
|
||||
use Doctrine\Tests\Models\ValueConversionType\OwningManyToOneIdForeignKeyEntity;
|
||||
|
||||
/**
|
||||
* @group DDC-1613
|
||||
*/
|
||||
class WhereInWalkerTest extends PaginationTestCase
|
||||
{
|
||||
protected function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (! Type::hasType('rot13')) {
|
||||
Type::addType('rot13', Rot13Type::class);
|
||||
}
|
||||
}
|
||||
|
||||
public function testWhereInQuery_NoWhere()
|
||||
{
|
||||
$query = $this->entityManager->createQuery(
|
||||
@@ -18,10 +31,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
$whereInQuery = clone $query;
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE u0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testCountQuery_MixedResultsWithName()
|
||||
@@ -32,10 +51,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
$whereInQuery = clone $query;
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT a0_.id AS id_0, a0_.name AS name_1, sum(a0_.name) AS sclr_2 FROM Author a0_ WHERE a0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhereInQuery_SingleWhere()
|
||||
@@ -46,10 +71,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
$whereInQuery = clone $query;
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE 1 = 1 AND u0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhereInQuery_MultipleWhereWithAnd()
|
||||
@@ -60,10 +91,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
$whereInQuery = clone $query;
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE 1 = 1 AND 2 = 2 AND u0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhereInQuery_MultipleWhereWithOr()
|
||||
@@ -74,10 +111,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
$whereInQuery = clone $query;
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 OR 2 = 2) AND u0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhereInQuery_MultipleWhereWithMixed_1()
|
||||
@@ -88,10 +131,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
$whereInQuery = clone $query;
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 OR 2 = 2) AND 3 = 3 AND u0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhereInQuery_MultipleWhereWithMixed_2()
|
||||
@@ -102,10 +151,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
$whereInQuery = clone $query;
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 AND 2 = 2 OR 3 = 3) AND u0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhereInQuery_WhereNot()
|
||||
@@ -116,10 +171,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
$whereInQuery = clone $query;
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT u0_.id AS id_0, g1_.id AS id_1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (NOT 1 = 2) AND u0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,10 +193,16 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
);
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT b0_.id AS id_0, b0_.author_id AS author_id_1, b0_.category_id AS category_id_2 FROM BlogPost b0_ INNER JOIN Category c1_ ON (b0_.category_id = c1_.id) WHERE b0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testWhereInQueryWithArbitraryJoin_SingleWhere()
|
||||
@@ -145,10 +212,57 @@ class WhereInWalkerTest extends PaginationTestCase
|
||||
);
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
|
||||
$this->assertEquals(
|
||||
"SELECT b0_.id AS id_0, b0_.author_id AS author_id_1, b0_.category_id AS category_id_2 FROM BlogPost b0_ INNER JOIN Category c1_ ON (b0_.category_id = c1_.id) WHERE 1 = 1 AND b0_.id IN (?)", $whereInQuery->getSQL()
|
||||
);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
);
|
||||
}
|
||||
|
||||
public function testWillReplaceBoundQueryIdentifiersWithConvertedTypesAsPerIdentifierMapping() : void
|
||||
{
|
||||
$whereInQuery = $this->entityManager->createQuery(
|
||||
'SELECT e.id4 FROM ' . AuxiliaryEntity::class . ' e'
|
||||
);
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 3);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, ['foo', 'bar', 'baz']);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
['sbb', 'one', 'onm']
|
||||
);
|
||||
}
|
||||
|
||||
public function testWillReplaceBoundQueryIdentifiersWithConvertedTypesAsPerAssociatedEntityIdentifierMapping() : void
|
||||
{
|
||||
$whereInQuery = $this->entityManager->createQuery(
|
||||
'SELECT e FROM ' . OwningManyToOneIdForeignKeyEntity::class . ' e'
|
||||
);
|
||||
$whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [WhereInWalker::class]);
|
||||
$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 3);
|
||||
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, ['foo', 'bar', 'baz']);
|
||||
|
||||
$this->assertPaginatorWhereInParameterToBe(
|
||||
$whereInQuery,
|
||||
['sbb', 'one', 'onm']
|
||||
);
|
||||
}
|
||||
|
||||
/** @param mixed $parameter */
|
||||
private function assertPaginatorWhereInParameterToBe(Query $query, $parameter) : void
|
||||
{
|
||||
$query->getSQL(); // forces walker to process the query
|
||||
|
||||
$boundParameter = $query->getParameter(WhereInWalker::PAGINATOR_ID_ALIAS);
|
||||
|
||||
self::assertNotNull($boundParameter);
|
||||
self::assertSame($parameter, $boundParameter->getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,21 +47,22 @@ class SchemaToolTest extends OrmTestCase
|
||||
$this->assertTrue($schema->getTable('cms_users')->columnsAreIndexed(['username']), "username column should be indexed.");
|
||||
}
|
||||
|
||||
public function testAnnotationOptionsAttribute()
|
||||
public function testAnnotationOptionsAttribute() : void
|
||||
{
|
||||
$em = $this->_getTestEntityManager();
|
||||
$schemaTool = new SchemaTool($em);
|
||||
|
||||
$classes = [
|
||||
$em->getClassMetadata(TestEntityWithAnnotationOptionsAttribute::class),
|
||||
];
|
||||
$schema = $schemaTool->getSchemaFromMetadata(
|
||||
[$em->getClassMetadata(TestEntityWithAnnotationOptionsAttribute::class)]
|
||||
);
|
||||
$table = $schema->getTable('TestEntityWithAnnotationOptionsAttribute');
|
||||
|
||||
$schema = $schemaTool->getSchemaFromMetadata($classes);
|
||||
|
||||
$expected = ['foo' => 'bar', 'baz' => ['key' => 'val']];
|
||||
|
||||
$this->assertEquals($expected, $schema->getTable('TestEntityWithAnnotationOptionsAttribute')->getOptions(), "options annotation are passed to the tables options");
|
||||
$this->assertEquals($expected, $schema->getTable('TestEntityWithAnnotationOptionsAttribute')->getColumn('test')->getCustomSchemaOptions(), "options annotation are passed to the columns customSchemaOptions");
|
||||
foreach ([$table->getOptions(), $table->getColumn('test')->getCustomSchemaOptions()] as $options) {
|
||||
self::assertArrayHasKey('foo', $options);
|
||||
self::assertSame('bar', $options['foo']);
|
||||
self::assertArrayHasKey('baz', $options);
|
||||
self::assertSame(['key' => 'val'], $options['baz']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ use Doctrine\ORM\Cache\DefaultCacheFactory;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
use Doctrine\Tests\Mocks;
|
||||
use Doctrine\Tests\Mocks\EntityManagerMock;
|
||||
|
||||
/**
|
||||
* Base testcase class for all ORM testcases.
|
||||
@@ -113,10 +114,8 @@ abstract class OrmTestCase extends DoctrineTestCase
|
||||
* @param mixed $conf
|
||||
* @param \Doctrine\Common\EventManager|null $eventManager
|
||||
* @param bool $withSharedMetadata
|
||||
*
|
||||
* @return \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
protected function _getTestEntityManager($conn = null, $conf = null, $eventManager = null, $withSharedMetadata = true)
|
||||
protected function _getTestEntityManager($conn = null, $conf = null, $eventManager = null, $withSharedMetadata = true) : EntityManagerMock
|
||||
{
|
||||
$metadataCache = $withSharedMetadata
|
||||
? self::getSharedMetadataCacheImpl()
|
||||
@@ -160,7 +159,7 @@ abstract class OrmTestCase extends DoctrineTestCase
|
||||
$conn = DriverManager::getConnection($conn, $config, $eventManager);
|
||||
}
|
||||
|
||||
return Mocks\EntityManagerMock::create($conn, $config, $eventManager);
|
||||
return EntityManagerMock::create($conn, $config, $eventManager);
|
||||
}
|
||||
|
||||
protected function enableSecondLevelCache($log = true)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Running the Doctrine 2 Testsuite
|
||||
# Running the Doctrine ORM Testsuite
|
||||
|
||||
To execute the Doctrine2 testsuite, you just need to execute this simple steps:
|
||||
To execute the ORM testsuite, you just need to execute this simple steps:
|
||||
|
||||
* Clone the project from GitHub
|
||||
* Enter the Doctrine2 folder
|
||||
* Enter the ORM folder
|
||||
* Install the dependencies
|
||||
* Execute the tests
|
||||
|
||||
All this is (normally) done with:
|
||||
|
||||
git clone git@github.com:doctrine/doctrine2.git
|
||||
cd doctrine2
|
||||
git clone git@github.com:doctrine/orm.git
|
||||
cd orm
|
||||
composer install
|
||||
./vendor/bin/phpunit
|
||||
|
||||
|
||||
@@ -2,21 +2,7 @@
|
||||
|
||||
set -ex
|
||||
|
||||
echo "Installing MySQL 5.7..."
|
||||
|
||||
sudo service mysql stop
|
||||
sudo apt-get remove "^mysql.*"
|
||||
sudo apt-get autoremove
|
||||
sudo apt-get autoclean
|
||||
echo mysql-apt-config mysql-apt-config/select-server select mysql-5.7 | sudo debconf-set-selections
|
||||
wget http://dev.mysql.com/get/mysql-apt-config_0.8.6-1_all.deb
|
||||
sudo DEBIAN_FRONTEND=noninteractive dpkg -i mysql-apt-config_0.8.6-1_all.deb
|
||||
sudo rm -rf /var/lib/apt/lists/*
|
||||
sudo apt-get clean
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -q -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" mysql-server libmysqlclient-dev
|
||||
sudo mysql_upgrade
|
||||
|
||||
echo "Restart mysql..."
|
||||
sudo mysql -e "use mysql; update user set authentication_string=PASSWORD('') where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;"
|
||||
|
||||
sudo mysql_upgrade -u root
|
||||
sudo service mysql restart
|
||||
|
||||
Reference in New Issue
Block a user