diff --git a/composer.json b/composer.json index 810c2f06a22..ed7615765ce 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "doctrine/annotations": "^1.13", "doctrine/coding-standard": "^9.0", "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "1.4.0", + "phpstan/phpstan": "1.4.1", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", "squizlabs/php_codesniffer": "3.6.2", "symfony/cache": "^4.4 || ^5.4 || ^6.0", diff --git a/docs/en/cookbook/accessing-private-properties-of-the-same-class-from-different-instance.rst b/docs/en/cookbook/accessing-private-properties-of-the-same-class-from-different-instance.rst new file mode 100644 index 00000000000..9730a883478 --- /dev/null +++ b/docs/en/cookbook/accessing-private-properties-of-the-same-class-from-different-instance.rst @@ -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 + + 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; + } + + // ... + } diff --git a/docs/en/reference/architecture.rst b/docs/en/reference/architecture.rst index 6c005e43298..b41b67445b7 100644 --- a/docs/en/reference/architecture.rst +++ b/docs/en/reference/architecture.rst @@ -93,6 +93,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 diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 5cb93961cbc..b92a6be5853 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -781,12 +781,18 @@ private function gatherRelationJoinColumns( */ 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; } diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 3cc10fe3f93..7567e69a26a 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -703,8 +703,9 @@ self::GENERATOR_TYPE_UUID - + canEmulateSchemas + canRequireSQLConversion $this->columnNames diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php index 5faa3146ece..4dd7fcf0ad4 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php @@ -35,6 +35,8 @@ 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 @@ public function testNullDefaultNotAddedToCustomSchemaOptions(): void 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 */