Skip to content

Commit 80a032a

Browse files
authored
Deprecate overriding the __sleep method of ClassMetadata or PersistentCollection (#2965)
* Deprecate overring the __sleep method of ClassMetadata or PersistentCollection * Test extending the ClassMetadata class * Discard phpstan issues with soft-final
1 parent 2f75535 commit 80a032a

File tree

5 files changed

+154
-6
lines changed

5 files changed

+154
-6
lines changed

phpstan-baseline.neon

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,20 @@ parameters:
415415
path: src/Mapping/ClassMetadata.php
416416

417417
-
418-
message: '#^Property Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\:\:\$rootClass \(class\-string\|null\) is never assigned null so it can be removed from the property type\.$#'
419-
identifier: property.unusedType
418+
message: '#^Result of && is always false\.$#'
419+
identifier: booleanAnd.alwaysFalse
420+
count: 1
421+
path: src/Mapping/ClassMetadata.php
422+
423+
-
424+
message: '#^Strict comparison using \!\=\= between ''Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata'' and ''Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata'' will always evaluate to false\.$#'
425+
identifier: notIdentical.alwaysFalse
426+
count: 1
427+
path: src/Mapping/ClassMetadata.php
428+
429+
-
430+
message: '#^Strict comparison using \!\=\= between mixed and \*NEVER\* will always evaluate to true\.$#'
431+
identifier: notIdentical.alwaysTrue
420432
count: 1
421433
path: src/Mapping/ClassMetadata.php
422434

@@ -1068,6 +1080,12 @@ parameters:
10681080
count: 1
10691081
path: tests/Tests/Functional/TargetDocumentTest.php
10701082

1083+
-
1084+
message: '#^Class Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\ExtendedClassMetadata extends @final class Doctrine\\ODM\\MongoDB\\Mapping\\ClassMetadata\.$#'
1085+
identifier: class.extendsFinalByPhpDoc
1086+
count: 1
1087+
path: tests/Tests/Mapping/ClassMetadataTest.php
1088+
10711089
-
10721090
message: '#^Method Doctrine\\ODM\\MongoDB\\Tests\\Mapping\\ClassMetadataTest\:\:testEmptyVectorSearchIndexDefinition\(\) has parameter \$definition with no value type specified in iterable type array\.$#'
10731091
identifier: missingType.iterableValue

src/Mapping/ClassMetadata.php

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use ProxyManager\Proxy\GhostObjectInterface;
3535
use ReflectionClass;
3636
use ReflectionEnum;
37+
use ReflectionMethod;
3738
use ReflectionNamedType;
3839
use ReflectionProperty;
3940
use Symfony\Component\Uid\UuidV1;
@@ -923,7 +924,7 @@ public static function getReferenceFieldName(string $storeAs, string $pathPrefix
923924
return $pathPrefix;
924925
}
925926

926-
return ($pathPrefix ? $pathPrefix . '.' : '') . static::getReferencePrefix($storeAs) . 'id';
927+
return ($pathPrefix ? $pathPrefix . '.' : '') . self::getReferencePrefix($storeAs) . 'id';
927928
}
928929

929930
public function getReflectionClass(): ReflectionClass
@@ -2645,7 +2646,31 @@ public function mapField(array $mapping): array
26452646
* - reflFields (ReflectionProperty array)
26462647
* - propertyAccessors (ReflectionProperty array)
26472648
*
2648-
* @return array The names of all the fields that should be serialized.
2649+
* @return array<string, mixed> The serialized data.
2650+
*/
2651+
public function __serialize(): array
2652+
{
2653+
if (static::class !== self::class && (new ReflectionMethod($this, '__sleep'))->getDeclaringClass() !== self::class) {
2654+
trigger_deprecation(
2655+
'doctrine/mongodb-odm',
2656+
'2.16',
2657+
'The method __sleep() is deprecated. Implement and use %s() instead.',
2658+
__METHOD__,
2659+
);
2660+
}
2661+
2662+
$data = [];
2663+
foreach ($this->__sleep() as $field) {
2664+
$data[$field] = $this->$field;
2665+
}
2666+
2667+
return $data;
2668+
}
2669+
2670+
/**
2671+
* @deprecated
2672+
*
2673+
* @return list<string> The names of all the fields that should be serialized.
26492674
*/
26502675
public function __sleep()
26512676
{
@@ -2665,8 +2690,10 @@ public function __sleep()
26652690
'generatorOptions',
26662691
'idGenerator',
26672692
'indexes',
2693+
'searchIndexes',
26682694
'shardKey',
26692695
'timeSeriesOptions',
2696+
'isEncrypted',
26702697
];
26712698

26722699
// The rest of the metadata is only serialized if necessary.
@@ -2700,7 +2727,7 @@ public function __sleep()
27002727
$serialized[] = 'isQueryResultDocument';
27012728
}
27022729

2703-
if ($this->isView()) {
2730+
if ($this->isView) {
27042731
$serialized[] = 'isView';
27052732
$serialized[] = 'rootClass';
27062733
}
@@ -2745,8 +2772,20 @@ public function __sleep()
27452772
}
27462773

