diff --git a/.gitignore b/.gitignore
index 5ce100385..2a766832b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@
/.phpunit.cache
/vendor/
/bin/tools/*/vendor/
-/bin/tools/php-cs-fixer/composer.lock
+/bin/tools/csfixer
/build/
/.php-cs-fixer.cache
/.phpunit.result.cache
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 5fca00a68..325e8db30 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -7,6 +7,13 @@
\file_get_contents('https://raw.githubusercontent.com/zenstruck/.github/main/.php-cs-fixer.dist.php')
);
+$finder = PhpCsFixer\Finder::create()
+ ->in([__DIR__.'/src', __DIR__.'/tests'])
+
+ // we want to keep the traits in "wrong order"
+ ->notName('KernelTestCaseWithBothTraitsInWrongOrderTest.php')
+;
+
try {
return require $file;
} finally {
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 95f1c3e92..15de54d9a 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -20,6 +20,7 @@
tests
tests/Integration/ResetDatabase
+ tests/Integration/ForceFactoriesTraitUsage
tests/Integration/ResetDatabase
diff --git a/src/Configuration.php b/src/Configuration.php
index 048604b4c..8e0dc219d 100644
--- a/src/Configuration.php
+++ b/src/Configuration.php
@@ -12,6 +12,7 @@
namespace Zenstruck\Foundry;
use Faker;
+use Zenstruck\Foundry\Exception\FactoriesTraitNotUsed;
use Zenstruck\Foundry\Exception\FoundryNotBooted;
use Zenstruck\Foundry\Exception\PersistenceDisabled;
use Zenstruck\Foundry\Exception\PersistenceNotAvailable;
@@ -90,6 +91,8 @@ public static function instance(): self
throw new FoundryNotBooted();
}
+ FactoriesTraitNotUsed::throwIfComingFromKernelTestCaseWithoutFactoriesTrait();
+
return \is_callable(self::$instance) ? (self::$instance)() : self::$instance;
}
diff --git a/src/Exception/FactoriesTraitNotUsed.php b/src/Exception/FactoriesTraitNotUsed.php
new file mode 100644
index 000000000..9d5015c58
--- /dev/null
+++ b/src/Exception/FactoriesTraitNotUsed.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Exception;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Zenstruck\Foundry\Test\Factories;
+
+/**
+ * @author Nicolas PHILIPPE
+ */
+final class FactoriesTraitNotUsed extends \LogicException
+{
+ /**
+ * @param class-string $class
+ */
+ private function __construct(string $class)
+ {
+ parent::__construct(
+ \sprintf('You must use the trait "%s" in "%s" in order to use Foundry.', Factories::class, $class)
+ );
+ }
+
+ public static function throwIfComingFromKernelTestCaseWithoutFactoriesTrait(): void
+ {
+ $backTrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); // @phpstan-ignore ekinoBannedCode.function
+
+ foreach ($backTrace as $trace) {
+ if (
+ '->' === ($trace['type'] ?? null)
+ && isset($trace['class'])
+ && KernelTestCase::class !== $trace['class']
+ && \is_a($trace['class'], KernelTestCase::class, allow_string: true)
+ && !(new \ReflectionClass($trace['class']))->hasMethod('_bootFoundry')
+ ) {
+ self::throwIfClassDoesNotHaveFactoriesTrait($trace['class']);
+ }
+ }
+ }
+
+ /**
+ * @param class-string $class
+ */
+ public static function throwIfClassDoesNotHaveFactoriesTrait(string $class): void
+ {
+ if (!(new \ReflectionClass($class))->hasMethod('_bootFoundry')) {
+ // throw new self($class);
+ trigger_deprecation(
+ 'zenstruck/foundry',
+ '2.4',
+ 'In order to use Foundry, you must use the trait "%s" in your "%s" tests. This will throw an exception in 3.0.',
+ KernelTestCase::class,
+ $class
+ );
+ }
+ }
+}
diff --git a/src/PHPUnit/BuildStoryOnTestPrepared.php b/src/PHPUnit/BuildStoryOnTestPrepared.php
index 8689dbfee..ff3ea9eb4 100644
--- a/src/PHPUnit/BuildStoryOnTestPrepared.php
+++ b/src/PHPUnit/BuildStoryOnTestPrepared.php
@@ -16,6 +16,7 @@
use PHPUnit\Event;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Zenstruck\Foundry\Attribute\WithStory;
+use Zenstruck\Foundry\Exception\FactoriesTraitNotUsed;
/**
* @internal
@@ -46,6 +47,8 @@ public function notify(Event\Test\Prepared $event): void
throw new \InvalidArgumentException(\sprintf('The test class "%s" must extend "%s" to use the "%s" attribute.', $test->className(), KernelTestCase::class, WithStory::class));
}
+ FactoriesTraitNotUsed::throwIfClassDoesNotHaveFactoriesTrait($test->className());
+
foreach ($withStoryAttributes as $withStoryAttribute) {
$withStoryAttribute->newInstance()->story::load();
}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php
new file mode 100644
index 000000000..f36dec860
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsInWrongOrderTest.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage;
+
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+use PHPUnit\Framework\Attributes\Test;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Zenstruck\Foundry\Test\Factories;
+use Zenstruck\Foundry\Test\ResetDatabase;
+use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
+
+#[RequiresPhpunit('>=11.0')]
+final class KernelTestCaseWithBothTraitsInWrongOrderTest extends KernelTestCase
+{
+ use ResetDatabase, Factories;
+
+ #[Test]
+ public function should_not_throw(): void
+ {
+ Object1Factory::createOne();
+
+ $this->expectNotToPerformAssertions();
+ }
+
+ #[Test]
+ public function should_not_throw_even_when_kernel_is_booted(): void
+ {
+ self::getContainer()->get('.zenstruck_foundry.configuration');
+
+ Object1Factory::createOne();
+
+ $this->expectNotToPerformAssertions();
+ }
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php
new file mode 100644
index 000000000..fd6de3f93
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithBothTraitsTest.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage;
+
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+use PHPUnit\Framework\Attributes\Test;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Zenstruck\Foundry\Test\Factories;
+use Zenstruck\Foundry\Test\ResetDatabase;
+use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
+
+#[RequiresPhpunit('>=11.0')]
+final class KernelTestCaseWithBothTraitsTest extends KernelTestCase
+{
+ use Factories, ResetDatabase;
+
+ #[Test]
+ public function should_not_throw(): void
+ {
+ Object1Factory::createOne();
+
+ $this->expectNotToPerformAssertions();
+ }
+
+ #[Test]
+ public function should_not_throw_even_when_kernel_is_booted(): void
+ {
+ self::getContainer()->get('.zenstruck_foundry.configuration');
+
+ Object1Factory::createOne();
+
+ $this->expectNotToPerformAssertions();
+ }
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php
new file mode 100644
index 000000000..80d40751d
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithFactoriesTraitTest.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage;
+
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+use PHPUnit\Framework\Attributes\Test;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Zenstruck\Foundry\Test\Factories;
+use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
+
+#[RequiresPhpunit('>=11.0')]
+final class KernelTestCaseWithFactoriesTraitTest extends KernelTestCase
+{
+ use Factories;
+
+ #[Test]
+ public function should_not_throw(): void
+ {
+ Object1Factory::createOne();
+
+ $this->expectNotToPerformAssertions();
+ }
+
+ #[Test]
+ public function should_not_throw_even_when_kernel_is_booted(): void
+ {
+ self::getContainer()->get('.zenstruck_foundry.configuration');
+
+ Object1Factory::createOne();
+
+ $this->expectNotToPerformAssertions();
+ }
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTest.php
new file mode 100644
index 000000000..6727c074f
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTest.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage;
+
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+use Zenstruck\Foundry\Test\ResetDatabase;
+
+#[RequiresPhpunit('>=11.0')]
+final class KernelTestCaseWithOnlyResetDatabaseTraitTest extends KernelTestCaseWithoutFactoriesTraitTestCase
+{
+ use ResetDatabase;
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php
new file mode 100644
index 000000000..dae664edc
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage;
+
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+
+#[RequiresPhpunit('>=11.0')]
+final class KernelTestCaseWithoutFactoriesTraitTest extends KernelTestCaseWithoutFactoriesTraitTestCase
+{
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTestCase.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTestCase.php
new file mode 100644
index 000000000..ba73e8a73
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTestCase.php
@@ -0,0 +1,91 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage;
+
+use PHPUnit\Framework\Attributes\After;
+use PHPUnit\Framework\Attributes\Before;
+use PHPUnit\Framework\Attributes\IgnoreDeprecations;
+use PHPUnit\Framework\Attributes\RequiresPhpunitExtension;
+use PHPUnit\Framework\Attributes\Test;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Zenstruck\Foundry\Configuration;
+use Zenstruck\Foundry\PHPUnit\FoundryExtension;
+use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
+use Zenstruck\Foundry\Tests\Fixture\Stories\ObjectStory;
+
+abstract class KernelTestCaseWithoutFactoriesTraitTestCase extends KernelTestCase
+{
+ #[Test]
+ public function not_using_foundry_should_not_throw(): void
+ {
+ $this->expectNotToPerformAssertions();
+ }
+
+ #[Test]
+ public function not_using_foundry_should_not_throw_even_when_container_is_used(): void
+ {
+ self::getContainer()->get('.zenstruck_foundry.configuration');
+
+ $this->expectNotToPerformAssertions();
+ }
+
+ #[Test]
+ #[IgnoreDeprecations]
+ public function using_foundry_without_trait_should_throw(): void
+ {
+ $this->expectUserDeprecationMessageMatches('/In order to use Foundry, you must use the trait/');
+
+ Object1Factory::createOne();
+ }
+
+ #[Test]
+ #[IgnoreDeprecations]
+ public function using_foundry_without_trait_should_throw_even_when_kernel_is_booted(): void
+ {
+ $this->expectUserDeprecationMessageMatches('/In order to use Foundry, you must use the trait/');
+
+ self::getContainer()->get('.zenstruck_foundry.configuration');
+
+ Object1Factory::createOne();
+ }
+
+ #[Test]
+ #[RequiresPhpunitExtension(FoundryExtension::class)]
+ #[IgnoreDeprecations]
+ public function using_a_story_without_factories_trait_should_throw(): void
+ {
+ $this->expectUserDeprecationMessageMatches('/In order to use Foundry, you must use the trait/');
+
+ ObjectStory::load();
+ }
+
+ /**
+ * We need to at least boot and shutdown Foundry to avoid unpredictable behaviors.
+ *
+ * In user land, Foundry can work without the trait, because it may have been booted in a previous test.
+ */
+ #[Before]
+ public function _beforeHook(): void
+ {
+ Configuration::boot(static function(): Configuration {
+ return static::getContainer()->get('.zenstruck_foundry.configuration'); // @phpstan-ignore return.type
+ });
+ }
+
+ #[After]
+ public static function _shutdownFoundry(): void
+ {
+ Configuration::shutdown();
+ }
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php
new file mode 100644
index 000000000..b1d2b59f9
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage;
+
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\TestCase;
+use Zenstruck\Foundry\Test\Factories;
+use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
+
+#[RequiresPhpunit('>=11.0')]
+final class UnitTestCaseWithFactoriesTraitTest extends TestCase
+{
+ use Factories;
+
+ #[Test]
+ public function should_not_throw(): void
+ {
+ Object1Factory::createOne();
+
+ $this->expectNotToPerformAssertions();
+ }
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php
new file mode 100644
index 000000000..2275b1af3
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithoutFactoriesTraitTest.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage;
+
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\TestCase;
+use Zenstruck\Foundry\Exception\FoundryNotBooted;
+use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
+
+#[RequiresPhpunit('>=11.0')]
+final class UnitTestCaseWithoutFactoriesTraitTest extends TestCase
+{
+ #[Test]
+ public function should_throw(): void
+ {
+ $this->expectException(FoundryNotBooted::class);
+
+ Object1Factory::createOne();
+ }
+}