From 6052e815ff2b580f3e2bc4091de4bca0840cd8eb Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Sun, 3 Apr 2022 08:16:40 -0400 Subject: [PATCH 1/4] [minor] Revert "[bug] fix global state with symfony/framework-bundle >= 5.4.6/6.0.6" (#260) This reverts commit c1317159d4489626dc8fc00bdb77932ac1d38bd8. --- src/Test/DatabaseResetter.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Test/DatabaseResetter.php b/src/Test/DatabaseResetter.php index 259950178..df6ecc788 100644 --- a/src/Test/DatabaseResetter.php +++ b/src/Test/DatabaseResetter.php @@ -5,6 +5,7 @@ use DAMA\DoctrineTestBundle\Doctrine\DBAL\StaticDriver; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\HttpKernel\KernelInterface; +use Zenstruck\Foundry\Factory; /** * @internal @@ -41,7 +42,7 @@ public static function resetDatabase(KernelInterface $kernel): void $databaseResetter->resetDatabase(); - self::flushGlobalState($kernel); + self::bootFoundry($kernel); self::$hasBeenReset = true; } @@ -56,7 +57,7 @@ public static function resetSchema(KernelInterface $kernel): void return; } - self::flushGlobalState($kernel); + self::bootFoundry($kernel); } /** @retrun array */ @@ -76,9 +77,11 @@ private static function schemaResetters(KernelInterface $kernel): array return $databaseResetters; } - private static function flushGlobalState(KernelInterface $kernel): void + private static function bootFoundry(KernelInterface $kernel): void { - TestState::bootFromContainer($kernel->getContainer()); + if (!Factory::isBooted()) { + TestState::bootFromContainer($kernel->getContainer()); + } TestState::flushGlobalState(); } From 8117f406394e3e0322fe92b6414d6eb48427faa1 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Mon, 11 Apr 2022 09:21:54 -0400 Subject: [PATCH 2/4] [minor] allow `dama/doctrine-test-bundle` 7.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d482cfa8b..e328f9e1b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4", - "dama/doctrine-test-bundle": "^6.0", + "dama/doctrine-test-bundle": "^6.0|^7.0", "doctrine/doctrine-bundle": "^2.0", "doctrine/doctrine-migrations-bundle": "^2.2|^3.0", "doctrine/mongodb-odm-bundle": "^3.1|^4.0", From 91609b4380b6c1014f0906d5b63e0cb3cffebd61 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Mon, 11 Apr 2022 09:43:04 -0400 Subject: [PATCH 3/4] [minor] remove scrutinizer --- .scrutinizer.yml | 10 ---------- README.md | 1 - 2 files changed, 11 deletions(-) delete mode 100644 .scrutinizer.yml diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 618f98d35..000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,10 +0,0 @@ -filter: - dependency_paths: [vendor/] -checks: - php: true -build: - nodes: - analysis: - tests: - override: - - php-scrutinizer-run diff --git a/README.md b/README.md index 876d78fc5..ccc404f55 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Foundry [![CI Status](https://github.com/zenstruck/foundry/workflows/CI/badge.svg)](https://github.com/zenstruck/foundry/actions?query=workflow%3ACI) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/zenstruck/foundry/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/zenstruck/foundry/?branch=master) [![Code Coverage](https://codecov.io/gh/zenstruck/foundry/branch/master/graph/badge.svg?token=77JIFYSUC5)](https://codecov.io/gh/zenstruck/foundry) [![Latest Version](https://img.shields.io/packagist/v/zenstruck/foundry.svg)](https://packagist.org/packages/zenstruck/foundry) From b9d2ed33ec9a7e7f84852e065d75eecdd32a9c6a Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Mon, 11 Apr 2022 10:07:00 -0400 Subject: [PATCH 4/4] [feature] add `Factory::delayFlush()` (#84) --- docs/index.rst | 16 +++++++++++ src/Configuration.php | 21 +++++++++++++++ src/Factory.php | 5 ++++ src/Proxy.php | 8 ++++-- tests/Fixtures/Entity/Post.php | 10 +++++++ tests/Functional/FactoryTest.php | 46 ++++++++++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 413125ccd..3d20a7e76 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -865,6 +865,22 @@ Foundry can be used to create factories for entities that you don't have model f $entity = create(Post::class, ['field' => 'value']); $entities = create_many(Post::class, 5, ['field' => 'value']); +Delay Flush +~~~~~~~~~~~ + +When creating/persisting many factories at once, it can be improve performance +to instantiate them all without saving to the database, then flush them all at +once. To do this, wrap the operations in a ``Factory::delayFlush()`` callback: + +.. code-block:: php + + use Zenstruck\Foundry\Factory; + + Factory::delayFlush(function() { + CategoryFactory::createMany(100); // instantiated/persisted but not flushed + TagFactory::createMany(200); // instantiated/persisted but not flushed + }); // single flush + .. _without-persisting: Without Persisting diff --git a/src/Configuration.php b/src/Configuration.php index 9bdd1db8f..d38640e96 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -32,6 +32,9 @@ final class Configuration /** @var bool|null */ private $defaultProxyAutoRefresh; + /** @var bool */ + private $flushEnabled = true; + public function __construct() { $this->stories = new StoryManager([]); @@ -124,6 +127,24 @@ public function disableDefaultProxyAutoRefresh(): self return $this; } + public function isFlushingEnabled(): bool + { + return $this->flushEnabled; + } + + public function delayFlush(callable $callback): void + { + $this->flushEnabled = false; + + $callback(); + + foreach ($this->managerRegistry()->getManagers() as $manager) { + $manager->flush(); + } + + $this->flushEnabled = true; + } + /** * @param object|string $objectOrClass * diff --git a/src/Factory.php b/src/Factory.php index fbc639432..ed37299c6 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -255,6 +255,11 @@ final public static function faker(): Faker\Generator return self::configuration()->faker(); } + final public static function delayFlush(callable $callback): void + { + self::configuration()->delayFlush($callback); + } + /** * @internal * diff --git a/src/Proxy.php b/src/Proxy.php index 30de66b85..691c1b4f2 100644 --- a/src/Proxy.php +++ b/src/Proxy.php @@ -110,7 +110,7 @@ public function isPersisted(): bool */ public function object(): object { - if (!$this->autoRefresh || !$this->persisted) { + if (!$this->autoRefresh || !$this->persisted || !Factory::configuration()->isFlushingEnabled()) { return $this->object; } @@ -139,7 +139,11 @@ public function object(): object public function save(): self { $this->objectManager()->persist($this->object); - $this->objectManager()->flush(); + + if (Factory::configuration()->isFlushingEnabled()) { + $this->objectManager()->flush(); + } + $this->persisted = true; return $this; diff --git a/tests/Fixtures/Entity/Post.php b/tests/Fixtures/Entity/Post.php index 8e47d3f65..0bd5bd08a 100644 --- a/tests/Fixtures/Entity/Post.php +++ b/tests/Fixtures/Entity/Post.php @@ -89,11 +89,21 @@ public function getTitle(): ?string return $this->title; } + public function setTitle(string $title): void + { + $this->title = $title; + } + public function getBody(): ?string { return $this->body; } + public function setBody(string $body): void + { + $this->body = $body; + } + public function getShortDescription(): ?string { return $this->shortDescription; diff --git a/tests/Functional/FactoryTest.php b/tests/Functional/FactoryTest.php index c23238eb9..4e3d0b76e 100644 --- a/tests/Functional/FactoryTest.php +++ b/tests/Functional/FactoryTest.php @@ -142,4 +142,50 @@ public function can_create_embeddable(): void $this->assertNull($object1->getValue()); $this->assertSame('an address', $object2->getValue()); } + + public function can_delay_flush(): void + { + AnonymousFactory::new(Post::class)->assert()->empty(); + AnonymousFactory::new(Category::class)->assert()->empty(); + + AnonymousFactory::delayFlush(function() { + AnonymousFactory::new(Post::class)->create([ + 'title' => 'title', + 'body' => 'body', + 'category' => AnonymousFactory::new(Category::class, ['name' => 'name']), + ]); + + AnonymousFactory::new(Post::class)->assert()->empty(); + AnonymousFactory::new(Category::class)->assert()->empty(); + }); + + AnonymousFactory::new(Post::class)->assert()->count(1); + AnonymousFactory::new(Category::class)->assert()->count(1); + } + + /** + * @test + */ + public function auto_refresh_is_disabled_during_delay_flush(): void + { + AnonymousFactory::new(Post::class)->assert()->empty(); + AnonymousFactory::new(Category::class)->assert()->empty(); + + AnonymousFactory::delayFlush(function() { + $post = AnonymousFactory::new(Post::class)->create([ + 'title' => 'title', + 'body' => 'body', + 'category' => AnonymousFactory::new(Category::class, ['name' => 'name']), + ]); + + $post->setTitle('new title'); + $post->setBody('new body'); + + AnonymousFactory::new(Post::class)->assert()->empty(); + AnonymousFactory::new(Category::class)->assert()->empty(); + }); + + AnonymousFactory::new(Post::class)->assert()->count(1); + AnonymousFactory::new(Category::class)->assert()->count(1); + } }