27472774
/**
2748-
* Restores some state that cannot be serialized/unserialized.
2775+
* Restores the serialized values and some state that cannot be serialized/unserialized.
2776+
*
2777+
* @param array<string, mixed> $data The serialized data.
27492778
*/
2779+
public function __unserialize(array $data): void
2780+
{
2781+
foreach ($data as $field => $value) {
2782+
$this->$field = $value;
2783+
}
2784+
2785+
$this->__wakeup();
2786+
}
2787+
2788+
/** @deprecated */
27502789
public function __wakeup(): void
27512790
{
27522791
// Restore ReflectionClass and properties

src/PersistentCollection/PersistentCollectionTrait.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,23 @@ public function slice($offset, $length = null)
509509
* Called by PHP when this collection is serialized. Ensures that the
510510
* internal state of the collection can be reproduced after serialization
511511
*
512+
* @return array<string, mixed>
513+
*/
514+
public function __serialize(): array
515+
{
516+
return [
517+
'coll' => $this->coll,
518+
'initialized' => $this->initialized,
519+
'mongoData' => $this->mongoData,
520+
'snapshot' => $this->snapshot,
521+
'isDirty' => $this->isDirty,
522+
'hints' => $this->hints,
523+
];
524+
}
525+
526+
/**
527+
* @deprecated Implement and use __serialize() instead.
528+
*
512529
* @return string[]
513530
*/
514531
public function __sleep()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\MongoDB\Tests;
6+
7+
use function call_user_func;
8+
use function restore_error_handler;
9+
use function set_error_handler;
10+
11+
use const E_USER_DEPRECATED;
12+
13+
trait CaptureDeprecationMessages
14+
{
15+
/**
16+
* This method can be replaced with expectUserDeprecationMessage() in PHPUnit 11+.
17+
* https://docs.phpunit.de/en/11.1/error-handling.html#expecting-deprecations-e-user-deprecated
18+
*
19+
* @param list<string> $errors
20+
*
21+
* @param-out list<string> $errors
22+
*/
23+
private function captureDeprecationMessages(callable $callable, ?array &$errors): mixed
24+
{
25+
$errors = [];
26+
27+
set_error_handler(static function (int $errno, string $errstr) use (&$errors): bool {
28+
$errors[] = $errstr;
29+
30+
return false;
31+
}, E_USER_DEPRECATED);
32+
33+
try {
34+
return call_user_func($callable);
35+
} finally {
36+
restore_error_handler();
37+
}
38+
}
39+
}

tests/Tests/Mapping/ClassMetadataTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Doctrine\ODM\MongoDB\Mapping\TimeSeries\Granularity;
1515
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
1616
use Doctrine\ODM\MongoDB\Tests\BaseTestCase;
17+
use Doctrine\ODM\MongoDB\Tests\CaptureDeprecationMessages;
1718
use Doctrine\ODM\MongoDB\Tests\ClassMetadataTestUtil;
1819
use Doctrine\ODM\MongoDB\Types\Type;
1920
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
@@ -51,6 +52,8 @@
5152

5253
class ClassMetadataTest extends BaseTestCase
5354
{
55+
use CaptureDeprecationMessages;
56+
5457
public function testClassMetadataInstanceSerialization(): void
5558
{
5659
$cm = new ClassMetadata(CmsUser::class);
@@ -84,6 +87,8 @@ public function testClassMetadataInstanceSerialization(): void
8487
$cm->setValidator(Document::fromJSON($validatorJson)->toPHP());
8588
$cm->setValidationAction(ClassMetadata::SCHEMA_VALIDATION_ACTION_WARN);
8689
$cm->setValidationLevel(ClassMetadata::SCHEMA_VALIDATION_LEVEL_OFF);
90+
$cm->isEncrypted = true;
91+
$cm->addSearchIndex(['mappings' => ['fields' => ['title' => ['type' => 'string']]]], 'custom_name');
8792
self::assertIsArray($cm->getFieldMapping('phonenumbers'));
8893
self::assertCount(1, $cm->fieldMappings);
8994
self::assertCount(1, $cm->associationMappings);
@@ -117,6 +122,25 @@ public function testClassMetadataInstanceSerialization(): void
117122
self::assertEquals(Document::fromJSON($validatorJson)->toPHP(), $cm->getValidator());
118123
self::assertEquals(ClassMetadata::SCHEMA_VALIDATION_ACTION_WARN, $cm->getValidationAction());
119124
self::assertEquals(ClassMetadata::SCHEMA_VALIDATION_LEVEL_OFF, $cm->getValidationLevel());
125+
self::assertTrue($cm->isEncrypted);
126+
self::assertSame([['definition' => ['mappings' => ['fields' => ['title' => ['type' => 'string']]]], 'name' => 'custom_name', 'type' => 'search']], $cm->getSearchIndexes());
127+
}
128+
129+
public function testExtendingClassMetadata(): void
130+
{
131+
$cm = new ExtendedClassMetadata(CmsUser::class);
132+
$cm->setCollection('custom_collection');
133+
$cm->customProperty = 'some_value';
134+
135+
$serialized = $this->captureDeprecationMessages(static fn () => serialize($cm), $deprecations);
136+
$cm = unserialize($serialized);
137+
138+
self::assertInstanceOf(ExtendedClassMetadata::class, $cm);
139+
self::assertSame(CmsUser::class, $cm->name);
140+
self::assertSame('custom_collection', $cm->getCollection());
141+
self::assertSame('some_value', $cm->customProperty);
142+
143+
self::assertSame(['Since doctrine/mongodb-odm 2.16: The method __sleep() is deprecated. Implement and use Doctrine\ODM\MongoDB\Mapping\ClassMetadata::__serialize() instead.'], $deprecations);
120144
}
121145

122146
public function testOwningSideAndInverseSide(): void
@@ -1136,3 +1160,14 @@ class TimeSeriesTestDocument
11361160
#[ODM\Field]
11371161
public string $metadata;
11381162
}
1163+
1164+
class ExtendedClassMetadata extends ClassMetadata
1165+
{
1166+
public string $customProperty;
1167+
1168+
/** @return list<string> */
1169+
public function __sleep(): array
1170+
{
1171+
return array_merge(parent::__sleep(), ['customProperty']);
1172+
}
1173+
}

0 commit comments

Comments
 (0)