Compare commits

..

56 Commits

Author SHA1 Message Date
Luís Cobucci 16751d210f Bump up version 2019-11-18 12:06:51 +01:00
Luís Cobucci 686f508576 Merge pull request #7905 from lcobucci/7890-paginator-objecti
[Paginator] Fix type conversion during hydration of pagination limit subquery
2019-11-18 10:50:54 +01:00
Luís Cobucci 00ef1eba90 Add paginator query hint to force type conversion
We're keeping a BC layer in the hydrator, which prevents type conversion
in scalar results.

This makes bypasses such layer in order to always convert the identifier
types when limiting the result set during a pagination.

The main goal here is to keep the conversion DB->PHP inside of the
hydrator components.
2019-11-18 10:27:10 +01:00
Gabriel Ostrolucký 3843eee5cb [Paginator] Add test case for regression with custom id
Co-authored-by: Alexei Korolev <alexei.korolev@gmail.com>
2019-11-18 10:27:10 +01:00
Luís Cobucci 6a827d5b61 Merge pull request #7861 from ferrastas/bug_removing_collection
Delete statements will not be created using `clear`
2019-11-15 22:58:31 +01:00
Gabriel Ostrolucký 7d77984306 Restore ability to clear deferred explicit tracked collections
This was regression from #7862 which tried to respect tracking config
when clearing collections, but this logic can happen in UOW only,
PersistentCollection::clear is triggered too early to know what
is (going to be) persisted.

Fixes #7862
2019-11-15 22:49:06 +01:00
Ferran Vidal ec93014713 Delete statements will not be created using clear. 2019-11-15 22:43:53 +01:00
Luís Cobucci c83094bde0 Merge pull request #7684 from rharink/2.6
only replace '_id' at end of columnName
2019-11-15 16:50:16 +01:00
Robert den Harink 982d1519db only replace '_id' at end of columnName 2019-11-15 16:36:48 +01:00
Luís Cobucci 855244fd10 Merge pull request #7865 from Ocramius/fix/#7837-paginate-with-custom-identifier-types-even-with-cached-dql-parsing
#7837 paginate with custom identifier types even with enabled DQL query cache
2019-11-15 11:08:22 +01:00
Guilherme Blanco c62977412c Merge pull request #7869 from BenMorel/patch-4
UnitOfWork::clear() misses $eagerLoadingEntities
2019-11-15 00:27:08 -05:00
Gabriel Ostrolucký 98e557b68e Improve assertion failure message for testWillFindSongsInPaginatorEvenWithCachedQueryParsing 2019-11-14 23:37:13 +01:00
Gabriel Ostrolucký 1dde2c9e8e Add test case verifying eager loads are clear
Otherwise, getClassMetadata would be triggered more times
2019-11-14 22:17:06 +01:00
Marco Pivetta adfd010a78 Merge pull request #7889 from ajgarlag/hotfix/fix-tests-with-dbal-2.10
Use quoted collation declaration when available.
2019-11-05 15:52:18 +01:00
Antonio J. García Lagar 1bc4e1f594 Use quoted collation declaration when available. 2019-11-05 14:58:24 +01:00
Marco Pivetta 21680df9bd Merge pull request #7884 from rogeriolino/patch-1
[Documentation] Advanced field value... - missing entity alias
2019-11-05 01:23:52 +01:00
Rogério Alencar Lino Filho 19aa3c125c missing entity alias 2019-10-31 18:20:58 -03:00
Marco Pivetta e9e012a037 Merge pull request #7880 from kuraobi/update-doc-dql-qb
Update documentation to recommend DQL over QueryBuilder when possible
2019-10-29 19:04:03 +01:00
Mathieu Lemoine d1db0655ac Update documentation to recommend DQL over QueryBuilder when possible 2019-10-29 16:26:17 +01:00
Guilherme Blanco 9fef4e86e4 Merge pull request #7871 from BenMorel/2.6
AbstractQuery::getSingleScalarResult() throws exception when no result
2019-10-18 10:37:53 -04:00
Benjamin Morel 4781dc03e9 AbstractQuery::getSingleScalarResult() throws exception when no result 2019-10-16 20:41:00 +02:00
Benjamin Morel cc5f84ac22 UnitOfWork::clear() misses $eagerLoadingEntities 2019-10-16 10:11:55 +02:00
Marco Pivetta 023e94661a #7837 force expiry of query cache when WhereInWalker is being used
In order to figure out the paginated query identifier type, we would
have to parse the DQL query into an AST+SQL anyway, so we'd have
to re-parse it manually: instead of doing that, we can force the
`WhereInWalker` to be reached at all times by forcing the
`$whereInQuery` to use no query cache.

While it is a sad performance regression, it is also not a
noticeable one, since we'll be performing an `O(1)` operation
around an I/O one (query execution, in this case).
2019-10-10 18:23:31 +02:00
Marco Pivetta b59fc23f86 #7837 reproduced issue: DQL caching prevents WhereInWalker run
Since `WhereInWalker` does not run, query parameters are not translated
from their in-memory type to the expected SQL type when the paginator
is run again with the same DQL string. This is an architectural
issue, since (for the sake of simplicity) we moved parameter
translation into the SQL walker, we didn't consider that SQL
walkers only act when no cache is in place. The translatio
needs to be moved into the paginator logic again.
2019-10-10 17:30:43 +02:00
Luís Cobucci d71dd5d94f Bump up version 2019-10-08 20:04:50 +02:00
Luís Cobucci 63513e9a05 Merge pull request #7856 from lcobucci/fix/underscore-strategy-dont-work-with-numbers
Fix underscore naming strategy behaviour with numbers
2019-10-08 12:06:24 +02:00
Luís Cobucci c802bc46a5 Format NamingStrategyTest according to our CS 2019-10-08 11:56:11 +02:00
Luís Cobucci 506bf0ee12 Allow numbers in property names on underscore naming strategy 2019-10-08 11:56:11 +02:00
Luís Cobucci a36809db72 Merge pull request #7851 from peter-gribanov/reflFieldValue2.6
Remove not used variable $reflFieldValue in ObjectHydrator
2019-10-04 07:50:26 +02:00
Peter Gribanov 5b00d7ba5e remove not used variable $reflFieldValue in ObjectHydrator 2019-10-03 11:14:24 +03:00
Luís Cobucci b22604352d Merge pull request #7849 from axi/patch-1
Mention SQL logger impact on batch processing
2019-10-02 14:14:48 +02:00
axi 00c6b1bc60 Update batch-processing.rst
Clarify note
2019-10-02 14:00:06 +02:00
Luís Cobucci 4b0d86ee92 Merge pull request #7842 from vpArth-php/gh-7841
#7841 SchemaTool generates extra diff for platforms without FK support
2019-10-02 10:50:42 +02:00
Alexander Deider 3707c39124 #7841 SchemaTool generates extra diff for platforms without FK support 2019-10-02 15:35:59 +07:00
Luís Cobucci fe72b00df2 Merge pull request #7850 from nlx-lars/nlx-lars/bugfix/7836-dont-merge-criteria
Don't merge PersistentCollection orderBy with criteria in matching()
2019-10-02 10:02:38 +02:00
Lars Lauger 79a7ecc92f Don't merge PersistentCollection orderBy with criteria in matching()
If no orderings are given to PersistentCollection::matching(), the
orderBy annotation will be used if present. If the criteria contains
orderings, those will be used without merging them with the orderBy.

