Skip to content
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

Introduce PluginConfigurator #477

Merged
merged 3 commits into from
Nov 24, 2024
Merged

Introduce PluginConfigurator #477

merged 3 commits into from
Nov 24, 2024

Conversation

ruudk
Copy link
Contributor

@ruudk ruudk commented Nov 18, 2024

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Related tickets
Documentation
License MIT

What's in this PR?

This allows for configuring plugins via the normal config.

When you have a plugin that does not require config, the old behavior is the easiest.

But when you do want custom config per client, it will be cumbersome to create separate services
and reference them everywhere.

It would be easier to have the same type of configuration as the built-in clients.

That's now possible with the PluginConfigurator.

Example Usage

Define the plugins as follows:

'plugins' => [
    [
        'configurator' => [
            'id' => CustomPluginConfigurator::class,
            'config' => [
                'name' => 'foo',
            ]
        ],
    ],
],

The CustomPluginConfigurator looks like this:

final class CustomPluginConfigurator implements PluginConfigurator
{
    public static function getConfigTreeBuilder() : TreeBuilder
    {
        $treeBuilder = new TreeBuilder('custom_plugin');
        $rootNode = $treeBuilder->getRootNode();

        $rootNode
            ->children()
                ->scalarNode('name')
                    ->isRequired()
                    ->cannotBeEmpty()
                ->end()
            ->end();

        return $treeBuilder;
    }

    public function create(array $config) : CustomPlugin
    {
        return new CustomPlugin($config['name']);
    }
}

On compile time, the config will be evaluated. It will create the service definition
by calling the create method with the given config.

On runtime you will have the CustomPlugin instantiated with the custom config.

Checklist

  • Updated CHANGELOG.md to describe BC breaks / deprecations | new feature | bugfix
  • Documentation pull request created (if not simply a bugfix)

@ruudk
Copy link
Contributor Author

ruudk commented Nov 18, 2024

@dbu @Nyholm Curious what you thin of this 😊

@ruudk
Copy link
Contributor Author

ruudk commented Nov 19, 2024

In this PR I went for a pragmatic solution that rides on the current reference implementation.

But maybe it would be better/nicer if we removed the built-in config completely, and use this configurator pattern for all of them.

It would allow writing the config like this:

'plugins' => [
    [
        'my_custom_plugin' => [
            'name' => 'foo',
        ],
    ],
    [
        'base_uri' => [
            'uri' => env('SOME_ENDPOINT'),
        ],
    ],
    [
        'header_defaults' => [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'User-Agent' => 'TicketSwap',
            ],
        ],
    ],
],

In order to use custom plugin configurator, you have to register it by adding it to the plugin_configurators with an alias.

'plugin_configurators' => [
    'my_custom_plugin' => CustomPluginConfigurator::class,
],

The built-in plugins (header_defaults, etc) are already configured there.

Now the Extension can check the config of each plugin, by calling its configurator.

@dbu
Copy link
Collaborator

dbu commented Nov 20, 2024

i like what you are aiming for here. i think hijacking reference for this is a bit confusing. the idea of reference is that you configure the plugin as a service and reference that service, which has its use e.g. when the configuration is more complicated and you have several clients that should share the plugin.

if you are up for it, i really like your second suggestion of modularizing the plugin configuration definition so that custom plugins can look the same as the default plugins. besides the explicit configuration, we should also support autowiring based on the interface and a DI tag. for autowiring, the PluginConfigurator interface needs to have a method to request the name to be used for the plugin as well.
our internal configurators should be configured with an explicit map or explicit tags in the service definition, not autowired as per the bundle best practices.

reference must be a reserved name and continue to work. imho we should throw an exception when two services try to define the same plugin name.

as long as the names of the built-in plugins do not change (and they should not) i think this can be done as minor version, there is no BC break involved.

do you want to dig into that?

@ruudk
Copy link
Contributor Author

ruudk commented Nov 21, 2024

I tried to do the above today but realized it is a lot of work and I don't know all the internals how this connects. I also don't think it's worth the effort.

But I think I have a better solution now.

Instead of hijacking the reference implementation I created a configurator one.

Please have a look again @dbu

Copy link
Collaborator

@dbu dbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i can see this would be a significant rewrite of the whole configuration mechanics.

i think your solution is good enough - the upside also is that it avoids confusion as it is explicit when using a 3rd party plugin.

can you please add a note in the changelog and a short section in the doc to explain how this works?
https://github.com/php-http/documentation/blob/main/integrations/symfony-bundle.rst
plus https://github.com/php-http/documentation/blob/main/integrations/symfony-full-configuration.rst for the full config reference

This allows for configuring plugins via the normal config.

When you have a plugin that does not require config, the old behavior is the easiest.

But when you do want custom config per client, it will be cumbersome to create separate services
and reference them everywhere.

It would be easier to have the same type of configuration as the built-in clients.

That's now possible with the PluginConfigurator.

Define the plugins as follows:
```yaml
'plugins' => [
    [
        'configurator' => [
            'id' => CustomPluginConfigurator::class,
            'config' => [
                'name' => 'foo',
            ]
        ],
    ],
],
```

The `CustomPluginConfigurator` looks like this:
```php
final class CustomPluginConfigurator implements PluginConfigurator
{
    public static function getConfigTreeBuilder() : TreeBuilder
    {
        $treeBuilder = new TreeBuilder('custom_plugin');
        $rootNode = $treeBuilder->getRootNode();

        $rootNode
            ->children()
                ->scalarNode('name')
                    ->isRequired()
                    ->cannotBeEmpty()
                ->end()
            ->end();

        return $treeBuilder;
    }

    public function create(array $config) : CustomPlugin
    {
        return new CustomPlugin($config['name']);
    }
}
```

On compile time, the config will be evaluated. It will create the service definition
by calling the `create` method with the given config.

On runtime you will have the CustomPlugin instantiated with the custom config.
ruudk added a commit to ruudk/documentation that referenced this pull request Nov 23, 2024
@ruudk
Copy link
Contributor Author

ruudk commented Nov 23, 2024

Added the changelog and created a PR for the documentation:

dbu pushed a commit to php-http/documentation that referenced this pull request Nov 24, 2024
@ruudk
Copy link
Contributor Author

ruudk commented Nov 24, 2024

Now that the docs are merged, should we also merge this one? Thanks!

CHANGELOG.md Outdated Show resolved Hide resolved
Copy link
Collaborator

@dbu dbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep. i was interrupted by breakfast :)

@dbu dbu merged commit 451c065 into php-http:2.x Nov 24, 2024
13 checks passed
@dbu
Copy link
Collaborator

dbu commented Nov 24, 2024

@dbu
Copy link
Collaborator

dbu commented Nov 24, 2024

thanks a lot again, nice contribution 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants