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 parameters to the DecoratorResolver callable call #801

Open
gusarov112 opened this issue Dec 1, 2021 · 2 comments
Open

Add parameters to the DecoratorResolver callable call #801

gusarov112 opened this issue Dec 1, 2021 · 2 comments

Comments

@gusarov112
Copy link

gusarov112 commented Dec 1, 2021

Given

class DecoratorDefinition extends FactoryDefinition implements Definition, ExtendsPreviousDefinition

private $parameters = [];

DecoratorDefinition extends FactoryDefinition, but resolving script does not pass parameters to decorator callable as it is done in the FactoryResolver.
return call_user_func($callable, $decorated, $this->container);

return $this->invoker->call($callable, $providedParams);

Proposal

Parameters from $definition->getParameters() should be added to the decorator factory callable call.

call_user_func method call in DecoratorResolver.

return call_user_func($callable, $decorated, $this->container, $definition->getParameters());

call_user_func method call in Compiler.

$code = sprintf(
                    'return call_user_func(%s, %s, $this->delegateContainer, %s);',
                    $this->compileValue($definition->getCallable()),
                    $this->compileValue($decoratedDefinition),
                    $this->compileValue($definition->getParameters())
                );

I think plain parameters (without resolution) would be enough, because you already have your $value resolved, and you have $container, I do not think you need to pass unresolved definitions as a parameters, you can resolve everything from container using your factory callable.

How to use then

And when you create your definition, for example using the helper, you do:

$compilableClosure = function ($value, ContainerInterface $container, array $params) {
    ... modify $value using data from $params, use container if you need ...
    return $value;
};
DI\decorate($compilableClosure)->parameter('foo', 'bar');

When it is useful

When you want to decorate scalar values, for example value from EnvironmentVariableDefinition.
Environment variable could be resolved only to string value now, but with decorator you can decorate your definition, and then split your string, cast it, or even create a value object from it.

@mnapoli
Copy link
Member

mnapoli commented Dec 13, 2021

Can you show a code example that isn't working right now, and that would work with your suggestion? That will help understand.

@gusarov112
Copy link
Author

gusarov112 commented Dec 15, 2021

# We define env parameters in some core definition, let's assume we use "core library" in a several microservices,
# and we want to put this core definitions into the library.
# What I want to achieve is an analogue of symfony EnvVarProcessor,
# and benefit from container compilation in the meantime.

class CoreParametersDefinitions extends DefinitionArray {
    public function __construct() {
#  Environment "HOSTS_LIST=host1,host2" is a list of a hosts
# but this could be any other primitive like boolean or double, or it could be a json string
# So we could have different processors
        parent::__construct( [ 'env(csv:HOSTS_LIST)' => env('HOSTS_LIST')]); 
    }
}
class EnvVarProcessorDefinitions extends DefinitionArray {
    public function __construct() {
        parent::__construct(
              [
# We can add any processor we want. So this 'csv' processor here will process env(csv:HOSTS_LIST)
                   EnvVarProcessorFactory::class => autowire()
                             ->method('addProcessor', 'csv', get(CSVEnvVarProcessor::class))
              ]
          ); 
    }
}

# This is a part of a "core library" - we decorate definitions before container is compiled. 
class DecoratedDefinitions extends DefinitionArray {
    public function __construct(DefinitionSource $previousDefinitions) {
        $normalizedEnvDefinitions = [];
        foreach ($previousDefinitions->getDefinitions() as $key => $definition) {
            if (strpos($key, 'env(') === 0 && strpos($key, ':') > 0) {
                preg_match('/^env\(([^:]+):/', $key, $matches);

                #Does not use "static"/"self"/"$this" or "use" keywords, and could be compiled.
                $compilableClosure = function ($value, ContainerInterface $container, array $params) {
                    #The issue is here - currently we have only value in the closure, and params are empty,
                    # but it would be nice to have params here
                    return $container->get(EnvVarProcessorFactory::class)->getProcessor($params['type'])->process($value);
                };
                # decorate and pass the parameters to the helper.
                # Currently the values we pass to the 'parameter()' method also are passed to the DecoratorDefinition,
                # but later that parameters are not used during resolution or compilation.
                $normalizedEnvDefinitions[$key] = decorate($compilableClosure)
                    ->parameter('type', $matches[1]);

                $keyWithoutType                    = str_replace($matches[1] . ':', '', $key);
                $normalizedEnvDefinitions[$keyWithoutType] = get($key);
            }
        }
        parent::__construct($normalizedEnvDefinitions);
    }
}
# Factory could be configured by adding different processors - predefined and custom ones
class EnvVarProcessorFactory {
    private array $processors = [];
    public function addProcessor(string $type, EnvVarProcessor $processor): void {
           $this->processors[$type] = $processor;
    }
    public function getProcessor(string $type) {
          return $this->processors[$type];
    }
}

class MyService {
    public function __construct(array $hosts);
}
class ServiceDefinitions extends DefinitionArray {
    public function __construct() {
        parent::__construct( [MyService::class=> autowire()->constructorParameter('hosts', get('env(HOSTS_LIST)'))]); 
    }
}

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

No branches or pull requests

2 participants