Skip to content

Commit b05a825

Browse files
committed
Typed properties support
1 parent 5a59a8a commit b05a825

File tree

6 files changed

+221
-17
lines changed

6 files changed

+221
-17
lines changed

composer.json

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"doctrine/event-manager": "^1.0",
3131
"doctrine/instantiator": "^1.1",
3232
"doctrine/persistence": "^1.3.5",
33+
"doctrine/reflection": "1.2.1",
3334
"jean85/pretty-package-versions": "^1.3.0",
3435
"mongodb/mongodb": "^1.2.0",
3536
"ocramius/proxy-manager": "^2.2",
@@ -56,6 +57,7 @@
5657
"Doctrine\\ODM\\MongoDB\\Benchmark\\": "benchmark",
5758
"Doctrine\\ODM\\MongoDB\\Tests\\": "tests/Doctrine/ODM/MongoDB/Tests",
5859
"Documents\\": "tests/Documents",
60+
"Documents74\\": "tests/Documents74",
5961
"Stubs\\": "tests/Stubs",
6062
"TestDocuments\\" :"tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures"
6163
}

lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php

+18-14
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Doctrine\ODM\MongoDB\Types\Versionable;
1515
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
1616
use Doctrine\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
17+
use Doctrine\Persistence\Mapping\ReflectionService;
18+
use Doctrine\Persistence\Mapping\RuntimeReflectionService;
1719
use InvalidArgumentException;
1820
use LogicException;
1921
use ProxyManager\Proxy\GhostObjectInterface;
@@ -24,6 +26,7 @@
2426
use function array_keys;
2527
use function array_map;
2628
use function array_pop;
29+
use function assert;
2730
use function class_exists;
2831
use function constant;
2932
use function count;
@@ -514,6 +517,9 @@
514517
/** @var InstantiatorInterface */
515518
private $instantiator;
516519

520+
/** @var ReflectionService */
521+
private $reflectionService;
522+
517523
/** @var string|null */
518524
private $rootClass;
519525

@@ -523,9 +529,10 @@
523529
*/
524530
public function __construct(string $documentName)
525531
{
526-
$this->name = $documentName;
527-
$this->rootDocumentName = $documentName;
528-
$this->reflClass = new ReflectionClass($documentName);
532+
$this->name = $documentName;
533+
$this->rootDocumentName = $documentName;
534+
$this->reflectionService = new RuntimeReflectionService();
535+
$this->reflClass = new ReflectionClass($documentName);
529536
$this->setCollection($this->reflClass->getShortName());
530537
$this->instantiator = new Instantiator();
531538
}
@@ -2003,8 +2010,8 @@ public function mapField(array $mapping) : array
20032010
$this->associationMappings[$mapping['fieldName']] = $mapping;
20042011
}
20052012

2006-
$reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2007-
$reflProp->setAccessible(true);
2013+
$reflProp = $this->reflectionService->getAccessibleProperty($this->name, $mapping['fieldName']);
2014+
assert($reflProp instanceof ReflectionProperty);
20082015
$this->reflFields[$mapping['fieldName']] = $reflProp;
20092016

20102017
return $mapping;
@@ -2114,17 +2121,14 @@ public function __sleep()
21142121
public function __wakeup()
21152122
{
21162123
// Restore ReflectionClass and properties
2117-
$this->reflClass = new ReflectionClass($this->name);
2118-
$this->instantiator = new Instantiator();
2124+
$this->reflectionService = new RuntimeReflectionService();
2125+
$this->reflClass = new ReflectionClass($this->name);
2126+
$this->instantiator = new Instantiator();
21192127

21202128
foreach ($this->fieldMappings as $field => $mapping) {
2121-
if (isset($mapping['declared'])) {
2122-
$reflField = new ReflectionProperty($mapping['declared'], $field);
2123-
} else {
2124-
$reflField = $this->reflClass->getProperty($field);
2125-
}
2126-
$reflField->setAccessible(true);
2127-
$this->reflFields[$field] = $reflField;
2129+
$prop = $this->reflectionService->getAccessibleProperty($mapping['declared'] ?? $this->name, $field);
2130+
assert($prop instanceof ReflectionProperty);
2131+
$this->reflFields[$field] = $prop;
21282132
}
21292133
}
21302134

