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
66 changes: 43 additions & 23 deletions src/Factories/ImmutableFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -21,9 +22,7 @@
{
public Generator $faker;

/**
* @param array<string, mixed> $state
*/
/** @param array<array-key, mixed> $state */
final public function __construct(
?Generator $faker = null,
public array $state = [],
Expand All @@ -33,31 +32,27 @@ final public function __construct(
}

/**
* @param array<string, mixed> $state
* @param array<array-key, mixed> $state
* @return static<TClass>
*/
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<TClass>
*/
/** @return static<TClass> */
public function times(int $count): static
{
return new static($this->faker, $this->state, $count);
}

/**
* @param array<string, mixed> $attributes
* @param array<array-key, mixed> $attributes
* @return TClass
*/
abstract protected function instance(array $attributes): object;
abstract protected function instance(array $attributes): mixed;

/**
* @return array<string, mixed>
*/
/** @return array<array-key, mixed> */
abstract public function definition(): array;

private function resolveValue(mixed $value): mixed
Expand All @@ -77,12 +72,12 @@ private function resolveValue(mixed $value): mixed
}

/**
* @param array<string, mixed> $attributes
* @return array<string, mixed>
* @param array<array-key, mixed> $attributes
* @return array<array-key, mixed>
*/
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,
Expand All @@ -91,7 +86,7 @@ public function raw(array $attributes = []): array

/**
* @param array<string, mixed> $attributes
* @return array<int, array<string, mixed>>
* @return array<int, array<array-key, mixed>>
*/
public function rawMany(array $attributes = []): array
{
Expand All @@ -100,24 +95,24 @@ public function rawMany(array $attributes = []): array

/**
* @param array<string, mixed> $attributes
* @return Collection<int, array<string, mixed>>
* @return Collection<int, array<array-key, mixed>>
*/
public function rawCollection(array $attributes = []): Collection
{
return Collection::times($this->count, fn(): array => $this->raw($attributes));
}

/**
* @param array<string, mixed> $attributes
* @param array<array-key, mixed> $attributes
* @return TClass
*/
public function makeOne(array $attributes = []): mixed
public function makeOne(array $attributes = []): object
{
return $this->instance($this->raw($attributes));
}

/**
* @param array<string, mixed> $attributes
* @param array<array-key, mixed> $attributes
* @return array<TClass>
*/
public function makeMany(array $attributes = []): array
Expand All @@ -126,11 +121,36 @@ public function makeMany(array $attributes = []): array
}

/**
* @param array<string, mixed> $attributes
* @param array<array-key, mixed> $attributes
* @return Collection<int, TClass>
*/
public function makeCollection(array $attributes = []): Collection
{
return Collection::times($this->count, fn(): mixed => $this->makeOne($attributes));
}

/**
* @param array<array-key, mixed> $base
* @param array<array-key, mixed> ...$replacements
* @return array<array-key, mixed>
*/
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);
}
}
41 changes: 41 additions & 0 deletions src/Factories/ImmutableFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down
Loading