Skip to content

Commit 48a4ff2

Browse files
committed
Remove stdClass support, enable pure entity trees
Drop stdClass as entity fallback — EntityFactory now throws DomainException for unknown classes. Remove dynamic property fallbacks from set/get/extractProperties, eliminate persistableCache and reflectPersistable(). Add EntityFactory::extractColumns() to derive FK values from entity object properties (e.g. $author -> author_id). Rewrite wireRelationships to use collection-tree parent matching instead of FK property scanning. Update resolveEntityName to accept object|array for Nested hydrator compatibility. Entity stubs use pure entity trees: relation properties ($post, $author) replace FK scalars ($post_id, $author_id). Remove redundant tests, add extractColumns unit coverage.
1 parent 169cf0b commit 48a4ff2

19 files changed

Lines changed: 341 additions & 186 deletions

src/Collections/Collection.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ public function fetchAll(mixed $extra = null): mixed
7171
return $this->resolveMapper()->fetchAll($this, $extra);
7272
}
7373

74-
public function resolveEntityName(EntityFactory $factory, object $row): string
74+
/** @param object|array<string, mixed> $row */
75+
public function resolveEntityName(EntityFactory $factory, object|array $row): string
7576
{
7677
return $this->name ?? '';
7778
}

src/Collections/Typed.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Respect\Data\EntityFactory;
88

9+
use function is_array;
910
use function is_string;
1011

1112
final class Typed extends Collection
@@ -17,9 +18,10 @@ public function __construct(
1718
parent::__construct($name);
1819
}
1920

20-
public function resolveEntityName(EntityFactory $factory, object $row): string
21+
/** @param object|array<string, mixed> $row */
22+
public function resolveEntityName(EntityFactory $factory, object|array $row): string
2123
{
22-
$name = $factory->get($row, $this->type);
24+
$name = is_array($row) ? ($row[$this->type] ?? null) : $factory->get($row, $this->type);
2325

2426
return is_string($name) ? $name : ($this->name ?? '');
2527
}

src/EntityFactory.php

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44

55
namespace Respect\Data;
66

7+
use DomainException;
78
use ReflectionClass;
8-
use ReflectionException;
99
use ReflectionProperty;
10-
use stdClass;
1110

1211
use function class_exists;
13-
use function get_object_vars;
12+
use function is_object;
1413

1514
/** Creates and manipulates entity objects using Style-based naming conventions */
1615
class EntityFactory
@@ -21,9 +20,6 @@ class EntityFactory
2120
/** @var array<string, array<string, ReflectionProperty>> */
2221
private array $propertyCache = [];
2322

