diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 45378a9..db01604 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,7 +4,7 @@ parameters: - src/ - tests/ ignoreErrors: - - message: '/Call to an undefined (static )?method Respect\\Data\\(AbstractMapper|InMemoryMapper|Collections\\Collection)::\w+\(\)\./' + - message: '/Call to an undefined (static )?method Respect\\Data\\(AbstractMapper|InMemoryMapper|Scope)::\w+\(\)\./' - message: '/Unsafe usage of new static\(\)\./' - message: '/Property .+ is never read, only written\./' diff --git a/src/AbstractMapper.php b/src/AbstractMapper.php index c6c44db..d0f3f09 100644 --- a/src/AbstractMapper.php +++ b/src/AbstractMapper.php @@ -4,7 +4,6 @@ namespace Respect\Data; -use Respect\Data\Collections\Collection; use SplObjectStorage; use function count; @@ -15,18 +14,15 @@ abstract class AbstractMapper { - /** @var SplObjectStorage Maps entity → source Collection */ + /** @var SplObjectStorage Maps entity → source Scope */ protected SplObjectStorage $tracked; /** @var SplObjectStorage Maps entity → 'insert'|'update'|'delete' */ protected SplObjectStorage $pending; - /** @var array> Identity-indexed map: [collectionName][idValue] → entity */ + /** @var array> Identity-indexed map: [scopeName][idValue] → entity */ protected array $identityMap = []; - /** @var array */ - private array $collections = []; - public EntityFactory $entityFactory { get => $this->hydrator->entityFactory; } public Styles\Stylable $style { get => $this->entityFactory->style; } @@ -40,10 +36,10 @@ public function __construct( abstract public function flush(): void; - abstract public function fetch(Collection $collection, mixed $extra = null): mixed; + abstract public function fetch(Scope $scope, mixed $extra = null): mixed; /** @return array */ - abstract public function fetchAll(Collection $collection, mixed $extra = null): array; + abstract public function fetchAll(Scope $scope, mixed $extra = null): array; public function reset(): void { @@ -55,29 +51,7 @@ public function clearIdentityMap(): void $this->identityMap = []; } - public function trackedCount(): int - { - return count($this->tracked); - } - - public function identityMapCount(): int - { - $total = 0; - foreach ($this->identityMap as $entries) { - $total += count($entries); - } - - return $total; - } - - public function markTracked(object $entity, Collection $collection): bool - { - $this->tracked[$entity] = $collection; - - return true; - } - - public function persist(object $object, Collection $onCollection): object + public function persist(object $object, Scope $onScope): object { if ($this->isTracked($object)) { $currentOp = $this->pending[$object] ?? null; @@ -88,45 +62,56 @@ public function persist(object $object, Collection $onCollection): object return $object; } - $merged = $this->tryMergeWithIdentityMap($object, $onCollection); + $merged = $this->tryMergeWithIdentityMap($object, $onScope); if ($merged !== null) { return $merged; } $this->pending[$object] = 'insert'; - $this->markTracked($object, $onCollection); - $this->registerInIdentityMap($object, $onCollection); + $this->markTracked($object, $onScope); + $this->registerInIdentityMap($object, $onScope); return $object; } - public function remove(object $object, Collection $fromCollection): bool + public function remove(object $object, Scope $fromScope): void { $this->pending[$object] = 'delete'; - if (!$this->isTracked($object)) { - $this->markTracked($object, $fromCollection); + if ($this->isTracked($object)) { + return; } - return true; + $this->markTracked($object, $fromScope); } - public function isTracked(object $entity): bool + public function trackedCount(): int { - return $this->tracked->offsetExists($entity); + return count($this->tracked); } - public function registerCollection(string $alias, Collection $collection): void + public function identityMapCount(): int { - $this->collections[$alias] = $collection; + $total = 0; + foreach ($this->identityMap as $entries) { + $total += count($entries); + } + + return $total; } - protected function registerInIdentityMap(object $entity, Collection $coll): void + public function markTracked(object $entity, Scope $scope): void { - if ($coll->name === null) { - return; - } + $this->tracked[$entity] = $scope; + } + public function isTracked(object $entity): bool + { + return $this->tracked->offsetExists($entity); + } + + protected function registerInIdentityMap(object $entity, Scope $coll): void + { $idValue = $this->entityIdValue($entity, $coll->name); if ($idValue === null) { return; @@ -135,12 +120,8 @@ protected function registerInIdentityMap(object $entity, Collection $coll): void $this->identityMap[$coll->name][$idValue] = $entity; } - protected function evictFromIdentityMap(object $entity, Collection $coll): void + protected function evictFromIdentityMap(object $entity, Scope $coll): void { - if ($coll->name === null) { - return; - } - $idValue = $this->entityIdValue($entity, $coll->name); if ($idValue === null) { return; @@ -149,26 +130,22 @@ protected function evictFromIdentityMap(object $entity, Collection $coll): void unset($this->identityMap[$coll->name][$idValue]); } - protected function findInIdentityMap(Collection $collection): object|null + protected function findInIdentityMap(Scope $scope): object|null { - if ($collection->name === null || !is_scalar($collection->filter) || $collection->hasChildren) { + if (!is_scalar($scope->filter) || $scope->hasChildren) { return null; } - $condition = $this->normalizeIdValue($collection->filter); + $condition = $this->normalizeIdValue($scope->filter); if ($condition === null) { return null; } - return $this->identityMap[$collection->name][$condition] ?? null; + return $this->identityMap[$scope->name][$condition] ?? null; } - private function tryMergeWithIdentityMap(object $entity, Collection $coll): object|null + private function tryMergeWithIdentityMap(object $entity, Scope $coll): object|null { - if ($coll->name === null) { - return null; - } - $entityId = $this->entityIdValue($entity, $coll->name); $idValue = $entityId ?? $this->normalizeIdValue($coll->filter); @@ -237,22 +214,9 @@ private function normalizeIdValue(mixed $value): int|string|null return null; } - public function __isset(string $alias): bool + /** @param array $arguments */ + public function __call(string $name, array $arguments): Scope { - return isset($this->collections[$alias]); - } - - /** @param list $arguments */ - public function __call(string $name, array $arguments): Collection - { - if (isset($this->collections[$name])) { - if (empty($arguments)) { - return clone $this->collections[$name]; - } - - return $this->collections[$name]->derive(...$arguments); // @phpstan-ignore argument.type - } - - return new Collection($name, ...$arguments); // @phpstan-ignore argument.type + return new Scope($name, ...$arguments); // @phpstan-ignore argument.type } } diff --git a/src/EntityFactory.php b/src/EntityFactory.php index 5f77a10..e5f467b 100644 --- a/src/EntityFactory.php +++ b/src/EntityFactory.php @@ -203,17 +203,17 @@ public function extractProperties(object $entity): array } /** - * Enumerate persistable fields for a collection, mapping DB column names to styled property names. + * Enumerate persistable fields for a scope, mapping DB column names to styled property names. * * @return array DB column name → styled property name */ - public function enumerateFields(string $collectionName): array + public function enumerateFields(string $scopeName): array { - if (isset($this->fieldCache[$collectionName])) { - return $this->fieldCache[$collectionName]; + if (isset($this->fieldCache[$scopeName])) { + return $this->fieldCache[$scopeName]; } - $class = $this->resolveClass($collectionName); + $class = $this->resolveClass($scopeName); $relations = $this->detectRelationProperties($class); $fields = []; @@ -225,7 +225,7 @@ public function enumerateFields(string $collectionName): array $fields[$this->style->realProperty($name)] = $name; } - return $this->fieldCache[$collectionName] = $fields; + return $this->fieldCache[$scopeName] = $fields; } /** diff --git a/src/Hydrator.php b/src/Hydrator.php index 7bed807..74b4b34 100644 --- a/src/Hydrator.php +++ b/src/Hydrator.php @@ -4,10 +4,9 @@ namespace Respect\Data; -use Respect\Data\Collections\Collection; use SplObjectStorage; -/** Transforms raw backend data into entity instances mapped to their collections */ +/** Transforms raw backend data into entity instances mapped to their scopes */ interface Hydrator { public EntityFactory $entityFactory { get; } @@ -15,12 +14,12 @@ interface Hydrator /** Returns just the root entity */ public function hydrate( mixed $raw, - Collection $collection, + Scope $scope, ): object|false; - /** @return SplObjectStorage|false */ + /** @return SplObjectStorage|false */ public function hydrateAll( mixed $raw, - Collection $collection, + Scope $scope, ): SplObjectStorage|false; } diff --git a/src/Hydrators/Base.php b/src/Hydrators/Base.php index 9ccff1f..efbdcdd 100644 --- a/src/Hydrators/Base.php +++ b/src/Hydrators/Base.php @@ -5,12 +5,12 @@ namespace Respect\Data\Hydrators; use DomainException; -use Respect\Data\Collections\Collection; use Respect\Data\EntityFactory; use Respect\Data\Hydrator; +use Respect\Data\Scope; use SplObjectStorage; -/** Base hydrator providing collection-tree entity wiring */ +/** Base hydrator providing scope-tree entity wiring */ abstract class Base implements Hydrator { public function __construct( @@ -20,52 +20,52 @@ public function __construct( public function hydrate( mixed $raw, - Collection $collection, + Scope $scope, ): object|false { - $entities = $this->hydrateAll($raw, $collection); + $entities = $this->hydrateAll($raw, $scope); if ($entities === false) { return false; } foreach ($entities as $entity) { - if ($entities[$entity] === $collection) { + if ($entities[$entity] === $scope) { return $entity; } } throw new DomainException( - 'Hydration produced no entity for collection "' . $collection->name . '"', + 'Hydration produced no entity for scope "' . $scope->name . '"', ); } - /** @param SplObjectStorage $entities */ + /** @param SplObjectStorage $entities */ protected function wireRelationships(SplObjectStorage $entities): void { $style = $this->entityFactory->style; $others = clone $entities; foreach ($entities as $entity) { - $coll = $entities[$entity]; + $scope = $entities[$entity]; foreach ($others as $other) { if ($other === $entity) { continue; } - $otherColl = $others[$other]; - if ($otherColl->parent !== $coll || $otherColl->name === null) { + $otherScope = $others[$other]; + if ($otherScope->parent !== $scope) { continue; } $relationName = $style->relationProperty( - $style->remoteIdentifier($otherColl->name), + $style->remoteIdentifier($otherScope->name), ); if ($relationName === null) { continue; } - $id = $this->entityFactory->get($other, $style->identifier($otherColl->name)); + $id = $this->entityFactory->get($other, $style->identifier($otherScope->name)); if ($id === null) { continue; } diff --git a/src/Hydrators/Nested.php b/src/Hydrators/Nested.php index 175aeb1..a1a1e05 100644 --- a/src/Hydrators/Nested.php +++ b/src/Hydrators/Nested.php @@ -4,27 +4,27 @@ namespace Respect\Data\Hydrators; -use Respect\Data\Collections\Collection; +use Respect\Data\Scope; use SplObjectStorage; use function is_array; -/** Hydrates entities from a nested associative array keyed by collection name */ +/** Hydrates entities from a nested associative array keyed by scope name */ final class Nested extends Base { - /** @return SplObjectStorage|false */ + /** @return SplObjectStorage|false */ public function hydrateAll( mixed $raw, - Collection $collection, + Scope $scope, ): SplObjectStorage|false { if (!is_array($raw)) { return false; } - /** @var SplObjectStorage $entities */ + /** @var SplObjectStorage $entities */ $entities = new SplObjectStorage(); - $this->hydrateNode($raw, $collection, $entities); + $this->hydrateNode($raw, $scope, $entities); if ($entities->count() > 1) { $this->wireRelationships($entities); @@ -35,15 +35,15 @@ public function hydrateAll( /** * @param array $data - * @param SplObjectStorage $entities + * @param SplObjectStorage $entities */ private function hydrateNode( array $data, - Collection $collection, + Scope $scope, SplObjectStorage $entities, ): void { $entity = $this->entityFactory->create( - $this->entityFactory->resolveClass((string) $collection->name), + $this->entityFactory->resolveClass((string) $scope->name), ); foreach ($data as $key => $value) { @@ -54,24 +54,24 @@ private function hydrateNode( $this->entityFactory->set($entity, $key, $value); } - $entities[$entity] = $collection; + $entities[$entity] = $scope; - foreach ($collection->with as $child) { + foreach ($scope->with as $child) { $this->hydrateChild($data, $child, $entities); } } /** * @param array $parentData - * @param SplObjectStorage $entities + * @param SplObjectStorage $entities */ private function hydrateChild( array $parentData, - Collection $child, + Scope $child, SplObjectStorage $entities, ): void { $key = $child->name; - if ($key === null || !isset($parentData[$key]) || !is_array($parentData[$key])) { + if (!isset($parentData[$key]) || !is_array($parentData[$key])) { return; } diff --git a/src/Hydrators/PrestyledAssoc.php b/src/Hydrators/PrestyledAssoc.php index 983c097..f83dc2d 100644 --- a/src/Hydrators/PrestyledAssoc.php +++ b/src/Hydrators/PrestyledAssoc.php @@ -5,8 +5,8 @@ namespace Respect\Data\Hydrators; use DomainException; -use Respect\Data\CollectionIterator; -use Respect\Data\Collections\Collection; +use Respect\Data\Scope; +use Respect\Data\ScopeIterator; use SplObjectStorage; use function explode; @@ -21,21 +21,21 @@ */ final class PrestyledAssoc extends Base { - /** @var array */ - private array $collMap = []; + /** @var array */ + private array $scopeMap = []; - private Collection|null $cachedCollection = null; + private Scope|null $cachedScope = null; - /** @return SplObjectStorage|false */ + /** @return SplObjectStorage|false */ public function hydrateAll( mixed $raw, - Collection $collection, + Scope $scope, ): SplObjectStorage|false { if (!$raw || !is_array($raw)) { return false; } - $collMap = $this->buildCollMap($collection); + $scopeMap = $this->buildScopeMap($scope); /** @var array> $grouped */ $grouped = []; @@ -44,23 +44,23 @@ public function hydrateAll( $grouped[$prefix][$prop] = $value; } - /** @var SplObjectStorage $entities */ + /** @var SplObjectStorage $entities */ $entities = new SplObjectStorage(); /** @var array $instances */ $instances = []; foreach ($grouped as $prefix => $props) { - if (!isset($collMap[$prefix])) { + if (!isset($scopeMap[$prefix])) { throw new DomainException('Unknown column prefix "' . $prefix . '" in hydration row'); } $basePrefix = $prefix; if (!isset($instances[$basePrefix])) { - $coll = $collMap[$basePrefix]; - $class = $this->entityFactory->resolveClass((string) $coll->name); + $matched = $scopeMap[$basePrefix]; + $class = $this->entityFactory->resolveClass((string) $matched->name); $instances[$basePrefix] = $this->entityFactory->create($class); - $entities[$instances[$basePrefix]] = $coll; + $entities[$instances[$basePrefix]] = $matched; } $entity = $instances[$basePrefix]; @@ -76,20 +76,20 @@ public function hydrateAll( return $entities; } - /** @return array */ - private function buildCollMap(Collection $collection): array + /** @return array */ + private function buildScopeMap(Scope $scope): array { - if ($this->cachedCollection === $collection) { - return $this->collMap; + if ($this->cachedScope === $scope) { + return $this->scopeMap; } - $this->collMap = []; - foreach (CollectionIterator::recursive($collection) as $spec => $c) { - $this->collMap[$spec] = $c; + $this->scopeMap = []; + foreach (ScopeIterator::recursive($scope) as $spec => $c) { + $this->scopeMap[$spec] = $c; } - $this->cachedCollection = $collection; + $this->cachedScope = $scope; - return $this->collMap; + return $this->scopeMap; } } diff --git a/src/Collections/Collection.php b/src/Scope.php similarity index 57% rename from src/Collections/Collection.php rename to src/Scope.php index d8da3dc..e13d2fe 100644 --- a/src/Collections/Collection.php +++ b/src/Scope.php @@ -2,23 +2,23 @@ declare(strict_types=1); -namespace Respect\Data\Collections; +namespace Respect\Data; -class Collection +class Scope { - public private(set) Collection|null $parent = null; + public private(set) Scope|null $parent = null; - /** @var list */ + /** @var list */ public private(set) array $with; public bool $hasChildren { get => !empty($this->with); } /** - * @param list $with + * @param list $with * @param array|scalar|null $filter */ public function __construct( - public readonly string|null $name = null, + public readonly string $name, array $with = [], public readonly array|int|float|string|bool|null $filter = null, public readonly bool $required = false, @@ -27,26 +27,9 @@ public function __construct( } /** - * @param list $with - * @param array|scalar|null $filter - */ - public function derive( - array $with = [], - array|int|float|string|bool|null $filter = null, - bool|null $required = null, - ): static { - return new static( - $this->name, - with: [...$this->with, ...$with], - filter: $filter ?? $this->filter, - required: $required ?? $this->required, - ); - } - - /** - * @param list $children + * @param list $children * - * @return list + * @return list */ private function adoptChildren(array $children): array { diff --git a/src/CollectionIterator.php b/src/ScopeIterator.php similarity index 59% rename from src/CollectionIterator.php rename to src/ScopeIterator.php index 3d95763..4bdaa31 100644 --- a/src/CollectionIterator.php +++ b/src/ScopeIterator.php @@ -6,22 +6,20 @@ use RecursiveArrayIterator; use RecursiveIteratorIterator; -use Respect\Data\Collections\Collection; -use function array_filter; use function is_array; -/** @extends RecursiveArrayIterator */ -final class CollectionIterator extends RecursiveArrayIterator +/** @extends RecursiveArrayIterator */ +final class ScopeIterator extends RecursiveArrayIterator { /** @var array */ protected array $namesCounts = []; /** - * @param Collection|array $target + * @param Scope|array $target * @param array $namesCounts */ - public function __construct(Collection|array $target = [], array &$namesCounts = []) + public function __construct(Scope|array $target = [], array &$namesCounts = []) { $this->namesCounts = &$namesCounts; @@ -30,15 +28,15 @@ public function __construct(Collection|array $target = [], array &$namesCounts = parent::__construct($items); } - /** @return RecursiveIteratorIterator&iterable */ - public static function recursive(Collection $target): RecursiveIteratorIterator + /** @return RecursiveIteratorIterator&iterable */ + public static function recursive(Scope $target): RecursiveIteratorIterator { return new RecursiveIteratorIterator(new self($target), 1); } public function key(): string { - $name = $this->current()->name ?? ''; + $name = $this->current()->name; if (isset($this->namesCounts[$name])) { return $name . ++$this->namesCounts[$name]; @@ -57,7 +55,7 @@ public function hasChildren(): bool public function getChildren(): RecursiveArrayIterator { return new static( - array_filter($this->current()->with, static fn(Collection $c): bool => $c->name !== null), + $this->current()->with, $this->namesCounts, ); } diff --git a/src/Styles/Plural.php b/src/Styles/Plural.php index 5ebde4a..fda8a4d 100644 --- a/src/Styles/Plural.php +++ b/src/Styles/Plural.php @@ -9,8 +9,6 @@ use function implode; use function preg_match; use function preg_replace; -use function strtolower; -use function substr; use function ucfirst; /** @@ -30,19 +28,6 @@ public function remoteIdentifier(string $name): string return $this->pluralToSingular($name) . '_id'; } - public function remoteFromIdentifier(string $name): string|null - { - return $this->isRemoteIdentifier($name) ? $this->singularToPlural(substr($name, 0, -3)) : null; - } - - public function realName(string $name): string - { - return implode('_', array_map( - $this->singularToPlural(...), - explode('_', strtolower($this->camelCaseToSeparator($name, '_'))), - )); - } - public function styledName(string $name): string { $pieces = array_map($this->pluralToSingular(...), explode('_', $name)); @@ -57,28 +42,26 @@ public function composed(string $left, string $right): string private function pluralToSingular(string $name): string { - $replacements = [ + return $this->applyFirstMatch($name, [ '/^(.+)ies$/' => '$1y', '/^(.+)s$/' => '$1', - ]; - foreach ($replacements as $key => $value) { - if (preg_match($key, $name)) { - return (string) preg_replace($key, $value, $name); - } - } - - return $name; + ]); } private function singularToPlural(string $name): string { - $replacements = [ + return $this->applyFirstMatch($name, [ '/^(.+)y$/' => '$1ies', '/^(.+)([^s])$/' => '$1$2s', - ]; - foreach ($replacements as $key => $value) { - if (preg_match($key, $name)) { - return (string) preg_replace($key, $value, $name); + ]); + } + + /** @param array $replacements */ + private function applyFirstMatch(string $name, array $replacements): string + { + foreach ($replacements as $pattern => $replacement) { + if (preg_match($pattern, $name)) { + return (string) preg_replace($pattern, $replacement, $name); } } diff --git a/src/Styles/Standard.php b/src/Styles/Standard.php index 4084614..1997c29 100644 --- a/src/Styles/Standard.php +++ b/src/Styles/Standard.php @@ -17,11 +17,6 @@ public function styledProperty(string $name): string return $this->separatorToCamelCase($name, '_'); } - public function realName(string $name): string - { - return strtolower($this->camelCaseToSeparator($name, '_')); - } - public function realProperty(string $name): string { return strtolower($this->camelCaseToSeparator($name, '_')); @@ -52,18 +47,8 @@ public function isRemoteIdentifier(string $name): bool return strlen($name) - 3 === strripos($name, '_id'); } - public function remoteFromIdentifier(string $name): string|null - { - return $this->isRemoteIdentifier($name) ? substr($name, 0, -3) : null; - } - public function relationProperty(string $field): string|null { return $this->isRemoteIdentifier($field) ? substr($field, 0, -3) : null; } - - public function isRelationProperty(string $name): bool - { - return !$this->isRemoteIdentifier($name) && $this->isRemoteIdentifier($name . '_id'); - } } diff --git a/src/Styles/Stylable.php b/src/Styles/Stylable.php index e135a03..c44f94a 100644 --- a/src/Styles/Stylable.php +++ b/src/Styles/Stylable.php @@ -8,8 +8,6 @@ interface Stylable { public function styledName(string $entityName): string; - public function realName(string $styledName): string; - public function styledProperty(string $name): string; public function realProperty(string $name): string; @@ -18,13 +16,9 @@ public function identifier(string $name): string; public function remoteIdentifier(string $name): string; - public function remoteFromIdentifier(string $name): string|null; - public function isRemoteIdentifier(string $name): bool; public function relationProperty(string $remoteIdentifierField): string|null; - public function isRelationProperty(string $name): bool; - public function composed(string $left, string $right): string; } diff --git a/tests/AbstractMapperTest.php b/tests/AbstractMapperTest.php index 8b0f426..f8696a7 100644 --- a/tests/AbstractMapperTest.php +++ b/tests/AbstractMapperTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use ReflectionObject; -use Respect\Data\Collections\Collection; use Respect\Data\Hydrators\Nested; use Respect\Data\Styles\Plural; use Respect\Data\Styles\Standard; @@ -27,13 +26,13 @@ public function flush(): void { } - public function fetch(Collection $collection, mixed $extra = null): mixed + public function fetch(Scope $scope, mixed $extra = null): mixed { return false; } /** @return array */ - public function fetchAll(Collection $collection, mixed $extra = null): array + public function fetchAll(Scope $scope, mixed $extra = null): array { return []; } @@ -41,37 +40,14 @@ public function fetchAll(Collection $collection, mixed $extra = null): array } #[Test] - public function registerCollectionShouldAddCollectionToPool(): void + public function magicCallShouldBypassToScope(): void { - $coll = Collection::foo(); - $this->mapper->registerCollection('my_alias', $coll); - - $this->assertTrue(isset($this->mapper->my_alias)); - $clone = $this->mapper->my_alias(); - $this->assertEquals($coll->name, $clone->name); - } - - #[Test] - public function callingRegisteredCollectionWithoutArgsClones(): void - { - $coll = Collection::post(); - $this->mapper->registerCollection('post', $coll); - - $clone = $this->mapper->post(); - - $this->assertNotSame($coll, $clone); - $this->assertEquals('post', $clone->name); - } - - #[Test] - public function magicCallShouldBypassToCollection(): void - { - $collection = $this->mapper->author([$this->mapper->post([$this->mapper->comment()])]); - $this->assertEquals('author', $collection->name); - $this->assertCount(1, $collection->with); - $this->assertEquals('post', $collection->with[0]->name); - $this->assertCount(1, $collection->with[0]->with); - $this->assertEquals('comment', $collection->with[0]->with[0]->name); + $scope = $this->mapper->author([$this->mapper->post([$this->mapper->comment()])]); + $this->assertEquals('author', $scope->name); + $this->assertCount(1, $scope->with); + $this->assertEquals('post', $scope->with[0]->name); + $this->assertCount(1, $scope->with[0]->with); + $this->assertEquals('comment', $scope->with[0]->with[0]->name); } #[Test] @@ -98,13 +74,13 @@ public function flush(): void { } - public function fetch(Collection $collection, mixed $extra = null): mixed + public function fetch(Scope $scope, mixed $extra = null): mixed { return false; } /** @return array */ - public function fetchAll(Collection $collection, mixed $extra = null): array + public function fetchAll(Scope $scope, mixed $extra = null): array { return []; } @@ -116,8 +92,8 @@ public function fetchAll(Collection $collection, mixed $extra = null): array public function persistShouldMarkObjectAsTracked(): void { $entity = new Stubs\Foo(); - $collection = Collection::foo(); - $this->mapper->persist($entity, $collection); + $scope = Scope::foo(); + $this->mapper->persist($entity, $scope); $this->assertTrue($this->mapper->isTracked($entity)); } @@ -125,9 +101,9 @@ public function persistShouldMarkObjectAsTracked(): void public function persistAlreadyTrackedShouldReturnEntity(): void { $entity = new Stubs\Foo(); - $collection = Collection::foo(); - $this->mapper->markTracked($entity, $collection); - $result = $this->mapper->persist($entity, $collection); + $scope = Scope::foo(); + $this->mapper->markTracked($entity, $scope); + $result = $this->mapper->persist($entity, $scope); $this->assertSame($entity, $result); } @@ -135,20 +111,19 @@ public function persistAlreadyTrackedShouldReturnEntity(): void public function removeShouldMarkObjectAsTracked(): void { $entity = new Stubs\Foo(); - $collection = Collection::foo(); - $result = $this->mapper->remove($entity, $collection); - $this->assertTrue($result); + $scope = Scope::foo(); + $this->mapper->remove($entity, $scope); $this->assertTrue($this->mapper->isTracked($entity)); } #[Test] - public function removeAlreadyTrackedShouldReturnTrue(): void + public function removeAlreadyTrackedShouldNotThrow(): void { $entity = new Stubs\Foo(); - $collection = Collection::foo(); - $this->mapper->markTracked($entity, $collection); - $result = $this->mapper->remove($entity, $collection); - $this->assertTrue($result); + $scope = Scope::foo(); + $this->mapper->markTracked($entity, $scope); + $this->mapper->remove($entity, $scope); + $this->assertTrue($this->mapper->isTracked($entity)); } #[Test] @@ -157,21 +132,13 @@ public function isTrackedShouldReturnFalseForUntrackedEntity(): void $this->assertFalse($this->mapper->isTracked(new Stubs\Foo())); } - #[Test] - public function markTrackedShouldReturnTrue(): void - { - $entity = new Stubs\Foo(); - $collection = Collection::foo(); - $this->assertTrue($this->mapper->markTracked($entity, $collection)); - } - #[Test] public function resetShouldClearPending(): void { $entity = new Stubs\Foo(); - $collection = Collection::foo(); - $this->mapper->persist($entity, $collection); - $this->mapper->remove($entity, $collection); + $scope = Scope::foo(); + $this->mapper->persist($entity, $scope); + $this->mapper->remove($entity, $scope); $this->mapper->reset(); $ref = new ReflectionObject($this->mapper); @@ -182,20 +149,6 @@ public function resetShouldClearPending(): void $this->assertCount(0, $pendingStorage); } - #[Test] - public function issetShouldReturnTrueForRegisteredCollection(): void - { - $coll = Collection::foo(); - $this->mapper->registerCollection('my_alias', $coll); - $this->assertTrue(isset($this->mapper->my_alias)); - } - - #[Test] - public function issetShouldReturnFalseForUnregisteredCollection(): void - { - $this->assertFalse(isset($this->mapper->nonexistent)); - } - #[Test] public function hydrationWiresRelatedEntity(): void { @@ -212,7 +165,7 @@ public function hydrationWiresRelatedEntity(): void $comment = $mapper->fetch($mapper->comment([$mapper->post()])); $this->assertIsObject($comment); - // Related entity wired via collection tree + // Related entity wired via scope tree $post = $mapper->entityFactory->get($comment, 'post'); $this->assertIsObject($post); $this->assertEquals(5, $mapper->entityFactory->get($post, 'id')); @@ -286,26 +239,7 @@ public function hydrationWiresRelationWithStringPk(): void } #[Test] - public function callingRegisteredCollectionReturnsImmutableClone(): void - { - $mapper = new InMemoryMapper(new Nested(new EntityFactory( - entityNamespace: 'Respect\\Data\\Stubs\\', - ))); - $mapper->seed('post', []); - $mapper->seed('comment', []); - - $coll = Collection::posts(); - $mapper->registerCollection('commentedPosts', $coll->derive(with: [Collection::comment()])); - - $clone = $mapper->commentedPosts(); - - // Clone has the child from the registered collection - $this->assertCount(1, $clone->with); - $this->assertEquals('comment', $clone->with[0]->name); - } - - #[Test] - public function directPersistWithoutRegisteredCollection(): void + public function directPersistWithoutRegisteredScope(): void { $mapper = new InMemoryMapper(new Nested(new EntityFactory( entityNamespace: 'Respect\\Data\\Stubs\\', @@ -493,23 +427,6 @@ public function trackedCountReflectsTrackedEntities(): void $this->assertSame(1, $mapper->trackedCount()); } - #[Test] - public function registerSkipsEntityWithNullCollectionName(): void - { - $mapper = new InMemoryMapper(new Nested(new EntityFactory( - entityNamespace: 'Respect\\Data\\Stubs\\', - ))); - $entity = new Stubs\Foo(); - $entity->id = 1; - - // Collection with null name — register should be a no-op - $coll = new Collection(); - $mapper->persist($entity, $coll); - $mapper->flush(); - - $this->assertSame(0, $mapper->identityMapCount()); - } - #[Test] public function registerSkipsEntityWithNoPkValue(): void { @@ -529,7 +446,7 @@ public function registerSkipsEntityWithNoPkValue(): void } #[Test] - public function deleteEvictsUsingTrackedCollection(): void + public function deleteEvictsUsingTrackedScope(): void { $mapper = new InMemoryMapper(new Nested(new EntityFactory( entityNamespace: 'Respect\\Data\\Stubs\\', @@ -541,32 +458,13 @@ public function deleteEvictsUsingTrackedCollection(): void $entity = $mapper->fetch($mapper->post(filter: 1)); $this->assertSame(1, $mapper->identityMapCount()); - // Remove via a different collection — flush uses the tracked one (name='post') + // Remove via a different scope — flush uses the tracked one (name='post') $mapper->remove($entity, $mapper->post()); $mapper->flush(); $this->assertSame(0, $mapper->identityMapCount()); } - #[Test] - public function evictSkipsNullCollectionName(): void - { - $mapper = new InMemoryMapper(new Nested(new EntityFactory( - entityNamespace: 'Respect\\Data\\Stubs\\', - ))); - - // Track a new entity directly against a null-name collection - $entity = new Stubs\Foo(); - $entity->id = 1; - $nullColl = new Collection(); - $mapper->markTracked($entity, $nullColl); - $mapper->remove($entity, $nullColl); - $mapper->flush(); - - // Evict should be a no-op (null name), identity map stays empty - $this->assertSame(0, $mapper->identityMapCount()); - } - #[Test] public function evictSkipsEntityWithNoPkValue(): void { @@ -609,7 +507,7 @@ public function findInIdentityMapSkipsNonScalarCondition(): void } #[Test] - public function findInIdentityMapSkipsCollectionWithChildren(): void + public function findInIdentityMapSkipsScopeWithChildren(): void { $mapper = new InMemoryMapper(new Nested(new EntityFactory( entityNamespace: 'Respect\\Data\\Stubs\\', @@ -675,7 +573,7 @@ public function persistReadOnlyEntityInsertWorks(): void } #[Test] - public function persistReadOnlyViaCollectionPkUpdates(): void + public function persistReadOnlyViaScopePkUpdates(): void { $mapper = new InMemoryMapper(new Nested(new EntityFactory( entityNamespace: 'Respect\\Data\\Stubs\\', @@ -688,7 +586,7 @@ public function persistReadOnlyViaCollectionPkUpdates(): void $fetched = $mapper->fetch($mapper->read_only_author(filter: 1)); $this->assertSame('Original', $fetched->name); - // Create new readonly entity (no PK) and persist via collection[pk] + // Create new readonly entity (no PK) and persist via scope[pk] $updated = $mapper->entityFactory->create(Stubs\ReadOnlyAuthor::class, name: 'Updated', bio: 'new bio'); $merged = $mapper->persist($updated, $mapper->read_only_author(filter: 1)); @@ -710,7 +608,7 @@ public function persistReadOnlyViaCollectionPkUpdates(): void } #[Test] - public function persistReadOnlyViaCollectionPkFlushUpdatesStorage(): void + public function persistReadOnlyViaScopePkFlushUpdatesStorage(): void { $mapper = new InMemoryMapper(new Nested(new EntityFactory( entityNamespace: 'Respect\\Data\\Stubs\\', @@ -912,7 +810,7 @@ public function readOnlyInsertWithRelationExtractsFk(): void } #[Test] - public function readOnlyReplaceViaCollectionPkPreservesRelation(): void + public function readOnlyReplaceViaScopePkPreservesRelation(): void { $mapper = new InMemoryMapper(new Nested(new EntityFactory( entityNamespace: 'Respect\\Data\\Stubs\\Immutable\\', @@ -1058,7 +956,7 @@ public function identityMapReplaceSkipsSetWhenPkAlreadyInitialized(): void $updated = new Stubs\Immutable\Author(id: 1, name: 'Bob'); - // persist via collection[1] — PK already set, merge produces new entity + // persist via scope[1] — PK already set, merge produces new entity $merged = $mapper->persist($updated, $mapper->author(filter: 1)); $this->assertSame(1, $merged->id); @@ -1290,14 +1188,4 @@ public function mergeWithIdentityMapNormalizesConditionFallback(): void $this->assertTrue($mapper->isTracked($merged)); $this->assertFalse($mapper->isTracked($fetched)); } - - #[Test] - public function callingRegisteredCollectionWithArgsDerives(): void - { - $coll = Collection::post(); - $this->mapper->registerCollection('post', $coll); - $derived = $this->mapper->post(filter: 5); - $this->assertEquals('post', $derived->name); - $this->assertEquals(5, $derived->filter); - } } diff --git a/tests/Hydrators/NestedTest.php b/tests/Hydrators/NestedTest.php index 2cb3fa4..d5808f6 100644 --- a/tests/Hydrators/NestedTest.php +++ b/tests/Hydrators/NestedTest.php @@ -7,8 +7,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -use Respect\Data\Collections\Collection; use Respect\Data\EntityFactory; +use Respect\Data\Scope; #[CoversClass(Nested::class)] #[CoversClass(Base::class)] @@ -27,20 +27,20 @@ protected function setUp(): void #[Test] public function hydrateReturnsFalseForNonArray(): void { - $collection = Collection::author(); + $scope = Scope::author(); - $this->assertFalse($this->hydrator->hydrateAll(null, $collection)); - $this->assertFalse($this->hydrator->hydrateAll(false, $collection)); - $this->assertFalse($this->hydrator->hydrateAll('string', $collection)); + $this->assertFalse($this->hydrator->hydrateAll(null, $scope)); + $this->assertFalse($this->hydrator->hydrateAll(false, $scope)); + $this->assertFalse($this->hydrator->hydrateAll('string', $scope)); } #[Test] public function hydrateSingleEntity(): void { $raw = ['id' => 1, 'name' => 'Author Name']; - $collection = Collection::author(); + $scope = Scope::author(); - $result = $this->hydrator->hydrateAll($raw, $collection); + $result = $this->hydrator->hydrateAll($raw, $scope); $this->assertNotFalse($result); $this->assertCount(1, $result); @@ -48,7 +48,7 @@ public function hydrateSingleEntity(): void $entity = $result->current(); $this->assertEquals(1, $this->factory->get($entity, 'id')); $this->assertEquals('Author Name', $this->factory->get($entity, 'name')); - $this->assertSame($collection, $result[$entity]); + $this->assertSame($scope, $result[$entity]); } #[Test] @@ -59,9 +59,9 @@ public function hydrateWithNestedChild(): void 'title' => 'Post Title', 'author' => ['id' => 5, 'name' => 'Author'], ]; - $collection = Collection::post([Collection::author()]); + $scope = Scope::post([Scope::author()]); - $result = $this->hydrator->hydrateAll($raw, $collection); + $result = $this->hydrator->hydrateAll($raw, $scope); $this->assertNotFalse($result); $this->assertCount(2, $result); @@ -71,9 +71,9 @@ public function hydrateWithNestedChild(): void public function hydrateWithMissingNestedKeyReturnsPartial(): void { $raw = ['id' => 1, 'title' => 'Post Title']; - $collection = Collection::post([Collection::author()]); + $scope = Scope::post([Scope::author()]); - $result = $this->hydrator->hydrateAll($raw, $collection); + $result = $this->hydrator->hydrateAll($raw, $scope); $this->assertNotFalse($result); $this->assertCount(1, $result); @@ -91,11 +91,11 @@ public function hydrateDeeplyNested(): void 'author' => ['id' => 100, 'name' => 'Author'], ], ]; - $collection = Collection::comment([ - Collection::post([Collection::author()]), + $scope = Scope::comment([ + Scope::post([Scope::author()]), ]); - $result = $this->hydrator->hydrateAll($raw, $collection); + $result = $this->hydrator->hydrateAll($raw, $scope); $this->assertNotFalse($result); $this->assertCount(3, $result); @@ -110,36 +110,23 @@ public function hydrateWithChildren(): void 'author' => ['id' => 5, 'name' => 'Author'], 'category' => ['id' => 3, 'label' => 'Tech'], ]; - $authorColl = Collection::author(); - $categoryColl = Collection::category(); - $collection = Collection::post([$authorColl, $categoryColl]); + $authorColl = Scope::author(); + $categoryColl = Scope::category(); + $scope = Scope::post([$authorColl, $categoryColl]); - $result = $this->hydrator->hydrateAll($raw, $collection); + $result = $this->hydrator->hydrateAll($raw, $scope); $this->assertNotFalse($result); $this->assertCount(3, $result); } - #[Test] - public function hydrateChildWithNullNameIsSkipped(): void - { - $raw = ['id' => 1]; - $child = new Collection(); - $collection = Collection::post([$child]); - - $result = $this->hydrator->hydrateAll($raw, $collection); - - $this->assertNotFalse($result); - $this->assertCount(1, $result); - } - #[Test] public function hydrateScalarNestedValueIsIgnored(): void { $raw = ['id' => 1, 'author' => 'not-an-array']; - $collection = Collection::post([Collection::author()]); + $scope = Scope::post([Scope::author()]); - $result = $this->hydrator->hydrateAll($raw, $collection); + $result = $this->hydrator->hydrateAll($raw, $scope); $this->assertNotFalse($result); $this->assertCount(1, $result); @@ -148,14 +135,14 @@ public function hydrateScalarNestedValueIsIgnored(): void #[Test] public function hydrateReturnsFalseForInvalidInput(): void { - $this->assertFalse($this->hydrator->hydrate(null, Collection::author())); + $this->assertFalse($this->hydrator->hydrate(null, Scope::author())); } #[Test] public function hydrateReturnsRootEntity(): void { $raw = ['id' => 1, 'name' => 'Alice']; - $result = $this->hydrator->hydrate($raw, Collection::author()); + $result = $this->hydrator->hydrate($raw, Scope::author()); $this->assertNotFalse($result); $this->assertEquals(1, $this->factory->get($result, 'id')); @@ -170,9 +157,9 @@ public function wireRelationshipsSkipsChildWithNullId(): void 'title' => 'Post', 'author' => ['name' => 'No ID'], ]; - $collection = Collection::post([Collection::author()]); + $scope = Scope::post([Scope::author()]); - $result = $this->hydrator->hydrateAll($raw, $collection); + $result = $this->hydrator->hydrateAll($raw, $scope); $this->assertNotFalse($result); $result->rewind(); @@ -189,9 +176,9 @@ public function hydrateReturnsRootWithWiredRelation(): void 'title' => 'Post', 'author' => ['id' => 5, 'name' => 'Author'], ]; - $collection = Collection::post([Collection::author()]); + $scope = Scope::post([Scope::author()]); - $result = $this->hydrator->hydrate($raw, $collection); + $result = $this->hydrator->hydrate($raw, $scope); $this->assertNotFalse($result); $this->assertEquals(1, $this->factory->get($result, 'id')); diff --git a/tests/Hydrators/PrestyledAssocTest.php b/tests/Hydrators/PrestyledAssocTest.php index a904860..e729735 100644 --- a/tests/Hydrators/PrestyledAssocTest.php +++ b/tests/Hydrators/PrestyledAssocTest.php @@ -8,8 +8,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -use Respect\Data\Collections\Collection; use Respect\Data\EntityFactory; +use Respect\Data\Scope; use Respect\Data\Stubs\Author; #[CoversClass(PrestyledAssoc::class)] @@ -27,7 +27,7 @@ protected function setUp(): void public function hydrateReturnsFalseForEmpty(): void { $hydrator = new PrestyledAssoc($this->factory); - $coll = Collection::author(); + $coll = Scope::author(); $this->assertFalse($hydrator->hydrateAll(null, $coll)); $this->assertFalse($hydrator->hydrateAll([], $coll)); @@ -38,11 +38,11 @@ public function hydrateReturnsFalseForEmpty(): void public function hydrateSingleEntity(): void { $hydrator = new PrestyledAssoc($this->factory); - $collection = Collection::author(); + $scope = Scope::author(); $result = $hydrator->hydrateAll( ['author__id' => 1, 'author__name' => 'Alice'], - $collection, + $scope, ); $this->assertNotFalse($result); @@ -57,7 +57,7 @@ public function hydrateSingleEntity(): void public function hydrateMultipleEntitiesFromJoinedRow(): void { $hydrator = new PrestyledAssoc($this->factory); - $collection = Collection::author([Collection::post()]); + $scope = Scope::author([Scope::post()]); $result = $hydrator->hydrateAll( [ @@ -67,7 +67,7 @@ public function hydrateMultipleEntitiesFromJoinedRow(): void 'post__title' => 'Hello', 'post__author' => 1, ], - $collection, + $scope, ); $this->assertNotFalse($result); @@ -88,7 +88,7 @@ public function hydrateMultipleEntitiesFromJoinedRow(): void public function hydrateWiresRelationships(): void { $hydrator = new PrestyledAssoc($this->factory); - $collection = Collection::post([Collection::author()]); + $scope = Scope::post([Scope::author()]); $result = $hydrator->hydrateAll( [ @@ -98,7 +98,7 @@ public function hydrateWiresRelationships(): void 'author__id' => 1, 'author__name' => 'Alice', ], - $collection, + $scope, ); $this->assertNotFalse($result); @@ -113,7 +113,7 @@ public function hydrateWiresRelationships(): void public function hydrateReturnsRootRegardlessOfColumnOrder(): void { $hydrator = new PrestyledAssoc($this->factory); - $collection = Collection::post([Collection::author()]); + $scope = Scope::post([Scope::author()]); // Author columns appear before post columns $result = $hydrator->hydrate( @@ -124,7 +124,7 @@ public function hydrateReturnsRootRegardlessOfColumnOrder(): void 'post__title' => 'Hello', 'post__author' => 1, ], - $collection, + $scope, ); $this->assertNotFalse($result); @@ -136,15 +136,15 @@ public function hydrateReturnsRootRegardlessOfColumnOrder(): void public function hydrateCachesCollMapAcrossRows(): void { $hydrator = new PrestyledAssoc($this->factory); - $collection = Collection::author(); + $scope = Scope::author(); $first = $hydrator->hydrateAll( ['author__id' => 1, 'author__name' => 'Alice'], - $collection, + $scope, ); $second = $hydrator->hydrateAll( ['author__id' => 2, 'author__name' => 'Bob'], - $collection, + $scope, ); $this->assertNotFalse($first); @@ -155,13 +155,13 @@ public function hydrateCachesCollMapAcrossRows(): void public function hydrateThrowsOnUnknownPrefix(): void { $hydrator = new PrestyledAssoc($this->factory); - $collection = Collection::author(); + $scope = Scope::author(); $this->expectException(DomainException::class); $this->expectExceptionMessage('Unknown column prefix'); $hydrator->hydrateAll( ['author__id' => 1, 'unknown__foo' => 'bar'], - $collection, + $scope, ); } } diff --git a/tests/InMemoryMapper.php b/tests/InMemoryMapper.php index 878e952..99ecb3f 100644 --- a/tests/InMemoryMapper.php +++ b/tests/InMemoryMapper.php @@ -4,8 +4,6 @@ namespace Respect\Data; -use Respect\Data\Collections\Collection; - use function array_filter; use function array_merge; use function array_values; @@ -24,28 +22,28 @@ public function seed(string $table, array $rows): void $this->tables[$table] = $rows; } - public function fetch(Collection $collection, mixed $extra = null): mixed + public function fetch(Scope $scope, mixed $extra = null): mixed { if ($extra === null) { - $cached = $this->findInIdentityMap($collection); + $cached = $this->findInIdentityMap($scope); if ($cached !== null) { return $cached; } } - $row = $this->findRow((string) $collection->name, $collection->filter); + $row = $this->findRow((string) $scope->name, $scope->filter); - return $row !== null ? $this->hydrateRow($row, $collection) : false; + return $row !== null ? $this->hydrateRow($row, $scope) : false; } /** @return array */ - public function fetchAll(Collection $collection, mixed $extra = null): array + public function fetchAll(Scope $scope, mixed $extra = null): array { - $rows = $this->findRows((string) $collection->name, $collection->filter); + $rows = $this->findRows((string) $scope->name, $scope->filter); $result = []; foreach ($rows as $row) { - $entity = $this->hydrateRow($row, $collection); + $entity = $this->hydrateRow($row, $scope); if ($entity === false) { continue; } @@ -60,28 +58,28 @@ public function flush(): void { foreach ($this->pending as $entity) { $op = $this->pending[$entity]; - $collection = $this->tracked[$entity]; - $tableName = (string) $collection->name; + $scope = $this->tracked[$entity]; + $tableName = (string) $scope->name; $id = $this->style->identifier($tableName); match ($op) { - 'insert' => $this->insertEntity($entity, $collection, $tableName, $id), - 'update' => $this->updateEntity($entity, $collection, $tableName, $id), + 'insert' => $this->insertEntity($entity, $scope, $tableName, $id), + 'update' => $this->updateEntity($entity, $scope, $tableName, $id), 'delete' => $this->deleteEntity($entity, $tableName, $id), default => null, }; if ($op === 'delete') { - $this->evictFromIdentityMap($entity, $collection); + $this->evictFromIdentityMap($entity, $scope); } else { - $this->registerInIdentityMap($entity, $collection); + $this->registerInIdentityMap($entity, $scope); } } $this->reset(); } - private function insertEntity(object $entity, Collection $collection, string $tableName, string $id): void + private function insertEntity(object $entity, Scope $scope, string $tableName, string $id): void { $row = $this->entityFactory->extractColumns($entity); @@ -94,7 +92,7 @@ private function insertEntity(object $entity, Collection $collection, string $ta $this->tables[$tableName][] = $row; } - private function updateEntity(object $entity, Collection $collection, string $tableName, string $id): void + private function updateEntity(object $entity, Scope $scope, string $tableName, string $id): void { $idValue = $this->entityFactory->get($entity, $id); $row = $this->entityFactory->extractColumns($entity); @@ -130,11 +128,11 @@ private function deleteEntity(object $entity, string $tableName, string $id): vo } /** @param array $row */ - private function hydrateRow(array $row, Collection $collection): object|false + private function hydrateRow(array $row, Scope $scope): object|false { - $this->attachRelated($row, $collection); + $this->attachRelated($row, $scope); - $entities = $this->hydrator->hydrateAll($row, $collection); + $entities = $this->hydrator->hydrateAll($row, $scope); if ($entities === false) { return false; } @@ -150,15 +148,15 @@ private function hydrateRow(array $row, Collection $collection): object|false } /** @param array $parentRow */ - private function attachRelated(array &$parentRow, Collection $collection): void + private function attachRelated(array &$parentRow, Scope $scope): void { - foreach ($collection->with as $child) { + foreach ($scope->with as $child) { $this->attachChild($parentRow, $child); } } /** @param array $parentRow */ - private function attachChild(array &$parentRow, Collection $child): void + private function attachChild(array &$parentRow, Scope $child): void { $childName = (string) $child->name; $refValue = $parentRow[$this->style->remoteIdentifier($childName)] ?? null; diff --git a/tests/CollectionIteratorTest.php b/tests/ScopeIteratorTest.php similarity index 52% rename from tests/CollectionIteratorTest.php rename to tests/ScopeIteratorTest.php index cf94560..d99f7da 100644 --- a/tests/CollectionIteratorTest.php +++ b/tests/ScopeIteratorTest.php @@ -7,34 +7,33 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -use Respect\Data\Collections\Collection; use function iterator_to_array; -#[CoversClass(CollectionIterator::class)] -class CollectionIteratorTest extends TestCase +#[CoversClass(ScopeIterator::class)] +class ScopeIteratorTest extends TestCase { #[Test] public function staticBuilderShouldCreateRecursiveIterator(): void { $this->assertInstanceOf( 'RecursiveIteratorIterator', - CollectionIterator::recursive(Collection::foo()), + ScopeIterator::recursive(Scope::foo()), ); } #[Test] - public function constructingShouldAcceptCollectionsOrArrays(): void + public function constructingShouldAcceptScopesOrArrays(): void { - $iterator = new CollectionIterator(Collection::foo()); - $iterator2 = new CollectionIterator([Collection::foo()]); + $iterator = new ScopeIterator(Scope::foo()); + $iterator2 = new ScopeIterator([Scope::foo()]); $this->assertEquals($iterator, $iterator2); } #[Test] public function keyShouldTrackNameCounts(): void { - $i = new CollectionIterator(Collection::foo()); + $i = new ScopeIterator(Scope::foo()); $this->assertEquals('foo', $i->key()); $this->assertEquals('foo2', $i->key()); $this->assertEquals('foo3', $i->key()); @@ -43,32 +42,32 @@ public function keyShouldTrackNameCounts(): void #[Test] public function hasChildrenConsiderEmpties(): void { - $coll = Collection::foo(); - $iterator = new CollectionIterator($coll); + $coll = Scope::foo(); + $iterator = new ScopeIterator($coll); $this->assertFalse($iterator->hasChildren()); } #[Test] - public function hasChildrenUseCollectionChildren(): void + public function hasChildrenUseScopeChildren(): void { - $coll = Collection::foo([Collection::bar()]); - $iterator = new CollectionIterator($coll); + $coll = Scope::foo([Scope::bar()]); + $iterator = new ScopeIterator($coll); $this->assertTrue($iterator->hasChildren()); } #[Test] public function getChildrenConsiderEmpties(): void { - $coll = Collection::foo(); - $iterator = new CollectionIterator($coll); - $this->assertEquals(new CollectionIterator(), $iterator->getChildren()); + $coll = Scope::foo(); + $iterator = new ScopeIterator($coll); + $this->assertEquals(new ScopeIterator(), $iterator->getChildren()); } #[Test] - public function getChildrenUseCollectionWith(): void + public function getChildrenUseScopeWith(): void { - $coll = Collection::foo([Collection::bar(), Collection::baz()]); - $items = iterator_to_array(CollectionIterator::recursive($coll)); + $coll = Scope::foo([Scope::bar(), Scope::baz()]); + $items = iterator_to_array(ScopeIterator::recursive($coll)); $names = []; foreach ($items as $item) { $names[] = $item->name; @@ -81,8 +80,8 @@ public function getChildrenUseCollectionWith(): void #[Test] public function recursiveTraversalShouldVisitNestedChildren(): void { - $coll = Collection::foo([Collection::bar([Collection::baz()])]); - $items = iterator_to_array(CollectionIterator::recursive($coll)); + $coll = Scope::foo([Scope::bar([Scope::baz()])]); + $items = iterator_to_array(ScopeIterator::recursive($coll)); $this->assertCount(3, $items); } } diff --git a/tests/Collections/CollectionTest.php b/tests/Scopes/ScopeTest.php similarity index 71% rename from tests/Collections/CollectionTest.php rename to tests/Scopes/ScopeTest.php index f007b8e..e4d7ac7 100644 --- a/tests/Collections/CollectionTest.php +++ b/tests/Scopes/ScopeTest.php @@ -2,51 +2,48 @@ declare(strict_types=1); -namespace Respect\Data\Collections; +namespace Respect\Data; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; -use Respect\Data\EntityFactory; use Respect\Data\Hydrators\Nested; -use Respect\Data\InMemoryMapper; -use Respect\Data\Stubs; use function count; -#[CoversClass(Collection::class)] -class CollectionTest extends TestCase +#[CoversClass(Scope::class)] +class ScopeTest extends TestCase { #[Test] - public function collectionCanBeCreatedStaticallyWithJustName(): void + public function scopeCanBeCreatedStaticallyWithJustName(): void { - $coll = Collection::fooBarName(); - $this->assertInstanceOf('Respect\Data\Collections\Collection', $coll); + $coll = Scope::fooBarName(); + $this->assertInstanceOf(Scope::class, $coll); } #[Test] - public function collectionCanBeCreatedStaticallyWithChildren(): void + public function scopeCanBeCreatedStaticallyWithChildren(): void { - $children1 = Collection::bar(); - $children2 = Collection::baz(); - $coll = Collection::foo([$children1, $children2]); - $this->assertInstanceOf('Respect\Data\Collections\Collection', $coll); + $children1 = Scope::bar(); + $children2 = Scope::baz(); + $coll = Scope::foo([$children1, $children2]); + $this->assertInstanceOf(Scope::class, $coll); $this->assertTrue($coll->hasChildren); $this->assertEquals(2, count($coll->with)); } #[Test] - public function collectionCanBeCreatedStaticallyWithFilter(): void + public function scopeCanBeCreatedStaticallyWithFilter(): void { - $coll = Collection::fooBar(filter: 42); - $this->assertInstanceOf('Respect\Data\Collections\Collection', $coll); + $coll = Scope::fooBar(filter: 42); + $this->assertInstanceOf(Scope::class, $coll); $this->assertEquals(42, $coll->filter); } #[Test] public function objectConstructorShouldSetObjectAttributes(): void { - $coll = new Collection('some_irrelevant_name'); + $coll = new Scope('some_irrelevant_name'); $this->assertNull( $coll->filter, 'Default filter should be null', @@ -57,18 +54,18 @@ public function objectConstructorShouldSetObjectAttributes(): void #[Test] public function objectConstructorWithFilterShouldSetIt(): void { - $coll = new Collection('some_irrelevant_name', filter: 123); + $coll = new Scope('some_irrelevant_name', filter: 123); $this->assertEquals(123, $coll->filter); } #[Test] public function constructorCompositionShouldSetChildrenAndParent(): void { - $child = new Collection('bar_child'); - $coll = new Collection('foo_collection', [$child]); + $child = new Scope('bar_child'); + $coll = new Scope('foo_scope', [$child]); $children = $coll->with; $this->assertCount(1, $children); - $this->assertInstanceOf(Collection::class, $children[0]); + $this->assertInstanceOf(Scope::class, $children[0]); $this->assertEquals(false, $children[0]->required); $this->assertEquals($coll->name, $children[0]->parent?->name); } @@ -76,56 +73,28 @@ public function constructorCompositionShouldSetChildrenAndParent(): void #[Test] public function childrenShouldMakeHasChildrenTrue(): void { - $coll = Collection::foo([Collection::thisIsAChildren()]); + $coll = Scope::foo([Scope::thisIsAChildren()]); $this->assertTrue($coll->hasChildren); } #[Test] public function noChildrenShouldMakeHasChildrenFalse(): void { - $coll = Collection::foo(); + $coll = Scope::foo(); $this->assertFalse($coll->hasChildren); } #[Test] public function getParentShouldReturnNullWhenNoParent(): void { - $coll = new Collection('foo'); + $coll = new Scope('foo'); $this->assertNull($coll->parent); } - #[Test] - public function deriveCreatesNewCollectionWithMergedWith(): void - { - $original = Collection::foo([Collection::bar()]); - $derived = $original->derive(with: [Collection::baz()]); - - $this->assertNotSame($original, $derived); - $this->assertEquals('foo', $derived->name); - $this->assertCount(2, $derived->with); - $this->assertEquals('bar', $derived->with[0]->name); - $this->assertEquals('baz', $derived->with[1]->name); - $this->assertCount(1, $original->with, 'Original should be unchanged'); - } - - #[Test] - public function derivePreservesFilterAndOverridesWhenProvided(): void - { - $original = new Collection('foo', filter: 42, required: true); - - $sameFilter = $original->derive(); - $this->assertEquals(42, $sameFilter->filter); - $this->assertTrue($sameFilter->required); - - $newFilter = $original->derive(filter: 99, required: false); - $this->assertEquals(99, $newFilter->filter); - $this->assertFalse($newFilter->required); - } - #[Test] public function cloneDeepClonesChildrenAndClearsParent(): void { - $parent = Collection::foo([Collection::bar([Collection::baz()])]); + $parent = Scope::foo([Scope::bar([Scope::baz()])]); $clone = clone $parent; $this->assertNull($clone->parent); diff --git a/tests/Styles/AbstractStyleTest.php b/tests/Styles/AbstractStyleTest.php index 059b98a..072bfd6 100644 --- a/tests/Styles/AbstractStyleTest.php +++ b/tests/Styles/AbstractStyleTest.php @@ -22,11 +22,6 @@ public function styledProperty(string $name): string return $name; } - public function realName(string $name): string - { - return $name; - } - public function realProperty(string $name): string { return $name; @@ -57,20 +52,10 @@ public function isRemoteIdentifier(string $name): bool return false; } - public function remoteFromIdentifier(string $name): string|null - { - return null; - } - public function relationProperty(string $field): string|null { return null; } - - public function isRelationProperty(string $name): bool - { - return false; - } }; } diff --git a/tests/Styles/PluralTest.php b/tests/Styles/PluralTest.php index 2c3d4e1..f610b9b 100644 --- a/tests/Styles/PluralTest.php +++ b/tests/Styles/PluralTest.php @@ -67,7 +67,6 @@ public static function foreignProvider(): array public function testTableAndEntitiesMethods(string $table, string $entity): void { $this->assertEquals($entity, $this->style->styledName($table)); - $this->assertEquals($table, $this->style->realName($entity)); $this->assertEquals('id', $this->style->identifier($table)); } @@ -77,7 +76,6 @@ public function testColumnsAndPropertiesMethods(string $column): void $this->assertEquals($column, $this->style->styledProperty($column)); $this->assertEquals($column, $this->style->realProperty($column)); $this->assertFalse($this->style->isRemoteIdentifier($column)); - $this->assertNull($this->style->remoteFromIdentifier($column)); } #[DataProvider('manyToManyTableProvider')] @@ -90,7 +88,6 @@ public function testTableFromLeftRightTable(string $left, string $right, string public function testForeign(string $table, string $foreign): void { $this->assertTrue($this->style->isRemoteIdentifier($foreign)); - $this->assertEquals($table, $this->style->remoteFromIdentifier($foreign)); $this->assertEquals($foreign, $this->style->remoteIdentifier($table)); } } diff --git a/tests/Styles/StandardTest.php b/tests/Styles/StandardTest.php index 369dc26..fa3f912 100644 --- a/tests/Styles/StandardTest.php +++ b/tests/Styles/StandardTest.php @@ -67,7 +67,6 @@ public static function foreignProvider(): array public function testTableAndEntitiesMethods(string $table, string $entity): void { $this->assertEquals($entity, $this->style->styledName($table)); - $this->assertEquals($table, $this->style->realName($entity)); $this->assertEquals('id', $this->style->identifier($table)); } @@ -77,7 +76,6 @@ public function testColumnsAndPropertiesMethods(string $name): void $this->assertEquals($name, $this->style->styledProperty($name)); $this->assertEquals($name, $this->style->realProperty($name)); $this->assertFalse($this->style->isRemoteIdentifier($name)); - $this->assertNull($this->style->remoteFromIdentifier($name)); } #[DataProvider('manyToManyTableProvider')] @@ -90,7 +88,6 @@ public function testTableFromLeftRightTable(string $left, string $right, string public function testForeign(string $table, string $foreign): void { $this->assertTrue($this->style->isRemoteIdentifier($foreign)); - $this->assertEquals($table, $this->style->remoteFromIdentifier($foreign)); $this->assertEquals($foreign, $this->style->remoteIdentifier($table)); } @@ -100,17 +97,4 @@ public function testRelationProperty(string $table, string $foreign): void $this->assertEquals($table, $this->style->relationProperty($foreign)); $this->assertNull($this->style->relationProperty($table)); } - - #[DataProvider('foreignProvider')] - public function testIsRelationProperty(string $table, string $foreign): void - { - $this->assertTrue($this->style->isRelationProperty($table)); - $this->assertFalse($this->style->isRelationProperty($foreign)); - } - - #[DataProvider('foreignProvider')] - public function testForeignKeyIsNotRelationProperty(string $table, string $foreign): void - { - $this->assertFalse($this->style->isRelationProperty($foreign)); - } }