Compare commits

...

61 Commits

Author SHA1 Message Date
Vincent Langlet dafe298ce5 Fix phpdoc (#8074) 2020-03-19 07:41:02 +01:00
Matthias Pigulla 58b8130ea1 Fix regression in 2.7.1 when mysqli is used with discriminator column that is not a string (#8055)
* Add a test case showing the regression

* Cast the discriminator value to string

* Fix CS
2020-03-16 11:19:12 +01:00
Benjamin Eberlei a705f526fb [GH-7633] disallow cache partial objects (#8050)
* [GH-7633] Bugfix: Partial queries were stored in 2LC.

There was a check in DefaultQueryCache that prevented partial queries,
because they are not supported. However the checked hint
Query::HINT_FORCE_PARTIAL_LOAD is optional, so cant be used to prevent
caching partial DQL queries.

Introduce a new hint that the SqlWalker sets on detecing a PARTIAL
query and throw an exception in the DefaultQueryCache if thats found.

* Housekeeping: CS

* [GH-7633] HINT_FORCE_PARTIAL_LOAD still needs to be checked.

* Housekeeping: Fix CS
2020-03-15 01:11:34 +01:00
Maciej Malarz a9b6b72017 Fix inherited embeddables and nesting after AnnotationDriver change #8006 (#8036)
* Add test case

* Treat parent embeddables as mapped superclasses

* [GH-8031] Bugfix: Get working again on nested embeddables in inherited embeddables.

* Housekeeping: CS

* Update note on limitations

* [GH-8031] Verify assocations still do not work with Embeddables.

* Housekeeping: CS

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
2020-03-15 01:00:58 +01:00
Jorrit Schippers cd905fff77 Fix documentation of default generated value behavior (#8068) 2020-03-13 20:40:31 +01:00
Andreas Möller eb700405be Fix: Use neutral pronouns (#8059) 2020-03-06 16:08:53 +01:00
Rosemary Orchard 9273057649 Annotations override naming strategy (#8041)
Add a note/warning that annotations override the naming strategy.
2020-03-01 14:01:26 +01:00
Grégoire Paris e04a79526e Merge pull request #7230 from holtkamp/patch-2
Mention that lifecycle callbacks do not support Embeddables
2020-02-17 23:00:44 +01:00
Menno Holtkamp d157a6cbeb Mention that lifecycle callbacks do not support Embeddables
As discussed in https://github.com/doctrine/doctrine2/issues/6855
2020-02-17 22:25:00 +01:00
Benjamin Eberlei ca57222010 Merge pull request #8023 from peterkeatingie/query-cache-fix
Put into cache using root entity name
2020-02-16 10:50:24 +01:00
Peter Keating 9bb2bf0cce Put into cache using root entity name 2020-02-15 15:53:47 +00:00
Benjamin Eberlei 445796af0e Travis: Use 7.4 instead of 7.4snapshot 2020-02-15 15:35:56 +01:00
Benjamin Eberlei ab93285284 Remove nightly builds from .travis.yml 2020-02-15 15:34:36 +01:00
Benjamin Eberlei a692670469 Merge pull request #8006 from doctrine/malarzm-patch-1
Make Embeddable not transient
2020-02-13 21:31:02 +01:00
Maciej Malarz 58677c29b4 Make Embeddable not transient 2020-02-13 21:05:52 +01:00
Benjamin Eberlei 9a0fcb5a86 Merge pull request #7940 from doctrine/GH-7864-ExtraLazyRemoveElement
[GH-7864] Bugfix in PersistentCollection::removeElement for EXTRA_LAZY.
2020-02-12 23:42:06 +01:00
Benjamin Eberlei 8104c65d6c Merge pull request #7987 from beberlei/GH-7982-NoSqlExecutor
[GH-7982] no sql executor leads to parse error
2020-02-12 23:37:39 +01:00
Benjamin Eberlei a64d254d07 [GH-7982] Bugfix: Passing string|null DQL to Lexer(string $input) leads to downstream notice. 2020-02-12 23:23:12 +01:00
Benjamin Eberlei fdad48278b Merge pull request #7991 from greg0ire/7.4-sa
Try running phpstan on php 7.4
2020-01-17 00:06:40 +01:00
Benjamin Eberlei fc94127d7f Make ocramius/package-versions 1.2 the lowest version for phpstan 2020-01-16 23:51:17 +01:00
Grégoire Paris dea3e5df44 Try running phpstan on php 7.4
It might be easier to find packages compatible with both our locked deps
and phpstan with that version of php.
2020-01-16 23:49:15 +01:00
Benjamin Eberlei 5d7d3e99a0 Downgrade ocramius/package-versions to lowest in composer.lock to support all PHP versions. 2020-01-16 23:18:00 +01:00
Benjamin Eberlei 3bc1096fd0 [GH-7982] Default Query state to dirty to fix execution of empty query. 2020-01-15 23:30:59 +01:00
Benjamin Eberlei a2f01f7ccc Allow everything from ocramius/package-versions ^1.0. 2020-01-15 22:56:08 +01:00
Benjamin Eberlei 401db453a2 Merge pull request #7974 from beberlei/gh-7505
[GH-7505] Bug in SimpleObjectHydrator when using inheritance with same field
2020-01-15 22:02:25 +01:00
Benjamin Eberlei 6e59ec8f16 [GH-7505] Fix cs 2020-01-15 21:52:11 +01:00
Benjamin Eberlei 87e491465a Add @group 2020-01-15 21:13:25 +01:00
Luís Cobucci 8b588eceb2 Merge pull request #7973 from DocFX/patch-1
Just a micro grammar update.
2020-01-14 11:26:21 +01:00
Benjamin Eberlei edce36598f Adjust tests back for 2.x. 2020-01-09 00:41:47 +01:00
Woody Gilk 20c46035d1 [Docs] Prefer PhpFileCache for caching and remove APC/XCache. 2020-01-08 19:36:08 +01:00
William Pinaud 324aacfb54 Just a micro grammar update. 2020-01-08 18:52:11 +01:00
Benjamin Eberlei 1edfcabead Merge pull request #7894 from TomckySan/convert-default-value-to-boolean
Fix boolean properties default value when generating entities.
2020-01-05 16:11:33 +01:00
Luís Cobucci 2785cde792 Merge pull request #7957 from lcobucci/fix-version-information
Fix version information
2019-12-17 19:19:05 +01:00
Luís Cobucci d67e3e8b1b Rely on ocramius/package-versions to render the version
Since `Doctrine\ORM\Version` is now deprecated it shall not be updated
on future releases.

This ensures that our CLI tool will present the correct version number.
2019-12-17 15:47:55 +01:00
Luís Cobucci d629c4e487 Remove build.xml and related files
We aren't using ant/phing to handle the releases any more.
2019-12-17 01:03:34 +01:00
Luís Cobucci 4a4226213f Merge pull request #7875 from nicolas-grekas/schema-tool
Whilelist existing assets we know about from metadata in SchemaTool::getUpdateSchemaSql()
2019-12-16 23:59:31 +01:00
Andreas Braun 0ce1440884 Add upgrade note about schema_filter change 2019-12-16 23:45:49 +01:00
Laurent VOULLEMIER 9aa28b4e33 Test asset whitelisting on SchemaTool#getUpdateSchemasSql() 2019-12-16 23:45:49 +01:00
Nicolas Grekas 5c2b6870bf Whitelist existing assets we know about from metadata in SchemaTool::getUpdateSchemaSql() 2019-12-16 23:22:50 +01:00
Luís Cobucci 4389b2c188 Merge pull request #7956 from lcobucci/fix-test-suite
Ignore Doctrine\Common\Persistence\ObjectManagerDecorator deprecation
2019-12-16 21:49:25 +01:00
Luís Cobucci e481d9880b Ignore Doctrine\Common\Persistence\ObjectManagerDecorator deprecation
Since applying the fixes requires bumping up the dependency, which isn't
done in a patch release.

This should be removed in v2.8.0.
2019-12-16 21:22:28 +01:00
Luís Cobucci 85528f28e2 Fix CS errors 2019-12-16 21:22:23 +01:00
Luís Cobucci 5873242fb5 Merge pull request #7937 from doctrine/GH-7930-SqliteForeignKeys
Revert SchemaTool change to check for foreign key support
2019-12-16 10:45:07 +01:00
Luís Cobucci c9e41d0aa7 Merge pull request #7934 from BenMorel/php74
Fix Trying to access array offset on value of type null
2019-12-09 21:32:58 +01:00
Benjamin Morel f37c12834d Fix Trying to access array offset on value of type null 2019-12-09 21:24:29 +01:00
Benjamin Eberlei 041404e8b3 [GH-7864] Revert removeElement EXTRA_LAZY support. 2019-12-07 00:03:09 +01:00
Benjamin Eberlei bfc68b3aba Add warning about removeElement on extra lazy 2019-12-03 20:34:44 +01:00
Benjamin Eberlei 1e628370c4 [GH-7864] Address review comments. 2019-12-03 19:35:49 +01:00
Benjamin Eberlei ae2b9b1921 Housekeeping: phpcbf to fix issues. 2019-12-01 21:11:09 +01:00
Benjamin Eberlei 419df77a09 [GH-7864] ExtraLazyCollectionTest is not cacahble and should not fail SLC suite. 2019-12-01 20:28:30 +01:00
Benjamin Eberlei d6f6b2e97c [GH-7864] Remove tests that checked invalid behavior. 2019-12-01 19:47:58 +01:00
Benjamin Eberlei 75d5adf599 [GH-7864] Bugfix in PersistentCollection::removeElement for EXTRA_LAZY. 2019-12-01 19:27:45 +01:00
Benjamin Eberlei cfd6fadf9c Revert "#7841 SchemaTool generates extra diff for platforms without FK support"
This reverts commit 3707c39124.
2019-12-01 11:23:45 +01:00
Tomoka Baba 2bf7916c52 Fix to pass code quality check. 2019-11-20 17:03:34 +09:00
Tomoka Baba 253fd10cc0 Modified test to use assertTrue. 2019-11-20 17:02:25 +09:00
Tomoka Baba 2c956d55f2 Fix to pass code quality check. 2019-11-20 17:02:25 +09:00
Tomoka Baba 3db992e953 Add test code. 2019-11-20 17:01:52 +09:00
Tomoka Baba 6fc9b3ab16 Fix to pass code quality check. 2019-11-20 17:01:52 +09:00
Tomoka Baba 2d833a5e86 Fix boolean properties default value when generating entities. 2019-11-20 17:01:19 +09:00
Luís Cobucci a416a9a8b2 Bump up version 2019-11-19 09:43:57 +01:00
Mickaël RAYBAUD-ROIG 3a32c00dcf Add a failing test for issue #7505 2019-11-14 23:28:42 +01:00
57 changed files with 1073 additions and 958 deletions
+4 -5
View File
@@ -6,8 +6,7 @@ php:
- 7.1
- 7.2
- 7.3
- 7.4snapshot
- nightly
- 7.4
env:
- DB=sqlite
@@ -55,7 +54,7 @@ jobs:
- stage: Test
dist: xenial
env: DB=mysql MYSQL_VERSION=5.7
php: 7.4snapshot
php: 7.4
services:
- mysql
before_script:
@@ -87,6 +86,7 @@ jobs:
- stage: Code Quality
env: DB=none STATIC_ANALYSIS
php: 7.4
install: travis_retry composer install --prefer-dist
before_script:
- echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
@@ -102,7 +102,7 @@ jobs:
- stage: Code Quality
if: NOT type = pull_request
env: DB=none CODING_STANDARDS
php: 7.4snapshot
php: 7.4
install: travis_retry composer install --prefer-dist
script:
- ./vendor/bin/phpcs
@@ -123,7 +123,6 @@ jobs:
- php git-phpcs.phar origin/$TRAVIS_BRANCH...$TRAVIS_PULL_REQUEST_SHA
allow_failures:
- php: nightly
- stage: Code Quality
env: DB=none CODING_STANDARDS
+6
View File
@@ -10,6 +10,12 @@ Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for bot
To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to
perform the pagination with join collections when max results isn't set in the query.
## Minor BC BREAK: tables filtered with `schema_filter` are no longer created
When generating schema diffs, if a source table is filtered out by a `schema_filter` expression, then a `CREATE TABLE` was
always generated, even if the table already existed. This has been changed in this release and the table will no longer
be created.
## Deprecated number unaware `Doctrine\ORM\Mapping\UnderscoreNamingStrategy`
In the last patch of the `v2.6.x` series, we fixed a bug that was not converting names properly when they had numbers
-3
View File
@@ -1,3 +0,0 @@
# Version class and file
project.version_class = Doctrine\\ORM\\Version
project.version_file = lib/Doctrine/ORM/Version.php
-16
View File
@@ -1,16 +0,0 @@
version=2.0.0BETA2
dependencies.common=2.0.0BETA4
dependencies.dbal=2.0.0BETA4
stability=beta
build.dir=build
dist.dir=dist
report.dir=reports
log.archive.dir=logs
project.pirum_dir=
project.download_dir=
project.xsd_dir=
test.phpunit_configuration_file=
test.phpunit_generate_coverage=0
test.pmd_reports=0
test.pdepend_exec=
test.phpmd_exec=
-101
View File
@@ -1,101 +0,0 @@
<?xml version="1.0"?>
<project name="DoctrineORM" default="build" basedir=".">
<property file="build.properties" />
<target name="php">
<exec executable="which" outputproperty="php_executable">
<arg value="php" />
</exec>
</target>
<target name="prepare">
<mkdir dir="build" />
</target>
<target name="build" depends="check-git-checkout-clean,prepare,php,composer">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="archive" />
<arg value="--dir=build" />
</exec>
</target>
<target name="composer" depends="php,composer-check,composer-download">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="install" />
</exec>
</target>
<target name="composer-check" depends="prepare">
<available file="build/composer.phar" property="composer.present"/>
</target>
<target name="composer-download" unless="composer.present">
<exec executable="wget">
<arg value="-Obuild/composer.phar" />
<arg value="http://getcomposer.org/composer.phar" />
</exec>
</target>
<target name="make-release" depends="check-git-checkout-clean,prepare,php">
<replace file="${project.version_file}" token="-DEV" value="" failOnNoReplacements="true" />
<exec executable="${php_executable}" outputproperty="doctrine.current_version" failonerror="true">
<arg value="-r" />
<arg value="require_once '${project.version_file}';echo ${project.version_class}::VERSION;" />
</exec>
<exec executable="${php_executable}" outputproperty="doctrine.next_version" failonerror="true">
<arg value="-r" />
<arg value="$parts = explode('.', str_ireplace(array('-DEV', '-ALPHA', '-BETA'), '', '${doctrine.current_version}'));
if (count($parts) != 3) {
throw new \InvalidArgumentException('Version is assumed in format x.y.z, ${doctrine.current_version} given');
}
$parts[2]++;
echo implode('.', $parts);
" />
</exec>
<git-commit file="${project.version_file}" message="Release ${doctrine.current_version}" />
<git-tag version="${doctrine.current_version}" />
<replace file="${project.version_file}" token="${doctrine.current_version}" value="${doctrine.next_version}-DEV" />
<git-commit file="${project.version_file}" message="Bump version to ${doctrine.next_version}" />
</target>
<target name="check-git-checkout-clean">
<exec executable="git" failonerror="true">
<arg value="diff-index" />
<arg value="--quiet" />
<arg value="HEAD" />
</exec>
</target>
<macrodef name="git-commit">
<attribute name="file" default="NOT SET"/>
<attribute name="message" default="NOT SET"/>
<sequential>
<exec executable="git">
<arg value="add" />
<arg value="@{file}" />
</exec>
<exec executable="git">
<arg value="commit" />
<arg value="-m" />
<arg value="@{message}" />
</exec>
</sequential>
</macrodef>
<macrodef name="git-tag">
<attribute name="version" default="NOT SET" />
<sequential>
<exec executable="git">
<arg value="tag" />
<arg value="-m" />
<arg value="v@{version}" />
<arg value="v@{version}" />
</exec>
</sequential>
</macrodef>
</project>
+1
View File
@@ -26,6 +26,7 @@
"doctrine/event-manager": "^1.1",
"doctrine/instantiator": "^1.3",
"doctrine/persistence": "^1.2",
"ocramius/package-versions": "^1.2",
"symfony/console": "^3.0|^4.0|^5.0"
},
"require-dev": {
Generated
+51 -2
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "4e24e01f599825550170acce0dda0b49",
"content-hash": "22be7b4b42da2931c1033f8818a0caa9",
"packages": [
{
"name": "doctrine/annotations",
@@ -805,6 +805,55 @@
],
"time": "2018-06-14T14:45:07+00:00"
},
{
"name": "ocramius/package-versions",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/Ocramius/PackageVersions.git",
"reference": "ad8a245decad4897cc6b432743913dad0d69753c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/ad8a245decad4897cc6b432743913dad0d69753c",
"reference": "ad8a245decad4897cc6b432743913dad0d69753c",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0",
"php": "~7.0"
},
"require-dev": {
"composer/composer": "^1.3",
"ext-zip": "*",
"humbug/humbug": "dev-master",
"phpunit/phpunit": "^6.4"
},
"type": "composer-plugin",
"extra": {
"class": "PackageVersions\\Installer",
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"PackageVersions\\": "src/PackageVersions"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
"time": "2017-11-24T11:07:03+00:00"
},
{
"name": "psr/log",
"version": "1.1.2",
@@ -2735,7 +2784,7 @@
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"prefer-lowest": true,
"platform": {
"php": "^7.1",
"ext-pdo": "*"
+1 -1
View File
@@ -44,7 +44,7 @@ Serializing entity into the session
-----------------------------------
Entities that are serialized into the session normally contain references to
other entities as well. Think of the user entity has a reference to his
other entities as well. Think of the user entity has a reference to their
articles, groups, photos or many other different entities. If you serialize
this object into the session then you don't want to serialize the related
entities as well. This is why you should call ``EntityManager#detach()`` on this
+2 -2
View File
@@ -25,8 +25,8 @@ the additional benefit of being able to re-use your validation in
any other part of your domain.
Say we have an ``Order`` with several ``OrderLine`` instances. We
never want to allow any customer to order for a larger sum than he
is allowed to:
never want to allow any customer to order for a larger sum than they
are allowed to:
.. code-block:: php
+1 -1
View File
@@ -99,7 +99,7 @@ Optional attributes:
- **length**: Used by the "string" type to determine its maximum
length in the database. Doctrine does not validate the length of a
string values for you.
string value for you.
- **precision**: The precision for a decimal (exact numeric) column
(applies only for decimal column), which is the maximum number of
+2 -2
View File
@@ -328,8 +328,8 @@ annotation.
In most cases using the automatic generator strategy (``@GeneratedValue``) is
what you want. It defaults to the identifier generation mechanism your current
database vendor prefers: AUTO_INCREMENT with MySQL, SERIAL with PostgreSQL,
Sequences with Oracle and so on.
database vendor prefers: AUTO_INCREMENT with MySQL, sequences with PostgreSQL
and Oracle and so on.
Identifier Generation Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+40 -49
View File
@@ -45,41 +45,29 @@ 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>`_.
APC
~~~
PhpFileCache
~~~~~~~~~~~~
In order to use the APC cache driver you must have it compiled and
enabled in your php.ini. You can read about APC
`in the PHP Documentation <http://us2.php.net/apc>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
The preferred cache driver for metadata and query caches is ``PhpFileCache``.
This driver serializes cache items and writes them to a file. This allows for
opcode caching to be used and provides high performance in most scenarios.
Below is a simple example of how you could use the APC cache driver
by itself.
In order to use the ``PhpFileCache`` driver it must be able to write to
a directory.
Below is an example of how to use the ``PhpFileCache`` driver by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
'/path/to/writable/directory'
);
$cacheDriver->save('cache_id', 'my_data');
APCu
~~~~
In order to use the APCu cache driver you must have it compiled and
enabled in your php.ini. You can read about APCu
`in the PHP Documentation <http://us2.php.net/apcu>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
Below is a simple example of how you could use the APCu cache driver
by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
$cacheDriver->save('cache_id', 'my_data');
The PhpFileCache is not distributed across multiple machines if you are running
your application in a distributed setup. This is ok for the metadata and query
cache but is not a good approach for the result cache.
Memcache
~~~~~~~~
@@ -128,24 +116,6 @@ driver by itself.
$cacheDriver->setMemcached($memcached);
$cacheDriver->save('cache_id', 'my_data');
Xcache
~~~~~~
In order to use the Xcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Xcache
`here <http://xcache.lighttpd.net/>`_. It will give you a little
background information about what it is and how you can use it as
well as how to install it.
Below is a simple example of how you could use the Xcache cache
driver by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
$cacheDriver->save('cache_id', 'my_data');
Redis
~~~~~
@@ -306,8 +276,11 @@ use on your ORM configuration.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
'/path/to/writable/directory'
);
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setQueryCacheImpl($cacheDriver);
Result Cache
~~~~~~~~~~~~
@@ -320,7 +293,11 @@ cache implementation.
.. code-block:: php
<?php
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
'/path/to/writable/directory'
);
$config = new \Doctrine\ORM\Configuration();
$config->setResultCacheImpl($cacheDriver);
Now when you're executing DQL queries you can configure them to use
the result cache.
@@ -337,7 +314,11 @@ result cache driver.
.. code-block:: php
<?php
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
'/path/to/writable/directory'
);
$config = new \Doctrine\ORM\Configuration();
$query->setResultCacheDriver($cacheDriver);
.. note::
@@ -389,7 +370,11 @@ first.
.. code-block:: php
<?php
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
'/path/to/writable/directory'
);
$config = new \Doctrine\ORM\Configuration();
$config->setMetadataCacheImpl($cacheDriver);
Now the metadata information will only be parsed once and stored in
the cache driver.
@@ -425,6 +410,12 @@ To clear the result cache use the ``orm:clear-cache:result`` task.
All these tasks accept a ``--flush`` option to flush the entire
contents of the cache instead of invalidating the entries.
.. note::
None of these tasks will work with APC, APCu, or XCache drivers
because the memory that the cache is stored in is only accessible
to the webserver.
Cache Chaining
--------------
@@ -250,7 +250,7 @@ Retrieve the Username and Name of a CmsUser:
$users = $query->getResult(); // array of CmsUser username and name values
echo $users[0]['username'];
Retrieve a ForumUser and his single associated entity:
Retrieve a ForumUser and its single associated entity:
.. code-block:: php
@@ -259,7 +259,7 @@ Retrieve a ForumUser and his single associated entity:
$users = $query->getResult(); // array of ForumUser objects with the avatar association loaded
echo get_class($users[0]->getAvatar());
Retrieve a CmsUser and fetch join all the phonenumbers he has:
Retrieve a CmsUser and fetch join all the phonenumbers it has:
.. code-block:: php
+5
View File
@@ -243,6 +243,11 @@ a relevant lifecycle event. More than one callback can be defined for each
lifecycle event. Lifecycle Callbacks are best used for simple operations
specific to a particular entity class's lifecycle.
.. note::
Note that Licecycle Callbacks are not supported for Embeddables.
.. code-block:: php
<?php
+5 -1
View File
@@ -4,9 +4,13 @@ Implementing a NamingStrategy
.. versionadded:: 2.3
Using a naming strategy you can provide rules for generating database identifiers,
column or table names when the column or table name is not given. This feature helps
column or table names. This feature helps
reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``).
.. warning
The naming strategy is always overridden by entity mapping such as the `Table` annotation.
Configuring a naming strategy
-----------------------------
The default strategy used by Doctrine is quite minimal.
@@ -39,7 +39,7 @@ side of the association and these 2 references both represent the
same association but can change independently of one another. Of
course, in a correct application the semantics of the bidirectional
association are properly maintained by the application developer
(that's his responsibility). Doctrine needs to know which of these
(that's their responsibility). Doctrine needs to know which of these
two in-memory references is the one that should be persisted and
which not. This is what the owning/inverse concept is mainly used
for.
+2 -2
View File
@@ -148,8 +148,8 @@ Hydration
~~~~~~~~~
Responsible for creating a final result from a raw database statement and a
result-set mapping object. The developer can choose which kind of result he
wishes to be hydrated. Default result-types include:
result-set mapping object. The developer can choose which kind of result they
wish to be hydrated. Default result-types include:
- SQL to Entities
- SQL to structured Arrays
+3 -1
View File
@@ -8,7 +8,9 @@ or address are the primary use case for this feature.
.. note::
Embeddables can only contain properties with basic ``@Column`` mapping.
Embeddables can not contain references to entities. They can however compose
other embeddables in addition to holding properties with basic ``@Column``
mapping.
For the purposes of this tutorial, we will assume that you have a ``User``
class in your application and you would like to store an address in
@@ -35,6 +35,15 @@ With extra lazy collections you can now not only add entities to large collectio
easily using a combination of ``count`` and ``slice``.
.. warning::
``removeElement`` directly issued DELETE queries to the database from
version 2.4.0 to 2.7.0. This circumvents the flush operation and might run
outside a transactional boundary if you don't create one yourself. We
consider this a critical bug in the assumptio of how the ORM works and
reverted ``removeElement`` EXTRA_LAZY behavior in 2.7.1.
Enabling Extra-Lazy Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+1 -1
View File
@@ -72,7 +72,7 @@ requirements:
- Bug reporters and engineers are both Users of the system.
- A User can create new Bugs.
- The assigned engineer can close a Bug.
- A User can see all his reported or assigned Bugs.
- A User can see all their reported or assigned Bugs.
- Bugs can be paginated through a list-view.
Project Setup
+6 -3
View File
@@ -260,7 +260,7 @@ class DefaultQueryCache implements QueryCache
throw new CacheException("Second-level cache query supports only select statements.");
}
if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) {
if (($hints[Query\SqlWalker::HINT_PARTIAL] ?? false) === true || ($hints[Query::HINT_FORCE_PARTIAL_LOAD] ?? false) === true) {
throw new CacheException("Second level cache does not support partial entities.");
}
@@ -279,9 +279,12 @@ class DefaultQueryCache implements QueryCache
$region = $persister->getCacheRegion();
$cm = $this->em->getClassMetadata($entityName);
assert($cm instanceof ClassMetadata);
foreach ($result as $index => $entity) {
$identifier = $this->uow->getEntityIdentifier($entity);
$entityKey = new EntityCacheKey($entityName, $identifier);
$identifier = $this->uow->getEntityIdentifier($entity);
$entityKey = new EntityCacheKey($cm->rootEntityName, $identifier);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
// Cancel put result if entity put fail
@@ -243,20 +243,6 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
return $this->persister->get($collection, $index);
}
/**
* {@inheritdoc}
*/
public function removeElement(PersistentCollection $collection, $element)
{
if ($persisterResult = $this->persister->removeElement($collection, $element)) {
$this->evictCollectionCache($collection);
$this->evictElementCache($this->sourceEntity->rootEntityName, $collection->getOwner());
$this->evictElementCache($this->targetEntity->rootEntityName, $element);
}
return $persisterResult;
}
/**
* {@inheritdoc}
*/
@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Internal\Hydration;
use PDO;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query;
use function in_array;
class SimpleObjectHydrator extends AbstractHydrator
{
@@ -78,8 +79,9 @@ class SimpleObjectHydrator extends AbstractHydrator
*/
protected function hydrateRowData(array $sqlResult, array &$result)
{
$entityName = $this->class->name;
$data = [];
$entityName = $this->class->name;
$data = [];
$discrColumnValue = null;
// We need to find the correct entity class name if we have inheritance in resultset
if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
@@ -104,7 +106,8 @@ class SimpleObjectHydrator extends AbstractHydrator
throw HydrationException::invalidDiscriminatorValue($sqlResult[$discrColumnName], array_keys($discrMap));
}
$entityName = $discrMap[$sqlResult[$discrColumnName]];
$entityName = $discrMap[$sqlResult[$discrColumnName]];
$discrColumnValue = $sqlResult[$discrColumnName];
unset($sqlResult[$discrColumnName]);
}
@@ -134,6 +137,11 @@ class SimpleObjectHydrator extends AbstractHydrator
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
if ( ! isset($data[$fieldName]) || ! $valueIsNull) {
// If we have inheritance in resultset, make sure the field belongs to the correct class
if (isset($cacheKeyInfo['discriminatorValues']) && ! in_array((string) $discrColumnValue, $cacheKeyInfo['discriminatorValues'], true)) {
continue;
}
$data[$fieldName] = $value;
}
}
@@ -31,6 +31,7 @@ use Doctrine\ORM\Id\BigIntegerIdentityGenerator;
use Doctrine\ORM\Id\IdentityGenerator;
use Doctrine\ORM\ORMException;
use ReflectionException;
use function assert;
/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
@@ -401,10 +402,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
{
foreach ($parentClass->fieldMappings as $mapping) {
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass && ! $parentClass->isEmbeddedClass) {
$mapping['inherited'] = $parentClass->name;
}
if ( ! isset($mapping['declared'])) {
if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$subClass->addInheritedFieldMapping($mapping);
@@ -469,10 +470,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
private function addNestedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass, $prefix)
{
foreach ($subClass->embeddedClasses as $property => $embeddableClass) {
if (isset($embeddableClass['inherited'])) {
continue;
}
$embeddableMetadata = $this->getMetadataFor($embeddableClass['class']);
$parentClass->mapEmbedded(
@@ -780,7 +777,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
*/
protected function isEntity(ClassMetadataInterface $class)
{
return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
assert($class instanceof ClassMetadata);
return $class->isMappedSuperclass === false && $class->isEmbeddedClass === false;
}
/**
@@ -44,6 +44,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
protected $entityAnnotationClasses = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
Mapping\Embeddable::class => 3,
];
/**
@@ -276,6 +277,8 @@ class AnnotationDriver extends AbstractAnnotationDriver
/* @var $property \ReflectionProperty */
foreach ($class->getProperties() as $property) {
if ($metadata->isMappedSuperclass && ! $property->isPrivate()
||
$metadata->isEmbeddedClass && $property->getDeclaringClass()->getName() !== $class->getName()
||
$metadata->isInheritedField($property->name)
||
-10
View File
@@ -367,16 +367,6 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
*/
public function removeElement($element)
{
if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
if ($this->collection->contains($element)) {
return $this->collection->removeElement($element);
}
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
return $persister->removeElement($this, $element);
}
$removed = parent::removeElement($element);
if ( ! $removed) {
@@ -90,16 +90,6 @@ interface CollectionPersister
*/
public function containsKey(PersistentCollection $collection, $key);
/**
* Removes an element.
*
* @param \Doctrine\ORM\PersistentCollection $collection
* @param object $element
*
* @return mixed
*/
public function removeElement(PersistentCollection $collection, $element);
/**
* Gets an element by key.
*
@@ -211,22 +211,6 @@ class ManyToManyPersister extends AbstractCollectionPersister
return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
}
/**
* {@inheritDoc}
*/
public function removeElement(PersistentCollection $collection, $element)
{
if ( ! $this->isValidEntityState($element)) {
return false;
}
list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, false);
$sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->conn->executeUpdate($sql, $params, $types);
}
/**
* {@inheritDoc}
*/
@@ -166,28 +166,6 @@ class OneToManyPersister extends AbstractCollectionPersister
return $persister->exists($element, $criteria);
}
/**
* {@inheritdoc}
*/
public function removeElement(PersistentCollection $collection, $element)
{
$mapping = $collection->getMapping();
if ( ! $mapping['orphanRemoval']) {
// no-op: this is not the owning side, therefore no operations should be applied
return false;
}
if ( ! $this->isValidEntityState($element)) {
return false;
}
return $this
->uow
->getEntityPersister($mapping['targetEntity'])
->delete($element);
}
/**
* {@inheritdoc}
*/
+2 -2
View File
@@ -133,7 +133,7 @@ final class Query extends AbstractQuery
*
* @var integer
*/
private $_state = self::STATE_CLEAN;
private $_state = self::STATE_DIRTY;
/**
* A snapshot of the parameter types the query was parsed with.
@@ -624,7 +624,7 @@ final class Query extends AbstractQuery
/**
* Sets the position of the first result to retrieve (the "offset").
*
* @param integer $firstResult The first result to return.
* @param int|null $firstResult The first result to return.
*
* @return Query This query object.
*/
+2 -2
View File
@@ -189,7 +189,7 @@ class Parser
{
$this->query = $query;
$this->em = $query->getEntityManager();
$this->lexer = new Lexer($query->getDQL());
$this->lexer = new Lexer((string) $query->getDQL());
$this->parserResult = new ParserResult();
}
@@ -302,7 +302,7 @@ class Parser
*/
public function match($token)
{
$lookaheadType = $this->lexer->lookahead['type'];
$lookaheadType = $this->lexer->lookahead['type'] ?? null;
// Short-circuit on first condition, usually types match
if ($lookaheadType === $token) {
+7
View File
@@ -46,6 +46,11 @@ class SqlWalker implements TreeWalker
*/
const HINT_DISTINCT = 'doctrine.distinct';
/**
* Used to mark a query as containing a PARTIAL expression, which needs to be known by SLC.
*/
public const HINT_PARTIAL = 'doctrine.partial';
/**
* @var ResultSetMapping
*/
@@ -1366,6 +1371,8 @@ class SqlWalker implements TreeWalker
default:
// IdentificationVariable or PartialObjectExpression
if ($expr instanceof AST\PartialObjectExpression) {
$this->query->setHint(self::HINT_PARTIAL, true);
$dqlAlias = $expr->identificationVariable;
$partialFieldSet = $expr->partialFieldSet;
} else {
+3 -3
View File
@@ -100,7 +100,7 @@ class QueryBuilder
/**
* The index of the first result to retrieve.
*
* @var integer
* @var int|null
*/
private $_firstResult = null;
@@ -616,7 +616,7 @@ class QueryBuilder
/**
* Sets the position of the first result to retrieve (the "offset").
*
* @param integer $firstResult The first result to return.
* @param int|null $firstResult The first result to return.
*
* @return self
*/
@@ -631,7 +631,7 @@ class QueryBuilder
* Gets the position of the first result the query object was set to retrieve (the "offset").
* Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
*
* @return integer The position of the first result.
* @return int|null The position of the first result.
*/
public function getFirstResult()
{
@@ -22,7 +22,8 @@ namespace Doctrine\ORM\Tools\Console;
use Doctrine\DBAL\Tools\Console as DBALConsole;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Doctrine\ORM\Version;
use OutOfBoundsException;
use PackageVersions\Versions;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\HelperSet;
@@ -70,10 +71,11 @@ final class ConsoleRunner
* @param array $commands
*
* @return \Symfony\Component\Console\Application
* @throws OutOfBoundsException
*/
public static function createApplication(HelperSet $helperSet, array $commands = []) : Application
{
$cli = new Application('Doctrine Command Line Interface', Version::VERSION);
$cli = new Application('Doctrine Command Line Interface', Versions::getVersion('doctrine/orm'));
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
self::addCommands($cli);
+11 -2
View File
@@ -26,6 +26,7 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo;
use const E_USER_DEPRECATED;
use function str_replace;
use function trigger_error;
use function var_export;
/**
* Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
@@ -1328,9 +1329,17 @@ public function __construct(<params>)
continue;
}
$defaultValue = '';
if (isset($fieldMapping['options']['default'])) {
if ($fieldMapping['type'] === 'boolean' && $fieldMapping['options']['default'] === '1') {
$defaultValue = ' = true';
} else {
$defaultValue = ' = ' . var_export($fieldMapping['options']['default'], true);
}
}
$lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
$lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
. (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n";
$lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName'] . $defaultValue . ";\n";
}
return implode("\n", $lines);
+34 -9
View File
@@ -19,7 +19,7 @@
namespace Doctrine\ORM\Tools;
use Doctrine\ORM\ORMException;
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
@@ -28,6 +28,7 @@ use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
use Doctrine\DBAL\Schema\Visitor\RemoveNamespacedAssets;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
@@ -709,11 +710,6 @@ class SchemaTool
}
$compositeName = $theJoinTable->getName().'.'.implode('', $localColumns);
if (! $this->platform->supportsForeignKeyConstraints()) {
return;
}
if (isset($addedFks[$compositeName])
&& ($foreignTableName != $addedFks[$compositeName]['foreignTableName']
|| 0 < count(array_diff($foreignColumns, $addedFks[$compositeName]['foreignColumns'])))
@@ -896,10 +892,8 @@ class SchemaTool
*/
public function getUpdateSchemaSql(array $classes, $saveMode = false)
{
$sm = $this->em->getConnection()->getSchemaManager();
$fromSchema = $sm->createSchema();
$toSchema = $this->getSchemaFromMetadata($classes);
$fromSchema = $this->createSchemaForComparison($toSchema);
$comparator = new Comparator();
$schemaDiff = $comparator->compare($fromSchema, $toSchema);
@@ -910,4 +904,35 @@ class SchemaTool
return $schemaDiff->toSql($this->platform);
}
/**
* Creates the schema from the database, ensuring tables from the target schema are whitelisted for comparison.
*/
private function createSchemaForComparison(Schema $toSchema) : Schema
{
$connection = $this->em->getConnection();
$schemaManager = $connection->getSchemaManager();
// backup schema assets filter
$config = $connection->getConfiguration();
$previousFilter = $config->getSchemaAssetsFilter();
if ($previousFilter === null) {
return $schemaManager->createSchema();
}
// whitelist assets we already know about in $toSchema, use the existing filter otherwise
$config->setSchemaAssetsFilter(static function ($asset) use ($previousFilter, $toSchema) : bool {
$assetName = $asset instanceof AbstractAsset ? $asset->getName() : $asset;
return $toSchema->hasTable($assetName) || $toSchema->hasSequence($assetName) || $previousFilter($asset);
});
try {
return $schemaManager->createSchema();
} finally {
// restore schema assets filter
$config->setSchemaAssetsFilter($previousFilter);
}
}
}
+1 -1
View File
@@ -37,7 +37,7 @@ class Version
/**
* Current Doctrine Version
*/
const VERSION = '2.7.0';
const VERSION = '2.7.1-DEV';
/**
* Compares a Doctrine version with the current one.
@@ -227,23 +227,6 @@ abstract class AbstractCollectionPersisterTest extends OrmTestCase
$this->assertFalse($persister->containsKey($collection, 0));
}
public function testInvokeRemoveElement()
{
$entity = new State("Foo");
$element = new State("Bar");
$persister = $this->createPersisterDefault();
$collection = $this->createCollection($entity);
$this->em->getUnitOfWork()->registerManaged($entity, ['id'=>1], ['id'=>1, 'name'=>'Foo']);
$this->collectionPersister->expects($this->once())
->method('removeElement')
->with($this->equalTo($collection), $this->equalTo($element))
->will($this->returnValue(false));
$this->assertFalse($persister->removeElement($collection, $element));
}
public function testInvokeGet()
{
$entity = new State("Foo");
@@ -28,6 +28,12 @@ class EntityManagerDecoratorTest extends TestCase
*/
private $wrapped;
/** @before */
public function ignoreDeprecationMessagesFromDoctrinePersistence() : void
{
$this->ignoreDeprecationMessage('The Doctrine\Common\Persistence\ObjectManagerDecorator class is deprecated since doctrine/persistence 1.3 and will be removed in 2.0. Use \Doctrine\Persistence\ObjectManagerDecorator instead.');
}
public function setUp()
{
$this->wrapped = $this->createMock(EntityManagerInterface::class);
File diff suppressed because it is too large Load Diff
@@ -1095,11 +1095,25 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->loadFixturesCountries();
$this->_em->createQuery("SELECT PARTIAL c.{id} FROM Doctrine\Tests\Models\Cache\Country c")
->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->setCacheable(true)
->getResult();
}
/**
* @expectedException \Doctrine\ORM\Cache\CacheException
* @expectedExceptionMessage Second level cache does not support partial entities.
*/
public function testCacheableForcePartialLoadHintQueryException()
{
$this->evictRegions();
$this->loadFixturesCountries();
$this->_em->createQuery('SELECT c FROM Doctrine\Tests\Models\Cache\Country c')
->setCacheable(true)
->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->getResult();
}
/**
* @expectedException \Doctrine\ORM\Cache\CacheException
* @expectedExceptionMessage Second-level cache query supports only select statements.
@@ -14,9 +14,6 @@ class DDC2138Test extends OrmFunctionalTestCase
public function testForeignKeyOnSTIWithMultipleMapping()
{
$em = $this->_em;
if (! $em->getConnection()->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$this->markTestSkipped('Platform does not support foreign keys.');
}
$schemaTool = new SchemaTool($em);
$classes = [
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace tests\Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Cache\Region\DefaultMultiGetRegion;
use Doctrine\Tests\Models\Cache\Attraction;
use Doctrine\Tests\Models\Cache\Bar;
use Doctrine\Tests\ORM\Functional\SecondLevelCacheAbstractTest;
class DDC7969Test extends SecondLevelCacheAbstractTest
{
public function testChildEntityRetrievedFromCache() : void
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesAttractions();
// Entities are already cached due to fixtures - hence flush before testing
$region = $this->cache->getEntityCacheRegion(Attraction::class);
if ($region instanceof DefaultMultiGetRegion) {
$region->getCache()->flushAll();
}
/** @var Bar $bar */
$bar = $this->attractions[0];
$repository = $this->_em->getRepository(Bar::class);
$this->assertFalse($this->cache->containsEntity(Bar::class, $bar->getId()));
$this->assertFalse($this->cache->containsEntity(Attraction::class, $bar->getId()));
$repository->findOneBy([
'name' => $bar->getName(),
]);
$this->assertTrue($this->cache->containsEntity(Bar::class, $bar->getId()));
$repository->findOneBy([
'name' => $bar->getName(),
]);
// One hit for entity cache, one hit for query cache
$this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
}
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @group GH7505
*/
final class GH7505Test extends OrmFunctionalTestCase
{
/**
* {@inheritDoc}
*/
protected function setUp() : void
{
parent::setUp();
$this->setUpEntitySchema([
GH7505AbstractResponse::class,
GH7505ArrayResponse::class,
GH7505TextResponse::class,
]);
}
public function testSimpleArrayTypeHydratedCorrectly() : void
{
$arrayResponse = new GH7505ArrayResponse();
$this->_em->persist($arrayResponse);
$textResponse = new GH7505TextResponse();
$this->_em->persist($textResponse);
$this->_em->flush();
$this->_em->clear();
$repository = $this->_em->getRepository(GH7505AbstractResponse::class);
/** @var GH7505ArrayResponse $arrayResponse */
$arrayResponse = $repository->find($arrayResponse->id);
self::assertSame([], $arrayResponse->value);
/** @var GH7505TextResponse $textResponse */
$textResponse = $repository->find($textResponse->id);
self::assertNull($textResponse->value);
}
}
/**
* @Entity()
* @Table(name="gh7505_responses")
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({
* "array" = GH7505ArrayResponse::class,
* "text" = GH7505TextResponse::class,
* })
*/
abstract class GH7505AbstractResponse
{
/**
* @Id @GeneratedValue
* @Column(type="integer")
*/
public $id;
}
/**
* @Entity()
*/
class GH7505ArrayResponse extends GH7505AbstractResponse
{
/**
* @Column(name="value_array", type="simple_array")
* @var array
*/
public $value = [];
}
/**
* @Entity()
*/
class GH7505TextResponse extends GH7505AbstractResponse
{
/**
* @Column(name="value_string", type="string")
* @var string|null
*/
public $value;
}
@@ -1,57 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @group GH7841
*/
class GH7841Test extends OrmFunctionalTestCase
{
public function testForeignKeysNotCompare() : void
{
if ($this->_em->getConnection()->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$this->markTestSkipped('Test for platforms without foreign keys support');
}
$class = $this->_em->getClassMetadata(GH7841Child::class);
$this->_schemaTool->updateSchema([$class], true);
$diff = $this->_schemaTool->getUpdateSchemaSql([$class], true);
self::assertEmpty($diff);
$this->_schemaTool->dropSchema([$class]);
}
}
/**
* @Entity
*/
class GH7841Parent
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @OneToMany(targetEntity=GH7841Child::class, mappedBy="parent") */
public $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
}
/**
* @Entity
*/
class GH7841Child
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @ManyToOne(targetEntity=GH7841Parent::class) */
public $parent;
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Tests\OrmFunctionalTestCase;
use function array_values;
/**
* @group gh7864
*/
class GH7864Test extends OrmFunctionalTestCase
{
protected function setUp() : void
{
parent::setup();
$this->setUpEntitySchema(
[
GH7864User::class,
GH7864Tweet::class,
]
);
}
public function testExtraLazyRemoveElement()
{
$user = new GH7864User();
$user->name = 'test';
$tweet1 = new GH7864Tweet();
$tweet1->content = 'Hello World!';
$user->addTweet($tweet1);
$tweet2 = new GH7864Tweet();
$tweet2->content = 'Goodbye, and thanks for all the fish';
$user->addTweet($tweet2);
$this->_em->persist($user);
$this->_em->persist($tweet1);
$this->_em->persist($tweet2);
$this->_em->flush();
$this->_em->clear();
$user = $this->_em->find(GH7864User::class, $user->id);
$tweet = $this->_em->find(GH7864Tweet::class, $tweet1->id);
$user->tweets->removeElement($tweet);
$tweets = $user->tweets->map(static function (GH7864Tweet $tweet) {
return $tweet->content;
});
$this->assertEquals(['Goodbye, and thanks for all the fish'], array_values($tweets->toArray()));
}
}
/**
* @Entity
*/
class GH7864User
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="string") */
public $name;
/** @OneToMany(targetEntity="GH7864Tweet", mappedBy="user", fetch="EXTRA_LAZY") */
public $tweets;
public function __construct()
{
$this->tweets = new ArrayCollection();
}
public function addTweet(GH7864Tweet $tweet)
{
$tweet->user = $this;
$this->tweets->add($tweet);
}
}
/**
* @Entity
*/
class GH7864Tweet
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="string") */
public $content;
/** @ManyToOne(targetEntity="GH7864User", inversedBy="tweets") */
public $user;
}
@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\OrmFunctionalTestCase;
use function array_filter;
use function current;
use function method_exists;
use function sprintf;
use function strpos;
/** @group GH7875 */
final class GH7875Test extends OrmFunctionalTestCase
{
/** @after */
public function cleanUpSchema() : void
{
$connection = $this->_em->getConnection();
$connection->exec('DROP TABLE IF EXISTS gh7875_my_entity');
$connection->exec('DROP TABLE IF EXISTS gh7875_my_other_entity');
if ($connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
$connection->exec('DROP SEQUENCE IF EXISTS gh7875_my_entity_id_seq');
$connection->exec('DROP SEQUENCE IF EXISTS gh7875_my_other_entity_id_seq');
}
}
/**
* @param string[] $sqls
*
* @return string[]
*/
private function filterCreateTable(array $sqls, string $tableName) : array
{
return array_filter($sqls, static function (string $sql) use ($tableName) : bool {
return strpos($sql, sprintf('CREATE TABLE %s (', $tableName)) === 0;
});
}
public function testUpdateSchemaSql() : void
{
$classes = [$this->_em->getClassMetadata(GH7875MyEntity::class)];
$tool = new SchemaTool($this->_em);
$sqls = $this->filterCreateTable($tool->getUpdateSchemaSql($classes), 'gh7875_my_entity');
self::assertCount(1, $sqls);
$this->_em->getConnection()->exec(current($sqls));
$sqls = array_filter($tool->getUpdateSchemaSql($classes), static function (string $sql) : bool {
return strpos($sql, ' gh7875_my_entity ') !== false;
});
self::assertSame([], $sqls);
$classes[] = $this->_em->getClassMetadata(GH7875MyOtherEntity::class);
$sqls = $tool->getUpdateSchemaSql($classes);
self::assertCount(0, $this->filterCreateTable($sqls, 'gh7875_my_entity'));
self::assertCount(1, $this->filterCreateTable($sqls, 'gh7875_my_other_entity'));
}
/**
* @return array<array<string|callable|null>>
*/
public function provideUpdateSchemaSqlWithSchemaAssetFilter() : array
{
return [
['/^(?!my_enti)/', null],
[
null,
static function ($assetName) : bool {
return $assetName !== 'gh7875_my_entity';
},
],
];
}
/** @dataProvider provideUpdateSchemaSqlWithSchemaAssetFilter */
public function testUpdateSchemaSqlWithSchemaAssetFilter(?string $filterRegex, ?callable $filterCallback) : void
{
if ($filterRegex && ! method_exists(Configuration::class, 'setFilterSchemaAssetsExpression')) {
self::markTestSkipped(sprintf('Test require %s::setFilterSchemaAssetsExpression method', Configuration::class));
}
$classes = [$this->_em->getClassMetadata(GH7875MyEntity::class)];
$tool = new SchemaTool($this->_em);
$tool->createSchema($classes);
$config = $this->_em->getConnection()->getConfiguration();
if ($filterRegex) {
$config->setFilterSchemaAssetsExpression($filterRegex);
} else {
$config->setSchemaAssetsFilter($filterCallback);
}
$previousFilter = $config->getSchemaAssetsFilter();
$sqls = $tool->getUpdateSchemaSql($classes);
$sqls = array_filter($sqls, static function (string $sql) : bool {
return strpos($sql, ' gh7875_my_entity ') !== false;
});
self::assertCount(0, $sqls);
self::assertSame($previousFilter, $config->getSchemaAssetsFilter());
}
}
/**
* @Entity
* @Table(name="gh7875_my_entity")
*/
class GH7875MyEntity
{
/** @Id @Column(type="integer") @GeneratedValue(strategy="AUTO") */
public $id;
}
/**
* @Entity
* @Table(name="gh7875_my_other_entity")
*/
class GH7875MyOtherEntity
{
/** @Id @Column(type="integer") @GeneratedValue(strategy="AUTO") */
public $id;
}
@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH8031Test extends OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
$this->setUpEntitySchema([
GH8031Invoice::class,
]);
}
public function testEntityIsFetched()
{
$entity = new GH8031Invoice(new GH8031InvoiceCode(1, 2020, new GH8031Nested(10)));
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
/** @var GH8031Invoice $fetched */
$fetched = $this->_em->find(GH8031Invoice::class, $entity->getId());
$this->assertInstanceOf(GH8031Invoice::class, $fetched);
$this->assertSame(1, $fetched->getCode()->getNumber());
$this->assertSame(2020, $fetched->getCode()->getYear());
$this->_em->clear();
$this->assertCount(
1,
$this->_em->getRepository(GH8031Invoice::class)->findBy([], ['code.number' => 'ASC'])
);
}
public function testEmbeddableWithAssociationNotAllowed()
{
$cm = $this->_em->getClassMetadata(GH8031EmbeddableWithAssociation::class);
$this->assertArrayHasKey('invoice', $cm->associationMappings);
$cm = $this->_em->getClassMetadata(GH8031Invoice::class);
$this->assertCount(0, $cm->associationMappings);
}
}
/**
* @Embeddable
*/
class GH8031EmbeddableWithAssociation
{
/** @ManyToOne(targetEntity=GH8031Invoice::class) */
public $invoice;
}
/**
* @Embeddable
*/
class GH8031Nested
{
/**
* @Column(type="integer", name="number", length=6)
* @var int
*/
protected $number;
public function __construct(int $number)
{
$this->number = $number;
}
public function getNumber() : int
{
return $this->number;
}
}
/**
* @Embeddable
*/
class GH8031InvoiceCode extends GH8031AbstractYearSequenceValue
{
}
/**
* @Embeddable
*/
abstract class GH8031AbstractYearSequenceValue
{
/**
* @Column(type="integer", name="number", length=6)
* @var int
*/
protected $number;
/**
* @Column(type="smallint", name="year", length=4)
* @var int
*/
protected $year;
/** @Embedded(class=GH8031Nested::class) */
protected $nested;
public function __construct(int $number, int $year, GH8031Nested $nested)
{
$this->number = $number;
$this->year = $year;
$this->nested = $nested;
}
public function getNumber() : int
{
return $this->number;
}
public function getYear() : int
{
return $this->year;
}
}
/**
* @Entity
*/
class GH8031Invoice
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
private $id;
/**
* @Embedded(class=GH8031InvoiceCode::class)
* @var GH8031InvoiceCode
*/
private $code;
/** @Embedded(class=GH8031EmbeddableWithAssociation::class) */
private $embeddedAssoc;
public function __construct(GH8031InvoiceCode $code)
{
$this->code = $code;
}
public function getId()
{
return $this->id;
}
public function getCode() : GH8031InvoiceCode
{
return $this->code;
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @group GH8055
*/
final class GH8055Test extends OrmFunctionalTestCase
{
/**
* {@inheritDoc}
*/
protected function setUp() : void
{
parent::setUp();
$this->setUpEntitySchema([
GH8055BaseClass::class,
GH8055SubClass::class,
]);
}
public function testNumericDescriminatorColumn() : void
{
$entity = new GH8055SubClass();
$entity->value = 'test';
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$repository = $this->_em->getRepository(GH8055SubClass::class);
$hydrated = $repository->find($entity->id);
self::assertSame('test', $hydrated->value);
}
}
/**
* @Entity()
* @Table(name="gh8055")
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="integer")
* @DiscriminatorMap({
* "1" = GH8055BaseClass::class,
* "2" = GH8055SubClass::class
* })
*/
class GH8055BaseClass
{
/**
* @Id @GeneratedValue
* @Column(type="integer")
*/
public $id;
}
/**
* @Entity()
*/
class GH8055SubClass extends GH8055BaseClass
{
/**
* @Column(name="test", type="string")
* @var string
*/
public $value;
}
@@ -328,6 +328,11 @@ class ValueObjectsTest extends OrmFunctionalTestCase
['DDCNestingEmbeddable1', 'DDCNestingEmbeddable4'],
];
}
public function testEmbeddableIsNotTransient()
{
$this->assertFalse($this->_em->getMetadataFactory()->isTransient(DDC93Address::class));
}
}
@@ -134,6 +134,8 @@ class LanguageRecognitionTest extends OrmTestCase
['SELECT \'foo\' AS foo\\\\bar FROM Doctrine\Tests\Models\CMS\CmsUser u'],
['SELECT \'foo\' AS foo: FROM Doctrine\Tests\Models\CMS\CmsUser u'],
['SELECT \'foo\' AS foo:bar FROM Doctrine\Tests\Models\CMS\CmsUser u'],
['0'],
];
}
@@ -134,6 +134,24 @@ class ParserTest extends OrmTestCase
];
}
/**
* PHP 7.4 would fail with Notice: Trying to access array offset on value of type null.
*
* @see https://github.com/doctrine/orm/pull/7934
*
* @group GH7934
*/
public function testNullLookahead() : void
{
$query = new Query($this->_getTestEntityManager());
$query->setDQL('SELECT CURRENT_TIMESTAMP()');
$parser = new Parser($query);
$this->expectException(QueryException::class);
$parser->match(Lexer::T_SELECT);
}
private function createParser($dql)
{
$query = new Query($this->_getTestEntityManager());
@@ -420,4 +420,13 @@ class QueryTest extends OrmTestCase
self::assertEmpty($query->getResult());
}
/** @group 7982 */
public function testNonExistentExecutor()
{
$this->expectException(QueryException::class);
$this->expectExceptionMessage('[Syntax Error] line 0, col -1: Error: Expected SELECT, UPDATE or DELETE, got end of string.');
$query = $this->_em->createQuery('0')->execute();
}
}
@@ -3,8 +3,8 @@
namespace Doctrine\Tests\ORM\Tools\Console;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Version;
use Doctrine\Tests\DoctrineTestCase;
use PackageVersions\Versions;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\HelperSet;
@@ -21,7 +21,7 @@ final class ConsoleRunnerTest extends DoctrineTestCase
$app = ConsoleRunner::createApplication($helperSet);
self::assertSame($helperSet, $app->getHelperSet());
self::assertEquals(Version::VERSION, $app->getVersion());
self::assertSame(Versions::getVersion('doctrine/orm'), $app->getVersion());
self::assertTrue($app->has('dbal:import'));
self::assertTrue($app->has('dbal:reserved-words'));
@@ -13,6 +13,7 @@ use Doctrine\Tests\Models\DDC2372\DDC2372Admin;
use Doctrine\Tests\Models\DDC2372\DDC2372User;
use Doctrine\Tests\OrmTestCase;
use Doctrine\Tests\VerifyDeprecations;
use ReflectionClass;
class EntityGeneratorTest extends OrmTestCase
{
@@ -327,6 +328,25 @@ class EntityGeneratorTest extends OrmTestCase
$this->assertEquals($isbnMetadata->name, $reflParameters[0]->getClass()->name);
}
public function testBooleanDefaultValue()
{
$metadata = $this->generateBookEntityFixture(['isbn' => $this->generateIsbnEmbeddableFixture()]);
$metadata->mapField(['fieldName' => 'foo', 'type' => 'boolean', 'options' => ['default' => '1']]);
$testEmbeddableMetadata = $this->generateTestEmbeddableFixture();
$this->mapEmbedded('testEmbedded', $metadata, $testEmbeddableMetadata);
$this->_generator->writeEntityClass($metadata, $this->_tmpDir);
$this->assertFileExists($this->_tmpDir . '/' . $this->_namespace . '/EntityGeneratorBook.php~');
$book = $this->newInstance($metadata);
$reflClass = new ReflectionClass($metadata->name);
$this->assertTrue($book->getfoo());
}
public function testEntityUpdatingWorks()
{
$metadata = $this->generateBookEntityFixture(['isbn' => $this->generateIsbnEmbeddableFixture()]);
@@ -9,7 +9,6 @@ use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\ORM\Mapping\Driver\YamlDriver;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\Version;
use Doctrine\Tests\OrmTestCase;
use Doctrine\Tests\VerifyDeprecations;
@@ -22,10 +21,6 @@ class SetupTest extends OrmTestCase
public function setUp()
{
if (strpos(Version::VERSION, "DEV") === false) {
$this->markTestSkipped("Test only runs in a dev-installation from Github");
}
$this->originalAutoloaderCount = count(spl_autoload_functions());
$this->originalIncludePath = get_include_path();
}
+16 -1
View File
@@ -1,10 +1,12 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests;
use function set_error_handler;
use const E_USER_DEPRECATED;
use function in_array;
use function set_error_handler;
trait VerifyDeprecations
{
@@ -14,6 +16,9 @@ trait VerifyDeprecations
/** @var string[] */
private $actualDeprecations = [];
/** @var string[] */
private $ignoredDeprecations = [];
/** @var callable|null */
private $originalHandler;
@@ -22,9 +27,14 @@ trait VerifyDeprecations
{
$this->actualDeprecations = [];
$this->expectedDeprecations = [];
$this->ignoredDeprecations = [];
$this->originalHandler = set_error_handler(
function (int $errorNumber, string $errorMessage) : void {
if (in_array($errorMessage, $this->ignoredDeprecations, true)) {
return;
}
$this->actualDeprecations[] = $errorMessage;
},
E_USER_DEPRECATED
@@ -52,6 +62,11 @@ trait VerifyDeprecations
);
}
protected function ignoreDeprecationMessage(string $message) : void
{
$this->ignoredDeprecations[] = $message;
}
protected function expectDeprecationMessage(string $message) : void
{
$this->expectedDeprecations[] = $message;