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 23, 2020
1 parent 8ec5d0f commit a475d0d
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 14 deletions.
1 change: 1 addition & 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",
"mongodb/mongodb": "^1.2.0",
"ocramius/proxy-manager": "^2.2",
"symfony/console": "^3.4|^4.1|^5.0",
Expand Down
22 changes: 11 additions & 11 deletions lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Doctrine\ODM\MongoDB\Types\Type;
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 Down Expand Up @@ -501,6 +503,9 @@ class ClassMetadata implements BaseClassMetadata
/** @var InstantiatorInterface */
private $instantiator;

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

/**
* Initializes a new ClassMetadata instance that will hold the object-document mapping
* metadata of the class with the given name.
Expand All @@ -509,7 +514,8 @@ public function __construct(string $documentName)
{
$this->name = $documentName;
$this->rootDocumentName = $documentName;
$this->reflClass = new ReflectionClass($documentName);
$this->reflectionService = new RuntimeReflectionService();
$this->reflClass = $this->reflectionService->getClass($documentName);
$this->setCollection($this->reflClass->getShortName());
$this->instantiator = new Instantiator();
}
Expand Down Expand Up @@ -1950,8 +1956,7 @@ 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']);
$this->reflFields[$mapping['fieldName']] = $reflProp;

return $mapping;
Expand Down Expand Up @@ -2056,17 +2061,12 @@ public function __sleep()
public function __wakeup()
{
// Restore ReflectionClass and properties
$this->reflClass = new ReflectionClass($this->name);
$this->reflectionService = new RuntimeReflectionService();
$this->reflClass = $this->reflectionService->getClass($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;
$this->reflFields[$field] = $this->reflectionService->getAccessibleProperty($this->name, $field);
}
}

Expand Down
12 changes: 9 additions & 3 deletions lib/Doctrine/ODM/MongoDB/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
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;
Expand Down Expand Up @@ -249,6 +251,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 +275,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 @@ -1758,9 +1764,9 @@ 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);
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,73 @@
<?php

declare(strict_types=1);

namespace Doctrine\ODM\MongoDB\Tests\Functional;

use Doctrine\ODM\MongoDB\Tests\BaseTest;
use Documents\TypedDocument;
use Documents\TypedEmbeddedDocument;
use MongoDB\BSON\ObjectId;

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/Documents/TypedDocument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Documents;

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;
}
}
45 changes: 45 additions & 0 deletions tests/Documents/TypedEmbeddedDocument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Documents;

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;
}

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

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

0 comments on commit a475d0d

Please sign in to comment.