mirror of
https://github.com/doctrine/orm.git
synced 2026-04-27 00:18:04 +02:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b88ce787d | |||
| f9c3470a8d | |||
| c1b131b67e | |||
| 16b82ea061 | |||
| f8f370ace6 | |||
| d5c69fb73f | |||
| 93f9eb7af2 | |||
| 6d5da83c68 | |||
| 5f01dd8d09 | |||
| b596e6a665 | |||
| 79d3cf5880 | |||
| d7b7c28ae5 | |||
| d6fd510c49 | |||
| a2a7d5bb01 | |||
| 223b2650c4 | |||
| 01c1644d9c | |||
| 3eff2d4b3f | |||
| 9ddf8b96f8 | |||
| 3d00fa817a | |||
| c0a1404e4c |
@@ -78,7 +78,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.4"
|
||||
- "8.1"
|
||||
dbal-version:
|
||||
- "default"
|
||||
postgres-version:
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.4"
|
||||
- "8.1"
|
||||
dbal-version:
|
||||
- "default"
|
||||
mariadb-version:
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.4"
|
||||
- "8.1"
|
||||
dbal-version:
|
||||
- "default"
|
||||
mysql-version:
|
||||
|
||||
+1
-1
@@ -15,5 +15,5 @@ vendor/
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
composer.lock
|
||||
/.phpunit.result.cache
|
||||
.phpunit.result.cache
|
||||
/*.phpunit.xml
|
||||
|
||||
+2
-2
@@ -43,12 +43,12 @@
|
||||
"doctrine/annotations": "^1.13",
|
||||
"doctrine/coding-standard": "^9.0",
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||
"phpstan/phpstan": "1.3.3",
|
||||
"phpstan/phpstan": "1.4.3",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
|
||||
"squizlabs/php_codesniffer": "3.6.2",
|
||||
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
|
||||
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
|
||||
"vimeo/psalm": "4.18.1"
|
||||
"vimeo/psalm": "4.19.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >= 2.0"
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
Accessing private/protected properties/methods of the same class from different instance
|
||||
========================================================================================
|
||||
|
||||
.. sectionauthor:: Michael Olsavsky (olsavmic)
|
||||
|
||||
As explained in the :doc:`restrictions for entity classes in the manual <../reference/architecture>`,
|
||||
it is dangerous to access private/protected properties of different entity instance of the same class because of lazy loading.
|
||||
|
||||
The proxy instance that's injected instead of the real entity may not be initialized yet
|
||||
and therefore not contain expected data which may result in unexpected behavior.
|
||||
That's a limitation of current proxy implementation - only public methods automatically initialize proxies.
|
||||
|
||||
It is usually preferable to use a public interface to manipulate the object from outside the `$this`
|
||||
context but it may not be convenient in some cases. The following example shows how to do it safely.
|
||||
|
||||
Safely accessing private properties from different instance of the same class
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
To safely access private property of different instance of the same class, make sure to initialise
|
||||
the proxy before use manually as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Entity
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Entity")
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
*/
|
||||
private self $parent;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", nullable=false)
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
// ...
|
||||
|
||||
public function doSomethingWithParent()
|
||||
{
|
||||
// Always initializing the proxy before use
|
||||
if ($this->parent instanceof Proxy) {
|
||||
$this->parent->__load();
|
||||
}
|
||||
|
||||
// Accessing the `$this->parent->name` property without loading the proxy first
|
||||
// may throw error in case the Proxy has not been initialized yet.
|
||||
$this->parent->name;
|
||||
}
|
||||
|
||||
public function doSomethingWithAnotherInstance(self $instance)
|
||||
{
|
||||
// Always initializing the proxy before use
|
||||
if ($instance instanceof Proxy) {
|
||||
$instance->__load();
|
||||
}
|
||||
|
||||
// Accessing the `$instance->name` property without loading the proxy first
|
||||
// may throw error in case the Proxy has not been initialized yet.
|
||||
$instance->name;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -82,9 +82,13 @@ be any regular PHP class observing the following restrictions:
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
- An entity class must not implement ``__wakeup`` or
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
Also consider implementing
|
||||
`Serializable <https://php.net/manual/en/class.serializable.php>`_
|
||||
instead.
|
||||
You can also consider implementing
|
||||
`Serializable <https://php.net/manual/en/class.serializable.php>`_,
|
||||
but be aware that it is deprecated since PHP 8.1. We do not recommend its usage.
|
||||
- PHP 7.4 introduces :doc:`the new magic method <https://php.net/manual/en/language.oop5.magic.php#object.unserialize>`
|
||||
``__unserialize``, which changes the execution priority between
|
||||
``__wakeup`` and itself when used. This can cause unexpected behaviour in
|
||||
an Entity.
|
||||
- Any two entity classes in a class hierarchy that inherit
|
||||
directly or indirectly from one another must not have a mapped
|
||||
property with the same name. That is, if B inherits from A then B
|
||||
@@ -93,6 +97,7 @@ be any regular PHP class observing the following restrictions:
|
||||
- An entity cannot make use of func_get_args() to implement variable parameters.
|
||||
Generated proxies do not support this for performance reasons and your code might
|
||||
actually fail to work when violating this restriction.
|
||||
- Entity cannot access private/protected properties/methods of another entity of the same class or :doc:`do so safely <../cookbook/accessing-private-properties-of-the-same-class-from-different-instance>`.
|
||||
|
||||
Entities support inheritance, polymorphic associations, and
|
||||
polymorphic queries. Both abstract and concrete classes can be
|
||||
@@ -161,7 +166,8 @@ possible for ``__sleep`` to return names of private properties in
|
||||
parent classes. On the other hand it is not a solution for proxy
|
||||
objects to implement ``Serializable`` because Serializable does not
|
||||
work well with any potential cyclic object references (at least we
|
||||
did not find a way yet, if you did, please contact us).
|
||||
did not find a way yet, if you did, please contact us). The
|
||||
``Serializable`` interface is also deprecated beginning with PHP 8.1.
|
||||
|
||||
The EntityManager
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -694,6 +694,7 @@ specified by their respective tags:
|
||||
- ``<cascade-merge />``
|
||||
- ``<cascade-remove />``
|
||||
- ``<cascade-refresh />``
|
||||
- ``<cascade-detach />``
|
||||
|
||||
Join Column Element
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -81,10 +81,11 @@ that directory with the following contents:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/orm": "^2.10.2",
|
||||
"doctrine/dbal": "^3.1.1",
|
||||
"symfony/yaml": "2.*",
|
||||
"symfony/cache": "^5.3"
|
||||
"doctrine/orm": "^2.11.0",
|
||||
"doctrine/dbal": "^3.2",
|
||||
"doctrine/annotations": "1.13.2",
|
||||
"symfony/yaml": "^5.4",
|
||||
"symfony/cache": "^5.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {"": "src/"}
|
||||
|
||||
@@ -24,7 +24,7 @@ interface EntityHydrator
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param EntityCacheKey $key The entity cache key.
|
||||
* @param EntityCacheEntry $entry The entity cache entry.
|
||||
* @param object $entity The entity to load the cache into. If not specified, a new entity is created.
|
||||
* @param object|null $entity The entity to load the cache into. If not specified, a new entity is created.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
|
||||
@@ -1056,9 +1056,19 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
|
||||
foreach ($this->fieldMappings as $field => $mapping) {
|
||||
if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
|
||||
$childProperty = $this->getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField']);
|
||||
assert($childProperty !== null);
|
||||
|
||||
if (isset($mapping['enumType'])) {
|
||||
$childProperty = new ReflectionEnumProperty(
|
||||
$childProperty,
|
||||
$mapping['enumType']
|
||||
);
|
||||
}
|
||||
|
||||
$this->reflFields[$field] = new ReflectionEmbeddedProperty(
|
||||
$parentReflFields[$mapping['declaredField']],
|
||||
$this->getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField']),
|
||||
$childProperty,
|
||||
$mapping['originalClass']
|
||||
);
|
||||
continue;
|
||||
@@ -1517,7 +1527,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
! isset($mapping['type'])
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName(), false)) {
|
||||
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['enumType'] = $type->getName();
|
||||
|
||||
$reflection = new ReflectionEnum($type->getName());
|
||||
|
||||
@@ -15,13 +15,13 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final class DiscriminatorColumn implements Annotation
|
||||
{
|
||||
/** @var string */
|
||||
/** @var string|null */
|
||||
public $name;
|
||||
|
||||
/** @var string */
|
||||
/** @var string|null */
|
||||
public $type;
|
||||
|
||||
/** @var int */
|
||||
/** @var int|null */
|
||||
public $length;
|
||||
|
||||
/**
|
||||
|
||||
@@ -281,7 +281,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
[
|
||||
'name' => $discrColumnAnnot->name,
|
||||
'type' => $discrColumnAnnot->type ?: 'string',
|
||||
'length' => $discrColumnAnnot->length ?: 255,
|
||||
'length' => $discrColumnAnnot->length ?? 255,
|
||||
'columnDefinition' => $discrColumnAnnot->columnDefinition,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ use Doctrine\Deprecations\Deprecation;
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class ManyToMany implements Annotation
|
||||
{
|
||||
/** @var string|null */
|
||||
/** @var class-string|null */
|
||||
public $targetEntity;
|
||||
|
||||
/** @var string|null */
|
||||
@@ -43,7 +43,8 @@ final class ManyToMany implements Annotation
|
||||
public $indexBy;
|
||||
|
||||
/**
|
||||
* @param string[]|null $cascade
|
||||
* @param class-string|null $targetEntity
|
||||
* @param string[]|null $cascade
|
||||
*/
|
||||
public function __construct(
|
||||
?string $targetEntity = null,
|
||||
|
||||
@@ -15,7 +15,7 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class ManyToOne implements Annotation
|
||||
{
|
||||
/** @var string|null */
|
||||
/** @var class-string|null */
|
||||
public $targetEntity;
|
||||
|
||||
/** @var string[]|null */
|
||||
@@ -33,7 +33,8 @@ final class ManyToOne implements Annotation
|
||||
public $inversedBy;
|
||||
|
||||
/**
|
||||
* @param string[]|null $cascade
|
||||
* @param class-string|null $targetEntity
|
||||
* @param string[]|null $cascade
|
||||
*/
|
||||
public function __construct(
|
||||
?string $targetEntity = null,
|
||||
|
||||
@@ -18,7 +18,7 @@ final class OneToMany implements Annotation
|
||||
/** @var string */
|
||||
public $mappedBy;
|
||||
|
||||
/** @var string */
|
||||
/** @var class-string|null */
|
||||
public $targetEntity;
|
||||
|
||||
/** @var array<string> */
|
||||
@@ -39,7 +39,8 @@ final class OneToMany implements Annotation
|
||||
public $indexBy;
|
||||
|
||||
/**
|
||||
* @param string[]|null $cascade
|
||||
* @param class-string|null $targetEntity
|
||||
* @param string[]|null $cascade
|
||||
*/
|
||||
public function __construct(
|
||||
?string $mappedBy = null,
|
||||
|
||||
@@ -15,7 +15,7 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class OneToOne implements Annotation
|
||||
{
|
||||
/** @var string|null */
|
||||
/** @var class-string|null */
|
||||
public $targetEntity;
|
||||
|
||||
/** @var string|null */
|
||||
@@ -39,6 +39,7 @@ final class OneToOne implements Annotation
|
||||
public $orphanRemoval = false;
|
||||
|
||||
/**
|
||||
* @param class-string|null $targetEntity
|
||||
* @param array<string>|null $cascade
|
||||
*/
|
||||
public function __construct(
|
||||
|
||||
@@ -29,6 +29,11 @@ class ReflectionEnumProperty extends ReflectionProperty
|
||||
{
|
||||
$this->originalReflectionProperty = $originalReflectionProperty;
|
||||
$this->enumType = $enumType;
|
||||
|
||||
parent::__construct(
|
||||
$originalReflectionProperty->getDeclaringClass()->getName(),
|
||||
$originalReflectionProperty->getName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -481,6 +481,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->get($offset);
|
||||
|
||||
@@ -781,12 +781,18 @@ class SchemaTool
|
||||
*/
|
||||
private function gatherColumnOptions(array $mapping): array
|
||||
{
|
||||
if (! isset($mapping['options'])) {
|
||||
$mappingOptions = $mapping['options'] ?? [];
|
||||
|
||||
if (isset($mapping['enumType'])) {
|
||||
$mappingOptions['enumType'] = $mapping['enumType'];
|
||||
}
|
||||
|
||||
if (empty($mappingOptions)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = array_intersect_key($mapping['options'], array_flip(self::KNOWN_COLUMN_OPTIONS));
|
||||
$options['customSchemaOptions'] = array_diff_key($mapping['options'], $options);
|
||||
$options = array_intersect_key($mappingOptions, array_flip(self::KNOWN_COLUMN_OPTIONS));
|
||||
$options['customSchemaOptions'] = array_diff_key($mappingOptions, $options);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
@@ -269,9 +269,11 @@
|
||||
<rule ref="Generic.WhiteSpace.ScopeIndent.Incorrect">
|
||||
<!-- see https://github.com/squizlabs/PHP_CodeSniffer/issues/3474 -->
|
||||
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/Suit.php</exclude-pattern>
|
||||
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/Unit.php</exclude-pattern>
|
||||
</rule>
|
||||
<rule ref="Generic.WhiteSpace.ScopeIndent.IncorrectExact">
|
||||
<!-- see https://github.com/squizlabs/PHP_CodeSniffer/issues/3474 -->
|
||||
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/Suit.php</exclude-pattern>
|
||||
<exclude-pattern>tests/Doctrine/Tests/Models/Enums/Unit.php</exclude-pattern>
|
||||
</rule>
|
||||
</ruleset>
|
||||
|
||||
+1
-15
@@ -270,11 +270,6 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
|
||||
|
||||
-
|
||||
message: "#^Array \\(array\\<class\\-string, object\\>\\) does not accept key string\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Mapping/DefaultEntityListenerResolver.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\:\\:mapEmbedded\\(\\)\\.$#"
|
||||
count: 1
|
||||
@@ -725,11 +720,6 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Persisters/Collection/OneToManyPersister.php
|
||||
|
||||
-
|
||||
message: "#^Array \\(array\\<class\\-string, string\\>\\) does not accept key string\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$hints of method Doctrine\\\\ORM\\\\Internal\\\\Hydration\\\\AbstractHydrator\\:\\:hydrateAll\\(\\) expects array\\<string, string\\>, array\\<string, Doctrine\\\\ORM\\\\PersistentCollection\\|true\\> given\\.$#"
|
||||
count: 1
|
||||
@@ -1000,11 +990,6 @@ parameters:
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php
|
||||
|
||||
-
|
||||
message: "#^Array \\(array\\<string, array\\<int, string\\>\\|string\\>\\) does not accept key int\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Query/SqlWalker.php
|
||||
|
||||
-
|
||||
message: "#^Call to function is_string\\(\\) with Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node will always evaluate to false\\.$#"
|
||||
count: 2
|
||||
@@ -1864,3 +1849,4 @@ parameters:
|
||||
message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\:\\:\\$subClasses\\.$#"
|
||||
count: 1
|
||||
path: lib/Doctrine/ORM/Utility/HierarchyDiscriminatorResolver.php
|
||||
|
||||
|
||||
+65
-13
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="4.18.1@dda05fa913f4dc6eb3386f2f7ce5a45d37a71bcb">
|
||||
<files psalm-version="4.19.0@a2ad69ae4f5ab1f7d225a8dc4e2ec2d9415ed599">
|
||||
<file src="lib/Doctrine/ORM/AbstractQuery.php">
|
||||
<DeprecatedClass occurrences="1">
|
||||
<code>IterableResult</code>
|
||||
@@ -508,6 +508,12 @@
|
||||
<code>$entity</code>
|
||||
</PossiblyNullArgument>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Id/SequenceGenerator.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="2">
|
||||
<code>serialize</code>
|
||||
<code>unserialize</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Id/TableGenerator.php">
|
||||
<PossiblyFalseOperand occurrences="3">
|
||||
<code>$currentLevel</code>
|
||||
@@ -703,8 +709,9 @@
|
||||
<DeprecatedConstant occurrences="1">
|
||||
<code>self::GENERATOR_TYPE_UUID</code>
|
||||
</DeprecatedConstant>
|
||||
<DeprecatedMethod occurrences="1">
|
||||
<DeprecatedMethod occurrences="2">
|
||||
<code>canEmulateSchemas</code>
|
||||
<code>canRequireSQLConversion</code>
|
||||
</DeprecatedMethod>
|
||||
<DeprecatedProperty occurrences="4">
|
||||
<code>$this->columnNames</code>
|
||||
@@ -736,6 +743,9 @@
|
||||
<code>$className</code>
|
||||
<code>$this->namespace . '\\' . $className</code>
|
||||
</LessSpecificReturnStatement>
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
<MissingClosureParamType occurrences="2">
|
||||
<code>$joinColumn</code>
|
||||
<code>$joinColumn</code>
|
||||
@@ -762,7 +772,7 @@
|
||||
<code>$fieldName</code>
|
||||
<code>$fieldName</code>
|
||||
</ParamNameMismatch>
|
||||
<PossiblyNullArgument occurrences="9">
|
||||
<PossiblyNullArgument occurrences="8">
|
||||
<code>$class</code>
|
||||
<code>$className</code>
|
||||
<code>$entityResult['entityClass']</code>
|
||||
@@ -771,7 +781,6 @@
|
||||
<code>$parentReflFields[$embeddedClass['declaredField']]</code>
|
||||
<code>$parentReflFields[$mapping['declaredField']]</code>
|
||||
<code>$queryMapping['resultClass']</code>
|
||||
<code>$this->getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField'])</code>
|
||||
</PossiblyNullArgument>
|
||||
<PossiblyNullPropertyFetch occurrences="2">
|
||||
<code>$embeddable->reflClass->name</code>
|
||||
@@ -861,11 +870,8 @@
|
||||
</MissingClosureParamType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php">
|
||||
<PossiblyNullPropertyAssignmentValue occurrences="4">
|
||||
<PossiblyNullPropertyAssignmentValue occurrences="1">
|
||||
<code>$columnDefinition</code>
|
||||
<code>$length</code>
|
||||
<code>$name</code>
|
||||
<code>$type</code>
|
||||
</PossiblyNullPropertyAssignmentValue>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php">
|
||||
@@ -1244,11 +1250,10 @@
|
||||
</MissingConstructor>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Mapping/OneToMany.php">
|
||||
<PossiblyNullPropertyAssignmentValue occurrences="4">
|
||||
<PossiblyNullPropertyAssignmentValue occurrences="3">
|
||||
<code>$cascade</code>
|
||||
<code>$indexBy</code>
|
||||
<code>$mappedBy</code>
|
||||
<code>$targetEntity</code>
|
||||
</PossiblyNullPropertyAssignmentValue>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php">
|
||||
@@ -2042,6 +2047,9 @@
|
||||
<DocblockTypeContradiction occurrences="1">
|
||||
<code>is_array($obj)</code>
|
||||
</DocblockTypeContradiction>
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
<RedundantConditionGivenDocblockType occurrences="1">
|
||||
<code>is_object($obj)</code>
|
||||
</RedundantConditionGivenDocblockType>
|
||||
@@ -2235,22 +2243,46 @@
|
||||
<ArgumentTypeCoercion occurrences="1">
|
||||
<code>$this->parts</code>
|
||||
</ArgumentTypeCoercion>
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
<PossiblyInvalidCast occurrences="1">
|
||||
<code>$this->parts[0]</code>
|
||||
</PossiblyInvalidCast>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/Comparison.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/Composite.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
<PossiblyInvalidCast occurrences="2">
|
||||
<code>$part</code>
|
||||
<code>$this->parts[0]</code>
|
||||
</PossiblyInvalidCast>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/From.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/Func.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/GroupBy.php">
|
||||
<NonInvariantDocblockPropertyType occurrences="1">
|
||||
<code>$parts</code>
|
||||
</NonInvariantDocblockPropertyType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/Join.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
<PossiblyNullArgument occurrences="1">
|
||||
<code>$this->conditionType</code>
|
||||
</PossiblyNullArgument>
|
||||
@@ -2260,6 +2292,16 @@
|
||||
<code>$parts</code>
|
||||
</NonInvariantDocblockPropertyType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/Math.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/OrderBy.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Expr/Orx.php">
|
||||
<NonInvariantDocblockPropertyType occurrences="2">
|
||||
<code>$allowedClasses</code>
|
||||
@@ -2273,6 +2315,9 @@
|
||||
</NonInvariantDocblockPropertyType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/Filter/SQLFilter.php">
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
<MissingClosureParamType occurrences="1">
|
||||
<code>$value</code>
|
||||
</MissingClosureParamType>
|
||||
@@ -2507,9 +2552,9 @@
|
||||
</PropertyNotSetInConstructor>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/QueryExpressionVisitor.php">
|
||||
<RedundantCondition occurrences="1">
|
||||
<RedundantConditionGivenDocblockType occurrences="1">
|
||||
<code>Comparison::EQ</code>
|
||||
</RedundantCondition>
|
||||
</RedundantConditionGivenDocblockType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Query/ResultSetMapping.php">
|
||||
<PropertyTypeCoercion occurrences="3">
|
||||
@@ -2526,6 +2571,9 @@
|
||||
<code>$renameMode</code>
|
||||
<code>$renameMode</code>
|
||||
</ArgumentTypeCoercion>
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
<PossiblyNullPropertyFetch occurrences="1">
|
||||
<code>$classMetadata->reflClass->name</code>
|
||||
</PossiblyNullPropertyFetch>
|
||||
@@ -2903,6 +2951,9 @@
|
||||
<InvalidScalarArgument occurrences="1">
|
||||
<code>$key</code>
|
||||
</InvalidScalarArgument>
|
||||
<MethodSignatureMustProvideReturnType occurrences="1">
|
||||
<code>__toString</code>
|
||||
</MethodSignatureMustProvideReturnType>
|
||||
<PossiblyFalseArgument occurrences="2">
|
||||
<code>$spacePos</code>
|
||||
<code>$spacePos</code>
|
||||
@@ -3236,8 +3287,9 @@
|
||||
</RedundantConditionGivenDocblockType>
|
||||
</file>
|
||||
<file src="lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php">
|
||||
<ArgumentTypeCoercion occurrences="1">
|
||||
<ArgumentTypeCoercion occurrences="2">
|
||||
<code>$metadata->changeTrackingPolicy</code>
|
||||
<code>$simpleXml->asXML()</code>
|
||||
</ArgumentTypeCoercion>
|
||||
<DeprecatedClass occurrences="1">
|
||||
<code>AbstractExporter</code>
|
||||
|
||||
@@ -90,18 +90,6 @@
|
||||
<file name="lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php"/>
|
||||
</errorLevel>
|
||||
</MissingDependency>
|
||||
<ParadoxicalCondition>
|
||||
<errorLevel type="suppress">
|
||||
<!-- See https://github.com/vimeo/psalm/issues/3381 -->
|
||||
<file name="lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php"/>
|
||||
</errorLevel>
|
||||
</ParadoxicalCondition>
|
||||
<NullArgument>
|
||||
<errorLevel type="suppress">
|
||||
<!-- See https://github.com/vimeo/psalm/issues/5920 -->
|
||||
<file name="lib/Doctrine/ORM/Mapping/Driver/AttributeReader.php"/>
|
||||
</errorLevel>
|
||||
</NullArgument>
|
||||
<RedundantCastGivenDocblockType>
|
||||
<errorLevel type="suppress">
|
||||
<!-- Can be removed once the "getMaxResults" methods of those classes have native parameter types -->
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Enums;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embedded;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
|
||||
#[Entity]
|
||||
class Product
|
||||
{
|
||||
#[Id, GeneratedValue, Column(type: 'integer')]
|
||||
public int $id;
|
||||
|
||||
#[Embedded(class: Quantity::class)]
|
||||
public Quantity $quantity;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Enums;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
|
||||
#[Embeddable]
|
||||
class Quantity
|
||||
{
|
||||
#[Column(type: 'integer')]
|
||||
public int $value;
|
||||
|
||||
#[Column(type: 'string', enumType: Unit::class)]
|
||||
public Unit $unit;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Enums;
|
||||
|
||||
enum Unit: string
|
||||
{
|
||||
case Gram = 'g';
|
||||
case Meter = 'm';
|
||||
}
|
||||
@@ -13,7 +13,7 @@ use Doctrine\ORM\Mapping\Table;
|
||||
#[Entity, Table(name: 'author')]
|
||||
class Author
|
||||
{
|
||||
#[Column, Id, GeneratedValue]
|
||||
#[Column, Id, GeneratedValue(strategy: 'IDENTITY')]
|
||||
private readonly int $id;
|
||||
|
||||
#[Column]
|
||||
|
||||
@@ -17,7 +17,7 @@ use Doctrine\ORM\Mapping\Table;
|
||||
#[Entity, Table(name: 'book')]
|
||||
class Book
|
||||
{
|
||||
#[Column, Id, GeneratedValue]
|
||||
#[Column, Id, GeneratedValue(strategy: 'IDENTITY')]
|
||||
private readonly int $id;
|
||||
|
||||
#[Column]
|
||||
|
||||
@@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping\Table;
|
||||
#[Entity, Table(name: 'simple_book')]
|
||||
class SimpleBook
|
||||
{
|
||||
#[Column, Id, GeneratedValue]
|
||||
#[Column, Id, GeneratedValue(strategy: 'IDENTITY')]
|
||||
private readonly int $id;
|
||||
|
||||
#[Column]
|
||||
|
||||
@@ -4,12 +4,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\Tests\Models\Enums\Card;
|
||||
use Doctrine\Tests\Models\Enums\Product;
|
||||
use Doctrine\Tests\Models\Enums\Quantity;
|
||||
use Doctrine\Tests\Models\Enums\Suit;
|
||||
use Doctrine\Tests\Models\Enums\TypedCard;
|
||||
use Doctrine\Tests\Models\Enums\Unit;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function dirname;
|
||||
@@ -22,11 +26,11 @@ class EnumTest extends OrmFunctionalTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->_em = $this->getEntityManager(null, new AttributeDriver([dirname(__DIR__, 2) . '/Models/Enums']));
|
||||
$this->_schemaTool = new SchemaTool($this->_em);
|
||||
|
||||
parent::setUp();
|
||||
|
||||
if ($this->isSecondLevelCacheEnabled) {
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
@@ -101,4 +105,34 @@ EXCEPTION
|
||||
TypedCard::class => [TypedCard::class],
|
||||
];
|
||||
}
|
||||
|
||||
public function testItAllowsReadingAttributes(): void
|
||||
{
|
||||
$metadata = $this->_em->getClassMetadata(Card::class);
|
||||
$property = $metadata->getReflectionProperty('suit');
|
||||
|
||||
$attributes = $property->getAttributes();
|
||||
|
||||
$this->assertCount(1, $attributes);
|
||||
$this->assertEquals(Column::class, $attributes[0]->getName());
|
||||
}
|
||||
|
||||
public function testEnumMappingWithEmbeddable(): void
|
||||
{
|
||||
$this->setUpEntitySchema([Product::class]);
|
||||
|
||||
$product = new Product();
|
||||
$product->quantity = new Quantity();
|
||||
$product->quantity->value = 10;
|
||||
$product->quantity->unit = Unit::Gram;
|
||||
|
||||
$this->_em->persist($product);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$fetchedProduct = $this->_em->find(Product::class, $product->id);
|
||||
|
||||
$this->assertInstanceOf(Unit::class, $fetchedProduct->quantity->unit);
|
||||
$this->assertEquals(Unit::Gram, $fetchedProduct->quantity->unit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,8 +312,18 @@ class QueryDqlFunctionTest extends OrmFunctionalTestCase
|
||||
self::assertArrayHasKey('now', $result);
|
||||
self::assertArrayHasKey('add', $result);
|
||||
|
||||
$now = new DateTimeImmutable($result['now']);
|
||||
$inOneUnit = $now->modify(sprintf('+%d %s', $amount, $unit));
|
||||
if (
|
||||
$unit === 'month'
|
||||
&& $inOneUnit->format('m') === $now->modify('+2 month')->format('m')
|
||||
&& ! $this->_em->getConnection()->getDatabasePlatform() instanceof SqlitePlatform
|
||||
) {
|
||||
$inOneUnit = new DateTimeImmutable('last day of next month');
|
||||
}
|
||||
|
||||
self::assertEqualsWithDelta(
|
||||
(new DateTimeImmutable($result['now']))->modify(sprintf('+%d %s', $amount, $unit)),
|
||||
$inOneUnit,
|
||||
new DateTimeImmutable($result['add']),
|
||||
$delta
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ use Doctrine\ORM\Mapping\Cache;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\DiscriminatorColumn;
|
||||
use Doctrine\ORM\Mapping\DiscriminatorMap;
|
||||
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
@@ -33,6 +34,7 @@ use Doctrine\Tests\Models\DDC1872\DDC1872ExampleEntityWithOverride;
|
||||
use Doctrine\Tests\Models\DirectoryTree\Directory;
|
||||
use Doctrine\Tests\Models\DirectoryTree\File;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceCart;
|
||||
use Generator;
|
||||
|
||||
class AnnotationDriverTest extends AbstractMappingDriverTest
|
||||
{
|
||||
@@ -274,6 +276,30 @@ class AnnotationDriverTest extends AbstractMappingDriverTest
|
||||
self::assertArrayHasKey('example_trait_bar_id', $metadataWithoutOverride->associationMappings['bar']['joinColumnFieldNames']);
|
||||
self::assertArrayHasKey('example_entity_overridden_bar_id', $metadataWithOverride->associationMappings['bar']['joinColumnFieldNames']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param class-string $class
|
||||
*
|
||||
* @dataProvider provideDiscriminatorColumnTestcases
|
||||
*/
|
||||
public function testLengthForDiscriminatorColumn(string $class, int $expectedLength): void
|
||||
{
|
||||
$factory = $this->createClassMetadataFactory();
|
||||
|
||||
$metadata = $factory->getMetadataFor($class);
|
||||
|
||||
self::assertNotNull($metadata->discriminatorColumn);
|
||||
self::assertArrayHasKey('length', $metadata->discriminatorColumn);
|
||||
self::assertSame($expectedLength, $metadata->discriminatorColumn['length']);
|
||||
}
|
||||
|
||||
public function provideDiscriminatorColumnTestcases(): Generator
|
||||
{
|
||||
yield [DiscriminatorColumnWithNullLength::class, 255];
|
||||
yield [DiscriminatorColumnWithNoLength::class, 255];
|
||||
yield [DiscriminatorColumnWithZeroLength::class, 0];
|
||||
yield [DiscriminatorColumnWithNonZeroLength::class, 60];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,3 +464,83 @@ class AnnotationSLCFoo
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(
|
||||
* name="type",
|
||||
* type="string",
|
||||
* length=0,
|
||||
* columnDefinition="enum('region','airport','station','poi') NOT NULL",
|
||||
* ),
|
||||
* @DiscriminatorMap({"s"="SuperEntity", "c"="ChildEntity"})
|
||||
*/
|
||||
class DiscriminatorColumnWithZeroLength
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @Id
|
||||
* @Column
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(
|
||||
* name="type",
|
||||
* type="string",
|
||||
* ),
|
||||
* @DiscriminatorMap({"s"="SuperEntity", "c"="ChildEntity"})
|
||||
*/
|
||||
class DiscriminatorColumnWithNoLength
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @Id
|
||||
* @Column
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(
|
||||
* name="type",
|
||||
* type="string",
|
||||
* length=60,
|
||||
* ),
|
||||
* @DiscriminatorMap({"s"="SuperEntity", "c"="ChildEntity"})
|
||||
*/
|
||||
class DiscriminatorColumnWithNonZeroLength
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @Id
|
||||
* @Column
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(
|
||||
* name="type",
|
||||
* type="string",
|
||||
* length=null,
|
||||
* ),
|
||||
* @DiscriminatorMap({"s"="SuperEntity", "c"="ChildEntity"})
|
||||
*/
|
||||
class DiscriminatorColumnWithNullLength
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @Id
|
||||
* @Column
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
+3
-5
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Mapping;
|
||||
namespace Doctrine\Tests\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
@@ -15,16 +15,14 @@ final class UnderscoreNamingStrategyTest extends TestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
/** @test */
|
||||
public function checkDeprecationMessage(): void
|
||||
public function testDeprecationMessage(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/7908');
|
||||
|
||||
new UnderscoreNamingStrategy(CASE_LOWER, false);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function checkNoDeprecationMessageWhenNumberAwareEnabled(): void
|
||||
public function testNoDeprecationMessageWhenNumberAwareEnabled(): void
|
||||
{
|
||||
$before = Deprecation::getTriggeredDeprecations()['https://github.com/doctrine/orm/pull/7908'] ?? 0;
|
||||
|
||||
@@ -35,6 +35,8 @@ use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\Models\CompositeKeyInheritance\JoinedDerivedChildClass;
|
||||
use Doctrine\Tests\Models\CompositeKeyInheritance\JoinedDerivedIdentityClass;
|
||||
use Doctrine\Tests\Models\CompositeKeyInheritance\JoinedDerivedRootClass;
|
||||
use Doctrine\Tests\Models\Enums\Card;
|
||||
use Doctrine\Tests\Models\Enums\Suit;
|
||||
use Doctrine\Tests\Models\Forum\ForumAvatar;
|
||||
use Doctrine\Tests\Models\Forum\ForumUser;
|
||||
use Doctrine\Tests\Models\NullDefault\NullDefaultColumn;
|
||||
@@ -189,6 +191,23 @@ class SchemaToolTest extends OrmTestCase
|
||||
self::assertSame([], $customSchemaOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8.1
|
||||
*/
|
||||
public function testEnumTypeAddedToCustomSchemaOptions(): void
|
||||
{
|
||||
$em = $this->getTestEntityManager();
|
||||
$schemaTool = new SchemaTool($em);
|
||||
|
||||
$customSchemaOptions = $schemaTool->getSchemaFromMetadata([$em->getClassMetadata(Card::class)])
|
||||
->getTable('Card')
|
||||
->getColumn('suit')
|
||||
->getCustomSchemaOptions();
|
||||
|
||||
self::assertArrayHasKey('enumType', $customSchemaOptions);
|
||||
self::assertSame(Suit::class, $customSchemaOptions['enumType']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-3671
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user