Git Product home page Git Product logo

foundry's Introduction

Foundry

CI Status Code Coverage Latest Version Downloads

Foundry makes creating fixtures data fun again, via an expressive, auto-completable, on-demand fixtures system with Symfony and Doctrine:

$post = PostFactory::new() // Create the factory for Post objects
    ->published()          // Make the post in a "published" state
    ->create([             // create & persist the Post object
        'slug' => 'post-a' // This Post object only requires the slug field - all other fields are random data
    ])
;

The factories can be used inside DoctrineFixturesBundle to load fixtures or inside your tests, where it has even more features.

Foundry supports doctrine/orm (with doctrine/doctrine-bundle), doctrine/mongodb-odm (with doctrine/mongodb-odm-bundle) or a combination of these.

Want to watch a screencast ๐ŸŽฅ about it? Check out https://symfonycasts.com/foundry

Read the Documentation

How to contribute

Running the Test Suite

The test suite of this library needs one or more databases, then it comes with a docker compose configuration.

Note

Docker and PHP installed locally (with mysql, pgsql & mongodb extensions) is required.

You can start the containers and run the test suite:

# start the container
$ docker compose up --detach

# install dependencies
$ composer update

# run test suite with all available permutations
$ composer test

# run only one permutation
$ vendor/bin/phpunit

# run test suite with dama/doctrine-test-bundle
$ vendor/bin/phpunit -c phpunit.dama.xml.dist

# run test suite with postgreSQL instead of MySQL
$ DATABASE_URL="postgresql://zenstruck:[email protected]:5433/zenstruck_foundry?serverVersion=15" vendor/bin/phpunit

Overriding the default configuration

You can override default environment variables by creating a .env.local file, to easily enable permutations:

# .env.local
DATABASE_URL="postgresql://zenstruck:[email protected]:5433/zenstruck_foundry?serverVersion=15"

# run test suite with postgreSQL
$ vendor/bin/phpunit

The .env.local file can also be used to override the port of the database containers, if it does not meet your local requirements. You'll also need to override docker compose configuration:

Here is an example to use MySQL on port 3308:

# docker-compose.override.yml
version: '3.9'

services:
    mysql:
        ports:
            - "3308:3306"
# .env.local
DATABASE_URL="mysql://root:[email protected]:3308/foundry_test?serverVersion=5.7.42"

Credit

The AAA style of testing was first introduced to me by Adam Wathan's excellent Test Driven Laravel Course. The inspiration for this libraries API comes from Laravel factories and christophrumpel/laravel-factories-reloaded.

foundry's People

Contributors

abeal-hottomali avatar benblub avatar bfoks avatar chris53897 avatar dependabot[bot] avatar domagoj-orioly avatar gazzatav avatar gnito-org avatar hypemc avatar jdreesen avatar jmsche avatar jrushlow avatar jschaedl avatar kbond avatar ker0x avatar micheh avatar mpiot avatar mryamous avatar ndench avatar nicolasne avatar nikophil avatar northblue333 avatar nyholm avatar oskarstark avatar pavelmaca avatar seb-jean avatar tavoniievez avatar weaverryan avatar wouterj avatar zairigimad avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

foundry's Issues

Using Stories multiple times in a test class

I'm using codeception and I'm trying to implement using a Story in a test class.
With my current configuration, each test method is wrapped in a transaction using a doctrine feature in codeception.
So I can isolate what is created for that test.

To that end, I have a couple tests that I'd like to call the same story in:

class TicketControllerCest
{
    use Factories;

    private int $testId;

    /**
     * @group ticket
     * @group index
     * Check to see that an internal user with proper permission has access to index lists.
     */
    public function indexWithInternalUserPermission(FunctionalTester $I)
    {
        //  first time this story would be loaded
        TicketStory::load();
        $I->loginAsInternalUser($I);

        $I->wantTo(sprintf('View ticket index list with internal user permission'));

        $I->seeIndexPage($I, 'Tickets', 'Tickets', 'Tickets', 'ticket');
        $I->seeListItems($I, 'Ticket');

        // fixes weird bug
        unset($_GET);

        $I->logoutInternal($I);
    }

