Skip to content

Commit b29c8d4

Browse files
committed
embeddables: implement support for relationships
- embeddables propagate PropertyMetadata with path - recursively set parent entity to embeddables' properties - implements path-style fetching of raw values
1 parent fdcd409 commit b29c8d4

37 files changed

Lines changed: 418 additions & 131 deletions

src/Collection/EntityIterator.php

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
use Nette\Utils\Arrays;
99
use Nextras\Orm\Entity\IEntity;
1010
use Nextras\Orm\Entity\IEntityHasPreloadContainer;
11+
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
1112
use Nextras\Orm\Exception\InvalidStateException;
13+
use function assert;
14+
use function count;
15+
use function spl_object_hash;
1216

1317

1418
/**
@@ -87,24 +91,23 @@ public function count(): int
8791
}
8892

8993

90-
public function getPreloadValues(string $property): array
94+
public function getPreloadValues(PropertyMetadata $property): array
9195
{
92-
if (isset($this->preloadCache[$property])) {
93-
return $this->preloadCache[$property];
96+
$cacheKey = spl_object_hash($property);
97+
if (isset($this->preloadCache[$cacheKey])) {
98+
return $this->preloadCache[$cacheKey];
9499
}
95100

96101
$values = [];
97102
foreach ($this->iteratable as $entity) {
98-
// property may not exist when using STI
99-
if ($entity->getMetadata()->hasProperty($property)) {
100-
// relationship may be already nulled in removed entity
101-
$value = $entity->getRawValue($property);
102-
if ($value !== null) {
103-
$values[] = $value;
104-
}
103+
// $checkPropertyExistence = false - property may not exist when using STI
104+
$value = $entity->getRawValue($property->path ?? $property->name, false);
105+
// relationship may be already null-ed in removed entity
106+
if ($value !== null) {
107+
$values[] = $value;
105108
}
106109
}
107110

108-
return $this->preloadCache[$property] = $values;
111+
return $this->preloadCache[$cacheKey] = $values;
109112
}
110113
}

src/Collection/IEntityPreloadContainer.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
namespace Nextras\Orm\Collection;
44

55

6+
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
7+
8+
69
interface IEntityPreloadContainer
710
{
811
/**
9-
* Returns array of $property values for preloading.
12+
* Returns array of values in $propertyMetadata position for preloading.
1013
* @phpstan-return list<mixed>
1114
*/
12-
public function getPreloadValues(string $property): array;
15+
public function getPreloadValues(PropertyMetadata $propertyMetadata): array;
1316
}

src/Collection/MultiEntityIterator.php

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
use Nette\Utils\Arrays;
99
use Nextras\Orm\Entity\IEntity;
1010
use Nextras\Orm\Entity\IEntityHasPreloadContainer;
11+
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
1112
use Nextras\Orm\Exception\InvalidStateException;
13+
use function assert;
14+
use function count;
15+
use function spl_object_hash;
1216

1317

