Skip to content

Commit f4864e5

Browse files
committed
EntityFactory improvements
PrepareEntityArray: - Entities must extend AbstractElasticEntity (to utilize its requirement of id method) - Handled entities are added to identityMap, to prevent insert loop (entity is inserted only once if it is missing in identityMap or is changed) - Persisting is handled via entityManager instead of services (deprecated method) - Added support for "STI" entities, ie to help entity factory figure out what class it should create when fetching from elasticsearch - Utilizes special key entityClass in its data to store entity class EntityFactory: - To figure out what entity to create (and child entities) that has abstract type or is just interface, new metadata were added - EntityClass - If property has type that is interface, then this metadata is used to figure out what entity should be created - This enables to use "STI" mechanic, without developer involvement, it happens automatically - Uses identityMap, if entity is allready created it skips creating and returns that version - Uses ChangeSet to figure out what entities and specially child entities were changed.
1 parent 53d5097 commit f4864e5

2 files changed

Lines changed: 118 additions & 26 deletions

File tree

src/Factory/EntityFactory.php

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,63 @@
77

88
public function __construct(
99
private \Spameri\Elastic\Reflection\Reflection $reflection,
10+
private \Spameri\Elastic\Model\IdentityMap $identityMap,
11+
private \Spameri\Elastic\Model\ChangeSet $changeSet,
1012
)
1113
{
14+
1215
}
1316