lib/Doctrine/ODM/MongoDB/UnitOfWork.php

+12-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@
2121
use Doctrine\ODM\MongoDB\Types\Type;
2222
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
2323
use Doctrine\ODM\MongoDB\Utility\LifecycleEventManager;
24+
use Doctrine\Persistence\Mapping\ReflectionService;
25+
use Doctrine\Persistence\Mapping\RuntimeReflectionService;
2426
use InvalidArgumentException;
2527
use MongoDB\BSON\UTCDateTime;
2628
use ProxyManager\Proxy\GhostObjectInterface;
29+
use ReflectionProperty;
2730
use UnexpectedValueException;
2831
use function array_filter;
32+
use function assert;
2933
use function count;
3034
use function get_class;
3135
use function in_array;
@@ -249,6 +253,9 @@ final class UnitOfWork implements PropertyChangedListener
249253
/** @var LifecycleEventManager */
250254
private $lifecycleEventManager;
251255

256+
/** @var ReflectionService */
257+
private $reflectionService;
258+
252259
/**
253260
* Array of embedded documents known to UnitOfWork. We need to hold them to prevent spl_object_hash
254261
* collisions in case already managed object is lost due to GC (so now it won't). Embedded documents
@@ -270,6 +277,7 @@ public function __construct(DocumentManager $dm, EventManager $evm, HydratorFact
270277
$this->evm = $evm;
271278
$this->hydratorFactory = $hydratorFactory;
272279
$this->lifecycleEventManager = new LifecycleEventManager($dm, $this, $evm);
280+
$this->reflectionService = new RuntimeReflectionService();
273281
}
274282

275283
/**
@@ -1778,9 +1786,10 @@ private function doMerge(object $document, array &$visited, ?object $prevManaged
17781786
}
17791787

17801788
// Merge state of $document into existing (managed) document
1781-
foreach ($class->reflClass->getProperties() as $prop) {
1782-
$name = $prop->name;
1783-
$prop->setAccessible(true);
1789+
foreach ($class->reflClass->getProperties() as $nativeReflection) {
1790+
$name = $nativeReflection->name;
1791+
$prop = $this->reflectionService->getAccessibleProperty($class->name, $name);
1792+
assert($prop instanceof ReflectionProperty);
17841793
if (! isset($class->associationMappings[$name])) {
17851794
if (! $class->isIdentifier($name)) {
17861795
$prop->setValue($managedCopy, $prop->getValue($document));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\MongoDB\Tests\Functional;
6+
7+
use Doctrine\ODM\MongoDB\Tests\BaseTest;
8+
use Documents74\TypedDocument;
9+
use Documents74\TypedEmbeddedDocument;
10+
use MongoDB\BSON\ObjectId;
11+
use function phpversion;
12+
use function version_compare;
13+
14+
class TypedPropertiesTest extends BaseTest
15+
{
16+
public function setUp() : void
17+
{
18+
if (version_compare((string) phpversion(), '7.4.0', '<')) {
19+
$this->markTestSkipped('PHP 7.4 is required to run this test');
20+
}
21+
22+
parent::setUp();
23+
}
24+
25+
public function testPersistNew() : void
26+
{
27+
$doc = new TypedDocument();
28+
$doc->setName('Maciej');
29+
$doc->setEmbedOne(new TypedEmbeddedDocument('The answer', 42));
30+
$doc->getEmbedMany()->add(new TypedEmbeddedDocument('Lucky number', 7));
31+
$this->dm->persist($doc);
32+
$this->dm->flush();
33+
$this->dm->clear();
34+
35+
/** @var TypedDocument $saved */
36+
$saved = $this->dm->find(TypedDocument::class, $doc->getId());
37+
$this->assertEquals($doc->getId(), $saved->getId());
38+
$this->assertSame($doc->getName(), $saved->getName());
39+
$this->assertEquals($doc->getEmbedOne(), $saved->getEmbedOne());
40+
$this->assertEquals($doc->getEmbedMany()->getValues(), $saved->getEmbedMany()->getValues());
41+
}
42+
43+
public function testMerge() : void
44+
{
45+
$doc = new TypedDocument();
46+
$doc->setId((string) new ObjectId());
47+
$doc->setName('Maciej');
48+
$doc->setEmbedOne(new TypedEmbeddedDocument('The answer', 42));
49+
$doc->getEmbedMany()->add(new TypedEmbeddedDocument('Lucky number', 7));
50+
51+
$merged = $this->dm->merge($doc);
52+
$this->assertEquals($doc->getId(), $merged->getId());
53+
$this->assertSame($doc->getName(), $merged->getName());
54+
$this->assertEquals($doc->getEmbedOne(), $merged->getEmbedOne());
55+
$this->assertEquals($doc->getEmbedMany()->getValues(), $merged->getEmbedMany()->getValues());
56+
}
57+
58+
public function testProxying() : void
59+
{
60+
$doc = new TypedDocument();
61+
$doc->setName('Maciej');
62+
$doc->setEmbedOne(new TypedEmbeddedDocument('The answer', 42));
63+
$doc->getEmbedMany()->add(new TypedEmbeddedDocument('Lucky number', 7));
64+
$this->dm->persist($doc);
65+
$this->dm->flush();
66+
$this->dm->clear();
67+
68+
/** @var TypedDocument $proxy */
69+
$proxy = $this->dm->getReference(TypedDocument::class, $doc->getId());
70+
$this->assertEquals($doc->getId(), $proxy->getId());
71+
$this->assertSame($doc->getName(), $proxy->getName());
72+
$this->assertEquals($doc->getEmbedOne(), $proxy->getEmbedOne());
73+
$this->assertEquals($doc->getEmbedMany()->getValues(), $proxy->getEmbedMany()->getValues());
74+
}
75+
}