1418
/**
@@ -105,26 +109,25 @@ public function count(): int
105109
}
106110

107111

108-
public function getPreloadValues(string $property): array
112+
public function getPreloadValues(PropertyMetadata $property): array
109113
{
110-
if (isset($this->preloadCache[$property])) {
111-
return $this->preloadCache[$property];
114+
$cacheKey = spl_object_hash($property);
115+
if (isset($this->preloadCache[$cacheKey])) {
116+
return $this->preloadCache[$cacheKey];
112117
}
113118

114119
$values = [];
115120
foreach ($this->data as $entities) {
116121
foreach ($entities as $entity) {
117-
// property may not exist when using STI
118-
if ($entity->getMetadata()->hasProperty($property)) {
119-
// relationship may be already nulled in removed entity
120-
$value = $entity->getRawValue($property);
121-
if ($value !== null) {
122-
$values[] = $value;
123-
}
122+
// $checkPropertyExistence = false - property may not exist when using STI
123+
$value = $entity->getRawValue($property->path ?? $property->name, false);
124+
// relationship may be already null-ed in removed entity
125+
if ($value !== null) {
126+
$values[] = $value;
124127
}
125128
}
126129
}
127130

128-
return $this->preloadCache[$property] = $values;
131+
return $this->preloadCache[$cacheKey] = $values;
129132
}
130133
}

src/Entity/AbstractEntity.php

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Nextras\Orm\Relationships\IRelationshipCollection;
1414
use Nextras\Orm\Relationships\IRelationshipContainer;
1515
use Nextras\Orm\Repository\IRepository;
16+
use function array_shift;
1617
use function assert;
1718
use function get_class;
1819

@@ -130,23 +131,39 @@ public function setRawValue(string $name, $value): void
130131
}
131132

132133

133-
public function &getRawValue(string $name)
134+
public function &getRawValue($name, bool $checkPropertyExistence = false)
134135
{
135-
$property = $this->metadata->getProperty($name);
136+
$path = (array) $name;
137+
$name = array_shift($path);
138+
139+
if (!$checkPropertyExistence && !$this->metadata->hasProperty($name)) {
140+
$value = null;
141+
return $value;
142+
}
143+
144+
$propertyMetadata = $this->metadata->getProperty($name);
136145

137146
if (!isset($this->validated[$name])) {
138-
$this->initProperty($property, $name);
147+
$this->initProperty($propertyMetadata, $name);
139148
}
140149

141150
$value = $this->data[$name];
142151

143-
if ($value instanceof IProperty) {
152+
if (count($path) > 0) {
153+
if (!$value instanceof IMultiPropertyPropertyContainer) {
154+
throw new InvalidStateException("Path to raw value doesn't go through IMultiPropertyPropertyContainer property.");
155+
}
156+
157+
$value = $value->getRawValueOf($path, $checkPropertyExistence);
158+
return $value;
159+
160+
} elseif ($value instanceof IProperty) {
144161
$value = $value->getRawValue();
145162
return $value;
146163
}
147164

148-
if ($property->isVirtual) {
149-
$value = $this->internalGetValue($property, $name);
165+
if ($propertyMetadata->isVirtual) {
166+
$value = $this->internalGetValue($propertyMetadata, $name);
150167
return $value;
151168
}
152169

@@ -229,18 +246,18 @@ public function __clone()
229246
$this->data['id'] = null;
230247
$this->persistedId = null;
231248
$this->data[$name] = clone $this->data[$name];
232-
$this->data[$name]->setPropertyEntity($this);
249+
$this->data[$name]->onAttach($this, $metadataProperty);
233250
$this->data[$name]->set($data);
234251
$this->data['id'] = $id;
235252
$this->persistedId = $persistedId;
236253

237254
} elseif ($this->data[$name] instanceof IRelationshipContainer) {
238255
$this->data[$name] = clone $this->data[$name];
239-
$this->data[$name]->setPropertyEntity($this);
256+
$this->data[$name]->onAttach($this, $metadataProperty);
240257

241258
} elseif ($this->data[$name] instanceof EmbeddableContainer) {
242259
$this->data[$name] = clone $this->data[$name];
243-
$this->data[$name]->setPropertyEntity($this);
260+
$this->data[$name]->onAttach($this, $metadataProperty);
244261

245262
} else {
246263
$this->data[$name] = clone $this->data[$name];
@@ -499,7 +516,7 @@ private function createPropertyWrapper(PropertyMetadata $metadata): IProperty
499516
assert($wrapper instanceof IProperty);
500517

501518
if ($wrapper instanceof IEntityAwareProperty) {
502-
$wrapper->setPropertyEntity($this);
519+
$wrapper->onAttach($this, $metadata);
503520
}
504521

505522
return $wrapper;

src/Entity/Embeddable/Embeddable.php

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Nextras\Orm\Entity\IEntity;
77
use Nextras\Orm\Entity\IEntityAwareProperty;
88
use Nextras\Orm\Entity\ImmutableDataTrait;
9+
use Nextras\Orm\Entity\IMultiPropertyPropertyContainer;
910
use Nextras\Orm\Entity\IProperty;
1011
use Nextras\Orm\Entity\IPropertyContainer;
1112
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
@@ -27,6 +28,9 @@ abstract class Embeddable implements IEmbeddable
2728
/** @var IEntity|null */
2829
protected $parentEntity;
2930

31+
/** @var PropertyMetadata */
32+
protected $propertyMetadata;
33+
3034

