Skip to content

Commit

Permalink
relationships: make relationship lazy until it is attached to entity
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Sep 26, 2020
1 parent e7386ba commit fdcd409
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 36 deletions.
116 changes: 84 additions & 32 deletions src/Relationships/HasOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Entity\Reflection\PropertyRelationshipMetadata;
use Nextras\Orm\Exception\InvalidArgumentException;
use Nextras\Orm\Exception\InvalidStateException;
use Nextras\Orm\Exception\NullValueException;
use Nextras\Orm\Mapper\IRelationshipMapper;
use Nextras\Orm\Repository\IRepository;
Expand All @@ -35,11 +36,17 @@ abstract class HasOne implements IRelationshipContainer
*/
protected $collection;

/** @var bool */
protected $isValueLoaded = true;

/** @var bool */
protected $isValueFromStorage = false;

/** @var mixed|null */
protected $primaryValue;

/** @var IEntity|null|false */
protected $value = false;
/** @var IEntity|null */
protected $value;

/**
* @var IRepository|null
Expand Down Expand Up @@ -72,6 +79,11 @@ public function __construct(PropertyMetadata $metadata)
public function setPropertyEntity(IEntity $parent): void
{
$this->parent = $parent;

if (!$this->isValueLoaded) {
// init value
$this->getEntity();
}
}


Expand All @@ -86,7 +98,10 @@ public function convertToRawValue($value)

public function setRawValue($value): void
{
$isChanged = $this->primaryValue !== $value;
$this->primaryValue = $value;
$this->isValueLoaded = !$isChanged && $value === null;
$this->isValueFromStorage = true;
}


Expand All @@ -98,6 +113,7 @@ public function getRawValue()

public function setInjectedValue($value): bool
{
$this->isValueFromStorage = false;
return $this->set($value);
}

Expand All @@ -117,7 +133,7 @@ public function hasInjectedValue(): bool

public function isLoaded(): bool
{
return $this->value !== false;
return $this->isValueLoaded;
}


Expand All @@ -132,39 +148,50 @@ public function set($value, bool $allowNull = false): bool
return false;
}

$value = $this->createEntity($value, $allowNull);
if ($this->parent === null) {
if ($value instanceof IEntity) {
$this->value = $value;
$this->primaryValue = $value !== null && $value->hasValue('id') ? $value->getValue('id') : null;
} else {
$this->primaryValue = $value;
}
$this->isValueLoaded = false;
return true;
}

$entity = $this->createEntity($value, $allowNull);
$isChanged = $this->isChanged($entity);

$isChanged = $this->isChanged($value);
if ($isChanged) {
$this->modify();
$oldValue = $this->value;
if ($oldValue === false) {
$primaryValue = $this->getPrimaryValue();
$oldValue = $primaryValue !== null ? $this->getTargetRepository()->getById($primaryValue) : null;
}
$this->updateRelationship($oldValue, $value, $allowNull);
$this->updateRelationship($oldValue, $entity, $allowNull);

} else {
$this->initReverseRelationship($value);
$this->initReverseRelationship($entity);
}

$this->primaryValue = $value !== null && $value->isPersisted() ? $value->getValue('id') : null;
$this->value = $value;
$this->isValueLoaded = true;
$this->primaryValue = $entity !== null && $entity->hasValue('id') ? $entity->getValue('id') : null;
$this->value = $entity;
return $isChanged;
}


public function getEntity(): ?IEntity
{
if ($this->value === false) {
$this->set($this->fetchValue());
if (!$this->isValueLoaded && $this->value === null) {
$this->initValue();
}

if ($this->value === null && !$this->metadata->isNullable) {
throw new NullValueException($this->parent, $this->metadata);
}

assert($this->value === null || $this->value instanceof IEntity);
return $this->value;
}

Expand All @@ -175,17 +202,33 @@ public function isModified(): bool
}


protected function fetchValue(): ?IEntity
protected function initValue(): void
{
if (!$this->parent->isAttached()) {
return null;
if ($this->parent === null) {
throw new InvalidStateException('Relationship is not attached to a parent entity.');
}
if ($this->isValueFromStorage) {
// load the value using relationship mapper to utilize preload container and not to validate if
// relationship's entity is really present in the database;
$this->set($this->fetchValue());
} else {
$collection = $this->getCollection();
return iterator_to_array($collection->getIterator())[0] ?? null;
// load value directly to utilize value check
if ($this->value !== null && $this->value->hasValue('id')) {
$this->set($this->value->getValue('id'));
} else {
$this->set($this->primaryValue);
}
}
}


protected function fetchValue(): ?IEntity
{
$collection = $this->getCollection();
return iterator_to_array($collection->getIterator())[0] ?? null;
}


/**
* @return mixed|null
*/
Expand Down Expand Up @@ -232,15 +275,7 @@ protected function getCollection(): ICollection
protected function createEntity($entity, bool $allowNull): ?IEntity
{
if ($entity instanceof IEntity) {
if ($this->parent->isAttached()) {
$repository = $this->parent->getRepository()->getModel()
->getRepository($this->metadataRelationship->repository);
$repository->attach($entity);

} elseif ($entity->isAttached()) {
$repository = $entity->getRepository()->getModel()->getRepositoryForEntity($this->parent);
$repository->attach($this->parent);
}
$this->attachIfPossible($entity);
return $entity;

} elseif ($entity === null) {
Expand All @@ -250,18 +285,35 @@ protected function createEntity($entity, bool $allowNull): ?IEntity
return null;

} elseif (is_scalar($entity)) {
return $this->getTargetRepository()->getById($entity);
$result = $this->getTargetRepository()->getById($entity);
if ($result === null && $entity !== null) {
throw new InvalidArgumentException("Entity with primary key '$entity' was not found.");
}
return $result;

} else {
throw new InvalidArgumentException('Value is not a valid entity representation.');
}
}


