Skip to content

Commit

Permalink
Typed properties support
Browse files Browse the repository at this point in the history
  • Loading branch information
malarzm committed May 25, 2020
1 parent 5a59a8a commit b05a825
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 17 deletions.
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"doctrine/event-manager": "^1.0",
"doctrine/instantiator": "^1.1",
"doctrine/persistence": "^1.3.5",
"doctrine/reflection": "1.2.1",
"jean85/pretty-package-versions": "^1.3.0",
"mongodb/mongodb": "^1.2.0",
"ocramius/proxy-manager": "^2.2",
Expand All @@ -56,6 +57,7 @@
"Doctrine\\ODM\\MongoDB\\Benchmark\\": "benchmark",
"Doctrine\\ODM\\MongoDB\\Tests\\": "tests/Doctrine/ODM/MongoDB/Tests",
"Documents\\": "tests/Documents",
"Documents74\\": "tests/Documents74",
"Stubs\\": "tests/Stubs",
"TestDocuments\\" :"tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures"
}
Expand Down
32 changes: 18 additions & 14 deletions lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Doctrine\ODM\MongoDB\Types\Versionable;
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
use Doctrine\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
use Doctrine\Persistence\Mapping\ReflectionService;
use Doctrine\Persistence\Mapping\RuntimeReflectionService;
use InvalidArgumentException;
use LogicException;
use ProxyManager\Proxy\GhostObjectInterface;
Expand All @@ -24,6 +26,7 @@
use function array_keys;
use function array_map;
use function array_pop;
use function assert;
use function class_exists;
use function constant;
use function count;
Expand Down Expand Up @@ -514,6 +517,9 @@
/** @var InstantiatorInterface */
private $instantiator;

/** @var ReflectionService */
private $reflectionService;

/** @var string|null */
private $rootClass;

Expand All @@ -523,9 +529,10 @@
*/
public function __construct(string $documentName)
{
$this->name = $documentName;
$this->rootDocumentName = $documentName;
$this->reflClass = new ReflectionClass($documentName);
$this->name = $documentName;
$this->rootDocumentName = $documentName;
$this->reflectionService = new RuntimeReflectionService();
$this->reflClass = new ReflectionClass($documentName);
$this->setCollection($this->reflClass->getShortName());
$this->instantiator = new Instantiator();
}
Expand Down Expand Up @@ -2003,8 +2010,8 @@ public function mapField(array $mapping) : array
$this->associationMappings[$mapping['fieldName']] = $mapping;
}

$reflProp = $this->reflClass->getProperty($mapping['fieldName']);
$reflProp->setAccessible(true);
$reflProp = $this->reflectionService->getAccessibleProperty($this->name, $mapping['fieldName']);
assert($reflProp instanceof ReflectionProperty);
$this->reflFields[$mapping['fieldName']] = $reflProp;

return $mapping;
Expand Down Expand Up @@ -2114,17 +2121,14 @@ public function __sleep()
public function __wakeup()
{
// Restore ReflectionClass and properties
$this->reflClass = new ReflectionClass($this->name);
$this->instantiator = new Instantiator();
$this->reflectionService = new RuntimeReflectionService();
$this->reflClass = new ReflectionClass($this->name);
$this->instantiator = new Instantiator();

foreach ($this->fieldMappings as $field => $mapping) {
if (isset($mapping['declared'])) {
$reflField = new ReflectionProperty($mapping['declared'], $field);
} else {
$reflField = $this->reflClass->getProperty($field);
}
$reflField->setAccessible(true);
$this->reflFields[$field] = $reflField;
$prop = $this->reflectionService->getAccessibleProperty($mapping['declared'] ?? $this->name, $field);
assert($prop instanceof ReflectionProperty);
$this->reflFields[$field] = $prop;
}
}

Expand Down
15 changes: 12 additions & 3 deletions lib/Doctrine/ODM/MongoDB/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
use Doctrine\ODM\MongoDB\Types\Type;
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
use Doctrine\ODM\MongoDB\Utility\LifecycleEventManager;
use Doctrine\Persistence\Mapping\ReflectionService;
use Doctrine\Persistence\Mapping\RuntimeReflectionService;
use InvalidArgumentException;
use MongoDB\BSON\UTCDateTime;
use ProxyManager\Proxy\GhostObjectInterface;
use ReflectionProperty;
use UnexpectedValueException;
use function array_filter;
use function assert;
use function count;
use function get_class;
use function in_array;
Expand Down Expand Up @@ -249,6 +253,9 @@ final class UnitOfWork implements PropertyChangedListener
/** @var LifecycleEventManager */
private $lifecycleEventManager;

/** @var ReflectionService */
private $reflectionService;

