diff --git a/documentation/components/bridges/symfony-filesystem-bundle.md b/documentation/components/bridges/symfony-filesystem-bundle.md index cd10667bb7..9a9b0e796b 100644 --- a/documentation/components/bridges/symfony-filesystem-bundle.md +++ b/documentation/components/bridges/symfony-filesystem-bundle.md @@ -81,6 +81,52 @@ final class ReportBuilder > Most applications need exactly one fstab with multiple filesystems mounted under it. Multi-fstab support > exists for advanced cases — see [Multi-Fstab Support](#multi-fstab-support). +### Injecting a Single Filesystem + +When a service only needs one mounted filesystem, inject the `Flow\Filesystem\Filesystem` directly instead +of the whole table. Every mount is registered as a private service and exposed two ways. + +**Named-argument autowiring** — each mount of the default fstab is aliased as `Filesystem $`, and +every mount (of any fstab) as `Filesystem $`. Protocols containing `-`, `.` or `+` are +camel-cased (`aws-s3` → `awsS3`). This mirrors the `FilesystemTable $Fstab` convention: + +```php +use Flow\Filesystem\Filesystem; + +final class ReportBuilder +{ + public function __construct( + private readonly Filesystem $warehouse, // → default fstab, 'warehouse' mount + private readonly Filesystem $analyticsFile, // → 'analytics' fstab, 'file' mount + ) { + } +} +``` + +**`#[AsFilesystem]` attribute** — for explicit selection or when the argument name should differ from the +protocol. Omitting `fstab` targets the default fstab: + +```php +use Flow\Bridge\Symfony\FilesystemBundle\Attribute\AsFilesystem; +use Flow\Filesystem\Filesystem; + +final class ReportBuilder +{ + public function __construct( + #[AsFilesystem('warehouse')] + private readonly Filesystem $primary, + #[AsFilesystem('cold', fstab: 'archive')] + private readonly Filesystem $coldStorage, + ) { + } +} +``` + +Named-argument aliases work for any autowired service; the attribute requires the consuming service to be +autoconfigured (the default for everything under your `App\` namespace). Both resolve through the fstab's +`FilesystemTable::for()`, so telemetry decoration is preserved. An unknown protocol or fstab fails at +container-compile time with the available options listed. + ## Configuration Reference ### Fstabs diff --git a/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/Attribute/AsFilesystem.php b/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/Attribute/AsFilesystem.php new file mode 100644 index 0000000000..62986139ba --- /dev/null +++ b/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/Attribute/AsFilesystem.php @@ -0,0 +1,20 @@ +camelCase($fstabNameStr) . 'Fstab'; $container->setAlias($aliasId, $serviceId)->setPublic(true); + + $this->registerMountServices( + $container, + $fstabNameStr, + $serviceId, + array_keys($resolvedFilesystems), + $fstabNameStr === $defaultFstab, + ); } if ($defaultFstab !== null) { @@ -175,7 +187,41 @@ private function buildTelemetryConfigReference( private function camelCase(string $name): string { - return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $name)))); + return lcfirst(str_replace(' ', '', ucwords(str_replace(['_', '-', '.', '+'], ' ', $name)))); + } + + /** + * @param list $protocols mount protocols within the fstab + */ + private function registerMountServices( + ContainerBuilder $container, + string $fstabName, + string $fstabServiceId, + array $protocols, + bool $isDefault, + ): void { + foreach ($protocols as $protocol) { + $mountServiceId = self::FS_SERVICE_PREFIX . $fstabName . '.' . $protocol; + + $mountDefinition = new Definition(Filesystem::class); + $mountDefinition->setFactory([new Reference($fstabServiceId), 'for']); + $mountDefinition->setArguments([$protocol]); + $mountDefinition->setPublic(false); + $container->setDefinition($mountServiceId, $mountDefinition); + + $container + ->setAlias( + Filesystem::class . ' $' . $this->camelCase($fstabName) . ucfirst($this->camelCase($protocol)), + $mountServiceId, + ) + ->setPublic(true); + + if ($isDefault) { + $container + ->setAlias(Filesystem::class . ' $' . $this->camelCase($protocol), $mountServiceId) + ->setPublic(true); + } + } } /** diff --git a/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/DependencyInjection/Compiler/ResolveFilesystemArgumentsPass.php b/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/DependencyInjection/Compiler/ResolveFilesystemArgumentsPass.php new file mode 100644 index 0000000000..39b01b5be6 --- /dev/null +++ b/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/DependencyInjection/Compiler/ResolveFilesystemArgumentsPass.php @@ -0,0 +1,87 @@ +hasParameter(BuildFstabsPass::CONFIG_PARAMETER)) { + return; + } + + $config = $container->getParameter(BuildFstabsPass::CONFIG_PARAMETER); + + if (!is_array($config)) { + return; + } + + $fstabs = is_array($config['fstabs'] ?? null) ? $config['fstabs'] : []; + $defaultFstab = is_string($config['default_fstab'] ?? null) ? $config['default_fstab'] : null; + + foreach ($container->findTaggedServiceIds(self::TAG) as $serviceId => $tags) { + $definition = $container->getDefinition($serviceId); + + // @mago-expect analysis:mixed-assignment + foreach ($tags as $tag) { + if (!is_array($tag)) { + continue; + } + + $argument = is_string($tag['argument'] ?? null) ? $tag['argument'] : ''; + $mount = is_string($tag['mount'] ?? null) ? $tag['mount'] : ''; + $fstab = is_string($tag['fstab'] ?? null) && $tag['fstab'] !== '' ? $tag['fstab'] : $defaultFstab; + + if ($fstab === null) { + throw new LogicException(sprintf( + 'Service "%s" uses #[AsFilesystem(\'%s\')] without an fstab, but no default fstab is configured.', + $serviceId, + $mount, + )); + } + + if (!array_key_exists($fstab, $fstabs) || !is_array($fstabs[$fstab])) { + throw new LogicException(sprintf( + 'Service "%s": #[AsFilesystem] references fstab "%s" which is not configured. Available fstabs: [%s].', + $serviceId, + $fstab, + implode(', ', array_keys($fstabs)), + )); + } + + $mounts = is_array($fstabs[$fstab]['filesystems'] ?? null) ? $fstabs[$fstab]['filesystems'] : []; + + if (!array_key_exists($mount, $mounts)) { + throw new LogicException(sprintf( + 'Service "%s": #[AsFilesystem] references mount "%s" which is not mounted in fstab "%s". Available mounts: [%s].', + $serviceId, + $mount, + $fstab, + implode(', ', array_keys($mounts)), + )); + } + + $definition->setArgument( + '$' . $argument, + new Reference(BuildFstabsPass::FS_SERVICE_PREFIX . $fstab . '.' . $mount), + ); + } + } + } +} diff --git a/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/FlowFilesystemBundle.php b/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/FlowFilesystemBundle.php index c9726020cf..16eb13cd64 100644 --- a/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/FlowFilesystemBundle.php +++ b/src/bridge/symfony/filesystem-bundle/src/Flow/Bridge/Symfony/FilesystemBundle/FlowFilesystemBundle.php @@ -4,14 +4,17 @@ namespace Flow\Bridge\Symfony\FilesystemBundle; +use Flow\Bridge\Symfony\FilesystemBundle\Attribute\AsFilesystem; use Flow\Bridge\Symfony\FilesystemBundle\Attribute\AsFilesystemFactory; use Flow\Bridge\Symfony\FilesystemBundle\DependencyInjection\Compiler\BuildFstabsPass; use Flow\Bridge\Symfony\FilesystemBundle\DependencyInjection\Compiler\RegisterFilesystemFactoriesPass; use Flow\Bridge\Symfony\FilesystemBundle\DependencyInjection\Compiler\RegisterFstabLocatorPass; +use Flow\Bridge\Symfony\FilesystemBundle\DependencyInjection\Compiler\ResolveFilesystemArgumentsPass; use Flow\Bridge\Symfony\FilesystemCache\FlowFilesystemCacheAdapter; use Flow\Filesystem\Filesystem; use Flow\Filesystem\Path; use Override; +use ReflectionParameter; use Reflector; use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; @@ -47,6 +50,7 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new RegisterFilesystemFactoriesPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); $container->addCompilerPass(new BuildFstabsPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new RegisterFstabLocatorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -10); + $container->addCompilerPass(new ResolveFilesystemArgumentsPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -20); $container->registerAttributeForAutoconfiguration(AsFilesystemFactory::class, static function ( ChildDefinition $definition, @@ -55,6 +59,18 @@ public function build(ContainerBuilder $container): void ): void { $definition->addTag(RegisterFilesystemFactoriesPass::TAG, ['type' => $attribute->type]); }); + + $container->registerAttributeForAutoconfiguration(AsFilesystem::class, static function ( + ChildDefinition $definition, + AsFilesystem $attribute, + ReflectionParameter $parameter, + ): void { + $definition->addTag(ResolveFilesystemArgumentsPass::TAG, [ + 'argument' => $parameter->getName(), + 'mount' => $attribute->mount, + 'fstab' => $attribute->fstab ?? '', + ]); + }); } #[Override] diff --git a/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Context/ResolveFilesystemArgumentsPassContext.php b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Context/ResolveFilesystemArgumentsPassContext.php new file mode 100644 index 0000000000..f747760db6 --- /dev/null +++ b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Context/ResolveFilesystemArgumentsPassContext.php @@ -0,0 +1,35 @@ + $config + */ + public function containerWithConfig(array $config): ContainerBuilder + { + $container = new ContainerBuilder(); + $container->setParameter('flow.filesystem.config', $config); + + return $container; + } + + /** + * @param array{argument: string, mount: string, fstab?: string} $tag + */ + public function registerConsumer(ContainerBuilder $container, string $serviceId, array $tag): Definition + { + $definition = new Definition('Flow\\Consumer'); + $definition->addTag(ResolveFilesystemArgumentsPass::TAG, $tag); + $container->setDefinition($serviceId, $definition); + + return $definition; + } +} diff --git a/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Fixtures/AliasFilesystemConsumer.php b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Fixtures/AliasFilesystemConsumer.php new file mode 100644 index 0000000000..84bcbe7692 --- /dev/null +++ b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Fixtures/AliasFilesystemConsumer.php @@ -0,0 +1,14 @@ +bootKernel([ + 'config' => static function (TestKernel $kernel): void { + $kernel->addTestExtensionConfig('flow_filesystem', [ + 'default_fstab' => 'primary', + 'fstabs' => [ + 'primary' => [ + 'filesystems' => [ + 'memory' => ['type' => 'memory'], + ], + ], + 'archive' => [ + 'filesystems' => [ + 'file' => ['type' => 'file'], + ], + ], + ], + ]); + $kernel->addTestContainerConfigurator(static function (ContainerBuilder $container): void { + $container + ->register('test.attribute_consumer', AttributeFilesystemConsumer::class) + ->setAutoconfigured(true) + ->setAutowired(true) + ->setPublic(true); + }); + }, + ]); + + $consumer = $this->symfonyContext()->getService('test.attribute_consumer', AttributeFilesystemConsumer::class); + + static::assertSame('memory', $consumer->primary->mount()->protocol); + static::assertSame('file', $consumer->cold->mount()->protocol); + } +} diff --git a/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Integration/FilesystemServiceRegistrationTest.php b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Integration/FilesystemServiceRegistrationTest.php new file mode 100644 index 0000000000..d84e0f7ba8 --- /dev/null +++ b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Integration/FilesystemServiceRegistrationTest.php @@ -0,0 +1,89 @@ +bootKernel([ + 'config' => static function (TestKernel $kernel): void { + $kernel->addTestExtensionConfig('flow_filesystem', [ + 'fstabs' => [ + 'default' => [ + 'filesystems' => [ + 'memory' => ['type' => 'memory'], + 'file' => ['type' => 'file'], + ], + ], + ], + ]); + }, + ]); + + $filesystem = $this->symfonyContext()->getService(Filesystem::class . ' $memory', Filesystem::class); + + static::assertSame('memory', $filesystem->mount()->protocol); + } + + public function test_prefixed_alias_resolves_named_fstab_mount(): void + { + $this->bootKernel([ + 'config' => static function (TestKernel $kernel): void { + $kernel->addTestExtensionConfig('flow_filesystem', [ + 'default_fstab' => 'primary', + 'fstabs' => [ + 'primary' => [ + 'filesystems' => [ + 'memory' => ['type' => 'memory'], + ], + ], + 'archive' => [ + 'filesystems' => [ + 'file' => ['type' => 'file'], + ], + ], + ], + ]); + }, + ]); + + $filesystem = $this->symfonyContext()->getService(Filesystem::class . ' $archiveFile', Filesystem::class); + + static::assertSame('file', $filesystem->mount()->protocol); + } + + public function test_named_argument_injects_default_mount_into_consumer(): void + { + $this->bootKernel([ + 'config' => static function (TestKernel $kernel): void { + $kernel->addTestExtensionConfig('flow_filesystem', [ + 'fstabs' => [ + 'default' => [ + 'filesystems' => [ + 'memory' => ['type' => 'memory'], + ], + ], + ], + ]); + $kernel->addTestContainerConfigurator(static function (ContainerBuilder $container): void { + $container + ->register('test.alias_consumer', AliasFilesystemConsumer::class) + ->setAutowired(true) + ->setPublic(true); + }); + }, + ]); + + $consumer = $this->symfonyContext()->getService('test.alias_consumer', AliasFilesystemConsumer::class); + + static::assertSame('memory', $consumer->memory->mount()->protocol); + } +} diff --git a/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/Attribute/AsFilesystemTest.php b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/Attribute/AsFilesystemTest.php new file mode 100644 index 0000000000..2fa3737447 --- /dev/null +++ b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/Attribute/AsFilesystemTest.php @@ -0,0 +1,35 @@ +getAttributes(Attribute::class); + + static::assertCount(1, $attributes); + static::assertSame(Attribute::TARGET_PARAMETER, $attributes[0]->newInstance()->flags); + } + + public function test_defaults_fstab_to_null(): void + { + static::assertNull((new AsFilesystem(mount: 'memory'))->fstab); + } + + public function test_exposes_mount_and_fstab_as_public_readonly(): void + { + $attribute = new AsFilesystem(mount: 'file', fstab: 'archive'); + + static::assertSame('file', $attribute->mount); + static::assertSame('archive', $attribute->fstab); + } +} diff --git a/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/DependencyInjection/Compiler/BuildFstabsPassTest.php b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/DependencyInjection/Compiler/BuildFstabsPassTest.php index 44dac32f0b..fbf4f4068a 100644 --- a/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/DependencyInjection/Compiler/BuildFstabsPassTest.php +++ b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/DependencyInjection/Compiler/BuildFstabsPassTest.php @@ -8,6 +8,7 @@ use Flow\Bridge\Symfony\FilesystemBundle\Exception\LogicException; use Flow\Bridge\Symfony\FilesystemBundle\Filesystem\FstabBuilder; use Flow\Bridge\Symfony\FilesystemBundle\Tests\Context\BuildFstabsPassContext; +use Flow\Filesystem\Filesystem; use Flow\Filesystem\FilesystemTable; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -223,4 +224,96 @@ public function test_throws_on_unknown_type_at_compile_time(): void (new BuildFstabsPass())->process($container); } + + public function test_registers_per_mount_filesystem_service_resolving_through_fstab(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => 'default', + 'fstabs' => [ + 'default' => [ + 'filesystems' => [ + 'memory' => ['type' => 'memory'], + ], + ], + ], + ]); + + (new BuildFstabsPass())->process($container); + + $definition = $container->getDefinition('.flow.filesystem.fs.default.memory'); + + static::assertSame(Filesystem::class, $definition->getClass()); + static::assertEquals([new Reference('.flow.filesystem.fstab.default'), 'for'], $definition->getFactory()); + static::assertSame(['memory'], $definition->getArguments()); + static::assertFalse($definition->isPublic()); + } + + public function test_registers_bare_and_prefixed_mount_aliases_for_default_fstab(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => 'default', + 'fstabs' => [ + 'default' => [ + 'filesystems' => [ + 'memory' => ['type' => 'memory'], + ], + ], + ], + ]); + + (new BuildFstabsPass())->process($container); + + static::assertSame( + '.flow.filesystem.fs.default.memory', + (string) $container->getAlias(Filesystem::class . ' $memory'), + ); + static::assertSame( + '.flow.filesystem.fs.default.memory', + (string) $container->getAlias(Filesystem::class . ' $defaultMemory'), + ); + } + + public function test_registers_only_prefixed_mount_alias_for_non_default_fstab(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => 'primary', + 'fstabs' => [ + 'primary' => [ + 'filesystems' => [ + 'memory' => ['type' => 'memory'], + ], + ], + 'secondary' => [ + 'filesystems' => [ + 'file' => ['type' => 'file'], + ], + ], + ], + ]); + + (new BuildFstabsPass())->process($container); + + static::assertTrue($container->hasAlias(Filesystem::class . ' $secondaryFile')); + static::assertFalse($container->hasAlias(Filesystem::class . ' $file')); + } + + public function test_camel_cases_separator_protocols_in_mount_aliases(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => 'default', + 'fstabs' => [ + 'default' => [ + 'filesystems' => [ + 'aws-s3' => ['type' => 'memory'], + ], + ], + ], + ]); + + (new BuildFstabsPass())->process($container); + + static::assertTrue($container->hasDefinition('.flow.filesystem.fs.default.aws-s3')); + static::assertTrue($container->hasAlias(Filesystem::class . ' $awsS3')); + static::assertTrue($container->hasAlias(Filesystem::class . ' $defaultAwsS3')); + } } diff --git a/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/DependencyInjection/Compiler/ResolveFilesystemArgumentsPassTest.php b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/DependencyInjection/Compiler/ResolveFilesystemArgumentsPassTest.php new file mode 100644 index 0000000000..3485769a31 --- /dev/null +++ b/src/bridge/symfony/filesystem-bundle/tests/Flow/Bridge/Symfony/FilesystemBundle/Tests/Unit/DependencyInjection/Compiler/ResolveFilesystemArgumentsPassTest.php @@ -0,0 +1,138 @@ +context = new ResolveFilesystemArgumentsPassContext(); + } + + public function test_binds_argument_to_default_fstab_mount_when_fstab_omitted(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => 'primary', + 'fstabs' => [ + 'primary' => ['filesystems' => ['memory' => ['type' => 'memory']]], + ], + ]); + $definition = $this->context->registerConsumer($container, 'app.consumer', [ + 'argument' => 'filesystem', + 'mount' => 'memory', + 'fstab' => '', + ]); + + (new ResolveFilesystemArgumentsPass())->process($container); + + static::assertEquals( + new Reference('.flow.filesystem.fs.primary.memory'), + $definition->getArgument('$filesystem'), + ); + } + + public function test_binds_argument_to_explicit_fstab_mount(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => 'primary', + 'fstabs' => [ + 'primary' => ['filesystems' => ['memory' => ['type' => 'memory']]], + 'archive' => ['filesystems' => ['file' => ['type' => 'file']]], + ], + ]); + $definition = $this->context->registerConsumer($container, 'app.consumer', [ + 'argument' => 'coldStorage', + 'mount' => 'file', + 'fstab' => 'archive', + ]); + + (new ResolveFilesystemArgumentsPass())->process($container); + + static::assertEquals( + new Reference('.flow.filesystem.fs.archive.file'), + $definition->getArgument('$coldStorage'), + ); + } + + public function test_does_nothing_when_config_parameter_missing(): void + { + $container = new ContainerBuilder(); + + (new ResolveFilesystemArgumentsPass())->process($container); + + static::assertFalse($container->hasDefinition('app.consumer')); + } + + public function test_throws_when_no_default_fstab_and_fstab_omitted(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => null, + 'fstabs' => [ + 'primary' => ['filesystems' => ['memory' => ['type' => 'memory']]], + ], + ]); + $this->context->registerConsumer($container, 'app.consumer', [ + 'argument' => 'filesystem', + 'mount' => 'memory', + 'fstab' => '', + ]); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Service "app.consumer" uses #[AsFilesystem(\'memory\')] without an fstab'); + + (new ResolveFilesystemArgumentsPass())->process($container); + } + + public function test_throws_on_unknown_fstab(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => 'primary', + 'fstabs' => [ + 'primary' => ['filesystems' => ['memory' => ['type' => 'memory']]], + ], + ]); + $this->context->registerConsumer($container, 'app.consumer', [ + 'argument' => 'filesystem', + 'mount' => 'memory', + 'fstab' => 'nope', + ]); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('references fstab "nope" which is not configured. Available fstabs: [primary]'); + + (new ResolveFilesystemArgumentsPass())->process($container); + } + + public function test_throws_on_unknown_mount(): void + { + $container = $this->context->containerWithConfig([ + 'default_fstab' => 'primary', + 'fstabs' => [ + 'primary' => ['filesystems' => ['memory' => ['type' => 'memory']]], + ], + ]); + $this->context->registerConsumer($container, 'app.consumer', [ + 'argument' => 'filesystem', + 'mount' => 'warehouse', + 'fstab' => 'primary', + ]); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage( + 'references mount "warehouse" which is not mounted in fstab "primary". Available mounts: [memory]', + ); + + (new ResolveFilesystemArgumentsPass())->process($container); + } +} diff --git a/src/lib/filesystem/src/Flow/Filesystem/FilesystemTable.php b/src/lib/filesystem/src/Flow/Filesystem/FilesystemTable.php index 06bd9c413a..c413bcce70 100644 --- a/src/lib/filesystem/src/Flow/Filesystem/FilesystemTable.php +++ b/src/lib/filesystem/src/Flow/Filesystem/FilesystemTable.php @@ -36,9 +36,9 @@ public function filesystems(): array return array_values($this->mounts); } - public function for(Path|string $protocol): Filesystem + public function for(Path|string $mount): Filesystem { - $name = $protocol instanceof Path ? $protocol->protocol() : $protocol; + $name = $mount instanceof Path ? $mount->protocol() : $mount; if (!array_key_exists($name, $this->mounts)) { throw new InvalidArgumentException("Filesystem with protocol {$name} is not mounted.");