/**
* @param IEntity|null $newValue
*/
protected function isChanged($newValue): bool
protected function attachIfPossible(IEntity $entity): void
{
if ($this->parent === null) return;

if ($this->parent->isAttached()) {
$repository = $this->parent->getRepository()->getModel()
->getRepository($this->metadataRelationship->repository);
$repository->attach($entity);

} elseif ($entity->isAttached()) {
$repository = $entity->getRepository()->getModel()->getRepositoryForEntity($this->parent);
$repository->attach($this->parent);
}
}


protected function isChanged(?IEntity $newValue): bool
{
if ($this->value instanceof IEntity && $newValue instanceof IEntity) {
return $this->value !== $newValue;
Expand All @@ -272,7 +324,7 @@ protected function isChanged($newValue): bool
return true;

} elseif ($newValue instanceof IEntity && $newValue->isPersisted()) {
// value is persited entity or null
// value is persisted entity or null
// newValue is persisted entity
return $this->getPrimaryValue() !== $newValue->getValue('id');

Expand Down
15 changes: 12 additions & 3 deletions src/Relationships/OneHasOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,27 @@ protected function createCollection(): ICollection
}


public function setRawValue($value): void
{
parent::setRawValue($value);
if (!$this->metadataRelationship->isMain) {
$this->isValueLoaded = false;
}
}


public function getRawValue()
{
if ($this->primaryValue === null && $this->value === false && !$this->metadataRelationship->isMain) {
$this->getEntity(); // init the value
if ($this->primaryValue === null && !$this->isValueLoaded && !$this->metadataRelationship->isMain) {
$this->initValue();
}
return parent::getRawValue();
}


public function hasInjectedValue(): bool
{
if ($this->primaryValue === null && $this->value === false && !$this->metadataRelationship->isMain) {
if ($this->primaryValue === null && !$this->isValueLoaded && !$this->metadataRelationship->isMain) {
return $this->fetchValue() !== null;
}
return parent::hasInjectedValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class RelationshipOneHasOneTest extends DataTestCase
}


public function testGetRawValue(): void
public function testGetRawValueOnNonPrimarySide(): void
{
$ean = new Ean();
$ean->code = '1234';
Expand Down

0 comments on commit fdcd409

Please sign in to comment.