/**
* Array of embedded documents known to UnitOfWork. We need to hold them to prevent spl_object_hash
* collisions in case already managed object is lost due to GC (so now it won't). Embedded documents
Expand All @@ -270,6 +277,7 @@ public function __construct(DocumentManager $dm, EventManager $evm, HydratorFact
$this->evm = $evm;
$this->hydratorFactory = $hydratorFactory;
$this->lifecycleEventManager = new LifecycleEventManager($dm, $this, $evm);
$this->reflectionService = new RuntimeReflectionService();
}

/**
Expand Down Expand Up @@ -1778,9 +1786,10 @@ private function doMerge(object $document, array &$visited, ?object $prevManaged
}

// Merge state of $document into existing (managed) document
foreach ($class->reflClass->getProperties() as $prop) {
$name = $prop->name;
$prop->setAccessible(true);
foreach ($class->reflClass->getProperties() as $nativeReflection) {
$name = $nativeReflection->name;
$prop = $this->reflectionService->getAccessibleProperty($class->name, $name);
assert($prop instanceof ReflectionProperty);
if (! isset($class->associationMappings[$name])) {
if (! $class->isIdentifier($name)) {
$prop->setValue($managedCopy, $prop->getValue($document));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Doctrine\ODM\MongoDB\Tests\Functional;

use Doctrine\ODM\MongoDB\Tests\BaseTest;
use Documents74\TypedDocument;
use Documents74\TypedEmbeddedDocument;
use MongoDB\BSON\ObjectId;
use function phpversion;
use function version_compare;

class TypedPropertiesTest extends BaseTest
{
public function setUp() : void
{
if (version_compare((string) phpversion(), '7.4.0', '<')) {
$this->markTestSkipped('PHP 7.4 is required to run this test');
}

parent::setUp();
}

public function testPersistNew() : void
{
$doc = new TypedDocument();
$doc->setName('Maciej');
$doc->setEmbedOne(new TypedEmbeddedDocument('The answer', 42));
$doc->getEmbedMany()->add(new TypedEmbeddedDocument('Lucky number', 7));
$this->dm->persist($doc);
$this->dm->flush();
$this->dm->clear();

/** @var TypedDocument $saved */
$saved = $this->dm->find(TypedDocument::class, $doc->getId());
$this->assertEquals($doc->getId(), $saved->getId());
$this->assertSame($doc->getName(), $saved->getName());
$this->assertEquals($doc->getEmbedOne(), $saved->getEmbedOne());
$this->assertEquals($doc->getEmbedMany()->getValues(), $saved->getEmbedMany()->getValues());
}

public function testMerge() : void
{
$doc = new TypedDocument();
$doc->setId((string) new ObjectId());
$doc->setName('Maciej');
$doc->setEmbedOne(new TypedEmbeddedDocument('The answer', 42));
$doc->getEmbedMany()->add(new TypedEmbeddedDocument('Lucky number', 7));

$merged = $this->dm->merge($doc);
$this->assertEquals($doc->getId(), $merged->getId());
$this->assertSame($doc->getName(), $merged->getName());
$this->assertEquals($doc->getEmbedOne(), $merged->getEmbedOne());
$this->assertEquals($doc->getEmbedMany()->getValues(), $merged->getEmbedMany()->getValues());
}

public function testProxying() : void
{
$doc = new TypedDocument();
$doc->setName('Maciej');
$doc->setEmbedOne(new TypedEmbeddedDocument('The answer', 42));
$doc->getEmbedMany()->add(new TypedEmbeddedDocument('Lucky number', 7));
$this->dm->persist($doc);
$this->dm->flush();
$this->dm->clear();

/** @var TypedDocument $proxy */
$proxy = $this->dm->getReference(TypedDocument::class, $doc->getId());
$this->assertEquals($doc->getId(), $proxy->getId());
$this->assertSame($doc->getName(), $proxy->getName());
$this->assertEquals($doc->getEmbedOne(), $proxy->getEmbedOne());
$this->assertEquals($doc->getEmbedMany()->getValues(), $proxy->getEmbedMany()->getValues());
}
}
75 changes: 75 additions & 0 deletions tests/Documents74/TypedDocument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Documents74;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
* @ODM\Document()
*/
class TypedDocument
{
/**
* @ODM\Id()
*/
private string $id;

/**
* @ODM\Field(type="string")
*/
private string $name;

/**
* @ODM\EmbedOne(targetDocument=TypedEmbeddedDocument::class)
*/
private TypedEmbeddedDocument $embedOne;

/**
* @ODM\EmbedMany(targetDocument=TypedEmbeddedDocument::class)
*/
private Collection $embedMany;

public function __construct()
{
$this->embedMany = new ArrayCollection();
}

public function getId() : string
{
return $this->id;
}

public function setId(string $id) : void
{
$this->id = $id;
}

public function getName() : string
{
return $this->name;
}

public function setName(string $name) : void
{
$this->name = $name;
}

public function getEmbedOne() : TypedEmbeddedDocument
{
return $this->embedOne;
}

public function setEmbedOne(TypedEmbeddedDocument $embedOne) : void
{
$this->embedOne = $embedOne;
}

public function getEmbedMany() : Collection
{
return $this->embedMany;
}
}
39 changes: 39 additions & 0 deletions tests/Documents74/TypedEmbeddedDocument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Documents74;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;

/**
* @ODM\EmbeddedDocument()
*/
class TypedEmbeddedDocument
{
/**
* @ODM\Field(type="string")
*/
private string $name;

/**
* @ODM\Field(type="int")
*/
private int $number;

public function __construct(string $name, int $number)
{
$this->name = $name;
$this->number = $number;
}

public function getName() : string
{
return $this->name;
}

public function getNumber() : int
{
return $this->number;
}
}

0 comments on commit b05a825

Please sign in to comment.