    /**
     * @group ticket
     * @group index
     * Check to see that an internal admin user with proper permission has access to index lists.
     */
    public function indexWithInternalAdminUserPermission(FunctionalTester $I)
    {
        // second time this story would be loaded
        TicketStory::load();
        $I->loginAsInternalAdminUser($I);

        $I->wantTo(sprintf('View ticket index list with internal admin user permission'));

        $I->seeIndexPage($I, 'Tickets', 'Tickets', 'Tickets', 'ticket');
        $I->seeListItems($I, 'Ticket');

        // fixes weird bug
        unset($_GET);

        $I->logoutInternal($I);
    }

If I run the whole test class:

codecept functionalFixture Controller/TicketControllerCest

The first test passes, and the second test fails - there are no Ticket entities found.

If I just run either test individually, they both pass.

Is this how Stories work typically? Is there a way to force them to re-load in each instance?
Or is the transaction wrapping need to be done with Foundry for it to be aware that the Story should be reloaded?

Single flush per "user-land" create

Currently, the following code calls $em->flush() twice. First, when creating the nested CategoryFactory and a second time after persisting the PostFactory.

PostFactory::new()->create([
    'category' => CategoryFactory::new(),
]);

It would be better if the nested CategoryFactory was just persisted, then after the PostFactory is persisted, a single flush occurs capturing all changes.

Using resetDatabase trait - database not resetting

I've been implementing foundry with a functional test. I've gotten most of it to work except the ResetDatabase is not happening.

My functional test is set up as such:

class AssetControllerCest extends WebTestCase
{
    use ResetDatabase;
    use Factories;

