Skip to content

Commit

Permalink
entity: implemented Embeddable
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Apr 22, 2017
1 parent 7ce3ba1 commit 331d990
Show file tree
Hide file tree
Showing 11 changed files with 387 additions and 1 deletion.
91 changes: 91 additions & 0 deletions src/Entity/Embeddable/Embeddable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Entity\Embeddable;

use Nextras\Orm\Entity\ImmutableDataTrait;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\NotSupportedException;


class Embeddable implements IEmbeddable
{
use ImmutableDataTrait;


public function __construct(array $data)
{
$this->metadata = $this->createMetadata();
if (count($data) !== count($this->metadata->getProperties())) {
$n = count($data);
$total = count($this->metadata->getProperties());
throw new InvalidArgumentException("Only $n of $total was set. Construct embeddable with all its properties. ");
}
$this->setImmutableData($data);
}


public function onLoad(array $data)
{
$this->metadata = $this->createMetadata();
$this->data = $data;
foreach ($this->metadata->getProperties() as $name => $metadataProperty) {
if (!isset($data[$name])) {
$this->data[$name] = null;
}
}
}


public function __get($name)
{
return $this->getValue($name);
}


public function __isset($name)
{
if (!$this->metadata->hasProperty($name)) {
return false;
}
return $this->hasValue($name);
}


public function __set($name, $value)
{
throw new NotSupportedException();
}


public function __unset($name)
{
throw new NotSupportedException();
}


private function initProperty(PropertyMetadata $metadata, string $name)
{
$this->validated[$name] = true;

if (!isset($this->data[$name])) {
$this->data[$name] = null;
}

if ($metadata->container) {
$property = $this->createPropertyContainer($metadata);
$property->loadValue($this->data);
$this->data[$name] = $property;

} elseif ($this->data[$name] !== null) {
// data type coercion
$this->validate($metadata, $name, $this->data[$name]);
}
}
}
119 changes: 119 additions & 0 deletions src/Entity/Embeddable/EmbeddableContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Entity\Embeddable;

use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\IProperty;
use Nextras\Orm\Entity\IPropertyContainer;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\Model\MetadataStorage;
use ReflectionClass;


class EmbeddableContainer implements IPropertyContainer
{
/** @var IEntity */
private $entity;

/** @var PropertyMetadata */
private $metadata;

/** @var PropertyMetadata[] */
private $properties = [];

/** @var IEmbeddable|null */
private $value;

/** @var string */
private $instanceType;


public function __construct(IEntity $entity, PropertyMetadata $propertyMetadata)
{
$this->entity = $entity;
$this->metadata = $propertyMetadata;
if (!isset($this->metadata->args[EmbeddableContainer::class])) {
throw new InvalidStateException();
}

$this->instanceType = $this->metadata->args[EmbeddableContainer::class]['class'];
$this->properties = MetadataStorage::get($this->instanceType)->getProperties();
}


public function loadValue(array $values)
{
$data = [];
$prefix = $this->metadata->name . '_';
foreach ($this->properties as $property) {
$data[$property->name] = $values[$prefix . $property->name] ?? null;
}

$this->setRawValue($data);
}


public function saveValue(array $values): array
{
return $this->getRawValue() + $values;
}


public function setRawValue($value)
{
$filtered = array_filter($value, function ($val) { return $val !== null; });
if ($filtered) {
$reflection = new ReflectionClass($this->instanceType);
$embeddable = $reflection->newInstanceWithoutConstructor();
assert($embeddable instanceof IEmbeddable);
$embeddable->onLoad($value);
} else {
$embeddable = null;
}

$this->setInjectedValue($embeddable);
}


public function getRawValue()
{
$value = [];
$prefix = $this->metadata->name . '_';
foreach ($this->properties as $property) {
$name = $prefix . $property->name;
$value[$name] = $this->value ? $this->value->getValue($property->name) : null;
}
return $value;
}


public function &getInjectedValue()
{
return $this->value;
}


public function hasInjectedValue(): bool
{
return $this->value !== null;
}


public function setInjectedValue($value)
{
if ($value !== null && !$value instanceof $this->instanceType) {
throw new InvalidArgumentException("Passed embeddable has to be instance of {$this->instanceType}.");
}

$this->value = $value;
$this->entity->setAsModified($this->metadata->name);
}
}
32 changes: 32 additions & 0 deletions src/Entity/Embeddable/IEmbeddable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Entity\Embeddable;


interface IEmbeddable
{
/**
* Returns value.
* @return mixed
*/
public function &getValue(string $name);


/**
* Returns true if property has a value (not null).
*/
public function hasValue(string $name): bool;


/**
* Load event.
* @return void
*/
public function onLoad(array $data);
}
7 changes: 7 additions & 0 deletions src/Entity/Reflection/IMetadataParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,11 @@ interface IMetadataParser
* @param array|null $fileDependencies
*/
public function parseEntity(string $entityClass, & $fileDependencies): EntityMetadata;


