Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce distribute() method #826

Merged
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
33 changes: 33 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,39 @@ Sequences help to create different objects in one call:
]
)->create();

Distribute values over a collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you have a collection of values that you want to distribute over a collection, you can use the ``distribute()`` method:

::

// let's say we have 2 categories...
$categories = CategoryFactory::createSequence(
[
['name' => 'category 1'],
['name' => 'category 2'],
]
);

// ...that we want to "distribute" over 2 posts
$posts = PostFactory::new()
->sequence(
[
['name' => 'post 1'],
['name' => 'post 2'],
]
)

// "post 1" will have "category 1" and "post 2" will have "category 2"
->distribute('category', $categories)

// you can even chain "distribute()" methods:
// first post is published today, second post is published tomorrow
->distribute('publishedAt', [new \DateTimeImmutable('today'), new \DateTimeImmutable('tomorrow')])

->create();

Faker
~~~~~

Expand Down
12 changes: 12 additions & 0 deletions src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ final public function sequence(iterable|callable $sequence): FactoryCollection
return FactoryCollection::sequence($this, $sequence); // @phpstan-ignore return.type
}

/**
* @param list<mixed> $values
*
* @return FactoryCollection<T, static>
*/
final public function distribute(string $field, array $values): FactoryCollection
{
return $this->sequence(
\array_map(fn($value) => [$field => $value], $values)
);
}

/**
* @phpstan-param Attributes $attributes
*
Expand Down
23 changes: 23 additions & 0 deletions src/FactoryCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,29 @@ function(Factory $f) {
);
}

/**
* @param list<mixed> $values
*
* @return self<T, TFactory>
*/
public function distribute(string $field, array $values): self
{
$factories = $this->all();

if (\count($factories) !== \count($values)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is perfect for a start.

I wonder if we could allow some options there in the future

A couple of ideas:

Cycle

categories : A, B
posts: P1, P2, P3

P1: A
P2: B
P3: A

All

categories : A, B, C
posts: P1, P2

P1: A, C
P2: B

Ignore missing

categories : A, B
posts: P1, P2, P3

P1: A
P2: B
P3: 

Copy link
Member Author

@nikophil nikophil Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we can totally pass a third parameter $strategy or something like that! Let's see what kind of use case will be needed

throw new \InvalidArgumentException('Number of values must match number of factories.');
}

return new self(
$this->factory,
static fn() => \array_map(
static fn(Factory $f, $value) => $f->with([$field => $value]),
$factories,
$values
)
);
}

public function getIterator(): \Traversable
{
return new \ArrayIterator($this->all());
Expand Down
43 changes: 43 additions & 0 deletions tests/Fixture/Factories/SimpleObjectFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

/*
* This file is part of the zenstruck/foundry package.
*
* (c) Kevin Bond <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Zenstruck\Foundry\Tests\Fixture\Factories;

use Zenstruck\Foundry\ObjectFactory;
use Zenstruck\Foundry\Tests\Fixture\SimpleObject;

/**
* @author Nicolas PHILIPPE <[email protected]>
*
* @extends ObjectFactory<SimpleObject>
*/
final class SimpleObjectFactory extends ObjectFactory
{
public static function class(): string
{
return SimpleObject::class;
}

public function withProps(string $prop1, ?string $prop2 = null): static
{
return $this->with([
'prop1' => $prop1,
'prop2' => $prop2,
]);
}

protected function defaults(): array
{
return [
'prop1' => self::faker()->word(),
];
}
}
24 changes: 24 additions & 0 deletions tests/Fixture/SimpleObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/*
* This file is part of the zenstruck/foundry package.
*
* (c) Kevin Bond <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Zenstruck\Foundry\Tests\Fixture;

/**
* @author Nicolas PHILIPPE <[email protected]>
*/
final class SimpleObject
{
public function __construct(
public string $prop1,
public ?string $prop2 = null,
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ final class FakerSeedSetFromLegacyConfigKernelTest extends KernelTestCase
{
use Factories, FakerTestTrait, ResetDatabase;

/**
* @test
*/
#[Test]
#[IgnoreDeprecations]
public function faker_seed_by_configuration_is_deprecated(): void
Expand Down
38 changes: 38 additions & 0 deletions tests/Unit/ObjectFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
use Zenstruck\Foundry\Tests\Fixture\Factories\Object2Factory;
use Zenstruck\Foundry\Tests\Fixture\Factories\SimpleObjectFactory;
use Zenstruck\Foundry\Tests\Fixture\Object1;

use function Zenstruck\Foundry\factory;
Expand Down Expand Up @@ -434,6 +435,43 @@ public function can_use_sequence_with_associative_array(): void
);
}

/**
* @test
*/
#[Test]
public function distribute(): void
{
$objects = SimpleObjectFactory::new()->distribute('prop1', ['foo', 'bar'])->create();

self::assertCount(2, $objects);
self::assertSame('foo', $objects[0]->prop1);
self::assertSame('bar', $objects[1]->prop1);
}

/**
* @test
*/
#[Test]
public function distribute_on_factory_collection(): void
{
$objects = SimpleObjectFactory::new()->many(2)->distribute('prop1', ['foo', 'bar'])->create();

self::assertCount(2, $objects);
self::assertSame('foo', $objects[0]->prop1);
self::assertSame('bar', $objects[1]->prop1);
}

/**
* @test
*/
#[Test]
public function providing_invalid_values_number_to_distribute_throws(): void
{
$this->expectException(\InvalidArgumentException::class);

SimpleObjectFactory::new()->many(2)->distribute('prop1', ['foo']);
}

/**
* @test
*/
Expand Down