3135
/**
3236
* @param array<string, mixed>|null $data
@@ -73,9 +77,53 @@ public function getRawValue(): array
7377
}
7478

7579

76-
public function onAttach(IEntity $entity): void
80+
public function &getRawValueOf(array $path, bool $checkPropertyExistence = true)
81+
{
82+
$name = array_shift($path);
83+
84+
if (!$checkPropertyExistence && !$this->metadata->hasProperty($name)) {
85+
$value = null;
86+
return $value;
87+
}
88+
89+
$propertyMetadata = $this->metadata->getProperty($name);
90+
91+
if (!isset($this->validated[$name])) {
92+
$this->initProperty($propertyMetadata, $name);
93+
}
94+
95+
$value = $this->data[$name];
96+
97+
if (count($path) > 0) {
98+
if (!$value instanceof IMultiPropertyPropertyContainer) {
99+
throw new InvalidStateException("Path to raw value doesn't go through IMultiPropertyPropertyContainer property.");
100+
}
101+
102+
$value = $value->getRawValueOf($path, $checkPropertyExistence);
103+
return $value;
104+
105+
} elseif ($value instanceof IProperty) {
106+
$value = $value->getRawValue();
107+
return $value;
108+
}
109+
110+
return $value;
111+
}
112+
113+
114+
public function onAttach(IEntity $entity, PropertyMetadata $propertyMetadata): void
77115
{
78116
$this->parentEntity = $entity;
117+
$this->propertyMetadata = $propertyMetadata;
118+
119+
foreach ($this->data as $key => $property) {
120+
if ($property instanceof IEntityAwareProperty) {
121+
$property->onAttach(
122+
$entity,
123+
$this->metadata->getProperty($key)->withPath($this->propertyMetadata->path)
124+
);
125+
}
126+
}
79127
}
80128

81129

@@ -140,12 +188,8 @@ private function createPropertyWrapper(PropertyMetadata $metadata): IProperty
140188
$wrapper = new $class($metadata);
141189
assert($wrapper instanceof IProperty);
142190

143-
if ($wrapper instanceof IEntityAwareProperty) {
144-
if ($this->parentEntity === null) {
145-
throw new InvalidStateException("");
146-
} else {
147-
$wrapper->setPropertyEntity($this->parentEntity);
148-
}
191+
if ($wrapper instanceof IEntityAwareProperty && $this->parentEntity !== null) {
192+
$wrapper->onAttach($this->parentEntity, $metadata->withPath($this->propertyMetadata->path));
149193
}
150194

151195
return $wrapper;

src/Entity/Embeddable/EmbeddableContainer.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Nette\SmartObject;
77
use Nextras\Orm\Entity\IEntity;
88
use Nextras\Orm\Entity\IEntityAwareProperty;
9+
use Nextras\Orm\Entity\IMultiPropertyPropertyContainer;
910
use Nextras\Orm\Entity\IPropertyContainer;
1011
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
1112
use Nextras\Orm\Exception\InvalidArgumentException;
@@ -17,7 +18,7 @@
1718
use function count;
1819

1920

20-
class EmbeddableContainer implements IPropertyContainer, IEntityAwareProperty
21+
class EmbeddableContainer implements IPropertyContainer, IMultiPropertyPropertyContainer, IEntityAwareProperty
2122
{
2223
use SmartObject;
2324

@@ -50,9 +51,14 @@ public function __construct(PropertyMetadata $propertyMetadata)
5051
}
5152

5253

53-
public function setPropertyEntity(IEntity $entity): void
54+
public function onAttach(IEntity $entity, PropertyMetadata $propertyMetadata): void
5455
{
5556
$this->entity = $entity;
57+
$this->metadata = $propertyMetadata->withPath($propertyMetadata->path ?? []); // force creation
58+
59+
if ($this->value !== null) {
60+
$this->value->onAttach($entity, $this->metadata);
61+
}
5662
}
5763

5864

@@ -106,6 +112,16 @@ public function getRawValue()
106112
}
107113

108114

115+
public function getRawValueOf(array $path, bool $checkPropertyExistence = true)
116+
{
117+
if ($this->value !== null) {
118+
return $this->value->getRawValueOf($path, $checkPropertyExistence);
119+
}
120+
121+
return null;
122+
}
123+
124+
109125
public function setInjectedValue($value): bool
110126
{
111127
assert($this->entity !== null);
@@ -118,7 +134,7 @@ public function setInjectedValue($value): bool
118134

119135
if ($value !== null) {
120136
assert($value instanceof IEmbeddable);
121-
$value->onAttach($this->entity);
137+
$value->onAttach($this->entity, $this->metadata);
122138
}
123139

124140
$this->value = $value;

src/Entity/Embeddable/IEmbeddable.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55

66
use Nextras\Orm\Entity\IEntity;
7+
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
78

89

910
interface IEmbeddable
@@ -37,10 +38,19 @@ public function setRawValue(array $data): void;
3738
public function getRawValue(): array;
3839

3940

41+
/**
42+
* Returns raw value for specific property.
43+
* @param string[] $path
44+
* @phpstan-param list<string> $path
45+
* @return mixed
46+
*/
47+
public function getRawValueOf(array $path, bool $checkPropertyExistence = true);
48+
49+
4050
/**
4151
* Attaches entity to embeddable object.
4252
* This is called after injecting embeddable into property wrapper.
4353
* @internal
4454
*/
45-
public function onAttach(IEntity $entity): void;
55+
public function onAttach(IEntity $entity, PropertyMetadata $propertyMetadata): void;
4656
}

src/Entity/IEntity.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ public function setRawValue(string $name, $value): void;
5353

5454
/**
5555
* Returns raw value.
56-
* Raw value is normalized value which is suitable unique identification and storing.
56+
* Raw value is normalized value to be suitable for storing.
57+
* @param string|string[] $name
58+
* @phpstan-param string|list<string> $name
5759
* @return mixed
5860
*/
59-
public function &getRawValue(string $name);
61+
public function &getRawValue($name, bool $checkPropertyExistence = true);
6062

6163

6264
/**

0 commit comments

Comments
 (0)