24-
/** @var array<string, array<string, ReflectionProperty>> */
25-
private array $persistableCache = [];
26-
2723
public function __construct(
2824
public readonly Styles\Stylable $style = new Styles\Standard(),
2925
private readonly string $entityNamespace = '\\',
@@ -35,7 +31,11 @@ public function createByName(string $name): object
3531
{
3632
$entityName = $this->style->styledName($name);
3733
$entityClass = $this->entityNamespace . $entityName;
38-
$entityClass = class_exists($entityClass) ? $entityClass : stdClass::class;
34+
35+
if (!class_exists($entityClass)) {
36+
throw new DomainException('Entity class ' . $entityClass . ' not found for ' . $name);
37+
}
38+
3939
$ref = $this->reflectClass($entityClass);
4040

4141
if (!$this->disableConstructor) {
@@ -47,42 +47,56 @@ public function createByName(string $name): object
4747

4848
public function set(object $entity, string $prop, mixed $value): void
4949
{
50-
$properties = $this->reflectProperties($entity::class);
51-
52-
if (isset($properties[$prop])) {
53-
$properties[$prop]->setValue($entity, $value);
54-
55-
return;
56-
}
50+
$mirror = $this->reflectProperties($entity::class)[$prop] ?? null;
5751

58-
$entity->{$prop} = $value;
52+
$mirror?->setValue($entity, $value);
5953
}
6054

6155
public function get(object $entity, string $prop): mixed
6256
{
6357
$mirror = $this->reflectProperties($entity::class)[$prop] ?? null;
6458

65-
if ($mirror !== null) {
66-
return $mirror->isInitialized($entity) ? $mirror->getValue($entity) : null;
59+
if ($mirror === null || !$mirror->isInitialized($entity)) {
60+
return null;
6761
}
6862

69-
try {
70-
return (new ReflectionProperty($entity, $prop))->getValue($entity);
71-
} catch (ReflectionException) {
72-
return null;
63+
return $mirror->getValue($entity);
64+
}
65+
66+
/**
67+
* Extract persistable columns, resolving entity objects to their FK representations.
68+
*
69+
* @return array<string, mixed>
70+
*/
71+
public function extractColumns(object $entity): array
72+
{
73+
$cols = $this->extractProperties($entity);
74+
75+
foreach ($cols as $key => $value) {
76+
if (!is_object($value)) {
77+
continue;
78+
}
79+
80+
if ($this->style->isRelationProperty($key)) {
81+
$fk = $this->style->remoteIdentifier($key);
82+
$cols[$fk] = $this->get($value, $this->style->identifier($key));
83+
unset($cols[$key]);
84+
} else {
85+
$table = $this->style->remoteFromIdentifier($key) ?? $key;
86+
$cols[$key] = $this->get($value, $this->style->identifier($table));
87+
}
7388
}
89+
90+
return $cols;
7491
}
7592

7693
/** @return array<string, mixed> */
7794
public function extractProperties(object $entity): array
7895
{
79-
$props = get_object_vars($entity);
80-
$persistable = $this->reflectPersistable($entity::class);
96+
$props = [];
8197

8298
foreach ($this->reflectProperties($entity::class) as $name => $prop) {
83-
if (!isset($persistable[$name]) || !$prop->isInitialized($entity)) {
84-
unset($props[$name]);
85-
99+
if (!$prop->isInitialized($entity) || $prop->getAttributes(NotPersistable::class)) {
86100
continue;
87101
}
88102

@@ -96,8 +110,12 @@ public function hydrate(object $source, string $entityName): object
96110
{
97111
$entity = $this->createByName($entityName);
98112

99-
foreach (get_object_vars($source) as $prop => $value) {
100-
$this->set($entity, $prop, $value);
113+
foreach ($this->reflectProperties($source::class) as $name => $prop) {
114+
if (!$prop->isInitialized($source)) {
115+
continue;
116+
}
117+
118+
$this->set($entity, $name, $prop->getValue($source));
101119
}
102120

103121
return $entity;
@@ -126,22 +144,4 @@ private function reflectProperties(string $class): array
126144

127145
return $this->propertyCache[$class];
128146
}
129-
130-
/** @return array<string, ReflectionProperty> */
131-
private function reflectPersistable(string $class): array
132-
{
133-
if (!isset($this->persistableCache[$class])) {
134-
$this->persistableCache[$class] = [];
135-
136-
foreach ($this->reflectProperties($class) as $name => $prop) {
137-
if ($prop->getAttributes(NotPersistable::class)) {
138-
continue;
139-
}
140-
141-
$this->persistableCache[$class][$name] = $prop;
142-
}
143-
}
144-
145-
return $this->persistableCache[$class];
146-
}
147147
}

src/Hydrators/Base.php

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,42 @@
99
use Respect\Data\Hydrator;
1010
use SplObjectStorage;
1111

12-
use function is_object;
13-
14-
/** Base hydrator providing FK-to-entity wiring shared by all strategies */
12+
/** Base hydrator providing collection-tree entity wiring */
1513
abstract class Base implements Hydrator
1614
{
1715
/** @param SplObjectStorage<object, Collection> $entities */
1816
protected function wireRelationships(SplObjectStorage $entities, EntityFactory $entityFactory): void
1917
{
2018
$style = $entityFactory->style;
21-
$entitiesClone = clone $entities;
19+
$others = clone $entities;
20+
21+
foreach ($entities as $entity) {
22+
$coll = $entities[$entity];
2223

23-
foreach ($entities as $instance) {
24-
foreach ($entityFactory->extractProperties($instance) as $field => $v) {
25-
if (!$style->isRemoteIdentifier($field)) {
24+
foreach ($others as $other) {
25+
if ($other === $entity) {
2626
continue;
2727
}
2828

29-
foreach ($entitiesClone as $sub) {
30-
if ($sub === $instance) {
31-
continue;
32-
}
33-
34-
$tableName = (string) $entities[$sub]->name;
35-
$primaryName = $style->identifier($tableName);
36-
37-
if (
38-
$tableName !== $style->remoteFromIdentifier($field)
39-
|| $entityFactory->get($sub, $primaryName) != $v
40-
) {
41-
continue;
42-
}
43-
44-
$v = $sub;
29+
$otherColl = $others[$other];
30+
if ($otherColl->parent !== $coll || $otherColl->name === null) {
31+
continue;
4532
}
4633

47-
if (!is_object($v)) {
34+
$relationName = $style->relationProperty(
35+
$style->remoteIdentifier($otherColl->name),
36+
);
37+
38+
if ($relationName === null) {
4839
continue;
4940
}
5041

51-
$relationName = $style->relationProperty($field);
52-
if ($relationName === null) {
42+
$pk = $entityFactory->get($other, $style->identifier($otherColl->name));
43+
if ($pk === null) {
5344
continue;
5445
}
5546

56-
$entityFactory->set($instance, $relationName, $v);
47+
$entityFactory->set($entity, $relationName, $other);
5748
}
5849
}
5950
}

src/Hydrators/Nested.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private function hydrateNode(
4545
EntityFactory $entityFactory,
4646
SplObjectStorage $entities,
4747
): void {
48-
$entityName = $collection->resolveEntityName($entityFactory, (object) $data);
48+
$entityName = $collection->resolveEntityName($entityFactory, $data);
4949
$entity = $entityFactory->createByName($entityName);
5050

5151
foreach ($data as $key => $value) {

0 commit comments

Comments
 (0)