diff --git a/src/Factories/ImmutableFactory.php b/src/Factories/ImmutableFactory.php index 4b373bb..799c114 100644 --- a/src/Factories/ImmutableFactory.php +++ b/src/Factories/ImmutableFactory.php @@ -8,8 +8,9 @@ use Faker\Generator; use Illuminate\Support\Collection; +use function array_is_list; use function array_map; -use function array_replace_recursive; +use function is_array; use function is_iterable; use function iterator_to_array; @@ -21,9 +22,7 @@ { public Generator $faker; - /** - * @param array $state - */ + /** @param array $state */ final public function __construct( ?Generator $faker = null, public array $state = [], @@ -33,31 +32,27 @@ final public function __construct( } /** - * @param array $state + * @param array $state * @return static */ public function state(array $state): static { - return new static($this->faker, array_replace_recursive($this->state, $state), $this->count); + return new static($this->faker, self::merge($this->state, $state), $this->count); } - /** - * @return static - */ + /** @return static */ public function times(int $count): static { return new static($this->faker, $this->state, $count); } /** - * @param array $attributes + * @param array $attributes * @return TClass */ - abstract protected function instance(array $attributes): object; + abstract protected function instance(array $attributes): mixed; - /** - * @return array - */ + /** @return array */ abstract public function definition(): array; private function resolveValue(mixed $value): mixed @@ -77,12 +72,12 @@ private function resolveValue(mixed $value): mixed } /** - * @param array $attributes - * @return array + * @param array $attributes + * @return array */ public function raw(array $attributes = []): array { - return array_map($this->resolveValue(...), array_replace_recursive( + return array_map($this->resolveValue(...), self::merge( $this->definition(), $this->state, $attributes, @@ -91,7 +86,7 @@ public function raw(array $attributes = []): array /** * @param array $attributes - * @return array> + * @return array> */ public function rawMany(array $attributes = []): array { @@ -100,7 +95,7 @@ public function rawMany(array $attributes = []): array /** * @param array $attributes - * @return Collection> + * @return Collection> */ public function rawCollection(array $attributes = []): Collection { @@ -108,16 +103,16 @@ public function rawCollection(array $attributes = []): Collection } /** - * @param array $attributes + * @param array $attributes * @return TClass */ - public function makeOne(array $attributes = []): mixed + public function makeOne(array $attributes = []): object { return $this->instance($this->raw($attributes)); } /** - * @param array $attributes + * @param array $attributes * @return array */ public function makeMany(array $attributes = []): array @@ -126,11 +121,36 @@ public function makeMany(array $attributes = []): array } /** - * @param array $attributes + * @param array $attributes * @return Collection */ public function makeCollection(array $attributes = []): Collection { return Collection::times($this->count, fn(): mixed => $this->makeOne($attributes)); } + + /** + * @param array $base + * @param array ...$replacements + * @return array + */ + private static function merge(array $base, array ...$replacements): array + { + foreach ($replacements as $replacement) { + foreach ($replacement as $key => $value) { + $current = $base[$key] ?? null; + $base[$key] = match (false) { + self::isMap($value), self::isMap($current) => $value, + default => self::merge($current, $value), + }; + } + } + + return $base; + } + + private static function isMap(mixed $value): bool + { + return is_array($value) && !array_is_list($value); + } } diff --git a/src/Factories/ImmutableFactoryTest.php b/src/Factories/ImmutableFactoryTest.php index 1bed1f7..e86b581 100644 --- a/src/Factories/ImmutableFactoryTest.php +++ b/src/Factories/ImmutableFactoryTest.php @@ -336,6 +336,47 @@ public function definition(): array ); } + #[Test] + public function itMergesMapsRecursivelyButReplacesLists(): void + { + $factory = new readonly class extends ImmutableFactory { + protected function instance(array $attributes): object + { + return (object) $attributes; + } + + public function definition(): array + { + return [ + 'list' => ['default'], + 'map' => [ + 'first' => 'First value', + 'last' => 'Last value', + ], + ]; + } + }; + + $raw = $factory + ->state(['list' => []]) + ->raw(['map' => [ + 'last' => 'Updated value', + 'added' => 'Added value', + ]]); + + $this->assertSame( + [ + 'list' => [], + 'map' => [ + 'first' => 'First value', + 'last' => 'Updated value', + 'added' => 'Added value', + ], + ], + $raw, + ); + } + public static function nestedFactories(): iterable { yield [