Compare commits

...

13 Commits
2.9.1 ... 2.9.2

Author SHA1 Message Date
Yup
75b4b88c5b Add automatic type detection for Embedded. (#8724)
* Add automatic type detection for Embedded.

* Inline statement.

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
2021-05-31 11:53:14 +02:00
Benjamin Eberlei
d9e59d6862 [GH-8723] Remove use of nullability to automatically detect nullable status (#8732)
* [GH-8723] Remove use of nullability to automatically detect nullable status.

* [GH-8723] Make Column::$nullable default to false again, fix tests.
2021-05-31 10:19:16 +02:00
Yup
5fa94969de Adapt flush($argument) in documentation as it's deprecated. (#8728) 2021-05-29 22:22:19 +02:00
Juan Iglesias
f2c3ddac97 Add note about performance and inheritance mapping (#8704)
Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2021-05-27 08:27:55 +02:00
Grégoire Paris
46f0da9ffa Merge pull request #8710 from franmomu/recompute
Handle generic parameters in UnitOfWork
2021-05-25 13:29:21 +02:00
Fran Moreno
1e832a6782 Add generics to parameters 2021-05-25 13:01:47 +02:00
Grégoire Paris
56bdb44efd Merge pull request #8722 from alcaeus/fix-metadata-cache-clear 2021-05-25 12:55:00 +02:00
Andreas Braun
fffac44991 Bump doctrine/cache patch dependency to fix build with lowest deps 2021-05-25 11:58:11 +02:00
Andreas Braun
e42b3d6584 Fix metadata cache compatibility layer 2021-05-25 10:38:00 +02:00
Grégoire Paris
7ab2c3abbd Merge pull request #8708 from VincentLanglet/patch-2
Fix ClassMetadaInfo template inference
2021-05-25 08:49:50 +02:00
Grégoire Paris
498c816b65 Merge pull request #8717 from greg0ire/update-branch-metadata
Mark 2.8.x as unmaintained, and 2.9.x as current
2021-05-25 00:01:21 +02:00
Vincent Langlet
eec740079d Fix ClassMetadataInfo template inference 2021-05-24 21:52:40 +02:00
Grégoire Paris
c359715a97 Mark 2.8.x as unmaintained, and 2.9.x as current 2021-05-24 21:32:26 +02:00
27 changed files with 102 additions and 70 deletions

View File

@@ -21,6 +21,7 @@
"name": "2.9",
"branchName": "2.9.x",
"slug": "2.9",
"current": true,
"aliases": [
"current",
"stable"
@@ -30,7 +31,7 @@
"name": "2.8",
"branchName": "2.8.x",
"slug": "2.8",
"current": true
"maintained": false
},
{
"name": "2.7",

View File

@@ -20,7 +20,7 @@
"ext-pdo": "*",
"composer/package-versions-deprecated": "^1.8",
"doctrine/annotations": "^1.13",
"doctrine/cache": "^1.11|^2.0",
"doctrine/cache": "^1.11.3|^2.0.3",
"doctrine/collections": "^1.5",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.13.0",

View File

@@ -10,7 +10,7 @@ code should look like. We will implement it on a
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
.. warning::
.. note::
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8383>`_)

View File

@@ -113,7 +113,7 @@ Optional attributes:
- **unique**: Boolean value to determine if the value of the column
should be unique across all rows of the underlying entities table.
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false. When using typed properties on entity class defaults to true when property is nullable.
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
- **options**: Array of additional options:
@@ -350,7 +350,7 @@ in order to specify that it is an embedded class.
Required attributes:
- **class**: The embeddable class
- **class**: The embeddable class. You can omit this value if you use a PHP property type instead.
.. code-block:: php
@@ -649,8 +649,6 @@ Optional attributes:
constraint level. Defaults to false.
- **nullable**: Determine whether the related entity is required, or if
null is an allowed state for the relation. Defaults to true.
When using typed properties on entity class defaults to false when
property is not nullable.
- **onDelete**: Cascade Action (Database-level)
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.

View File

@@ -61,7 +61,7 @@ This policy can be configured as follows:
Notify
~~~~~~
.. warning::
.. note::
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8383>`_)

View File

@@ -274,6 +274,9 @@ be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
There is also another important performance consideration that it is *NOT POSSIBLE*
to query for the base entity without any LEFT JOINs to the sub-types.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -2,7 +2,7 @@ Partial Objects
===============
.. warning::
.. note::
Creating Partial Objects through DQL is deprecated and
will be removed in the future, use data transfer object

View File

@@ -134,6 +134,10 @@ optimize the performance of the Flush Operation:
explicit strategies of notifying the UnitOfWork what objects/properties
changed.
.. note::
Flush only a single entity with ``$entityManager->flush($entity)`` is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8459>`_)
Query Internals
---------------

View File

@@ -309,7 +309,7 @@
<xs:complexType name="embedded">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="class" type="orm:fqcn" use="required" />
<xs:attribute name="class" type="orm:fqcn" use="optional" />
<xs:attribute name="column-prefix" type="xs:string" use="optional" />
<xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" />
</xs:complexType>

View File

@@ -26,6 +26,7 @@ use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\Deprecations\Deprecation;
@@ -326,6 +327,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
);
$this->_attributes['metadataCacheImpl'] = $cacheImpl;
$this->_attributes['metadataCache'] = CacheAdapter::wrap($cacheImpl);
}
public function getMetadataCache(): ?CacheItemPoolInterface
@@ -335,7 +337,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
public function setMetadataCache(CacheItemPoolInterface $cache): void
{
$this->_attributes['metadataCache'] = $cache;
$this->_attributes['metadataCache'] = $cache;
$this->_attributes['metadataCacheImpl'] = DoctrineProvider::wrap($cache);
}
/**
@@ -430,16 +433,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
throw ORMException::proxyClassesAlwaysRegenerating();
}
if ($this->getMetadataCache()) {
return;
if (! $this->getMetadataCache()) {
throw ORMException::metadataCacheNotConfigured();
}
$metadataCacheImpl = $this->getMetadataCacheImpl();
if (! $metadataCacheImpl) {
throw ORMException::metadataCacheNotConfigured();
}
if ($metadataCacheImpl instanceof ArrayCache) {
throw ORMException::metadataCacheUsesNonPersistentCache($metadataCacheImpl);
}

View File

@@ -710,6 +710,7 @@ class ClassMetadataInfo implements ClassMetadata
* metadata of the class with the given name.
*
* @param string $entityName The name of the entity class the new instance is used for.
* @psalm-param class-string<T> $entityName
*/
public function __construct($entityName, ?NamingStrategy $namingStrategy = null)
{
@@ -1467,10 +1468,6 @@ class ClassMetadataInfo implements ClassMetadata
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
if ($type) {
if (! isset($mapping['nullable'])) {
$mapping['nullable'] = $type->allowsNull();
}
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
@@ -1526,14 +1523,6 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['targetEntity'] = $type->getName();
}
if (isset($mapping['joinColumns'])) {
foreach ($mapping['joinColumns'] as &$joinColumn) {
if ($type->allowsNull() === false) {
$joinColumn['nullable'] = false;
}
}
}
return $mapping;
}
@@ -3604,9 +3593,16 @@ class ClassMetadataInfo implements ClassMetadata
{
$this->assertFieldNotMapped($mapping['fieldName']);
if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) {
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
if ($type instanceof ReflectionNamedType) {
$mapping['class'] = $type->getName();
}
}
$this->embeddedClasses[$mapping['fieldName']] = [
'class' => $this->fullyQualifiedClassName($mapping['class']),
'columnPrefix' => $mapping['columnPrefix'],
'columnPrefix' => $mapping['columnPrefix'] ?? null,
'declaredField' => $mapping['declaredField'] ?? null,
'originalField' => $mapping['originalField'] ?? null,
];

View File

@@ -57,8 +57,8 @@ final class Column implements Annotation
/** @var bool */
public $unique = false;
/** @var bool|null */
public $nullable;
/** @var bool */
public $nullable = false;
/** @var array<string,mixed> */
public $options = [];
@@ -76,7 +76,7 @@ final class Column implements Annotation
?int $precision = null,
?int $scale = null,
bool $unique = false,
?bool $nullable = null,
bool $nullable = false,
array $options = [],
?string $columnDefinition = null
) {

View File

@@ -324,7 +324,7 @@ class XmlDriver extends FileDriver
$mapping = [
'fieldName' => (string) $embeddedMapping['name'],
'class' => (string) $embeddedMapping['class'],
'class' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null,
'columnPrefix' => $useColumnPrefix ? $columnPrefix : false,
];

View File

@@ -408,7 +408,7 @@ class YamlDriver extends FileDriver
foreach ($element['embedded'] as $name => $embeddedMapping) {
$mapping = [
'fieldName' => $name,
'class' => $embeddedMapping['class'],
'class' => $embeddedMapping['class'] ?? null,
'columnPrefix' => $embeddedMapping['columnPrefix'] ?? null,
];
$metadata->mapEmbedded($mapping);

View File

@@ -31,16 +31,13 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Embedded implements Annotation
{
/**
* @Required
* @var string
*/
/** @var string|null */
public $class;
/** @var string|bool|null */
public $columnPrefix;
public function __construct(string $class, $columnPrefix = null)
public function __construct(?string $class = null, $columnPrefix = null)
{
$this->class = $class;
$this->columnPrefix = $columnPrefix;

View File

@@ -105,6 +105,7 @@ class ConvertDoctrine1Schema
/**
* @param mixed[] $mappingInformation
* @psalm-param class-string $className
*/
private function convertToClassMetadataInfo(
string $className,

View File

@@ -630,9 +630,13 @@ class UnitOfWork implements PropertyChangedListener
*
* @param ClassMetadata $class The class descriptor of the entity.
* @param object $entity The entity for which to compute the changes.
* @psalm-param ClassMetadata<T> $class
* @psalm-param T $entity
*
* @return void
*
* @template T of object
*
* @ignore
*/
public function computeChangeSet(ClassMetadata $class, $entity)
@@ -959,6 +963,10 @@ class UnitOfWork implements PropertyChangedListener
/**
* @param object $entity
* @psalm-param ClassMetadata<T> $class
* @psalm-param T $entity
*
* @template T of object
*/
private function persistNew(ClassMetadata $class, $entity): void
{
@@ -1017,11 +1025,14 @@ class UnitOfWork implements PropertyChangedListener
*
* @param ClassMetadata $class The class descriptor of the entity.
* @param object $entity The entity for which to (re)calculate the change set.
* @psalm-param ClassMetadata<T> $class
* @psalm-param T $entity
*
* @return void
*
* @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
*
* @template T of object
* @ignore
*/
public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity)
@@ -1144,6 +1155,10 @@ class UnitOfWork implements PropertyChangedListener
/**
* @param object $entity
* @psalm-param ClassMetadata<T> $class
* @psalm-param T $entity
*
* @template T of object
*/
private function addToEntityIdentifiersAndEntityMap(
ClassMetadata $class,
@@ -2020,8 +2035,13 @@ class UnitOfWork implements PropertyChangedListener
/**
* @param object $entity
* @param object $managedCopy
* @psalm-param ClassMetadata<T> $class
* @psalm-param T $entity
* @psalm-param T $managedCopy
*
* @throws OptimisticLockException
*
* @template T of object
*/
private function ensureVersionMatch(
ClassMetadata $class,

View File

@@ -557,7 +557,7 @@ parameters:
-
message: "#^Call to an undefined method ReflectionProperty\\:\\:getType\\(\\)\\.$#"
count: 2
count: 3
path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
-

View File

@@ -1125,9 +1125,7 @@
<code>$table</code>
<code>$tableGeneratorDefinition</code>
</PropertyNotSetInConstructor>
<PropertyTypeCoercion occurrences="13">
<code>$entityName</code>
<code>$entityName</code>
<PropertyTypeCoercion occurrences="11">
<code>$this-&gt;entityListeners</code>
<code>$this-&gt;fieldMappings</code>
<code>$this-&gt;fullyQualifiedClassName($repositoryClassName)</code>

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\TypedProperties;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Embeddable()
*/
#[ORM\Embeddable]
class Contact
{
/** @Column() */
#[ORM\Column]
public ?string $email = null;
}

View File

@@ -24,6 +24,7 @@ class UserTyped
*/
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
public int $id;
/** @Column(length=50) */
#[ORM\Column(length: 50)]
public ?string $status;
@@ -67,6 +68,10 @@ class UserTyped
#[ORM\ManyToOne]
public ?CmsEmail $mainEmail;
/** @Embedded */
#[ORM\Embedded]
public ?Contact $contact = null;
public static function loadMetadata(ClassMetadataInfo $metadata): void
{
$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE);
@@ -131,5 +136,7 @@ class UserTyped
$metadata->mapManyToOne(
['fieldName' => 'mainEmail']
);
$metadata->mapEmbedded(['fieldName' => 'contact']);
}
}

View File

@@ -133,6 +133,7 @@ class ConfigurationTest extends DoctrineTestCase
$queryCacheImpl = $this->createMock(Cache::class);
$this->configuration->setMetadataCacheImpl($queryCacheImpl);
$this->assertSame($queryCacheImpl, $this->configuration->getMetadataCacheImpl());
$this->assertNotNull($this->configuration->getMetadataCache());
}
public function testSetGetMetadataCache(): void
@@ -141,6 +142,7 @@ class ConfigurationTest extends DoctrineTestCase
$cache = $this->createStub(CacheItemPoolInterface::class);
$this->configuration->setMetadataCache($cache);
$this->assertSame($cache, $this->configuration->getMetadataCache());
$this->assertNotNull($this->configuration->getMetadataCacheImpl());
}
public function testAddGetNamedQuery(): void

View File

@@ -42,6 +42,7 @@ use Doctrine\Tests\Models\DDC889\DDC889Class;
use Doctrine\Tests\Models\DDC889\DDC889Entity;
use Doctrine\Tests\Models\DDC964\DDC964Admin;
use Doctrine\Tests\Models\DDC964\DDC964Guest;
use Doctrine\Tests\Models\TypedProperties\Contact;
use Doctrine\Tests\Models\TypedProperties\UserTyped;
use Doctrine\Tests\OrmTestCase;
@@ -265,24 +266,6 @@ abstract class AbstractMappingDriverTest extends OrmTestCase
return $class;
}
public function testFieldIsNullableByType(): void
{
if (PHP_VERSION_ID < 70400) {
$this->markTestSkipped('requies PHP 7.4');
}
$class = $this->createClassMetadata(UserTyped::class);
// Explicit Nullable
$this->assertTrue($class->isNullable('status'));
// Explicit Not Nullable
$this->assertFalse($class->isNullable('username'));
$this->assertEquals(CmsEmail::class, $class->getAssociationMapping('email')['targetEntity']);
$this->assertEquals(CmsEmail::class, $class->getAssociationMapping('mainEmail')['targetEntity']);
}
public function testFieldTypeFromReflection(): void
{
if (PHP_VERSION_ID < 70400) {
@@ -299,6 +282,10 @@ abstract class AbstractMappingDriverTest extends OrmTestCase
$this->assertEquals('json', $class->getTypeOfField('array'));
$this->assertEquals('boolean', $class->getTypeOfField('boolean'));
$this->assertEquals('float', $class->getTypeOfField('float'));
$this->assertEquals(CmsEmail::class, $class->getAssociationMapping('email')['targetEntity']);
$this->assertEquals(CmsEmail::class, $class->getAssociationMapping('mainEmail')['targetEntity']);
$this->assertEquals(Contact::class, $class->embeddedClasses['contact']['class']);
}
/**

View File

@@ -120,19 +120,14 @@ class ClassMetadataTest extends OrmTestCase
$cm = new ClassMetadata(TypedProperties\UserTyped::class);
$cm->initializeReflection(new RuntimeReflectionService());
// Explicit Nullable
$cm->mapField(['fieldName' => 'status', 'length' => 50]);
$this->assertTrue($cm->isNullable('status'));
// Explicit Not Nullable
$cm->mapField(['fieldName' => 'username', 'length' => 50]);
$this->assertFalse($cm->isNullable('username'));
$cm->mapOneToOne(['fieldName' => 'email', 'joinColumns' => [[]]]);
$this->assertEquals(CmsEmail::class, $cm->getAssociationMapping('email')['targetEntity']);
$cm->mapManyToOne(['fieldName' => 'mainEmail']);
$this->assertEquals(CmsEmail::class, $cm->getAssociationMapping('mainEmail')['targetEntity']);
$cm->mapEmbedded(['fieldName' => 'contact']);
$this->assertEquals(TypedProperties\Contact::class, $cm->embeddedClasses['contact']['class']);
}
public function testFieldTypeFromReflection(): void

View File

@@ -61,3 +61,5 @@ $metadata->mapOneToOne(
$metadata->mapManyToOne(
['fieldName' => 'mainEmail']
);
$metadata->mapEmbedded(['fieldName' => 'contact']);

View File

@@ -25,5 +25,7 @@
</one-to-one>
<many-to-one field="mainEmail"/>
<embedded name="contact" />
</entity>
</doctrine-mapping>

View File

@@ -24,3 +24,5 @@ Doctrine\Tests\Models\TypedProperties\UserTyped:
joinColumn: []
manyToOne:
mainEmail: []
embedded:
contact: ~