[DX] Extend adapter builder#186
Conversation
|
After a second thought, we could open it this way and think about the last 2 points later. |
4e314a1 to
856dbe6
Compare
ceb7865 to
409f306
Compare
GromNaN
left a comment
There was a problem hiding this comment.
I added some random comments. I think it's a good idea to review this.
| ->end(); | ||
| } | ||
|
|
||
| public function createAdapter(ContainerBuilder $container, string $storageName, array $options, ?string $defaultVisibilityForDirectories): string |
There was a problem hiding this comment.
This method should receive a ContainerConfigurator. The PHP-DSL for service declaration is very convenient. It's the same as service.php config files.
Example of how it's used in the MicroKernelTrait: https://github.com/symfony/symfony/blob/e48850bdb8161e1aa1338432ea469932857b2df5/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php#L194
There was a problem hiding this comment.
That's probably very convenient, but we need to dynamically register adapter services based on configuration. Since the conditions aren't always known in advance, the ContainerBuilder gives us direct access to service registration methods, which seems more straightforward for this conditional, programmatic service creation.
| Once registered, you can use the `debug:config flysystem` command to see your custom adapter | ||
| and all its available options in the configuration tree. | ||
|
|
||
| ### Testing your custom builder |
There was a problem hiding this comment.
Thanks for adding this section. Tests are too often skipped because people don't know how to write them.
| public function getRequiredPackages(): array | ||
| { | ||
| // Return required packages for your adapter | ||
| // Format: ['ClassName' => 'vendor/package-name'] | ||
| return []; | ||
| } |
There was a problem hiding this comment.
The required package is not only about the package name, but also the version. Does this syntax allow adding the version constraint after the package name?
There was a problem hiding this comment.
No, this syntax doesn't support version constraints. It could be interesting, but the use cases are very limited (if not non-existent).
Currently, this mechanism is very useful for official adapters because their packages are separate and have very specific dependencies for each use case.
For a public builder API, the adapter package should theoretically be self-contained. For example, with azure-oss/storage-blob-flysystem, there are two approaches: either Azure OSS provides a dedicated bundle to manage their adapter, or the adapter package includes the AdapterBuilder and users manually register it via the Kernel.
| /** @var FlysystemExtension $extension */ | ||
| $extension = $container->getExtension('flysystem'); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\AsyncAwsAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\AwsAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\AzureAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\FtpAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\GcloudAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\GridFSAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\LazyAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\LocalAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\MemoryAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\SftpAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\WebDAVAdapterDefinitionBuilder()); | ||
| $extension->addAdapterDefinitionBuilder(new Builder\BunnyCDNAdapterDefinitionBuilder()); |
There was a problem hiding this comment.
Features such as autoconfiguration cannot be used to automatically inject an adapter just by creating a class. This function must be called in the bundle or kernel to explicitly add the adapter class.
There was a problem hiding this comment.
Yes, it follows the same design pattern as Symfony's security authenticators. I don't think there's any other way (at least for now) to make them available in the TreeBuilder configuration.
409f306 to
ef2675f
Compare
2b035a2 to
93d5eee
Compare
93d5eee to
2a37a94
Compare
|
It's time to move forward on this topic ! |
This PR implements a solution to the issue discussed in thephpleague#181, enabling external bundles to register their custom storage adapters with Flysystem Bundle without requiring them to be directly added to the main bundle's codebase. I had to remove the `@internal` annotation from `AdapterDefinitionBuilderInterface` and `AbstractAdapterDefinitionBuilder` to allow them to be referenced by other bundles. External bundles can now register their adapters as follows: ``` php class AzureBlobStorageAdapterBundle extends Bundle { /** * {@inheritdoc} */ public function build(ContainerBuilder $container): void { parent::build($container); /** @var FlysystemExtension $extension */ $extension = $container->getExtension('flysystem'); $extension->addAdapterDefinitionBuilder(new AzureOssAdapterDefinitionBuilder()); } } ```
2a37a94 to
6930e66
Compare
|
@maxhelias not sure if you are aware of this but current 3.x seems be broken:
wanted to let you know before you plan a new release :) |
|
Oh thanks @alexander-schranz for the feedback, I'll take a look |
…lias) This PR was merged into the 5.x branch. Discussion ---------- fix(docs): fix configuration on documentation file Since thephpleague/flysystem-bundle#186 Commits ------- dbef511 fix(docs): fix configuration on documentation file
Description ----------- The changes from thephpleague/flysystem-bundle#186 got released as `3.7.0` and as the `@internal` class `League\FlysystemBundle\Adapter\AdapterDefinitionFactory` was deleted there we need conflict with that version until we update our usage of that class. Commits ------- 21f5878 Conflict with league/flysystem-bundle 3.7
Description ----------- The changes from thephpleague/flysystem-bundle#186 got released as `3.7.0` and as the `@internal` class `League\FlysystemBundle\Adapter\AdapterDefinitionFactory` was deleted there we need conflict with that version until we update our usage of that class. Commits ------- 21f58781 Conflict with league/flysystem-bundle 3.7
…em adapter Introduces a new sub-package `azure-oss/storage-blob-flysystem-symfony` that registers an `azure_oss` adapter shortcut with `league/flysystem-bundle` 3.7+'s pluggable `AdapterDefinitionBuilderInterface` (thephpleague/flysystem-bundle#186). This is the canonical extension point: per-adapter builders now live in the adapter package rather than being merged into flysystem-bundle's core builder list. The author of PR thephpleague/flysystem-bundle#181 ("feat: add Azure OSS adapter") was pointed at #186 as the recommended approach; this package implements that. New sub-package: src/BlobFlysystemSymfony/ - AzureOssAdapterDefinitionBuilder: maps the `azure_oss` config key to a BlobContainerClient (built via a user-supplied BlobServiceClient service id) and the AzureBlobStorageAdapter. - AzureOssFlysystemBundle: extends Symfony's Bundle and registers the builder on flysystem-bundle's FlysystemExtension during build(), mirroring how FlysystemBundle wires its own built-in adapters. - composer.json declares `type: symfony-bundle` so Symfony Flex auto-registers the bundle for users who have flex installed. Configuration shape (see src/BlobFlysystemSymfony/README.md for the full reference): flysystem: storages: default.storage: azure_oss: client: azure_blob_service_client container: '%env(AZURE_STORAGE_CONTAINER)%' # optional: prefix, mime_type_detector, # visibility_handling (throw|ignore), public_container Tests under tests/BlobFlysystemSymfony/: - AzureOssAdapterDefinitionBuilderTest extends flysystem-bundle's AbstractAdapterDefinitionBuilderTest (the same base used by the bundle's built-in builders), driving minimal and full option fixtures through the Config tree and asserting on the resulting Definition objects. Adds extra coverage for getName(), getRequiredPackages(), and the null-mime-type-detector path. - AzureOssFlysystemBundleTest boots a ContainerBuilder, runs both FlysystemBundle::build() and AzureOssFlysystemBundle::build() in the same order Symfony's Kernel would, and asserts that the azure_oss builder is registered on the FlysystemExtension. Root composer.json gains four require-dev entries needed by the new package's CI path: `league/flysystem-bundle`, `symfony/config`, `symfony/dependency-injection`, `symfony/http-kernel`. No other config changes — autoload PSR-4 `AzureOss\\Storage\\` -> `src/` already covers the new directory; phpunit.xml's `tests/` glob picks up the new test suite; phpstan and pint configs apply unchanged. The new package follows the existing conventions: - final class with @internal PHPDoc, declare(strict_types=1) - pint Laravel preset with declare_strict_types + final_internal_class - PHPStan level 10 + strict-rules clean (no asserts/@var/ignores) Verification: - vendor/bin/phpstan analyse --memory-limit=2G : OK - vendor/bin/phpunit --filter 'BlobFlysystemSymfony' : 8 tests / 32 assertions, all green - vendor/bin/pint src tests --test : PASS (146 files) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Before merging this PR: #182
I suggested in this comment a rework of parts of the bundle to improve separation of concerns and provide a better developer experience (DX), in preparation for allowing third parties to create their own custom adapters.
Proposed changes
Externalize the
getRequiredPackages()logicAdapterDefinitionBuilderInterface.AdapterDefinitionFactoryhandle the package check before callingcreateDefinition().AbstractAdapterDefinitionBuilderExtract Unix visibility/permissions logic
configureUnixOptions()andcreateUnixDefinition()out of the abstract base class.ProvidesUnixPermissions) – easier to share but harder to testUnixVisibilityHelper) – more testable but less flexibleAbstractAdapterDefinitionBuilderMake builder options discoverable via Symfony's config system
addConfiguration(NodeBuilder $builder)(similar to what Symfony Security does)adapterandoptions-based configuration style.Clean up the abstract class
AbstractAdapterDefinitionBuilderdown to only keepcreateDefinition().configureOptionsconfigureDefinitionI started this PR as a first step, like an RFC. Any feedback is welcome 😉
Benefits of this new config format :
debug:config flysystemandconfig:dump-referenceshow all available adapters and their options.