See #7836
2019-10-02 09:23:38 +02:00
Luís Cobucci 16df8bfe0d Merge pull request #7298 from dunglas/patch-2
Add a missing type in Query::getFirstResult PHPDoc
2019-10-02 04:27:19 +02:00
Kévin Dunglas b37ceaa9f7 Add a missing type in Query::getFirstResult and Query::getDQL 2019-10-02 04:13:42 +02:00
Luís Cobucci c41fdbce8a Merge pull request #7727 from madand/patch-1
[doc] Finish incomplete definition of class UTCDateTimeType
2019-10-02 04:11:20 +02:00
Luís Cobucci 7526adc80a Merge pull request #7443 from naitsirch/fix/issue6793
Added doc about exception in Query#getOneOrNullResult()
2019-10-02 04:07:25 +02:00
Andriy Kmit 766eb693fb Finish incomplete definition of class UTCDateTimeType 2019-10-02 03:57:06 +02:00
Luís Cobucci f9e2ae3488 Merge pull request #7667 from jschaedl/patch-1
Fixes example One-To-One, Self-referencing
2019-10-02 03:56:01 +02:00
Luís Cobucci 6bf2ff5d10 Merge pull request #7671 from jschaedl/patch-4
Added missing "the"
2019-10-02 03:45:50 +02:00
Jan Schädlich 27fcc01d81 Fixes example One-To-One, Self-referencing 2019-10-02 03:37:23 +02:00
Jan Schädlich 3ac1f8e680 Added missing "the" 2019-10-02 03:36:06 +02:00
Luís Cobucci b63db53552 Merge pull request #7764 from guillaume-a/7763
#7763 escape quotes in field comments
2019-10-02 02:56:05 +02:00
Guillaume Aveline bed8186573 Fix comment quoting in the EntityGenerator
Fixes: https://github.com/doctrine/orm/issues/7763
2019-10-02 02:42:09 +02:00
Luís Cobucci f08ff83d0a Merge pull request #7768 from mickaelandrieu/patch-1
EntityManagerHelper can't accept an array of paths
2019-10-01 22:51:13 +02:00
axi 7c8c0906be Update batch-processing.rst
Looking for a way to improve one of our bulk update treatment, I went back to this page then found elsewhere that setting logger to null was a really effective way to improve time and memory consumption. Might be a right place to state it ? Don't know if my edit style is ok
2019-10-01 17:46:09 +02:00
Grégoire Paris 167cb44ea1 Merge pull request #7742 from bocharsky-bw/patch-1
Start i var from 1 instead of 0
2019-09-28 18:54:56 +02:00
Mickaël Andrieu 5d74bdb240 Remove misleading documentation
EntityManagerHelper does not have a second argument, see
https://github.com/doctrine/orm/blob/ca38249f6c71bc1d1c355822040a1788e3eeebd6/lib/Doctrine/ORM/Tools/Console/Helper/EntityManagerHelper.php#L49
2019-09-28 12:32:09 +02:00
Luís Cobucci ca38249f6c Merge pull request #7838 from samnela/fix/name-classmetadata
Fix the name of ClassMetadata in documentation
2019-09-27 22:31:49 +02:00
Samuel NELA 6a74f373b9 Fix the name of ClassMetadata in documentation 2019-09-27 21:59:32 +02:00
naitsirch 1c45e1b744 Fixed grammatical mistake in doc
Co-Authored-By: Grégoire Paris <postmaster@greg0ire.fr>
2019-06-24 22:07:56 +02:00
Victor Bocharsky 5612790307 Start i var from 1 instead of 0
Because (0 % $batchSize) === 0 but we don't want to execute flush() and clear() on the first iteration.
2019-06-11 13:19:56 +03:00
naitsirch 17bc627bf2 Added hint about exception in Query#getOneOrNullResult()
When calling `Query#getOneOrNullResult()` and there are more than one
objects in the result an `NonUniqueResultException` is thrown.
This information was missing in the documentation about the query result
formats.