    /**
     * Check to see that an internal user with proper permission has access to index lists.
     */
    public function indexWithInternalUserPermission(FunctionalTester $I)
    {
        // create 10 Assets
        AssetFactory::new()->createMany(20);
        $repository = AssetFactory::repository();
        $repository->assertCount(20);

And my GlobalStory is doing some initial population when it's loaded by the bootstrap.php such as:

final class GlobalStory extends Story
{
    private ManagerRegistry $managerRegistry;

    public function __construct(ManagerRegistry $managerRegistry)
    {
        $this->managerRegistry = $managerRegistry;
    }

    public function build(): void
    {
        // add all of the type entities we need for testing

        // set the generator to type none
        $entityManager = $this->managerRegistry->getManagerForClass(App::class);
        $metadata = $entityManager->getClassMetaData(App::class);
        $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);

        // add apps
        $appNames = [
            1 => 'Backend',
            2 => 'Frontend',
            3 => 'API',
            4 => 'QuoteTracker',
        ];

The first time I run the test, it runs and passes:
functional Controller/AssetControllerCest:indexWithInternalUserPermission

But on the second or additional run, I get an error which indicates it's re-running the GlobalStory setup, but failing because there are already entities in the database.

There was 1 error:

---------
1) AssetControllerCest: Index with internal user permission
 Test  tests/functional/Controller/AssetControllerCest.php:indexWithInternalUserPermission


  [Doctrine\DBAL\Exception\UniqueConstraintViolationException] An exception occurred while executing 'INSERT INTO app (name, created_at, updated_at, id) VALUES (?, ?, ?, ?)' with params ["Backend", "2020-10-09 14:47:05", "2020-10-09 14:47:05", 1]:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'app.PRIMARY'

Is there anything else that needs to be done to enable the resetDatabase?

In my functional suite setup, I didn't have Doctrine2 cleanup enabled, so I tried that, but that didn't make any difference.
I wasn't clear on whether that process would be compatible with Foundry.

Bug after updating to 1.7: Can't save relationship entity

I updated to 1.7 with Symfony 5.2.2. Before this update, all my tests were passing, however after this update, Foundry is unable to create an entity with a relationship.

My code looks like this:

$department1 = DepartmentFactory::new([
    'company' => $company1,
    'name' => 'Human Resources',
])->create();

$department2 = DepartmentFactory::new([
    'company' => $company2,
    'name' => 'Human Resources',
])->create();

Both $company1 and $company2 are saved in the database (both are also created via a CompanyFactory), however, I get this error:

  1. App\Tests\Functional\DepartmentResourceTest::test_fetching_departments_items_with_same_slug_authenticated
    Doctrine\ORM\ORMInvalidArgumentException: A new entity was found through the relationship 'App\Entity\Department#company' that was not configured to cascade persist operations for entity: App\Entity\Company@000000001f917ea1000000007f4b7464. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @manytoone(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'App\Entity\Company#__toString()' to get a clue.

I tried adding cascade={"persist"} to my entity, but this isn't necessary for my business logic and it yielded other errors. The surprising thing is that this wasn't happening before the update.

Any ideas?

Ideas for random with matching attributes?

I'm not sure if something like this makes sense or is even possible?

CommentFactory::new()->many(5) 
    ->create(function() {
        return ['post' => PostFactory::randomWithAttribute(['group' => 'Events')];
    })
;

What i'm looking for here is being able to use a random entity which matches certain attributes.
In my actual use-case I have a multi-tenant application where my entities are scoped to a 'tenant', so I may want to fetch something randomly only belonging to that tenant's application.

Extra unwanted entities created when testing

Hi All,

Probably being a moron but how do I get my factory to stop creating new entities when passing in the entity I want via the create function.

i.e

$reply = ReplyFactory::new()->create(['thread' => $thread, 'body' => 'Test Body']);

Still creates a new thread despite me passing the specific thread instance to the create function

   protected function getDefaults(): array
    {
        return [
            'body' => self::faker()->paragraph(3, true),
            'thread' => ThreadFactory::new()->create()
        ];
    }

I presumed (rightly or wrongly) that the thread would only create when newing up a reply if not overridden.

DI support for ModelFactory class

There is no Dependency Injection support when initializing a new ModelFactory with the new() method. I came upon this problem very early on when creating a User factory because I was unable to inject the PasswordEncoderInterface service into the factory. This meant I couldn't contain all logic involved in creating a new User inside the factory.

Any plans to address this?

Setting an auto increment private $id field

I'm trying to set the private $id of an entity when I create it it a factory.

The $id is private and auto generates:

class Facility implements ActivatableInterface, CommonEntityInterface, OrderableInterface
{
    /**
     * @ORM\Column(type="string", length=255)
     */
    private string $description = '';

    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private ?int $id = null;
...

I have the always force properties setting to true:

     # Whether or not to skip setters and force set object properties (public/private/protected) directly.
    always_force_properties: true

And in my GlobalStory I have some code to create the entities as such:

        // add facilities
        $facilityNames = [
            263 => 'Pittsburgh - 810 Parish',
            267 => 'Cleveland (151)',
        ];

        foreach ($facilityNames as $id => $facilityName) {
            $facility = FacilityFactory::new()->create([
                'force:id' => $id,
                'name' => $facilityName,
            ]);
        }

When I run a test, it seems like the GlobalState gets initialized, but when I check the database, the IDs are not being set correctly.

mysql> select * from facility;
+------------+------------+-------------+-----------+------------+-------------------------+-------+---------------------+---------------------+
| id         | region_id  | description | is_active | is_default | name                    | order | created_at          | updated_at          |
+------------+------------+-------------+-----------+------------+-------------------------+-------+---------------------+---------------------+
|  181983240 | 1174584734 | quibusdam   |         1 |          0 | Cleveland (151)         |    37 | 2020-10-07 18:19:06 | 2020-10-07 18:19:06 |
| 1055737034 |  164350616 | nihil       |         1 |          0 | Pittsburgh - 810 Parish |    52 | 2020-10-07 18:19:06 | 2020-10-07 18:19:06 |

Is there something else I'm missing?

Problem with many() function in Doctrine Relationships

I use this code in AppFixtures.php
PostFactory::new()->many(6)->create(['comments' => CommentFactory::new()->many(0, 10)]);

When I load fixtures with symfony console doctrine:fixtures:load command I have an error
Attempted to call an undefined method named "many" of class "App\Factory\PostFactory".

How it's possible to generate relationship between entities with foundry ?

Refactor Proxy auto-refresh feature

Ref: #13 (comment) and slack discussions with Ryan

  1. Disable by default
  2. Update example doc to use ->refresh()
  3. Global config option
  4. In docs:
    1. First show how ->refresh() works (no mention of auto-refreshing)
    2. Then show how to enable/disable auto-refreshing on your Proxy
    3. Then show global config option to auto-refresh by default
  5. Factory::withAutoRefresh()/withoutAutoRefresh() (?)

Improve foundry not booted exception

Something like: "Using in a test: is your Test case using the Factories trait? Using in a fixture: is ZenstruckFoundryBundle enabled for this environment?"

Fatal error "Return value of Zenstruck\Foundry\RepositoryProxy::random() must be an instance of Zenstruck\Foundry\Proxy

I have a User entity and UserFactory.
I'm using UserFactory::random() in my fixtures and it fails randomly, might be on first call or last.

Error message is:
Return value of Zenstruck\Foundry\RepositoryProxy::random() must be an instance of Zenstruck\Foundry\Proxy, instance of Proxies\__CG__\App\Entity\User returned .

I debug it a little bit and I can see that method \Zenstruck\Foundry\RepositoryProxy::findAll return array with objects where part of it is
App\Entity\User and other part is Proxies\__CG__\App\Entity\User

Quick work around was modifying \Zenstruck\Foundry\RepositoryProxy::proxyResult object check

if (\is_object($result)
            && (
                $this->getClassName() === \get_class($result)
                || is_subclass_of(get_class($result), $this->getClassName())
            )
        ) {

Troubleshooting section

  • header for each "type" of exception that is thrown by foundry
  • link to the proper type in the exception messages

Solutions:

RuntimeException: Model Factories with dependencies (Model Factory services) cannot be created before foundry is booted.

  1. If you're in a test, is your TestCase using the Factories trait?

[RuntimeException] Foundry is not yet booted.

I added some Foundry calls to an acceptance test, and when I run the test, I'm getting the error:


  [RuntimeException] Foundry is not yet booted. Using in a test: is your Test case using the Factories trait? Using in a fixture: is ZenstruckFoundryBundle enabled for this environment?

Looking at the test definition, I am using

<?php

namespace App\Tests\acceptance\Controller;

use App\Factory\CustomerFactory;
use App\Factory\TicketFactory;
use Exception;
use Zenstruck\Foundry\Test\Factories;
use AcceptanceTester;

class CustomerTicketControllerCest
{
    use Factories;

    private int $testId;

    /**
     * @throws Exception
     */
    public function _before(AcceptanceTester $I)
    {
        $this->testId = time();

    }

    public function _after(AcceptanceTester $I)
    {
        // fixes weird bug
        unset($_GET);
    }

    /**
     * @group customer
     * @group export
     * Check to see that an internal user with proper permission has access to index lists.
     */
    public function exportTicketsWithInternalUserPermission(AcceptanceTester $I)
    {
        // create one customer
        $customer = CustomerFactory::new()->create();

        // create five tickets for that customer
        TicketFactory::new(['customer' => $customer])->many(5)->create();

        $I->loginInternal($I);

        // visit the customer page
        $I->expectTo(sprintf('See the customer details'));
        $I->amOnRoute('internal_customer_show', ['id' => $customer->getId()]);
        $I->seeCurrentUrlEquals('/client/'.$customer->getId());

        $I->wantTo(sprintf('View customer ticket index list with internal user permission'));

        $I->seeLink('Tickets');
        $I->click(['xpath' => "//ul[contains(@class, 'nav-tabs')]//a[contains(.,'Tickets')]"]);

        $I->expectTo(sprintf('See the %s index list', 'Client Tickets'));
        $I->seeCurrentUrlEquals('/client/'.$customer->getId().'/ticket/index');
        $I->see('Tickets', '.breadcrumb-item');
        $I->seeResponseCodeIs(200);

        $I->wantTo(sprintf('See the export link and click it'));

        $I->seeLink('Export');
        $I->click(['xpath' => "//a[contains(.,'Export')]"]);

        $I->expectTo(sprintf('Export the client ticket list'));

        $I->seeResponseCodeIs(200);

        // see that the file exists
        $I->seeFileExists('/tests/_output/downloads/ClientTicketExport.xls');

        // fixes weird bug
        unset($_GET);

        $I->logoutInternal($I);
    }

}

Any suggestions on troubleshooting? This is working successfully in other tests.

Codeception support

I have never used Codeception so would like help from someone familiar with it to help with possible integration. This is a naive POC that could be a possible starting point.

Do the PHPUnit assertions available on the RepositoryProxy and Proxy objects (ie RepositoryProxy::assertCount()) work with Codeception?

TestState::addGlobalState not loading

As per the documentation, I added code to bootstrap.php to load the global state before my tests run.

<?php

use Symfony\Component\Filesystem\Filesystem;
use Zenstruck\Foundry\Test\TestState;
use App\Story\GlobalStory;

require \dirname(__DIR__).'/vendor/autoload.php';

(new Filesystem())->remove(\sys_get_temp_dir().'/zenstruck-foundry');

if (!\getenv('USE_FOUNDRY_BUNDLE')) {
    TestState::withoutBundle();
}

Zenstruck\Foundry\Test\TestState::addGlobalState(function() {
    GlobalStory::load();
    die('after load global story has run');
});

die('did not add global state');

When I run a functional test, it never adds the global state. I added the die statements to see if it hits that block, and I only ever see the "did not add global state" message. So it is running bootstrap but for some reason the add GlobalState function is not called.

Any tips on debugging this?

Additional info: I'm using codeception as my test framework.

Question about DTOs for Entity Constructors

I like the look of this bundle, but I have a question about the best way to use it in my projects:

I currently construct my Entities with DTOs (example for a User entity: public function __construct(NewUserCommand $command) and do not have setter methods.

Would it be better to pass the values through defaults and then generate the DTO in beforeInstantiate():

PostFactory::new()
    ->beforeInstantiate(function(array $attributes): array {
        $command = new NewUserCommand();
        $command->username = $attributes['username'];

        return $command;
    })

or would it be better to use withoutConstructor()) and alwaysForceProperties() (probably through the bundle configuration)?

Question/Problem: Using Foundry in Doctrine Fixtures with service injection

I want to use UserPasswordEncoderInterface in my UserFactory to set the encoded password after instantiate. When I try to create my Factory ($userAdmin = UserFactory::new();), I get:

Model Factories with dependencies (Model Factory services) cannot be used without the foundry bundle.

but Flex registered the Bundle: Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true],

It looks like Foundry has booted, but the injected $factories in \Zenstruck\Foundry\ModelFactoryManager are empty.

Deprecate ModelFactory::random*() methods

I think there are too many static methods on ModelFactory and there is some confusion on what this does. I think calling these functions on the repository is more explicit (ModelFactory::repository()->random*()).

The downside is these methods autocomplete your object when using the generated factories.

Installation Issue

Hello,

When I setup the package, I have the following error:
You have requested to a non-existent service "Zenstruck\Foundry\Configuration"

Do you knw what can cause this issue ?

@kbond @jdreesen

Thks

[Make] Overriding file path and template file

Hello, thanks for your bundle,
We are currently adding foundry to our fixtures in our project, but we want to put all things in a subfolder inside DataFixtures folder like DataFixtures/Story and DataFixtures/Factory to group all things around ou fixtures.

Of course, we can do this without any problem but is there a way that we can override the folder destination in the make commands? I see the "test" option, but of course, this doesn't fit with our needs but it's the same spirit.

Since MakeFactory and MakeStory are final we can't extend them to change that. We can only copy/past it in our own command, but we will miss all of your future updates.

Maybe an option freer like --destination in the spirit of the test option can do the job?

And we would like to override the template because we don't need an empty instantiate function in all of our files, so if you have an idea to make that too, you're welcome.

Have a nice day guys!

Remove attribute prefix when doing findOrCreate() ?

I'm not totally sure whether we're wrongly using this library or whether I have stumbled on a bug (I'm having this bug in so many places that I almost believe it's the first). That's why I decided to open an issue first, I'm happy to provide a PR to fix this issue.


In our code, some properties are not set by the constructor or by property accessible setters. E.g. a User#subscribeTo($subscription) method. However, I want to create fixtures with subscriptions. From what I understand, the force: attribute prefix is the way forward: ['force:subscription' => $subscription].

However, this does not work with findOrCreate(), as doctrine will try to use a field called force:subscription instead of the expected subscription. I'm often using findOrCreate() in my tests, as I want to avoid creating new test fixtures if a matching is already found.

The solution would be to strip attribute prefixes before passing them into repository()->find($attributes):

final public static function findOrCreate(array $attributes): Proxy
{
if ($found = static::repository()->find($attributes)) {
return $found;
}

[Suggestion] Allow to get the object from assertExists()

UserFactory::repository()->assertExists([
    'fullName' => $this->userData['full-name'],
    'username' => $this->userData['username'],
]);

$user = UserFactory::repository()->find(['fullName' => $this->userData['full-name'], 'username' => $this->userData['username']])->object();
$this->assertTrue($container->get('security.password_encoder')->isPasswordValid($user, $this->userData['password']));

It would be nice if $user is returned from assertExists(), to avoid duplication. However, this will break the current fluent interface, so I'm not sure if (or how) we should fix this.

Questioning about the deprecation of instance method "createMany": MyFactory::new()->createMany(10)

Hi,
I've a question about the deprecation of the usage of instance method ->createMany() and its replacement by the static ::createMany() method.

In many cases I use something like:

MyFactory::new()
    ->firstState()
    ->secondState('myArg')
    ->createMany(10)
;

I've not find a way to continue to do that with the static method ::createMany(). By checking the code and the doc, I've seen we can pass sometimes pass state function names in the ::new() static function but this don't solve the problem (and do not support args).

I think I've miss something :D Thanks

Document running the test suite locally

DATABASE_URL=<dsn> ./run-tests

or:

cp phpunit.xml.dist phpunit.xml
cp phpunit-dama-doctrine.xml.dist phpunit-dama-doctrine.xml

And add the <env name="DATABASE_URL" value="<dsn>" /> inside the <php> element, then just run ./run-tests.

[Idea] Add a foundry console command

I would like to generate fixtures from outside the application. More concrete, we're using a "stress testing" application that runs a couple of stress tests. Before doing so, I want that application to set-up some fixtures.

The problem here is that the number of fixtures to generate is dynamic (e.g. in a stress test with 5 users, I need 5 user fixtures, but a stress test with 50 users, will require 50 user fixtures). A colleague of mine came with the idea of using a console command for this, e.g. bin/console foundry:generate --factory=App\Factory\UserFactory --amount=50. This command then invokes UserFactory::createMany(50) and outputs the data, so the stress test library can e.g. parse the usernames.

Would you be interested in such a command in the bundle of this package? Or is there another (already existing) way to accomplish this use-case? Or is this the wrong library for the job?

make:factory --all

--all flag to make:factory that generates factories that don't exist for all your entities. When using interactively, when listing registered entities, "All Missing" should be the final option.

Improve documentation using foundry without persisting

Ref: #21

Foundry can be used without doctrine if you just want to instantiate objects (without persisting) for Unit tests. In this case, the test traits and extending KernelTestCase is not required.

  • The ->withoutPersisting() method is currently hidden within "Using your Factory"
  • The instantiate/instantiate_many helper functions are currently hidden within "Anonymous Factories"
  • Perhaps a section "Using without Doctrine" (?)
    • Explain that the test traits are not required (can use Foundry without booting)
    • Fully explain ->withoutPersisting()
    • Have your model factories override initialize to disable
    • Create a base model factory class to disable all your factories

Enhance make:story

A feature of Story's is the ability access the story's state via magic static methods:

final class PostStory extends Story
{
    public function build(): void
    {
        $this->add('postA', PostFactory::create(['title' => 'Post A']));
    }
}

The object can be persisted/accessed via PostStory::postA().

A neat feature would be to allow the make:story command to accept an existing story, find all the calls to add, determine the object type being added and add @method php docs. Running on the above example would produce:

/**
 * @method static Post|Proxy postA()
 */
final class PostStory extends Story
{
    public function build(): void
    {
        $this->add('postA', PostFactory::create(['title' => 'Post A']));
    }
}

Add a randomOrCreate() method

Whenever I have an object that depends on another object (e.g. "Post" depends on a "User" as the author), I use UserFactory::random() in the initialize. However, this at the moment requires to have called UserFactory::new()->create() before calling PostFactory::new()->create().

I suggest adding a randomOrCreate() method. To be discussed: should this have an optional $attributes argument?

Auto-refresh proxy improvements

With auto-refreshing, there is a bit of a problem:

$post = PostFactory::new(['title' => 'Original Title', 'body' => 'Original Body'])
    ->create()
    ->enableAutoRefresh()
;

$post->setTitle('New Title');
$post->setBody('New Body'); // !!! this causes the object to be refreshed, which means the "New Title" title was replaced by the database contents
$post->save();

$post->getBody(); // "New Body"
$post->getTitle(); // "Original Title" !! because the subsequent call to ->setBody() "auto-refreshed" the object

This is the reason auto-refreshing is disabled by default.

A great improvement would be to only refresh when it actually needs to be refreshed (it's row in the db changed).

If this could be fixed, I think we could enable auto-refreshing by default.

ResetDatabase doesn't work with sqlite DB

Error occurs when run tests using ResetDatabase trait together with sqlite

Error running "doctrine:database:drop": 
In DBALException.php line 39:
Operation 'Doctrine\DBAL\Platforms\AbstractPlatform::getListDatabasesSQL' is not supported by platform. 

Looks like it's dbal issue.

In \Zenstruck\Foundry\Test\DatabaseResetter::runCommand I added check for sqllite, but I'm not sure if bundle should be responsible for checking this though.

if (isset($parameters['--if-exists'])
    && $application->getKernel()->getContainer()->get('doctrine')->getConnection()->getDatabasePlatform()->getName() === 'sqlite'
) {
    unset($parameters['--if-exists']);
}

Document using custom faker providers

With a faker generator factory service:

namespace Your\Namespace;

class FakerGenerator
{
    public static function create(): \Faker\Generator
    {
        $generator = \Faker\Factory::create();
        $generator->addProvider(new TitleProvider($generator));
        return $generator;
    }
}
services:
    my_faker:
        class: Faker\Generator
        factory: ['Your\Namespace\FakerGenerator', 'create']

zenstruck_foundry:
    faker:
        service: my_faker

Or with a custom faker generator:

namespace Your\Namespace;

use Your\Namespace\YourProvider;
use Faker\Generator;
use Faker\Factory;

class CustomGenerator extends Generator
{
    public function __construct()
    {
        // Get a default generator with default providers
        $generator = Factory::create();

        // Add custom providers
        $generator->addProvider(new YourProvider($generator));

        // copy default and custom providers to this custom generator
        foreach ($generator->getProviders() as $provider) {
            $this->addProvider($provider);
        }
    }
}
zenstruck_foundry:
    faker:
        service: Your\Namespace\CustomGenerator

Per slack conversation with @voltel.

Allow modifying attribute values before calling Doctrine's find() method?

Related to #105 and I've also had some similar use cases in the projects I'm working on.

Currently, you can't do UserFactory::repository()->assertExists(['username' => 'jane_admin', 'password' => 'kitten']); (or findOrCreate()). The issue is that the generated SELECT query uses the plaintext kitten password. In a afterInitialisation() callback, this plaintext password is hashed before saving to the database.

Maybe Foundry should have a way to consistently run a callback on attributes (independent if the attributes are used in create() or find() or assertExists())?

FindOneBy with orderBy do not return the expected entity/proxy

When using ***Factory::repository()->findOneBy([], ['id' => 'DESC']), the returned Proxy/Entity doesn't match.

As an example, I've something like:

$entity= EntityFactory::new()->create();

self::ensureKernelShutdown();
$client = static::createClient();
$this->logIn($client, 'ROLE_USER');
$client->request('GET', '/my-entity/'.$offer->getId().'/duplicate');

$this->assertResponseRedirects();

$duplicatedOffer = EntityFactory::repository()->findOneBy([], ['id' => 'DESC']);

$this->assertNotSame($offer->getId(), $duplicatedOffer->getId());

But this code fail, if I replace EntityFactory::repository()->findOneBy([], ['id' => 'DESC']) by self::getEntityManager()->getRepository(CustomerOffer::class)->findOneBy([], ['id' => 'DESC']) it works.

I can order by what I want: id, date; and set the order as ASC or DESC, I always have the bad.

Database not resetting and throws error in CI

In my CI environment (Gitlab) when I run my tests, I get the following error:

RuntimeException: Error running "doctrine:database:drop": Could not drop database "postgres" for connection named default
An exception occurred while executing 'DROP DATABASE "postgres"':
SQLSTATE[55006]: Object in use: 7 ERROR:  cannot drop the currently open database

I'm using PostgreSQL 12. Not sure how to get around this.

Advice appreciated!

[Question] Run 'clear' after 'flush' call

Hi, i have a question, is there no need to clean the object manager after every call to ->flush()? as I understand in Doctrine the data remains in memory after a call to ->persist() even after executing a ->flush() unless explicitly ->clear() is executed, if i execute a ->createMany(400) with a lot of data it would not increase that the memory usage incrementally after each persisted object?

foundry/src/Proxy.php

Lines 100 to 101 in fb5b4ff

$this->objectManager()->persist($this->object);
$this->objectManager()->flush();

foundry/src/Proxy.php

Lines 109 to 110 in fb5b4ff

$this->objectManager()->remove($this->object);
$this->objectManager()->flush();

Thanks in advance.

Can't install foundry in Symfony 3.4

I don't really know which versions of Symfony are supported by Foundry, but this error appears after trying to install in a new 3.4 project

Symfony operations: 1 recipe (f5b976f91fc829a4840e75cc8dfc3701)
  - Configuring zenstruck/foundry (>=v1.5.0): From auto-generated recipe
Executing script cache:clear [KO]
 [KO]
Script cache:clear returned with error code 255
!!
!!  Fatal error: Uncaught Symfony\Component\Debug\Exception\UndefinedMethodException: Attempted to call an undefined method named "getRootNode" of c
lass "Symfony\Component\Config\Definition\Builder\TreeBuilder". in C:\...\vendor\zenstruck\foundry\src\Bundle\DependencyInjection\Configuration
.php on line 17
!!
!!  Symfony\Component\Debug\Exception\UndefinedMethodException: Attempted to call an undefined method named "getRootNode" of class "Symfony\Componen
t\Config\Definition\Builder\TreeBuilder". in C:\...\vendor\zenstruck\foundry\src\Bundle\DependencyInjection\Configuration.php on line 17
!!
!!  Call Stack:
!!      0.4020   12135240   1. Symfony\Component\Debug\ErrorHandler->handleException() C:\...\vendor\symfony\debug\ErrorHandler.php:0
!!
!!  PHP Fatal error:  Uncaught Symfony\Component\Debug\Exception\UndefinedMethodException: Attempted to call an undefined method named "getRootNode"
 of class "Symfony\Component\Config\Definition\Builder\TreeBuilder". in C:\...\vendor\zenstruck\foundry\src\Bundle\DependencyInjection\Configur
ation.php:17
!!  Stack trace:
!!  #0 C:\...\vendor\symfony\config\Definition\Processor.php(50): Zenstruck\Foundry\Bundle\DependencyInjection\Configuration->getConfigTreeBuil
der()
!!  #1 C:\...\vendor\symfony\dependency-injection\Extension\Extension.php(102): Symfony\Component\Config\Definition\Processor->processConfigura
tion(Object(Zenstruck\Foundry\Bundle\DependencyInjection\Configuration), Array)
!!  #2 C:\...\vendor\symfony\http-kernel\DependencyInjection\ConfigurableExtension.php(35): Symfony\Component\DependencyInjection\Extension\Ext
ension->processConfiguration(Object(Zenstruck\Foundry\Bundle\DependencyInjection\Configuration), Array)
!!  #3 C:\...\vendor\symfony\dependency-injection\Compiler\MergeExtensionConfigurationPass.php(71): S in C:\...\vendor\zenstruck\foundry\s
rc\Bundle\DependencyInjection\Configuration.php on line 17
!!
Script @auto-scripts was called via post-update-cmd

Installation failed, reverting ./composer.json and ./composer.lock to their original content.

in any case a mention of the supported versions in the readme would be very useful.

2.0 Ideas

@wouterj and I have been discussing some potential changes for 2.0. This is a list of potential ideas:

  • #37
  • Rename ProxyRepository to RepositoryDecorator and mark internal ("proxy" is a bit of a misnomer - it is actually a "decorator")
  • Remove Proxy and change Factory::create() to return the real doctrine entity. While the proxy idea is clever and sort of creates a "quasi" AR system for your entities, it can be confusing (to use and document)
  • Move the AR helpers (save, refresh, delete) to the ModelFactory/RepositoryDecorator and some stand alone helper functions
  • Investigate if ocramius/proxy-manager could be used for auto-refresh as this would be lost with removal of the Proxy
  • Method names: $user = UserFactory::new()->create()->object() is unfortunate. The ->object() would be removed by removing the Proxy (above) but $user = UserFactory::new()->create() is still confusing... (solved in #111)
  • #113
  • ModelFactory::repository()/AnonymousFactory::repository() to return the "real" repository for the entity
  • #126
  • Move reset env variables to config options (FOUNDRY_DISABLE_DATABASE_RESET, FOUNDRY_RESET_CONNECTIONS, FOUNDRY_RESET_OBJECT_MANAGERS)? #320
  • Remove bundless usage. This is a vestigial feature that likely only I'm using (with no reason I can't switch to using the bundle). I believe only allowing bundle config would drastically reduce complexity. #319
  • [BC BREAK] make ModelFactory::getClass() public and remove ModelFactory::getEntityClass()? See #162 for details (specifically #162 (comment))
  • ODM Support (or at least make adding support for this easier)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.