-
Notifications
You must be signed in to change notification settings - Fork 73
[DX] Extend adapter builder #186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 3.x
Are you sure you want to change the base?
Changes from all commits
34c25a8
e583a78
2a6dd83
856dbe6
ef2675f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,5 +32,201 @@ storages: | |
flysystem: | ||
storages: | ||
users.storage: | ||
adapter: 'App\Flysystem\MyCustomAdapter' | ||
``` | ||
service: 'App\Flysystem\MyCustomAdapter' | ||
``` | ||
|
||
## Creating a custom adapter builder (advanced) | ||
|
||
For more complex custom adapters that require configuration validation, IDE auto-completion, | ||
and integration with the bundle's configuration system, you can create a custom adapter builder. | ||
|
||
This allows you to define your custom adapter directly in the configuration: | ||
|
||
```yaml | ||
# config/packages/flysystem.yaml | ||
|
||
flysystem: | ||
storages: | ||
users.storage: | ||
my_custom: # Your custom adapter type | ||
option1: 'value1' | ||
option2: 'value2' | ||
``` | ||
|
||
### Creating the adapter builder | ||
|
||
Create a class implementing `AdapterDefinitionBuilderInterface`: | ||
|
||
```php | ||
<?php | ||
|
||
namespace App\Flysystem\Builder; | ||
|
||
use App\Flysystem\MyCustomAdapter; | ||
use League\FlysystemBundle\Adapter\Builder\AdapterDefinitionBuilderInterface; | ||
use Symfony\Component\Config\Definition\Builder\NodeDefinition; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
class MyCustomAdapterDefinitionBuilder implements AdapterDefinitionBuilderInterface | ||
{ | ||
public function getName(): string | ||
{ | ||
return 'my_custom'; | ||
} | ||
|
||
public function getRequiredPackages(): array | ||
{ | ||
// Return required packages for your adapter | ||
// Format: ['ClassName' => 'vendor/package-name'] | ||
return []; | ||
} | ||
Comment on lines
+78
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this syntax doesn't support version constraints. It could be interesting, but the use cases are very limited (if not non-existent). |
||
|
||
public function addConfiguration(NodeDefinition $node): void | ||
{ | ||
$node | ||
->children() | ||
->scalarNode('option1') | ||
->isRequired() | ||
->info('Description of option1') | ||
->end() | ||
->scalarNode('option2') | ||
->defaultValue('default_value') | ||
->info('Description of option2') | ||
->end() | ||
->booleanNode('option3') | ||
->defaultFalse() | ||
->info('Description of option3') | ||
->end() | ||
->end(); | ||
} | ||
|
||
public function createAdapter(ContainerBuilder $container, string $storageName, array $options, ?string $defaultVisibilityForDirectories): string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method should receive a Example of how it's used in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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. |
||
{ | ||
$adapterId = 'flysystem.adapter.' . $storageName; | ||
|
||
$definition = new Definition(MyCustomAdapter::class); | ||
$definition->setPublic(false); | ||
|
||
// Configure your adapter with the options | ||
$definition->setArgument(0, $options['option1']); | ||
$definition->setArgument(1, $options['option2']); | ||
$definition->setArgument(2, $options['option3']); | ||
|
||
$container->setDefinition($adapterId, $definition); | ||
|
||
return $adapterId; | ||
} | ||
} | ||
``` | ||
|
||
### Registering the adapter builder | ||
|
||
#### Via Bundle (recommended for reusable bundles) | ||
|
||
If you're creating a bundle, register your builder in your bundle class: | ||
|
||
```php | ||
<?php | ||
|
||
namespace App\MyCustomBundle; | ||
|
||
use App\Flysystem\Builder\MyCustomAdapterDefinitionBuilder; | ||
use League\FlysystemBundle\FlysystemBundle; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\HttpKernel\Bundle\Bundle; | ||
|
||
class MyCustomBundle extends Bundle | ||
{ | ||
public function build(ContainerBuilder $container): void | ||
{ | ||
parent::build($container); | ||
|
||
// Register your custom adapter builder | ||
$extension = $container->getExtension('flysystem'); | ||
if ($extension instanceof FlysystemExtension) { | ||
$extension->addAdapterDefinitionBuilder(new MyCustomAdapterDefinitionBuilder()); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
#### Via Kernel (for application-specific adapters) | ||
|
||
For application-specific adapters, register your builder in your `Kernel`: | ||
|
||
```php | ||
<?php | ||
|
||
namespace App; | ||
|
||
use App\Flysystem\Builder\MyCustomAdapterDefinitionBuilder; | ||
use League\FlysystemBundle\DependencyInjection\FlysystemExtension; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\HttpKernel\Kernel as BaseKernel; | ||
|
||
class Kernel extends BaseKernel | ||
{ | ||
// ... | ||
|
||
protected function build(ContainerBuilder $container): void | ||
{ | ||
parent::build($container); | ||
|
||
// Register your custom adapter builder | ||
$extension = $container->getExtension('flysystem'); | ||
if ($extension instanceof FlysystemExtension) { | ||
$extension->addAdapterDefinitionBuilder(new MyCustomAdapterDefinitionBuilder()); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for adding this section. Tests are too often skipped because people don't know how to write them. |
||
|
||
Create a test class extending `AbstractAdapterDefinitionBuilderTest`: | ||
|
||
```php | ||
<?php | ||
|
||
namespace Tests\App\Flysystem\Builder; | ||
|
||
use App\Flysystem\Builder\MyCustomAdapterDefinitionBuilder; | ||
use App\Flysystem\MyCustomAdapter; | ||
use League\FlysystemBundle\Test\AbstractAdapterDefinitionBuilderTest; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
class MyCustomAdapterDefinitionBuilderTest extends AbstractAdapterDefinitionBuilderTest | ||
{ | ||
protected function createBuilder(): MyCustomAdapterDefinitionBuilder | ||
{ | ||
return new MyCustomAdapterDefinitionBuilder(); | ||
} | ||
|
||
public static function provideValidOptions(): \Generator | ||
{ | ||
yield 'minimal' => [[ | ||
'option1' => 'value1', | ||
]]; | ||
|
||
yield 'full' => [[ | ||
'option1' => 'value1', | ||
'option2' => 'custom_value', | ||
'option3' => true, | ||
]]; | ||
} | ||
|
||
protected function assertDefinition(Definition $definition): void | ||
{ | ||
$this->assertSame(MyCustomAdapter::class, $definition->getClass()); | ||
$this->assertSame('value1', $definition->getArgument(0)); | ||
$this->assertSame('custom_value', $definition->getArgument(1)); | ||
$this->assertTrue($definition->getArgument(2)); | ||
} | ||
} | ||
``` | ||
|
||
This provides comprehensive testing of your builder's configuration and adapter creation logic. |
Uh oh!
There was an error while loading. Please reload this page.