tests/Documents74/TypedDocument.php

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Documents74;
6+
7+
use Doctrine\Common\Collections\ArrayCollection;
8+
use Doctrine\Common\Collections\Collection;
9+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
10+
11+
/**
12+
* @ODM\Document()
13+
*/
14+
class TypedDocument
15+
{
16+
/**
17+
* @ODM\Id()
18+
*/
19+
private string $id;
20+
21+
/**
22+
* @ODM\Field(type="string")
23+
*/
24+
private string $name;
25+
26+
/**
27+
* @ODM\EmbedOne(targetDocument=TypedEmbeddedDocument::class)
28+
*/
29+
private TypedEmbeddedDocument $embedOne;
30+
31+
/**
32+
* @ODM\EmbedMany(targetDocument=TypedEmbeddedDocument::class)
33+
*/
34+
private Collection $embedMany;
35+
36+
public function __construct()
37+
{
38+
$this->embedMany = new ArrayCollection();
39+
}
40+
41+
public function getId() : string
42+
{
43+
return $this->id;
44+
}
45+
46+
public function setId(string $id) : void
47+
{
48+
$this->id = $id;
49+
}
50+
51+
public function getName() : string
52+
{
53+
return $this->name;
54+
}
55+
56+
public function setName(string $name) : void
57+
{
58+
$this->name = $name;
59+
}
60+
61+
public function getEmbedOne() : TypedEmbeddedDocument
62+
{
63+
return $this->embedOne;
64+
}
65+
66+
public function setEmbedOne(TypedEmbeddedDocument $embedOne) : void
67+
{
68+
$this->embedOne = $embedOne;
69+
}
70+
71+
public function getEmbedMany() : Collection
72+
{
73+
return $this->embedMany;
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Documents74;
6+
7+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
8+
9+
/**
10+
* @ODM\EmbeddedDocument()
11+
*/
12+
class TypedEmbeddedDocument
13+
{
14+
/**
15+
* @ODM\Field(type="string")
16+
*/
17+
private string $name;
18+
19+
/**
20+
* @ODM\Field(type="int")
21+
*/
22+
private int $number;
23+
24+
public function __construct(string $name, int $number)
25+
{
26+
$this->name = $name;
27+
$this->number = $number;
28+
}
29+
30+
public function getName() : string
31+
{
32+
return $this->name;
33+
}
34+
35+
public function getNumber() : int
36+
{
37+
return $this->number;
38+
}
39+
}

0 commit comments

Comments
 (0)