/**
* Parses metadata for entity.
* @param array|null $fileDependencies
*/
public function parseEmbeddable(string $entityClass, & $fileDependencies): EntityMetadata;
}
36 changes: 36 additions & 0 deletions src/Entity/Reflection/MetadataParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use DateTimeImmutable;
use Nette\Utils\Reflection;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\Embeddable\EmbeddableContainer;
use Nextras\Orm\Entity\Embeddable\IEmbeddable;
use Nextras\Orm\Entity\IProperty;
use Nextras\Orm\InvalidModifierDefinitionException;
use Nextras\Orm\InvalidStateException;
Expand All @@ -34,6 +36,7 @@ class MetadataParser implements IMetadataParser
'enum' => 'parseEnumModifier',
'virtual' => 'parseVirtualModifier',
'container' => 'parseContainerModifier',
'embeddable' => 'parseEmbeddableModifier',
'default' => 'parseDefaultModifier',
'primary' => 'parsePrimaryModifier',
'primary-proxy' => 'parsePrimaryProxyModifier',
Expand Down Expand Up @@ -89,6 +92,19 @@ public function parseEntity(string $class, & $fileDependencies): EntityMetadata
}


public function parseEmbeddable(string $class, & $fileDependencies): EntityMetadata
{
$this->reflection = new ReflectionClass($class);
$this->metadata = new EntityMetadata($class);

$this->loadProperties($fileDependencies);
$this->loadGettersSetters();

$fileDependencies = array_unique($fileDependencies);
return $this->metadata;
}


protected function loadGettersSetters()
{
$methods = [];
Expand Down Expand Up @@ -411,6 +427,26 @@ protected function parseContainerModifier(PropertyMetadata $property, array &$ar
}


public function parseEmbeddableModifier(PropertyMetadata $property, array &$args)
{
if (count($property->types) !== 1) {
$num = count( $property->types);
throw new InvalidModifierDefinitionException("Embeddable modifer requries only one class type definition, optionally nullable. $num types detected in {$this->currentReflection->name}::\${$property->name} property.");
}
$className = array_keys($property->types)[0];
if (!class_exists($className)) {
throw new InvalidModifierDefinitionException("Class '$className' in {embeddable} for {$this->currentReflection->name}::\${$property->name} property does not exist.");
}
$implements = class_implements($className);
if (!isset($implements[IEmbeddable::class])) {
throw new InvalidModifierDefinitionException("Class '$className' in {embeddable} for {$this->currentReflection->name}::\${$property->name} property does not implement Nextras\\Orm\\Entity\\Embeddable\\IEmbeddable interface.");
}

$property->container = EmbeddableContainer::class;
$property->args[EmbeddableContainer::class] = ['class' => $className];
}


protected function parseDefaultModifier(PropertyMetadata $property, array &$args)
{
$property->defaultValue = array_shift($args);
Expand Down
16 changes: 15 additions & 1 deletion src/Model/MetadataStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use Nette\Caching\Cache;
use Nette\Object;
use Nextras\Orm\Entity\Embeddable\EmbeddableContainer;
use Nextras\Orm\Entity\Reflection\EntityMetadata;
use Nextras\Orm\Entity\Reflection\IMetadataParserFactory;
use Nextras\Orm\Entity\Reflection\MetadataValidator;
Expand Down Expand Up @@ -45,10 +46,23 @@ public function __construct(
static::$metadata = $cache->derive('metadata')->load(
$entityClassesMap,
function (& $dp) use ($entityClassesMap, $metadataParserFactory, $repositoryLoader) {
/** @var EntityMetadata[] $metadata */
$metadata = [];
$embeddableClassNames = [];

$annotationParser = $metadataParserFactory->create($entityClassesMap);
foreach (array_keys($entityClassesMap) as $className) {
$metadata[$className] = $annotationParser->parseMetadata($className, $dp[Cache::FILES]);
$metadata[$className] = $annotationParser->parseEntity($className, $dp[Cache::FILES]);

foreach ($metadata[$className]->getProperties() as $property) {
if ($property->container === EmbeddableContainer::class) {
$embeddableClassNames[] = $property->args[EmbeddableContainer::class]['class'];
}
}
}

foreach ($embeddableClassNames as $embeddableClassName) {
$metadata[$embeddableClassName] = $annotationParser->parseEmbeddable($embeddableClassName, $dp[Cache::FILES]);
}

$validator = new MetadataValidator();
Expand Down
Loading

0 comments on commit 331d990

Please sign in to comment.