1417
/**
15-
* @return \Generator<\SpameriTests\Elastic\Data\Entity\Person>
18+
* @return \Generator<\Spameri\Elastic\Entity\AbstractElasticEntity>
1619
*/
1720
public function create(
1821
\Spameri\ElasticQuery\Response\Result\Hit $hit,
19-
string|null $class = null,
22+
string|null $class,
23+
\Spameri\Elastic\EntityManager|null $entityManager,
2024
): \Generator
2125
{
2226
if ($class === null) {
23-
throw new \Spameri\Elastic\Exception\InvalidArgument('Class must be set.');
27+
throw new \InvalidArgumentException('Class must be set');
2428
}
2529

26-
$properties = $this->resolveProperties($hit, $class);
30+
if ($entityManager === null) {
31+
throw new \InvalidArgumentException('EntityManager must be set');
32+
}
2733

2834
if ($hit->getValue(\Spameri\Elastic\Model\Insert\PrepareEntityArray::ENTITY_CLASS) !== null) {
2935
$class = $hit->getValue(\Spameri\Elastic\Model\Insert\PrepareEntityArray::ENTITY_CLASS);
3036
}
3137

32-
yield new $class(
38+
$entity = $this->identityMap->get(
39+
class: $class,
40+
id: $hit->id(),
41+
);
42+
if ($entity !== null) {
43+
yield $entity;
44+
}
45+
46+
$properties = $this->resolveProperties(
47+
hit: $hit,
48+
class: $class,
49+
entityManager: $entityManager,
50+
);
51+
52+
$entity = new $class(
3353
... $properties,
3454
);
55+
56+
$this->changeSet->markExisting($entity);
57+
58+
$this->identityMap->add($entity);
59+
60+
yield $entity;
3561
}
3662

3763
protected function resolveProperties(
3864
\Spameri\ElasticQuery\Response\Result\Hit $hit,
3965
string $class,
66+
\Spameri\Elastic\EntityManager $entityManager,
4067
string|null $parentFieldName = null,
4168
): array
4269
{
@@ -63,12 +90,17 @@ protected function resolveProperties(
6390
} elseif ($property->hasDefaultValue() === true && $value === null) {
6491
$propertyValue = $property->getDefaultValue();
6592

66-
} elseif ($propertyTypeName === \Spameri\Elastic\Entity\Property\Date::class) {
93+
} elseif (
94+
$propertyTypeName === \Spameri\Elastic\Entity\Property\Date::class
95+
|| $propertyTypeName === \Spameri\Elastic\Entity\Property\DateTime::class
96+
) {
6797
if ($value !== null) {
68-
$propertyValue = new \Spameri\Elastic\Entity\Property\Date(
98+
$propertyValue = new $propertyTypeName(
6999
datetime: $value,
70100
);
71101

102+
$this->changeSet->markExisting($propertyValue);
103+
72104
} else {
73105
$propertyValue = null;
74106
}
@@ -87,22 +119,53 @@ protected function resolveProperties(
87119

88120
} else {
89121
$propertyValue = new $arguments['class'](
90-
... $this->resolveProperties($hit, $propertyTypeName, $hitKey),
122+
... $this->resolveProperties(
123+
hit: $hit,
124+
class: $propertyTypeName,
125+
entityManager: $entityManager,
126+
parentFieldName: $hitKey,
127+
),
91128
);
129+
130+
$this->changeSet->markExisting($propertyValue);
92131
}
132+
93133
} elseif (
94134
$attribute->getName() === \Spameri\Elastic\Mapping\Collection::class
95135
) {
96136
$propertyValue = new $propertyTypeName();
137+
$this->changeSet->markExisting($propertyValue);
97138
if ($value !== null) {
98139
foreach ($value as $entityKey => $entity) {
99-
$propertyValue->add(
100-
new $entity[\Spameri\Elastic\Model\Insert\PrepareEntityArray::ENTITY_CLASS](
101-
... $this->resolveProperties($hit, $entity[\Spameri\Elastic\Model\Insert\PrepareEntityArray::ENTITY_CLASS], $hitKey . '.' . $entityKey),
140+
$collectionEntity = new $entity[\Spameri\Elastic\Model\Insert\PrepareEntityArray::ENTITY_CLASS](
141+
... $this->resolveProperties(
142+
hit: $hit,
143+
class: $entity[\Spameri\Elastic\Model\Insert\PrepareEntityArray::ENTITY_CLASS],
144+
entityManager: $entityManager,
145+
parentFieldName: $hitKey . '.' . $entityKey,
102146
),
103147
);
148+
149+
$propertyValue->add($collectionEntity);
150+
151+
$this->changeSet->markExisting($collectionEntity);
104152
}
105153
}
154+
155+
} elseif (
156+
$attribute->getName() === \Spameri\Elastic\Mapping\STIEntity::class
157+
) {
158+
$propertyValue = new $value[\Spameri\Elastic\Model\Insert\PrepareEntityArray::ENTITY_CLASS](
159+
... $this->resolveProperties(
160+
hit: $hit,
161+
class: $propertyTypeName,
162+
entityManager: $entityManager,
163+
parentFieldName: $hitKey,
164+
),
165+
);
166+
167+
$this->changeSet->markExisting($propertyValue);
168+
106169
} elseif (
107170
$attribute->getName() === \Spameri\Elastic\Mapping\Ignored::class
108171
) {
@@ -116,10 +179,28 @@ protected function resolveProperties(
116179
$value,
117180
);
118181

182+
$this->changeSet->markExisting($propertyValue);
183+
184+
} elseif (
185+
isset(\class_implements($propertyTypeName)[\Spameri\Elastic\Entity\ElasticEntityInterface::class]) === true
186+
&& \is_string($value) === true
187+
) {
188+
$propertyValue = $entityManager->find(
189+
id: $value,
190+
class: $propertyTypeName,
191+
);
192+
119193
} else {
120194
$propertyValue = new $propertyTypeName(
121-
... $this->resolveProperties($hit, $propertyTypeName, $hitKey),
195+
... $this->resolveProperties(
196+
hit: $hit,
197+
class: $propertyTypeName,
198+
entityManager: $entityManager,
199+
parentFieldName: $hitKey,
200+
),
122201
);
202+
203+
$this->changeSet->markExisting($propertyValue);
123204
}
124205

125206
} else {

src/Model/Insert/PrepareEntityArray.php

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,36 @@ class PrepareEntityArray
77

88
public const ENTITY_CLASS = 'entityClass';
99

10-
/**
11-
* @var array<string, bool>
12-
*/
13-
private array $insertedEntities;
10+
private \Spameri\Elastic\EntityManager $entityManager;
1411

1512

1613
public function __construct(
17-
private readonly \Spameri\Elastic\Model\ServiceLocatorInterface $serviceLocator,
14+
private readonly \Nette\DI\Container $container,
15+
private readonly \Spameri\Elastic\Model\IdentityMap $identityMap,
1816
)
1917
{
2018
}
2119

20+
private function getEntityManager(): \Spameri\Elastic\EntityManager
21+
{
22+
if (isset($this->entityManager)) {
23+
return $this->entityManager;
24+
}
25+
26+
$this->entityManager = $this->container->getByType(\Spameri\Elastic\EntityManager::class);
27+
28+
return $this->entityManager;
29+
}
2230

2331
/**
2432
* @return array<mixed>
2533
*/
2634
public function prepare(
27-
\Spameri\Elastic\Entity\ElasticEntityInterface $entity,
35+
\Spameri\Elastic\Entity\AbstractElasticEntity $entity,
2836
bool $hasSti = false,
2937
): array
3038
{
31-
$this->insertedEntities = [];
32-
$this->insertedEntities[$entity->id()->value()] = true;
39+
$this->identityMap->add($entity);
3340

3441
$entityVariables = $entity->entityVariables();
3542
if ($hasSti === true) {
@@ -52,12 +59,12 @@ public function iterateVariables(
5259

5360
foreach ($variables as $key => $property) {
5461
if ($property instanceof \Spameri\Elastic\Entity\AbstractElasticEntity) {
55-
if (\in_array($property->id->value(), $this->insertedEntities, true)) {
62+
if ($this->identityMap->isChanged($property) === false) {
5663
$preparedArray[$key] = $property->id()->value();
5764

5865
} else {
59-
$preparedArray[$key] = $this->serviceLocator->locate($property)->insert($property);
60-
$this->insertedEntities[$property->id()->value()] = true;
66+
$preparedArray[$key] = $this->getEntityManager()->persist($property);
67+
$this->identityMap->add($property);
6168
}
6269

6370
} elseif ($property instanceof \Spameri\Elastic\Entity\ElasticEntityInterface) {
@@ -95,12 +102,12 @@ public function iterateVariables(
95102
} else {
96103
/** @var \Spameri\Elastic\Entity\AbstractElasticEntity $item */
97104
foreach ($property as $item) {
98-
if (\in_array($item->id()->value(), $this->insertedEntities)) {
105+
if ($this->identityMap->isChanged($item) === false) {
99106
$preparedArray[$key][] = $item->id()->value();
100107

101108
} else {
102-
$preparedArray[$key][] = $this->serviceLocator->locate($item)->insert($item);
103-
$this->insertedEntities[$item->id()->value()] = true;
109+
$preparedArray[$key][] = $this->getEntityManager()->persist($item);
110+
$this->identityMap->add($item);
104111
}
105112
}
106113
}
@@ -151,6 +158,10 @@ public function iterateVariables(
151158
'Property ' . $key . ' with value' . $property . ' is not supported.',
152159
);
153160
}
161+
162+
if ($property instanceof \Spameri\Elastic\Entity\STIEntityInterface) {
163+
$preparedArray[$key][self::ENTITY_CLASS] = $property::class;
164+
}
154165
}
155166

156167
return $preparedArray;

0 commit comments

Comments
 (0)