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

Add Dependency Injection integration #996

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft

Add Dependency Injection integration #996

wants to merge 1 commit into from

Conversation

pierredup
Copy link
Member

This adds a Dependency Injection container which is used for all configuration options, actions and extensions

Motivation

Currently a custom dependency injection container is used for the gateway config, where config options, actions etc. are added to an ArrayObject instance. This has support for factories, which uses a function to construct a class and has access to all the values defined in the ArrayObject instance.

This has been replaced with an external dependency injection container using PHP-DI.

Benefits

There are several benefits with using PHP-DI as a container:

  • Autowiring
  • Lazy object creation
  • Automatic object creation without any config
    • For example, you can just call $container->get(SomeClass::class) and get an instance of the class with all the dependencies resolved without having to specify any configuration
  • Compatibility with psr/container

Having a container providing services is already available in the Symfony and Laravel integration. This just adds it to Core so that we can benefit from Dependency Injection outside of any framework integration.

Side Effects

This implementation also has some side-effects:

We can no longer determine if a config value should be an action or an extension without iterating through the entire container definition.

This means we are deprecating the payum.action.* and payum.extension.* config options

Instead, we introduce a new way to specify actions and extensions for a gateway by introducing a GatewayFactoryConfigInterface interface

interface GatewayFactoryConfigInterface
{
    public function createGateway(ContainerInterface $container): Gateway;

    /**
     * @return array<string, class-string<ActionInterface>>|list<class-string<ActionInterface>>
     */
    public function getActions(): array;

    /**
     * @return list<ExtensionInterface|class-string<ExtensionInterface>>
     */
    public function getExtensions(): array;
}

This interface provides dedicated methods for actions and interfaces, and also provides a method for creating the gateway which has access to the container.

Here is an example from the CoreGatewayFactory:

public function getActions(): array
{
    return [
        GetHttpRequestAction::class,
        CapturePaymentAction::class,
        AuthorizePaymentAction::class,
        PayoutPayoutAction::class,
        ExecuteSameRequestWithModelDetailsAction::class,
        RenderTemplateAction::class,
        GetCurrencyAction::class,
        GetTokenAction::class,
    ];
}

This also means we are deprecating the GatewayFactoryInterface in favour of the GatewayFactoryConfigInterface.

Usage

If a factory wants to provide it's own configuration for a container, the gateway factory can implement the ContainerConfiguration interface, which has the method public function configureContainer(): array; in order to define it's own container configuration (which will be merged with the config from CoreGateFactory), E.G

public function configureContainer(): array {
    return [
        SomeCustomClass:class,
    ];
}

If you want to override or decorate an existing service (E.G provide you own implementation for the GetCurrencyAction), you can use the built-in features from PHP-DI:

public function configureContainer(): array {
    return [
        GetCurrencyAction:class => \DI\decorate(function ($previous, ContainerInterface $c) {
        return new CustomGetCurrencyAction($previous, $c->get('some.other.service'));
    }),
    ];
}

Important: The container instance is unique for each gateway. This means if you define a service on one gateway, then it won't be available in the container for another gateway.

Backward Compatibility

The current implementation is fully backward compatible with the current gateway configuration. This means that all existing gateways will continue working, and new gateways can implement the new interfaces and start using the new container. Existing gateways can also migrate to the newer container configuration whenever they want without any BC breaks.

TODO

  • Add a way to define global service definitions.
    • Currently only the CoreGatewayFactory defines services that is available to all gateways. We need an extension point where we can add global services that will be available for every gateway
  • Add other global instances to the container (HttpRequestVerifierInterface, TokenFactoryInterface, StorageInterface etc)
    • This should allow these services to be available in every container instance, but also provide a way to override it (I.E provide a custom implementation)
  • Ensure the container can work together with other containers (E.G The Symfony container when using the PayumBundle)

This feature paves the way for much greater flexibility and additional features on the roadmap.

This is just a first-pass of the implementation. There are still lots to do, but opening this up now for some initial feedback

@pierredup pierredup self-assigned this Apr 4, 2024
@pierredup pierredup added this to the 2.0.0 milestone Apr 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

None yet

1 participant