From 5611b1732163cce1060ee50b7439a2a4824140c7 Mon Sep 17 00:00:00 2001 From: Quentin Schuler Date: Wed, 8 Jul 2026 12:02:47 +0200 Subject: [PATCH 1/2] Discover providers from the project itself. --- src/Manifests/ContainerMixinManifest.php | 7 ++++--- src/Manifests/PackageManifest.php | 13 +++++++++++-- tests/Support/composer.json | 3 +++ 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 tests/Support/composer.json diff --git a/src/Manifests/ContainerMixinManifest.php b/src/Manifests/ContainerMixinManifest.php index afb6f13..33b876e 100644 --- a/src/Manifests/ContainerMixinManifest.php +++ b/src/Manifests/ContainerMixinManifest.php @@ -120,8 +120,8 @@ public function build(array $extensionMethods, array $extensions) $tags[] = new Method( $methodName, - returnType: $reflectionMethod->hasReturnType() ? (new TypeResolver())->resolve($reflectionMethod->getReturnType()) : new Mixed_(), parameters: $parameters, + returnType: $reflectionMethod->hasReturnType() ? (new TypeResolver())->resolve($reflectionMethod->getReturnType()) : new Mixed_(), ); } @@ -142,8 +142,9 @@ public function build(array $extensionMethods, array $extensions) public function shouldRecompile(): bool { return !is_file($this->containerMixinPath) || - // We check here if the manifest has been generated before changing the installed.json composer file - filemtime($this->containerMixinPath) <= filemtime($this->vendorPath.'/composer/installed.json'); + // We check here if the manifest has been generated before changing the installed.json composer file or the project composer.json + filemtime($this->containerMixinPath) <= filemtime($this->vendorPath.'/composer/installed.json') || + filemtime($this->containerMixinPath) <= filemtime($this->basePath.'/composer.json'); } /** diff --git a/src/Manifests/PackageManifest.php b/src/Manifests/PackageManifest.php index 53e964b..c81c1ca 100644 --- a/src/Manifests/PackageManifest.php +++ b/src/Manifests/PackageManifest.php @@ -120,6 +120,14 @@ public function build() } } + if (is_file($path = $this->basePath.'/composer.json')) { + $package = json_decode(file_get_contents($path), true); + + if (isset($package['extra']['faker'])) { + $packagesToProvide[$package['name'] ?? 'root'] = $package['extra']['faker']; + } + } + $this->write( $packagesToProvide ); @@ -133,8 +141,9 @@ public function build() public function shouldRecompile(): bool { return !is_file($this->manifestPath) || - // We check here if the manifest has been generated before changing the installed.json composer file - filemtime($this->manifestPath) <= filemtime($this->vendorPath.'/composer/installed.json'); + // We check here if the manifest has been generated before changing the installed.json composer file or the project composer.json + filemtime($this->manifestPath) <= filemtime($this->vendorPath.'/composer/installed.json') || + filemtime($this->manifestPath) <= filemtime($this->basePath.'/composer.json'); } /** diff --git a/tests/Support/composer.json b/tests/Support/composer.json new file mode 100644 index 0000000..b593485 --- /dev/null +++ b/tests/Support/composer.json @@ -0,0 +1,3 @@ +{ + "name": "xefi/faker-php-tests-support" +} From 8841a31a08b440fe4839309673b8495b9cc6819e Mon Sep 17 00:00:00 2001 From: Gautier Deleglise Date: Fri, 31 Jul 2026 11:29:41 +0200 Subject: [PATCH 2/2] Add unit tests for the project provider discovery Cover the root composer.json branch added to PackageManifest::build(): providers declared by the project itself, merge with the installed packages ones, the "root" fallback key when the project has no name, and the ignored cases (no extra.faker, no composer.json at all). Also cover the new project composer.json freshness check in both PackageManifest::shouldRecompile() and ContainerMixinManifest::shouldRecompile(). --- .../Concerns/CreatesTemporaryProjects.php | 65 ++++++ tests/Unit/ContainerMixinManifestTest.php | 32 +++ tests/Unit/PackageManifestTest.php | 203 ++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 tests/Support/Concerns/CreatesTemporaryProjects.php diff --git a/tests/Support/Concerns/CreatesTemporaryProjects.php b/tests/Support/Concerns/CreatesTemporaryProjects.php new file mode 100644 index 0000000..c16548c --- /dev/null +++ b/tests/Support/Concerns/CreatesTemporaryProjects.php @@ -0,0 +1,65 @@ + $installedPackages]) + ); + + if ($composer !== null) { + file_put_contents($projectPath.'/composer.json', json_encode($composer)); + } + + $this->temporaryProjectPaths[] = $projectPath; + + return $projectPath; + } + + /** + * Remove every temporary project created during the test. + * + * @return void + */ + private function deleteTemporaryProjects(): void + { + foreach ($this->temporaryProjectPaths as $projectPath) { + $files = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($projectPath, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $file) { + $file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname()); + } + + rmdir($projectPath); + } + + $this->temporaryProjectPaths = []; + } +} diff --git a/tests/Unit/ContainerMixinManifestTest.php b/tests/Unit/ContainerMixinManifestTest.php index cfae1ea..6dce686 100644 --- a/tests/Unit/ContainerMixinManifestTest.php +++ b/tests/Unit/ContainerMixinManifestTest.php @@ -4,8 +4,12 @@ namespace Xefi\Faker\Tests\Unit; +use Xefi\Faker\Tests\Support\Concerns\CreatesTemporaryProjects; + final class ContainerMixinManifestTest extends TestCase { + use CreatesTemporaryProjects; + protected function setUp(): void { parent::setUp(); @@ -17,6 +21,13 @@ protected function setUp(): void ]); } + protected function tearDown(): void + { + $this->deleteTemporaryProjects(); + + parent::tearDown(); + } + public function testContainerMixinBuild() { @unlink('/tmp/ContainerMixin.php'); @@ -111,6 +122,27 @@ public function testShouldRecompile() unlink('/tmp/ContainerMixin.php'); } + public function testShouldRecompileWhenTheProjectComposerFileChanged() + { + $projectPath = $this->createTemporaryProject(['name' => 'xefi/my-project']); + + $container = new \Xefi\Faker\Container\Container(shouldBuildContainerMixin: false); + $manifest = new \Xefi\Faker\Manifests\ContainerMixinManifest($projectPath, $projectPath.'/ContainerMixin.php'); + $manifest->build($container->getExtensionMethods(), $container->getExtensions()); + touch($projectPath.'/vendor/composer/installed.json', time() - 1); + touch($projectPath.'/composer.json', time() - 1); + + $this->assertFalse($manifest->shouldRecompile()); + + // Test on current time + touch($projectPath.'/composer.json'); + $this->assertTrue($manifest->shouldRecompile()); + + // Test on future + touch($projectPath.'/composer.json', time() + 1); + $this->assertTrue($manifest->shouldRecompile()); + } + public function testNoExtension() { @unlink('/tmp/ContainerMixin.php'); diff --git a/tests/Unit/PackageManifestTest.php b/tests/Unit/PackageManifestTest.php index fc3e1e8..3e2b414 100644 --- a/tests/Unit/PackageManifestTest.php +++ b/tests/Unit/PackageManifestTest.php @@ -5,9 +5,20 @@ namespace Xefi\Faker\Tests\Unit; use Xefi\Faker\Manifests\PackageManifest; +use Xefi\Faker\Tests\Support\Concerns\CreatesTemporaryProjects; +use Xefi\Faker\Tests\Support\TestServiceProvider; final class PackageManifestTest extends TestCase { + use CreatesTemporaryProjects; + + protected function tearDown(): void + { + $this->deleteTemporaryProjects(); + + parent::tearDown(); + } + public function testAssetLoading() { @unlink('/tmp/packages.php'); @@ -41,4 +52,196 @@ public function testShouldRecompile() unlink('/tmp/packages.php'); } + + public function testProjectProvidersAreDiscovered() + { + $projectPath = $this->createTemporaryProject([ + 'name' => 'xefi/my-project', + 'extra' => [ + 'faker' => [ + 'providers' => [TestServiceProvider::class], + ], + ], + ]); + + $manifest = new PackageManifest($projectPath, $projectPath.'/packages.php'); + + $this->assertEquals( + [ + 'xefi/my-project' => [TestServiceProvider::class], + ], + $manifest->providers() + ); + } + + public function testProjectProvidersAreMergedWithTheInstalledPackagesOnes() + { + $projectPath = $this->createTemporaryProject( + [ + 'name' => 'xefi/my-project', + 'extra' => [ + 'faker' => [ + 'providers' => ['Xefi\Faker\Tests\Support\ProjectServiceProvider'], + ], + ], + ], + [ + [ + 'name' => 'xefi/faker-number', + 'extra' => [ + 'faker' => [ + 'providers' => [TestServiceProvider::class], + ], + ], + ], + ] + ); + + $manifest = new PackageManifest($projectPath, $projectPath.'/packages.php'); + + $this->assertEquals( + [ + 'xefi/faker-number' => [TestServiceProvider::class], + 'xefi/my-project' => ['Xefi\Faker\Tests\Support\ProjectServiceProvider'], + ], + $manifest->providers() + ); + } + + public function testProjectProvidersAreKeyedByRootWhenTheProjectHasNoName() + { + $projectPath = $this->createTemporaryProject([ + 'extra' => [ + 'faker' => [ + 'providers' => [TestServiceProvider::class], + ], + ], + ]); + + $manifest = new PackageManifest($projectPath, $projectPath.'/packages.php'); + + $this->assertEquals( + [ + 'root' => [TestServiceProvider::class], + ], + $manifest->providers() + ); + } + + public function testProjectWithoutFakerConfigurationIsIgnored() + { + $projectPath = $this->createTemporaryProject( + [ + 'name' => 'xefi/my-project', + 'extra' => [ + 'branch-alias' => ['dev-master' => '2.0.x-dev'], + ], + ], + [ + [ + 'name' => 'xefi/faker-number', + 'extra' => [ + 'faker' => [ + 'providers' => [TestServiceProvider::class], + ], + ], + ], + ] + ); + + $manifest = new PackageManifest($projectPath, $projectPath.'/packages.php'); + + $this->assertEquals( + [ + 'xefi/faker-number' => [TestServiceProvider::class], + ], + $manifest->providers() + ); + } + + public function testProjectTakesPrecedenceOverAnInstalledPackageOfTheSameName() + { + $projectPath = $this->createTemporaryProject( + [ + 'name' => 'xefi/faker-number', + 'extra' => [ + 'faker' => [ + 'providers' => ['Xefi\Faker\Tests\Support\ProjectServiceProvider'], + ], + ], + ], + [ + [ + 'name' => 'xefi/faker-number', + 'extra' => [ + 'faker' => [ + 'providers' => [TestServiceProvider::class], + ], + ], + ], + ] + ); + + $manifest = new PackageManifest($projectPath, $projectPath.'/packages.php'); + + $this->assertEquals( + [ + 'xefi/faker-number' => ['Xefi\Faker\Tests\Support\ProjectServiceProvider'], + ], + $manifest->providers() + ); + } + + public function testProjectWithoutComposerFileIsIgnored() + { + $projectPath = $this->createTemporaryProject(null, [ + [ + 'name' => 'xefi/faker-number', + 'extra' => [ + 'faker' => [ + 'providers' => [TestServiceProvider::class], + ], + ], + ], + ]); + + $manifest = new PackageManifest($projectPath, $projectPath.'/packages.php'); + $manifest->build(); + + $this->assertEquals( + [ + 'xefi/faker-number' => [ + 'providers' => [TestServiceProvider::class], + ], + ], + require $projectPath.'/packages.php' + ); + } + + public function testShouldRecompileWhenTheProjectComposerFileChanged() + { + $projectPath = $this->createTemporaryProject([ + 'name' => 'xefi/my-project', + 'extra' => [ + 'faker' => [ + 'providers' => [TestServiceProvider::class], + ], + ], + ]); + + $manifest = new PackageManifest($projectPath, $projectPath.'/packages.php'); + $manifest->build(); + touch($projectPath.'/vendor/composer/installed.json', time() - 1); + touch($projectPath.'/composer.json', time() - 1); + + $this->assertFalse($manifest->shouldRecompile()); + + // Test on current time + touch($projectPath.'/composer.json'); + $this->assertTrue($manifest->shouldRecompile()); + + // Test on future + touch($projectPath.'/composer.json', time() + 1); + $this->assertTrue($manifest->shouldRecompile()); + } }