diff --git a/.gitignore b/.gitignore
index 6d5aba94..58efde94 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 5fca00a6..325e8db3 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 c6b9f26c..d6204a00 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -20,6 +20,7 @@
tests
tests/Integration/Migration/ResetDatabaseWithMigrationTest.php
+ tests/Integration/ForceFactoriesTraitUsage
tests/Integration/Migration/ResetDatabaseWithMigrationTest.php
diff --git a/src/Configuration.php b/src/Configuration.php
index 048604b4..8e0dc219 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 00000000..e5c343ca
--- /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/ObjectFactory.php b/src/ObjectFactory.php
index 8d69933f..0ad66a22 100644
--- a/src/ObjectFactory.php
+++ b/src/ObjectFactory.php
@@ -11,6 +11,7 @@
namespace Zenstruck\Foundry;
+use Zenstruck\Foundry\Exception\FactoriesTraitNotUsed;
use Zenstruck\Foundry\Object\Instantiator;
/**
diff --git a/src/PHPUnit/BuildStoryOnTestPrepared.php b/src/PHPUnit/BuildStoryOnTestPrepared.php
index 8689dbfe..ff3ea9eb 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 00000000..f36dec86
--- /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 00000000..fd6de3f9
--- /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 00000000..80d40751
--- /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 00000000..c8f0457e
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithOnlyResetDatabaseTraitTest.php
@@ -0,0 +1,51 @@
+
+ *
+ * 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\IgnoreDeprecations;
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+use PHPUnit\Framework\Attributes\Test;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Zenstruck\Foundry\Exception\FactoriesTraitNotUsed;
+use Zenstruck\Foundry\Test\ResetDatabase;
+use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
+
+#[RequiresPhpunit('>=11.0')]
+final class KernelTestCaseWithOnlyResetDatabaseTraitTest extends KernelTestCase
+{
+ use ResetDatabase;
+
+ #[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_should_throw(): void
+ {
+ $this->expectUserDeprecationMessageMatches('/In order to use Foundry, you must use the trait/');
+
+ Object1Factory::createOne();
+ }
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php
new file mode 100644
index 00000000..83b36f35
--- /dev/null
+++ b/tests/Integration/ForceFactoriesTraitUsage/KernelTestCaseWithoutFactoriesTraitTest.php
@@ -0,0 +1,52 @@
+
+ *
+ * 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\IgnoreDeprecations;
+use PHPUnit\Framework\Attributes\RequiresPhpunit;
+use PHPUnit\Framework\Attributes\Test;
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
+
+#[RequiresPhpunit('>=11.0')]
+final class KernelTestCaseWithoutFactoriesTraitTest extends KernelTestCase
+{
+ #[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]
+ public function booting_kernel_without_using_foundry_and_without_factories_trait_should_not_throw(): void
+ {
+ self::getContainer()->get('.zenstruck_foundry.configuration');
+
+ $this->expectNotToPerformAssertions();
+ }
+}
diff --git a/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php b/tests/Integration/ForceFactoriesTraitUsage/UnitTestCaseWithFactoriesTraitTest.php
new file mode 100644
index 00000000..b1d2b59f
--- /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 00000000..2275b1af
--- /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();
+ }
+}
diff --git a/tests/Integration/Maker/MakerTestCase.php b/tests/Integration/Maker/MakerTestCase.php
index 1e830d3d..2984dd64 100644
--- a/tests/Integration/Maker/MakerTestCase.php
+++ b/tests/Integration/Maker/MakerTestCase.php
@@ -14,6 +14,7 @@
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\String\Slugger\AsciiSlugger;
+use Zenstruck\Foundry\Test\Factories;
/**
* @author Kevin Bond
@@ -21,6 +22,8 @@
*/
abstract class MakerTestCase extends KernelTestCase
{
+ use Factories;
+
/**
* @before
*/