diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 25a1e80..4c490c6 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 5 + level: 9 paths: - src/ - tests/ diff --git a/src/AbstractMapper.php b/src/AbstractMapper.php index 923fc49..f39d417 100644 --- a/src/AbstractMapper.php +++ b/src/AbstractMapper.php @@ -7,16 +7,22 @@ use Respect\Data\Collections\Collection; use SplObjectStorage; +use function assert; + abstract class AbstractMapper { protected Styles\Stylable|null $style = null; + /** @var SplObjectStorage */ protected SplObjectStorage $new; + /** @var SplObjectStorage */ protected SplObjectStorage $tracked; + /** @var SplObjectStorage */ protected SplObjectStorage $changed; + /** @var SplObjectStorage */ protected SplObjectStorage $removed; /** @var array */ @@ -33,7 +39,7 @@ public function __construct() public function getStyle(): Styles\Stylable { if ($this->style === null) { - $this->setStyle(new Styles\Standard()); + $this->style = new Styles\Standard(); } return $this->style; @@ -125,6 +131,7 @@ public function __isset(string $alias): bool public function __set(string $alias, mixed $collection): void { + assert($collection instanceof Collection); $this->registerCollection($alias, $collection); } diff --git a/src/CollectionIterator.php b/src/CollectionIterator.php index ed92497..184df61 100644 --- a/src/CollectionIterator.php +++ b/src/CollectionIterator.php @@ -6,9 +6,11 @@ use RecursiveArrayIterator; use RecursiveIteratorIterator; +use Respect\Data\Collections\Collection; use function is_array; +/** @extends RecursiveArrayIterator */ final class CollectionIterator extends RecursiveArrayIterator { /** @var array */ @@ -19,17 +21,21 @@ public function __construct(mixed $target = [], array &$namesCounts = []) { $this->namesCounts = &$namesCounts; - parent::__construct(is_array($target) ? $target : [$target]); + /** @var array $items */ + $items = is_array($target) ? $target : [$target]; + + parent::__construct($items); } + /** @return RecursiveIteratorIterator */ public static function recursive(mixed $target): RecursiveIteratorIterator { - return new RecursiveIteratorIterator(new static($target), 1); + return new RecursiveIteratorIterator(new self($target), 1); } - public function key(): string|int|null + public function key(): string { - $name = $this->current()->getName(); + $name = $this->current()->getName() ?? ''; if (isset($this->namesCounts[$name])) { return $name . ++$this->namesCounts[$name]; diff --git a/src/Collections/Collection.php b/src/Collections/Collection.php index 8057ec1..e1f9859 100644 --- a/src/Collections/Collection.php +++ b/src/Collections/Collection.php @@ -8,6 +8,9 @@ use Respect\Data\AbstractMapper; use RuntimeException; +use function assert; + +/** @implements ArrayAccess */ class Collection implements ArrayAccess { protected bool $required = true; @@ -163,7 +166,9 @@ public function offsetExists(mixed $offset): bool public function offsetGet(mixed $condition): mixed { - $this->last->condition = $condition; + if ($this->last !== null) { + $this->last->condition = $condition; + } return $this; } @@ -211,7 +216,10 @@ public function setRequired(bool $required): void public function stack(Collection $collection): static { - $this->last->setNext($collection); + if ($this->last !== null) { + $this->last->setNext($collection); + } + $this->last = $collection; return $this; @@ -228,7 +236,10 @@ public static function __callStatic(string $name, array $children): static public function __get(string $name): static { if (isset($this->mapper) && isset($this->mapper->$name)) { - return $this->stack(clone $this->mapper->$name); + assert($this->mapper->$name instanceof Collection); + $cloned = clone $this->mapper->$name; + + return $this->stack($cloned); } return $this->stack(new self($name)); diff --git a/src/Styles/AbstractStyle.php b/src/Styles/AbstractStyle.php index 676fe4e..918a6f9 100644 --- a/src/Styles/AbstractStyle.php +++ b/src/Styles/AbstractStyle.php @@ -14,14 +14,14 @@ abstract class AbstractStyle implements Stylable { protected function camelCaseToSeparator(string $name, string $separator = '_'): string { - return preg_replace('/(?<=[a-z])([A-Z])/', $separator . '$1', $name); + return (string) preg_replace('/(?<=[a-z])([A-Z])/', $separator . '$1', $name); } protected function separatorToCamelCase(string $name, string $separator = '_'): string { $separator = preg_quote($separator, '/'); - return preg_replace_callback( + return (string) preg_replace_callback( '/' . $separator . '([a-zA-Z])/', static fn($m) => strtoupper($m[1]), $name, @@ -36,7 +36,7 @@ protected function pluralToSingular(string $name): string ]; foreach ($replacements as $key => $value) { if (preg_match($key, $name)) { - return preg_replace($key, $value, $name); + return (string) preg_replace($key, $value, $name); } } @@ -51,7 +51,7 @@ protected function singularToPlural(string $name): string ]; foreach ($replacements as $key => $value) { if (preg_match($key, $name)) { - return preg_replace($key, $value, $name); + return (string) preg_replace($key, $value, $name); } } diff --git a/tests/AbstractMapperTest.php b/tests/AbstractMapperTest.php index eded59d..29ea259 100644 --- a/tests/AbstractMapperTest.php +++ b/tests/AbstractMapperTest.php @@ -43,7 +43,9 @@ public function registerCollectionShouldAddCollectionToPool(): void $ref = new ReflectionObject($this->mapper); $prop = $ref->getProperty('collections'); - $this->assertContains($coll, $prop->getValue($this->mapper)); + /** @var array $collections */ + $collections = $prop->getValue($this->mapper); + $this->assertContains($coll, $collections); $this->assertEquals($coll, $this->mapper->my_alias); } @@ -56,7 +58,9 @@ public function magicSetterShouldAddCollectionToPool(): void $ref = new ReflectionObject($this->mapper); $prop = $ref->getProperty('collections'); - $this->assertContains($coll, $prop->getValue($this->mapper)); + /** @var array $collections */ + $collections = $prop->getValue($this->mapper); + $this->assertContains($coll, $collections); $this->assertEquals($coll, $this->mapper->my_alias); } diff --git a/tests/Collections/CollectionTest.php b/tests/Collections/CollectionTest.php index 8b0e08c..a70d4c9 100644 --- a/tests/Collections/CollectionTest.php +++ b/tests/Collections/CollectionTest.php @@ -127,7 +127,9 @@ public function dynamicMethodCallShouldAcceptChildren(): void Collection::children(), Collection::here(), ); - $this->assertEquals(3, count($coll->getNext()->getChildren())); + $next = $coll->getNext(); + $this->assertNotNull($next); + $this->assertEquals(3, count($next->getChildren())); } #[Test] @@ -135,8 +137,9 @@ public function addChildShouldSetChildrenObjectProperties(): void { $coll = new Collection('foo_collection'); $coll->addChild(new Collection('bar_child')); - $child = $coll->getChildren(); - $child = reset($child); + $children = $coll->getChildren(); + $child = reset($children); + $this->assertInstanceOf(Collection::class, $child); $this->assertEquals(false, $child->isRequired()); $this->assertEquals($coll->getName(), $child->getParentName()); } diff --git a/tests/InMemoryMapper.php b/tests/InMemoryMapper.php index e777032..d16190a 100644 --- a/tests/InMemoryMapper.php +++ b/tests/InMemoryMapper.php @@ -154,10 +154,13 @@ public function flush(): void $pk = $this->getStyle()->identifier($tableName); $pkValue = $entity->{$pk}; - foreach ($this->tables[$tableName] as $index => $existing) { + $rows = $this->tables[$tableName]; + foreach ($rows as $index => $existing) { if (isset($existing[$pk]) && $existing[$pk] == $pkValue) { - unset($this->tables[$tableName][$index]); - $this->tables[$tableName] = array_values($this->tables[$tableName]); + unset($rows[$index]); + /** @var list> $reindexed */ + $reindexed = array_values($rows); + $this->tables[$tableName] = $reindexed; break; }