This commit addresses #6793.
2018-10-29 21:26:02 +01:00
38 changed files with 663 additions and 285 deletions
@@ -249,7 +249,7 @@ Example usage
$em->clear();
// Fetch the Location object
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();
/* @var Geo\ValueObject\Point */
+10 -2
View File
@@ -90,7 +90,10 @@ the UTC time at the time of the booking and the timezone the event happened in.
class UTCDateTimeType extends DateTimeType
{
static private $utc;
/**
* @var \DateTimeZone
*/
private static $utc;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
@@ -110,7 +113,7 @@ the UTC time at the time of the booking and the timezone the event happened in.
$converted = \DateTime::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
self::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC')
self::getUtc()
);
if (! $converted) {
@@ -123,6 +126,11 @@ the UTC time at the time of the booking and the timezone the event happened in.
return $converted;
}
private static function getUtc(): \DateTimeZone
{
return self::$utc ?: self::$utc = new \DateTimeZone('UTC');
}
}
This database type makes sure that every DateTime instance is always saved in UTC, relative
+1 -1
View File
@@ -286,7 +286,7 @@ below.
// ...
/**
* One Student has One Student.
* One Student has One Mentor.
* @OneToOne(targetEntity="Student")
* @JoinColumn(name="mentor_id", referencedColumnName="id")
*/
+11 -2
View File
@@ -16,6 +16,15 @@ especially what the strategies presented here provide help with.
operations.
.. note::
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
To avoid that you should disable it in the DBAL configuration:
.. code-block:: php
<?php
$em->getConnection()->getConfiguration()->setSQLLogger(null);
Bulk Inserts
------------
@@ -75,7 +84,7 @@ with the batching strategy that was already used for bulk inserts:
<?php
$batchSize = 20;
$i = 0;
$i = 1;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach ($iterableResult as $row) {
@@ -136,7 +145,7 @@ The following example shows how to do this:
<?php
$batchSize = 20;
$i = 0;
$i = 1;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
while (($row = $iterableResult->next()) !== false) {
@@ -996,8 +996,9 @@ the Query class. Here they are:
result contains more than one object, an ``NonUniqueResultException``
is thrown. If the result contains no objects, an ``NoResultException``
is thrown. The pure/mixed distinction does not apply.
- ``Query#getOneOrNullResult()``: Retrieve a single object. If no
object is found null will be returned.
- ``Query#getOneOrNullResult()``: Retrieve a single object. If the
result contains more than one object, a ``NonUniqueResultException``
is thrown. If no object is found null will be returned.
- ``Query#getArrayResult()``: Retrieves an array graph (a nested
array) that is largely interchangeable with the object graph
generated by ``Query#getResult()`` for read-only purposes.
+15
View File
@@ -198,6 +198,21 @@ No, it is not supported to sort by function in DQL. If you need this functionali
use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
starting with 1000 rows.
Is it better to write DQL or to generate it with the query builder?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The purpose of the ``QueryBuilder`` is to generate DQL dynamically,
which is useful when you have optional filters, conditional joins, etc.
But the ``QueryBuilder`` is not an alternative to DQL, it actually generates DQL
queries at runtime, which are then interpreted by Doctrine. This means that
using the ``QueryBuilder`` to build and run a query is actually always slower
than only running the corresponding DQL query.
So if you only need to generate a query and bind parameters to it,
you should use plain DQL, as this is a simpler and much more readable solution.
You should only use the ``QueryBuilder`` when you can't achieve what you want to do with a DQL query.
A Query fails, how can I debug it?
----------------------------------
+1 -1
View File
@@ -39,7 +39,7 @@ proper quoting of parameters.
<?php
namespace Example;
use Doctrine\ORM\Mapping\ClassMetaData,
use Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Query\Filter\SQLFilter;
class MyLocaleFilter extends SQLFilter
+10 -5
View File
@@ -9,6 +9,12 @@ programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other
as you want, or just pick a preferred one.
.. note::
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
You should still use plain DQL when you can, as it is simpler and more readable.
More about this in the :doc:`FAQ <faq>`_.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -80,7 +86,7 @@ Working with QueryBuilder
High level API methods
^^^^^^^^^^^^^^^^^^^^^^
To simplify even more the way you build a query in Doctrine, you can take
The most straightforward way to build a dynamic query with the ``QueryBuilder`` is by taking
advantage of Helper methods. For all base code, there is a set of
useful methods to simplify a programmer's life. To illustrate how
to work with them, here is the same example 6 re-written using
@@ -97,10 +103,9 @@ to work with them, here is the same example 6 re-written using
->orderBy('u.name', 'ASC');
``QueryBuilder`` helper methods are considered the standard way to
build DQL queries. Although it is supported, using string-based
queries should be avoided. You are greatly encouraged to use
``$qb->expr()->*`` methods. Here is a converted example 8 to
suggested standard way to build queries:
use the ``QueryBuilder``. The ``$qb->expr()->*`` methods can help you
build conditional expressions dynamically. Here is a converted example 8 to
suggested way to build queries with dynamic conditions:
.. code-block:: php
-9
View File
@@ -252,15 +252,6 @@ will output the SQL for the ran operation.
Before using the orm:schema-tool commands, remember to configure
your cli-config.php properly.
.. note::
When using the Annotation Mapping Driver you have to either setup
your autoloader in the cli-config.php correctly to find all the
entities, or you can use the second argument of the
``EntityManagerHelper`` to specify all the paths of your entities
(or mapping files), i.e.
``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);``
Entity Generation
-----------------
+1 -1
View File
@@ -156,7 +156,7 @@ wishes to be hydrated. Default result-types include:
- SQL to simple scalar result arrays
- SQL to a single result variable
Hydration to entities and arrays is one of most complex parts of Doctrine
Hydration to entities and arrays is one of the most complex parts of Doctrine
algorithm-wise. It can build results with for example:
- Single table selects
+3 -1
View File
@@ -800,7 +800,9 @@ DQL and its syntax as well as the Doctrine class can be found in
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
For programmatically building up queries based on conditions that
are only known at runtime, Doctrine provides the special
``Doctrine\ORM\QueryBuilder`` class. More information on
``Doctrine\ORM\QueryBuilder`` class. While this a powerful tool,
it also brings more complexity to your code compared to plain DQL,
so you should only use it when you need it. More information on
constructing queries with a QueryBuilder can be found
:doc:`in Query Builder chapter <query-builder>`.
+1 -2
View File
@@ -1210,8 +1210,7 @@ The console output of this script is then:
throw your ORM into the dumpster, because it doesn't support some
the more powerful SQL concepts.
Instead of handwriting DQL you can use the ``QueryBuilder`` retrieved
If you need to build your query dynamically, you can use the ``QueryBuilder`` retrieved
by calling ``$entityManager->createQueryBuilder()``. There are more
details about this in the relevant part of the documentation.
+2 -1
View File
@@ -822,8 +822,9 @@ abstract class AbstractQuery
*
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed The scalar result, or NULL if the query returned no result.
* @return mixed The scalar result.
*
* @throws NoResultException If the query returned no result.
* @throws NonUniqueResultException If the query result is not unique.
*/
public function getSingleScalarResult()
@@ -23,6 +23,7 @@ use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker;
use PDO;
use function array_map;
use function in_array;
@@ -351,13 +352,11 @@ abstract class AbstractHydrator
// WARNING: BC break! We know this is the desired behavior to type convert values, but this
// erroneous behavior exists since 2.0 and we're forced to keep compatibility.
if ( ! isset($cacheKeyInfo['isScalar'])) {
$dqlAlias = $cacheKeyInfo['dqlAlias'];
$type = $cacheKeyInfo['type'];
$fieldName = $dqlAlias . '_' . $fieldName;
$value = $type
? $type->convertToPHPValue($value, $this->_platform)
: $value;
if (! isset($cacheKeyInfo['isScalar'])) {
$type = $cacheKeyInfo['type'];
$value = $type ? $type->convertToPHPValue($value, $this->_platform) : $value;
$fieldName = $cacheKeyInfo['dqlAlias'] . '_' . $fieldName;
}
$rowData[$fieldName] = $value;
@@ -422,6 +421,12 @@ abstract class AbstractHydrator
'class' => new \ReflectionClass($mapping['className']),
];
case isset($this->_rsm->scalarMappings[$key], $this->_hints[LimitSubqueryWalker::FORCE_DBAL_TYPE_CONVERSION]):
return $this->_cache[$key] = [
'fieldName' => $this->_rsm->scalarMappings[$key],
'type' => Type::getType($this->_rsm->typeMappings[$key]),
'dqlAlias' => '',
];
case (isset($this->_rsm->scalarMappings[$key])):
return $this->_cache[$key] = [
'isScalar' => true,
@@ -422,7 +422,7 @@ class ObjectHydrator extends AbstractHydrator
$this->resultPointers[$dqlAlias] = $reflFieldValue[$index];
}
} else if ( ! $reflFieldValue) {
$reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
$this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
} else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
$reflFieldValue->setInitialized(true);
}
@@ -29,6 +29,7 @@ use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\MappingException;
use function preg_replace;
/**
* The DatabaseDriver reverse engineers the mapping metadata from a database.
@@ -548,7 +549,7 @@ class DatabaseDriver implements MappingDriver
// Replace _id if it is a foreignkey column
if ($fk) {
$columnName = str_replace('_id', '', $columnName);
$columnName = preg_replace('/_id$/', '', $columnName);
}
return Inflector::camelize($columnName);
@@ -20,17 +20,29 @@
namespace Doctrine\ORM\Mapping;
use const CASE_LOWER;
use const CASE_UPPER;
use function preg_replace;
use function strpos;
use function strrpos;
use function strtolower;
use function strtoupper;
use function substr;
/**
* Naming strategy implementing the underscore naming convention.
* Converts 'MyEntity' to 'my_entity' or 'MY_ENTITY'.
*
*
*
* @link www.doctrine-project.org
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class UnderscoreNamingStrategy implements NamingStrategy
{
private const DEFAULT_PATTERN = '/(?<=[a-z])([A-Z])/';
private const PATTERN_FOR_PROPERTIES = '/(?<=[a-z0-9])([A-Z])/';
/**
* @var integer
*/
@@ -57,7 +69,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
/**
* Sets string case CASE_LOWER | CASE_UPPER.
* Alphabetic characters converted to lowercase or uppercase.
*
*
* @param integer $case
*
* @return void
@@ -84,7 +96,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
*/
public function propertyToColumnName($propertyName, $className = null)
{
return $this->underscore($propertyName);
return $this->underscore($propertyName, self::PATTERN_FOR_PROPERTIES);
}
/**
@@ -108,7 +120,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
*/
public function joinColumnName($propertyName, $className = null)
{
return $this->underscore($propertyName) . '_' . $this->referenceColumnName();
return $this->underscore($propertyName, self::PATTERN_FOR_PROPERTIES) . '_' . $this->referenceColumnName();
}
/**
@@ -118,7 +130,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
{
return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity);
}
/**
* {@inheritdoc}
*/
@@ -127,15 +139,10 @@ class UnderscoreNamingStrategy implements NamingStrategy
return $this->classToTableName($entityName) . '_' .
($referencedColumnName ?: $this->referenceColumnName());
}
/**
* @param string $string
*
* @return string
*/
private function underscore($string)
private function underscore(string $string, string $pattern = self::DEFAULT_PATTERN) : string
{
$string = preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $string);
$string = preg_replace($pattern, '_$1', $string);
if ($this->case === CASE_UPPER) {
return strtoupper($string);
+2 -5
View File
@@ -25,7 +25,6 @@ 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;
/**
@@ -567,9 +566,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
if ($this->association['isOwningSide'] && $this->owner) {
$this->changed();
if (! $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingDeferredExplicit()) {
$uow->scheduleCollectionDeletion($this);
}
$uow->scheduleCollectionDeletion($this);
$this->takeSnapshot();
}
@@ -672,7 +669,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
$criteria = clone $criteria;
$criteria->where($expression);
$criteria->orderBy(array_merge($this->association['orderBy'] ?? [], $criteria->getOrderings()));
$criteria->orderBy($criteria->getOrderings() ?: $this->association['orderBy'] ?? []);
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
+4 -4
View File
@@ -145,7 +145,7 @@ final class Query extends AbstractQuery
/**
* Cached DQL query.
*
* @var string
* @var string|null
*/
private $_dql = null;
@@ -159,7 +159,7 @@ final class Query extends AbstractQuery
/**
* The first result to return (the "offset").
*
* @var integer
* @var int|null
*/
private $_firstResult = null;
@@ -587,7 +587,7 @@ final class Query extends AbstractQuery
/**
* Returns the DQL query that is represented by this query object.
*
* @return string DQL query.
* @return string|null
*/
public function getDQL()
{
@@ -640,7 +640,7 @@ final class Query extends AbstractQuery
* 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 query.
*
* @return integer The position of the first result.
* @return int|null The position of the first result.
*/
public function getFirstResult()
{
+2 -1
View File
@@ -23,6 +23,7 @@ use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Inflector\Inflector;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use function str_replace;
/**
* Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
@@ -1679,7 +1680,7 @@ public function __construct(<params>)
}
if (isset($fieldMapping['options']['comment']) && $fieldMapping['options']['comment']) {
$options[] = '"comment"="' . $fieldMapping['options']['comment'] .'"';
$options[] = '"comment"="' . str_replace('"', '""', $fieldMapping['options']['comment']) . '"';
}
if (isset($fieldMapping['options']['collation']) && $fieldMapping['options']['collation']) {
@@ -39,10 +39,9 @@ use Doctrine\ORM\Query\AST\SelectStatement;
*/
class LimitSubqueryWalker extends TreeWalkerAdapter
{
/**
* ID type hint.
*/
const IDENTIFIER_TYPE = 'doctrine_paginator.id.type';
public const IDENTIFIER_TYPE = 'doctrine_paginator.id.type';
public const FORCE_DBAL_TYPE_CONVERSION = 'doctrine_paginator.scalar_result.force_dbal_type_conversion';
/**
* Counter for generating unique order column aliases.
@@ -82,6 +81,8 @@ class LimitSubqueryWalker extends TreeWalkerAdapter
Type::getType($rootClass->fieldMappings[$identifier]['type'])
);
$this->_getQuery()->setHint(self::FORCE_DBAL_TYPE_CONVERSION, true);
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
$rootAlias,
@@ -166,6 +166,7 @@ class Paginator implements \Countable, \IteratorAggregate
$whereInQuery->setFirstResult(null)->setMaxResults(null);
$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
$whereInQuery->setCacheable($this->query->isCacheable());
$whereInQuery->expireQueryCache();
$result = $whereInQuery->getResult($this->query->getHydrationMode());
} else {
+5
View File
@@ -709,6 +709,11 @@ 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'])))
+29 -16
View File
@@ -46,6 +46,7 @@ use Doctrine\ORM\Utility\IdentifierFlattener;
use InvalidArgumentException;
use Throwable;
use UnexpectedValueException;
use function get_class;
/**
* The UnitOfWork is responsible for tracking changes to objects during an
@@ -380,7 +381,18 @@ class UnitOfWork implements PropertyChangedListener
try {
// Collection deletions (deletions of complete collections)
foreach ($this->collectionDeletions as $collectionToDelete) {
$this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
if (! $collectionToDelete instanceof PersistentCollection) {
$this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
continue;
}
// Deferred explicit tracked collections can be removed only when owning relation was persisted
$owner = $collectionToDelete->getOwner();
if ($this->em->getClassMetadata(get_class($owner))->isChangeTrackingDeferredImplicit() || $this->isScheduledForDirtyCheck($owner)) {
$this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
}
}
if ($this->entityInsertions) {
@@ -2483,22 +2495,23 @@ class UnitOfWork implements PropertyChangedListener
public function clear($entityName = null)
{
if ($entityName === null) {
$this->identityMap =
$this->entityIdentifiers =
$this->originalEntityData =
$this->entityChangeSets =
$this->entityStates =
$this->scheduledForSynchronization =
$this->entityInsertions =
$this->entityUpdates =
$this->entityDeletions =
$this->identityMap =
$this->entityIdentifiers =
$this->originalEntityData =
$this->entityChangeSets =
$this->entityStates =
$this->scheduledForSynchronization =
$this->entityInsertions =
$this->entityUpdates =
$this->entityDeletions =
$this->nonCascadedNewDetectedEntities =
$this->collectionDeletions =
$this->collectionUpdates =
$this->extraUpdates =
$this->readOnlyObjects =
$this->visitedCollections =
$this->orphanRemovals = [];
$this->collectionDeletions =
$this->collectionUpdates =
$this->extraUpdates =
$this->readOnlyObjects =
$this->visitedCollections =
$this->eagerLoadingEntities =
$this->orphanRemovals = [];
} else {
$this->clearIdentityMapForEntityName($entityName);
$this->clearEntityInsertionsForEntityName($entityName);
+1 -1
View File
@@ -35,7 +35,7 @@ class Version
/**
* Current Doctrine Version
*/
const VERSION = '2.6.4-DEV';
const VERSION = '2.6.5';
/**
* Compares a Doctrine version with the current one.
@@ -2,19 +2,24 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\Tests\DbalTypes\CustomIdObject;
use Doctrine\Tests\DbalTypes\CustomIdObjectType;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsEmail;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\Company\CompanyManager;
use Doctrine\Tests\Models\CustomType\CustomIdObjectTypeParent;
use Doctrine\Tests\Models\Pagination\Company;
use Doctrine\Tests\Models\Pagination\Department;
use Doctrine\Tests\Models\Pagination\Logo;
use Doctrine\Tests\Models\Pagination\User1;
use Doctrine\Tests\OrmFunctionalTestCase;
use ReflectionMethod;
use function iterator_to_array;
/**
* @group DDC-1613
@@ -26,6 +31,14 @@ class PaginationTest extends OrmFunctionalTestCase
$this->useModelSet('cms');
$this->useModelSet('pagination');
$this->useModelSet('company');
$this->useModelSet('custom_id_object_type');
if (DBALType::hasType(CustomIdObjectType::NAME)) {
DBALType::overrideType(CustomIdObjectType::NAME, CustomIdObjectType::class);
} else {
DBALType::addType(CustomIdObjectType::NAME, CustomIdObjectType::class);
}
parent::setUp();
$this->populate();
}
@@ -641,6 +654,27 @@ class PaginationTest extends OrmFunctionalTestCase
$this->assertEquals(1, $paginator->count());
}
/**
* @group GH-7890
*/
public function testCustomIdTypeWithoutOutputWalker()
{
$this->_em->persist(new CustomIdObjectTypeParent(new CustomIdObject('foo')));
$this->_em->flush();
$dql = 'SELECT p FROM Doctrine\Tests\Models\CustomType\CustomIdObjectTypeParent p';
$query = $this->_em->createQuery($dql);
$paginator = new Paginator($query, true);
$paginator->setUseOutputWalkers(false);
$matchedItems = iterator_to_array($paginator->getIterator());
self::assertCount(1, $matchedItems);
self::assertInstanceOf(CustomIdObjectTypeParent::class, $matchedItems[0]);
self::assertSame('foo', (string) $matchedItems[0]->id);
}
public function testCountQueryStripsParametersInSelect()
{
$query = $this->_em->createQuery(
@@ -5,6 +5,8 @@ namespace Doctrine\Tests\ORM\Functional\SchemaTool;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Tests\OrmFunctionalTestCase;
use Doctrine\Tests\Models;
use function method_exists;
use function sprintf;
class MySqlSchemaToolTest extends OrmFunctionalTestCase
{
@@ -28,15 +30,16 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
$tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes);
$collation = $this->getColumnCollationDeclarationSQL('utf8_unicode_ci');
$this->assertEquals("CREATE TABLE cms_groups (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(50) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[0]);
$this->assertEquals("CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[1]);
$this->assertEquals("CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[2]);
$this->assertEquals("CREATE TABLE cms_users_tags (user_id INT NOT NULL, tag_id INT NOT NULL, INDEX IDX_93F5A1ADA76ED395 (user_id), INDEX IDX_93F5A1ADBAD26311 (tag_id), PRIMARY KEY(user_id, tag_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[3]);
$this->assertEquals("CREATE TABLE cms_tags (id INT AUTO_INCREMENT NOT NULL, tag_name VARCHAR(50) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[4]);
$this->assertEquals("CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[5]);
$this->assertEquals("CREATE TABLE cms_emails (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(250) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[6]);
$this->assertEquals("CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[7]);
$this->assertEquals('CREATE TABLE cms_groups (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(50) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[0]);
$this->assertEquals('CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[1]);
$this->assertEquals('CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[2]);
$this->assertEquals('CREATE TABLE cms_users_tags (user_id INT NOT NULL, tag_id INT NOT NULL, INDEX IDX_93F5A1ADA76ED395 (user_id), INDEX IDX_93F5A1ADBAD26311 (tag_id), PRIMARY KEY(user_id, tag_id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[3]);
$this->assertEquals('CREATE TABLE cms_tags (id INT AUTO_INCREMENT NOT NULL, tag_name VARCHAR(50) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[4]);
$this->assertEquals('CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[5]);
$this->assertEquals('CREATE TABLE cms_emails (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(250) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[6]);
$this->assertEquals('CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[7]);
$this->assertEquals("ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id)", $sql[8]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)", $sql[9]);
$this->assertEquals("ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id)", $sql[10]);
@@ -48,6 +51,15 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
$this->assertEquals(15, count($sql));
}
private function getColumnCollationDeclarationSQL(string $collation) : string
{
if (method_exists($this->_em->getConnection()->getDatabasePlatform(), 'getColumnCollationDeclarationSQL')) {
return $this->_em->getConnection()->getDatabasePlatform()->getColumnCollationDeclarationSQL($collation);
}
return sprintf('COLLATE %s', $collation);
}
public function testGetCreateSchemaSql2()
{
$classes = [
@@ -56,9 +68,10 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
$tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes);
$collation = $this->getColumnCollationDeclarationSQL('utf8_unicode_ci');
$this->assertEquals(1, count($sql));
$this->assertEquals("CREATE TABLE decimal_model (id INT AUTO_INCREMENT NOT NULL, `decimal` NUMERIC(5, 2) NOT NULL, `high_scale` NUMERIC(14, 4) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[0]);
$this->assertEquals('CREATE TABLE decimal_model (id INT AUTO_INCREMENT NOT NULL, `decimal` NUMERIC(5, 2) NOT NULL, `high_scale` NUMERIC(14, 4) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[0]);
}
public function testGetCreateSchemaSql3()
@@ -69,9 +82,10 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
$tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes);
$collation = $this->getColumnCollationDeclarationSQL('utf8_unicode_ci');
$this->assertEquals(1, count($sql));
$this->assertEquals("CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[0]);
$this->assertEquals('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[0]);
}
/**
@@ -14,6 +14,9 @@ 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 = [
@@ -2,6 +2,9 @@
namespace Doctrine\Tests\ORM\Functional\Ticket;
use function method_exists;
use function sprintf;
class DDC2182Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function testPassColumnOptionsToJoinColumns()
@@ -16,11 +19,21 @@ class DDC2182Test extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->getClassMetadata(DDC2182OptionChild::class),
]
);
$collation = $this->getColumnCollationDeclarationSQL('utf8_unicode_ci');
$this->assertEquals("CREATE TABLE DDC2182OptionParent (id INT UNSIGNED NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[0]);
$this->assertEquals("CREATE TABLE DDC2182OptionChild (id VARCHAR(255) NOT NULL, parent_id INT UNSIGNED DEFAULT NULL, INDEX IDX_B314D4AD727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB", $sql[1]);
$this->assertEquals('CREATE TABLE DDC2182OptionParent (id INT UNSIGNED NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[0]);
$this->assertEquals('CREATE TABLE DDC2182OptionChild (id VARCHAR(255) NOT NULL, parent_id INT UNSIGNED DEFAULT NULL, INDEX IDX_B314D4AD727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 ' . $collation . ' ENGINE = InnoDB', $sql[1]);
$this->assertEquals("ALTER TABLE DDC2182OptionChild ADD CONSTRAINT FK_B314D4AD727ACA70 FOREIGN KEY (parent_id) REFERENCES DDC2182OptionParent (id)", $sql[2]);
}
private function getColumnCollationDeclarationSQL(string $collation) : string
{
if (method_exists($this->_em->getConnection()->getDatabasePlatform(), 'getColumnCollationDeclarationSQL')) {
return $this->_em->getConnection()->getDatabasePlatform()->getColumnCollationDeclarationSQL($collation);
}
return sprintf('COLLATE %s', $collation);
}
}
/**
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\DBAL\Schema\Table;
use Doctrine\Tests\ORM\Functional\DatabaseDriverTestCase;
/**
* Verifies that associations/columns with an inline '_id' get named properly
*
* Github issue: 7684
*/
class GH7684 extends DatabaseDriverTestCase
{
public function testIssue() : void
{
if (! $this->_em->getConnection()->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$this->markTestSkipped('Platform does not support foreign keys.');
}
$table1 = new Table('GH7684_identity_test_table');
$table1->addColumn('id', 'integer');
$table1->setPrimaryKey(['id']);
$table2 = new Table('GH7684_identity_test_assoc_table');
$table2->addColumn('id', 'integer');
$table2->addColumn('gh7684_identity_test_id', 'integer');
$table2->setPrimaryKey(['id']);
$table2->addForeignKeyConstraint('GH7684_identity_test', ['gh7684_identity_test_id'], ['id']);
$metadatas = $this->convertToClassMetadata([$table1, $table2]);
$metadata = $metadatas['Gh7684IdentityTestAssocTable'];
$this->assertArrayHasKey('gh7684IdentityTest', $metadata->associationMappings);
}
}
@@ -43,8 +43,23 @@ final class GH7761Test extends OrmFunctionalTestCase
$entity = $this->_em->find(GH7761Entity::class, 1);
self::assertCount(1, $entity->children);
}
/**
* @group GH-7862
*/
public function testCollectionClearDoesClearIfPersisted() : void
{
/** @var GH7761Entity $entity */
$entity = $this->_em->find(GH7761Entity::class, 1);
$entity->children->clear();
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$entity = $this->_em->find(GH7761Entity::class, 1);
self::assertCount(0, $entity->children);
}
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Cache\ClearableCache;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\StringType;
@@ -11,6 +12,7 @@ use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\Tests\OrmFunctionalTestCase;
use function array_map;
use function assert;
use function is_string;
use function iterator_to_array;
@@ -53,6 +55,9 @@ class GH7820Test extends OrmFunctionalTestCase
$this->setUpEntitySchema([GH7820Line::class]);
$this->_em->createQuery('DELETE FROM ' . GH7820Line::class . ' l')
->execute();
foreach (self::SONG as $index => $line) {
$this->_em->persist(new GH7820Line(GH7820LineText::fromText($line), $index));
}
@@ -73,6 +78,41 @@ class GH7820Test extends OrmFunctionalTestCase
}, iterator_to_array(new Paginator($query)))
);
}
/** @group GH7837 */
public function testWillFindSongsInPaginatorEvenWithCachedQueryParsing() : void
{
$cache = $this->_em->getConfiguration()
->getQueryCacheImpl();
assert($cache instanceof ClearableCache);
$cache->deleteAll();
$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))),
'Expected to return expected data before query cache is populated with DQL -> SQL translation. Were SQL parameters translated?'
);
$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))),
'Expected to return expected data even when DQL -> SQL translation is present in cache. Were SQL parameters translated again?'
);
}
}
/** @Entity */
@@ -0,0 +1,135 @@
<?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 GH7836
*/
class GH7836Test extends OrmFunctionalTestCase
{
protected function setUp() : void
{
parent::setUp();
$this->setUpEntitySchema([GH7836ParentEntity::class, GH7836ChildEntity::class]);
$parent = new GH7836ParentEntity();
$parent->addChild(100, 'foo');
$parent->addChild(100, 'bar');
$parent->addChild(200, 'baz');
$this->_em->persist($parent);
$this->_em->flush();
$this->_em->clear();
}
public function testMatchingRespectsCollectionOrdering() : void
{
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
assert($parent instanceof GH7836ParentEntity);
$children = $parent->getChildren()->matching(Criteria::create());
self::assertSame(100, $children[0]->position);
self::assertSame('bar', $children[0]->name);
self::assertSame(100, $children[1]->position);
self::assertSame('foo', $children[1]->name);
self::assertSame(200, $children[2]->position);
self::assertSame('baz', $children[2]->name);
}
public function testMatchingOverrulesCollectionOrdering() : void
{
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
assert($parent instanceof GH7836ParentEntity);
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC', 'name' => 'ASC']));
self::assertSame(200, $children[0]->position);
self::assertSame('baz', $children[0]->name);
self::assertSame(100, $children[1]->position);
self::assertSame('bar', $children[1]->name);
self::assertSame(100, $children[2]->position);
self::assertSame('foo', $children[2]->name);
}
public function testMatchingKeepsOrderOfCriteriaOrderingKeys() : void
{
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
assert($parent instanceof GH7836ParentEntity);
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['name' => 'ASC', 'position' => 'ASC']));
self::assertSame(100, $children[0]->position);
self::assertSame('bar', $children[0]->name);
self::assertSame(200, $children[1]->position);
self::assertSame('baz', $children[1]->name);
self::assertSame(100, $children[2]->position);
self::assertSame('foo', $children[2]->name);
}
}
/**
* @Entity
*/
class GH7836ParentEntity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
/**
* @OneToMany(targetEntity=GH7836ChildEntity::class, mappedBy="parent", fetch="EXTRA_LAZY", cascade={"persist"})
* @OrderBy({"position" = "ASC", "name" = "ASC"})
*/
private $children;
public function addChild(int $position, string $name) : void
{
$this->children[] = new GH7836ChildEntity($this, $position, $name);
}
public function getChildren() : PersistentCollection
{
return $this->children;
}
}
/**
* @Entity
*/
class GH7836ChildEntity
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
/** @Column(type="integer") */
public $position;
/** @Column(type="string") */
public $name;
/** @ManyToOne(targetEntity=GH7836ParentEntity::class, inversedBy="children") */
private $parent;
public function __construct(GH7836ParentEntity $parent, int $position, string $name)
{
$this->parent = $parent;
$this->position = $position;
$this->name = $name;
}
}
@@ -0,0 +1,57 @@
<?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,67 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Decorator\EntityManagerDecorator;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Mocks\ConnectionMock;
use Doctrine\Tests\Mocks\DriverMock;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\OrmTestCase;
/**
* @group GH7869
*/
class GH7869Test extends OrmTestCase
{
public function testDQLDeferredEagerLoad()
{
$decoratedEm = EntityManagerMock::create(new ConnectionMock([], new DriverMock()));
$em = $this->getMockBuilder(EntityManagerDecorator::class)
->setConstructorArgs([$decoratedEm])
->setMethods(['getClassMetadata'])
->getMock();
$em->expects($this->exactly(2))
->method('getClassMetadata')
->willReturnCallback([$decoratedEm, 'getClassMetadata']);
$hints = [
UnitOfWork::HINT_DEFEREAGERLOAD => true,
'fetchMode' => [GH7869Appointment::class => ['patient' => ClassMetadata::FETCH_EAGER]],
];
$uow = new UnitOfWork($em);
$uow->createEntity(GH7869Appointment::class, ['id' => 1, 'patient_id' => 1], $hints);
$uow->clear();
$uow->triggerEagerLoads();
}
}
/**
* @Entity
*/
class GH7869Appointment
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @OneToOne(targetEntity="GH7869Patient", inversedBy="appointment", fetch="EAGER") */
public $patient;
}
/**
* @Entity
*/
class GH7869Patient
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @OneToOne(targetEntity="GH7869Appointment", mappedBy="patient") */
public $appointment;
}
@@ -1,38 +1,33 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
use Doctrine\Tests\ORM\Mapping\NamingStrategy\JoinColumnClassNamingStrategy;
use Doctrine\Tests\OrmTestCase;
use const CASE_LOWER;
use const CASE_UPPER;
/**
* @group DDC-559
*/
class NamingStrategyTest extends OrmTestCase
{
/**
* @return DefaultNamingStrategy
*/
static private function defaultNaming()
private static function defaultNaming() : DefaultNamingStrategy
{
return new DefaultNamingStrategy();
}
/**
* @return UnderscoreNamingStrategy
*/
static private function underscoreNamingLower()
private static function underscoreNamingLower() : UnderscoreNamingStrategy
{
return new UnderscoreNamingStrategy(CASE_LOWER);
}
/**
* @return UnderscoreNamingStrategy
*/
static private function underscoreNamingUpper()
private static function underscoreNamingUpper() : UnderscoreNamingStrategy
{
return new UnderscoreNamingStrategy(CASE_UPPER);
}
@@ -40,113 +35,73 @@ class NamingStrategyTest extends OrmTestCase
/**
* Data Provider for NamingStrategy#classToTableName
*
* @return array
* @return array<NamingStrategy|string>
*/
static public function dataClassToTableName()
public static function dataClassToTableName() : array
{
return [
// DefaultNamingStrategy
[
self::defaultNaming(), 'SomeClassName',
'SomeClassName'
],
[
self::defaultNaming(), 'SomeClassName',
'\SomeClassName'
],
[
self::defaultNaming(), 'Name',
'\Some\Class\Name'
],
[self::defaultNaming(), 'SomeClassName', 'SomeClassName'],
[self::defaultNaming(), 'SomeClassName', '\SomeClassName'],
[self::defaultNaming(), 'Name', '\Some\Class\Name'],
// UnderscoreNamingStrategy
[
self::underscoreNamingLower(), 'some_class_name',
'\Name\Space\SomeClassName'
],
[
self::underscoreNamingLower(), 'name',
'\Some\Class\Name'
],
[
self::underscoreNamingUpper(), 'SOME_CLASS_NAME',
'\Name\Space\SomeClassName'
],
[
self::underscoreNamingUpper(), 'NAME',
'\Some\Class\Name'
],
[self::underscoreNamingLower(), 'some_class_name', '\Name\Space\SomeClassName'],
[self::underscoreNamingLower(), 'name', '\Some\Class\Name'],
[self::underscoreNamingUpper(), 'SOME_CLASS_NAME', '\Name\Space\SomeClassName'],
[self::underscoreNamingUpper(), 'NAME', '\Some\Class\Name'],
];
}
/**
* @dataProvider dataClassToTableName
*/
public function testClassToTableName(NamingStrategy $strategy, $expected, $className)
public function testClassToTableName(NamingStrategy $strategy, string $expected, string $className) : void
{
$this->assertEquals($expected, $strategy->classToTableName($className));
self::assertSame($expected, $strategy->classToTableName($className));
}
/**
* Data Provider for NamingStrategy#propertyToColumnName
*
* @return array
* @return array<NamingStrategy|string>
*/
static public function dataPropertyToColumnName()
public static function dataPropertyToColumnName() : array
{
return [
// DefaultNamingStrategy
[
self::defaultNaming(), 'someProperty',
'someProperty'
],
[
self::defaultNaming(), 'SOME_PROPERTY',
'SOME_PROPERTY'
],
[
self::defaultNaming(), 'some_property',
'some_property'
],
[self::defaultNaming(), 'someProperty', 'someProperty'],
[self::defaultNaming(), 'SOME_PROPERTY', 'SOME_PROPERTY'],
[self::defaultNaming(), 'some_property', 'some_property'],
[self::defaultNaming(), 'base64Encoded', 'base64Encoded'],
[self::defaultNaming(), 'base64_encoded', 'base64_encoded'],
// UnderscoreNamingStrategy
[
self::underscoreNamingLower(), 'some_property',
'someProperty'
],
[
self::underscoreNamingUpper(), 'SOME_PROPERTY',
'someProperty'
],
[
self::underscoreNamingUpper(), 'SOME_PROPERTY',
'some_property'
],
[
self::underscoreNamingUpper(), 'SOME_PROPERTY',
'SOME_PROPERTY'
],
[self::underscoreNamingLower(), 'some_property', 'someProperty'],
[self::underscoreNamingLower(), 'base64_encoded', 'base64Encoded'],
[self::underscoreNamingLower(), 'base64encoded', 'base64encoded'],
[self::underscoreNamingUpper(), 'SOME_PROPERTY', 'someProperty'],
[self::underscoreNamingUpper(), 'SOME_PROPERTY', 'some_property'],
[self::underscoreNamingUpper(), 'SOME_PROPERTY', 'SOME_PROPERTY'],
[self::underscoreNamingUpper(), 'BASE64_ENCODED', 'base64Encoded'],
[self::underscoreNamingUpper(), 'BASE64ENCODED', 'base64encoded'],
];
}
/**
* @dataProvider dataPropertyToColumnName
*
* @param NamingStrategy $strategy
* @param string $expected
* @param string $propertyName
*/
public function testPropertyToColumnName(NamingStrategy $strategy, $expected, $propertyName)
public function testPropertyToColumnName(NamingStrategy $strategy, string $expected, string $propertyName) : void
{
$this->assertEquals($expected, $strategy->propertyToColumnName($propertyName));
self::assertSame($expected, $strategy->propertyToColumnName($propertyName));
}
/**
* Data Provider for NamingStrategy#referenceColumnName
*
* @return array
* @return array<NamingStrategy|string>
*/
static public function dataReferenceColumnName()
public static function dataReferenceColumnName() : array
{
return [
// DefaultNamingStrategy
@@ -160,30 +115,31 @@ class NamingStrategyTest extends OrmTestCase
/**
* @dataProvider dataReferenceColumnName
*
* @param NamingStrategy $strategy
* @param string $expected
*/
public function testReferenceColumnName(NamingStrategy $strategy, $expected)
public function testReferenceColumnName(NamingStrategy $strategy, string $expected) : void
{
$this->assertEquals($expected, $strategy->referenceColumnName());
self::assertSame($expected, $strategy->referenceColumnName());
}
/**
* Data Provider for NamingStrategy#joinColumnName
*
* @return array
* @return array<NamingStrategy|string|null>
*/
static public function dataJoinColumnName()
public static function dataJoinColumnName() : array
{
return [
// DefaultNamingStrategy
[self::defaultNaming(), 'someColumn_id', 'someColumn', null],
[self::defaultNaming(), 'some_column_id', 'some_column', null],
[self::defaultNaming(), 'base64Encoded_id', 'base64Encoded', null],
[self::defaultNaming(), 'base64_encoded_id', 'base64_encoded', null],
// UnderscoreNamingStrategy
[self::underscoreNamingLower(), 'some_column_id', 'someColumn', null],
[self::underscoreNamingLower(), 'base64_encoded_id', 'base64Encoded', null],
[self::underscoreNamingUpper(), 'SOME_COLUMN_ID', 'someColumn', null],
[self::underscoreNamingUpper(), 'BASE64_ENCODED_ID', 'base64Encoded', null],
// JoinColumnClassNamingStrategy
[new JoinColumnClassNamingStrategy(), 'classname_someColumn_id', 'someColumn', 'Some\ClassName'],
[new JoinColumnClassNamingStrategy(), 'classname_some_column_id', 'some_column', 'ClassName'],
@@ -192,131 +148,83 @@ class NamingStrategyTest extends OrmTestCase
/**
* @dataProvider dataJoinColumnName
*
* @param NamingStrategy $strategy
* @param string $expected
* @param string $propertyName
*/
public function testJoinColumnName(NamingStrategy $strategy, $expected, $propertyName, $className = null)
{
$this->assertEquals($expected, $strategy->joinColumnName($propertyName, $className));
public function testJoinColumnName(
NamingStrategy $strategy,
string $expected,
string $propertyName,
?string $className = null
) : void {
self::assertSame($expected, $strategy->joinColumnName($propertyName, $className));
}
/**
* Data Provider for NamingStrategy#joinTableName
*
* @return array
* @return array<NamingStrategy|string|null>
*/
static public function dataJoinTableName()
public static function dataJoinTableName() : array
{
return [
// DefaultNamingStrategy
[
self::defaultNaming(), 'someclassname_classname',
'SomeClassName', 'Some\ClassName', null,
],
[
self::defaultNaming(), 'someclassname_classname',
'\SomeClassName', 'ClassName', null,
],
[
self::defaultNaming(), 'name_classname',
'\Some\Class\Name', 'ClassName', null,
],
[self::defaultNaming(), 'someclassname_classname', 'SomeClassName', 'Some\ClassName', null],
[self::defaultNaming(), 'someclassname_classname', '\SomeClassName', 'ClassName', null],
[self::defaultNaming(), 'name_classname', '\Some\Class\Name', 'ClassName', null],
// UnderscoreNamingStrategy
[
self::underscoreNamingLower(), 'some_class_name_class_name',
'SomeClassName', 'Some\ClassName', null,
],
[
self::underscoreNamingLower(), 'some_class_name_class_name',
'\SomeClassName', 'ClassName', null,
],
[
self::underscoreNamingLower(), 'name_class_name',
'\Some\Class\Name', 'ClassName', null,
],
[
self::underscoreNamingUpper(), 'SOME_CLASS_NAME_CLASS_NAME',
'SomeClassName', 'Some\ClassName', null,
],
[
self::underscoreNamingUpper(), 'SOME_CLASS_NAME_CLASS_NAME',
'\SomeClassName', 'ClassName', null,
],
[
self::underscoreNamingUpper(), 'NAME_CLASS_NAME',
'\Some\Class\Name', 'ClassName', null,
],
[self::underscoreNamingLower(), 'some_class_name_class_name', 'SomeClassName', 'Some\ClassName', null],
[self::underscoreNamingLower(), 'some_class_name_class_name', '\SomeClassName', 'ClassName', null],
[self::underscoreNamingLower(), 'name_class_name', '\Some\Class\Name', 'ClassName', null],
[self::underscoreNamingUpper(), 'SOME_CLASS_NAME_CLASS_NAME', 'SomeClassName', 'Some\ClassName', null],
[self::underscoreNamingUpper(), 'SOME_CLASS_NAME_CLASS_NAME', '\SomeClassName', 'ClassName', null],
[self::underscoreNamingUpper(), 'NAME_CLASS_NAME', '\Some\Class\Name', 'ClassName', null],
];
}
/**
* @dataProvider dataJoinTableName
*
* @param NamingStrategy $strategy
* @param string $expected
* @param string $ownerEntity
* @param string $associatedEntity
* @param string $propertyName
*/
public function testJoinTableName(NamingStrategy $strategy, $expected, $ownerEntity, $associatedEntity, $propertyName = null)
{
$this->assertEquals($expected, $strategy->joinTableName($ownerEntity, $associatedEntity, $propertyName));
public function testJoinTableName(
NamingStrategy $strategy,
string $expected,
string $ownerEntity,
string $associatedEntity,
?string $propertyName = null
) : void {
self::assertSame($expected, $strategy->joinTableName($ownerEntity, $associatedEntity, $propertyName));
}
/**
* Data Provider for NamingStrategy#joinKeyColumnName
*
* @return array
* @return array<NamingStrategy|string|null>
*/
static public function dataJoinKeyColumnName()
public static function dataJoinKeyColumnName() : array
{
return [
// DefaultNamingStrategy
[
self::defaultNaming(), 'someclassname_id',
'SomeClassName', null, null,
],
[
self::defaultNaming(), 'name_identifier',
'\Some\Class\Name', 'identifier', null,
],
[self::defaultNaming(), 'someclassname_id', 'SomeClassName', null, null],
[self::defaultNaming(), 'name_identifier', '\Some\Class\Name', 'identifier', null],
// UnderscoreNamingStrategy
[
self::underscoreNamingLower(), 'some_class_name_id',
'SomeClassName', null, null,
],
[
self::underscoreNamingLower(), 'class_name_identifier',
'\Some\Class\ClassName', 'identifier', null,
],
[self::underscoreNamingLower(), 'some_class_name_id', 'SomeClassName', null, null],
[self::underscoreNamingLower(), 'class_name_identifier', '\Some\Class\ClassName', 'identifier', null],
[
self::underscoreNamingUpper(), 'SOME_CLASS_NAME_ID',
'SomeClassName', null, null,
],
[
self::underscoreNamingUpper(), 'CLASS_NAME_IDENTIFIER',
'\Some\Class\ClassName', 'IDENTIFIER', null,
],
[self::underscoreNamingUpper(), 'SOME_CLASS_NAME_ID', 'SomeClassName', null, null],
[self::underscoreNamingUpper(), 'CLASS_NAME_IDENTIFIER', '\Some\Class\ClassName', 'IDENTIFIER', null],
];
}
/**
* @dataProvider dataJoinKeyColumnName
*
* @param NamingStrategy $strategy
* @param string $expected
* @param string $propertyEntityName
* @param string $referencedColumnName
* @param string $propertyName
*/
public function testJoinKeyColumnName(NamingStrategy $strategy, $expected, $propertyEntityName, $referencedColumnName = null, $propertyName = null)
{
$this->assertEquals($expected, $strategy->joinKeyColumnName($propertyEntityName, $referencedColumnName, $propertyName));
public function testJoinKeyColumnName(
NamingStrategy $strategy,
string $expected,
string $propertyEntityName,
?string $referencedColumnName = null,
?string $propertyName = null
) : void {
self::assertSame($expected, $strategy->joinKeyColumnName($propertyEntityName, $referencedColumnName, $propertyName));
}
}
@@ -274,16 +274,4 @@ class PersistentCollectionTest extends OrmTestCase
$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();
}
}
@@ -1223,6 +1223,10 @@ class
'@Column(name="test", type="string", length=10, options={"comment"="testing"})',
['type' => 'string', 'length' => 10, 'options' => ['comment' => 'testing']],
],
'string-comment-quote' => [
'@Column(name="test", type="string", length=10, options={"comment"="testing ""quotes"""})',
['type' => 'string', 'length' => 10, 'options' => ['comment' => 'testing "quotes"']],
],
'string-collation' => [
'@Column(name="test", type="string", length=10, options={"collation"="utf8mb4_general_ci"})',
['type' => 'string', 'length' => 10, 'options' => ['collation' => 'utf8mb4_general_ci']],