Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Collections/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public function fetchAll(mixed $extra = null): mixed
return $this->resolveMapper()->fetchAll($this, $extra);
}

public function resolveEntityName(EntityFactory $factory, object $row): string
/** @param object|array<string, mixed> $row */
public function resolveEntityName(EntityFactory $factory, object|array $row): string
{
return $this->name ?? '';
}
Expand Down
6 changes: 4 additions & 2 deletions src/Collections/Typed.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Respect\Data\EntityFactory;

use function is_array;
use function is_string;

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

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

return is_string($name) ? $name : ($this->name ?? '');
}
Expand Down
92 changes: 46 additions & 46 deletions src/EntityFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

namespace Respect\Data;

use DomainException;
use ReflectionClass;
use ReflectionException;
use ReflectionProperty;
use stdClass;

use function class_exists;
use function get_object_vars;
use function is_object;

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

/** @var array<string, array<string, ReflectionProperty>> */
private array $persistableCache = [];

public function __construct(
public readonly Styles\Stylable $style = new Styles\Standard(),
private readonly string $entityNamespace = '\\',
Expand All @@ -35,7 +31,11 @@ public function createByName(string $name): object
{
$entityName = $this->style->styledName($name);
$entityClass = $this->entityNamespace . $entityName;
$entityClass = class_exists($entityClass) ? $entityClass : stdClass::class;

if (!class_exists($entityClass)) {
throw new DomainException('Entity class ' . $entityClass . ' not found for ' . $name);
}

$ref = $this->reflectClass($entityClass);

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

public function set(object $entity, string $prop, mixed $value): void
{
$properties = $this->reflectProperties($entity::class);

if (isset($properties[$prop])) {
$properties[$prop]->setValue($entity, $value);

return;
}
$mirror = $this->reflectProperties($entity::class)[$prop] ?? null;

$entity->{$prop} = $value;
$mirror?->setValue($entity, $value);
}

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

if ($mirror !== null) {
return $mirror->isInitialized($entity) ? $mirror->getValue($entity) : null;
if ($mirror === null || !$mirror->isInitialized($entity)) {
return null;
}

try {
return (new ReflectionProperty($entity, $prop))->getValue($entity);
} catch (ReflectionException) {
return null;
return $mirror->getValue($entity);
}

/**
* Extract persistable columns, resolving entity objects to their FK representations.
*
* @return array<string, mixed>
*/
public function extractColumns(object $entity): array
{
$cols = $this->extractProperties($entity);

foreach ($cols as $key => $value) {
if (!is_object($value)) {
continue;
}

if ($this->style->isRelationProperty($key)) {
$fk = $this->style->remoteIdentifier($key);
$cols[$fk] = $this->get($value, $this->style->identifier($key));
unset($cols[$key]);
} else {
$table = $this->style->remoteFromIdentifier($key) ?? $key;
$cols[$key] = $this->get($value, $this->style->identifier($table));
}
}

return $cols;
}

/** @return array<string, mixed> */
public function extractProperties(object $entity): array
{
$props = get_object_vars($entity);
$persistable = $this->reflectPersistable($entity::class);
$props = [];

foreach ($this->reflectProperties($entity::class) as $name => $prop) {
if (!isset($persistable[$name]) || !$prop->isInitialized($entity)) {
unset($props[$name]);

if (!$prop->isInitialized($entity) || $prop->getAttributes(NotPersistable::class)) {
continue;
}

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

foreach (get_object_vars($source) as $prop => $value) {
$this->set($entity, $prop, $value);
foreach ($this->reflectProperties($source::class) as $name => $prop) {
if (!$prop->isInitialized($source)) {
continue;
}

$this->set($entity, $name, $prop->getValue($source));
}

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

return $this->propertyCache[$class];
}

/** @return array<string, ReflectionProperty> */
private function reflectPersistable(string $class): array
{
if (!isset($this->persistableCache[$class])) {
$this->persistableCache[$class] = [];

foreach ($this->reflectProperties($class) as $name => $prop) {
if ($prop->getAttributes(NotPersistable::class)) {
continue;
}

$this->persistableCache[$class][$name] = $prop;
}
}

return $this->persistableCache[$class];
}
}
45 changes: 18 additions & 27 deletions src/Hydrators/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,42 @@
use Respect\Data\Hydrator;
use SplObjectStorage;

use function is_object;

/** Base hydrator providing FK-to-entity wiring shared by all strategies */
/** Base hydrator providing collection-tree entity wiring */
abstract class Base implements Hydrator
{
/** @param SplObjectStorage<object, Collection> $entities */
protected function wireRelationships(SplObjectStorage $entities, EntityFactory $entityFactory): void
{
$style = $entityFactory->style;
$entitiesClone = clone $entities;
$others = clone $entities;

foreach ($entities as $entity) {
$coll = $entities[$entity];

foreach ($entities as $instance) {
foreach ($entityFactory->extractProperties($instance) as $field => $v) {
if (!$style->isRemoteIdentifier($field)) {
foreach ($others as $other) {
if ($other === $entity) {
continue;
}

foreach ($entitiesClone as $sub) {
if ($sub === $instance) {
continue;
}

$tableName = (string) $entities[$sub]->name;
$primaryName = $style->identifier($tableName);

if (
$tableName !== $style->remoteFromIdentifier($field)
|| $entityFactory->get($sub, $primaryName) != $v
) {
continue;
}

$v = $sub;
$otherColl = $others[$other];
if ($otherColl->parent !== $coll || $otherColl->name === null) {
continue;
}

if (!is_object($v)) {
$relationName = $style->relationProperty(
$style->remoteIdentifier($otherColl->name),
);

if ($relationName === null) {
continue;
}

$relationName = $style->relationProperty($field);
if ($relationName === null) {
$pk = $entityFactory->get($other, $style->identifier($otherColl->name));
if ($pk === null) {
continue;
}

$entityFactory->set($instance, $relationName, $v);
$entityFactory->set($entity, $relationName, $other);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Hydrators/Nested.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private function hydrateNode(
EntityFactory $entityFactory,
SplObjectStorage $entities,
): void {
$entityName = $collection->resolveEntityName($entityFactory, (object) $data);
$entityName = $collection->resolveEntityName($entityFactory, $data);
$entity = $entityFactory->createByName($entityName);

foreach ($data as $key => $value) {
Expand Down
Loading
Loading