zendframework / zend-servicemanager

ServiceManager component from Zend Framework
BSD 3-Clause "New" or "Revised" License
188 stars 89 forks source link

[Feature Request] Add context binding or something like that #284

Closed khuyennguyen7007 closed 5 years ago

khuyennguyen7007 commented 5 years ago

I'm using zend service manager to manage my dependencies. In this case, I want to inject params to constructor base on context. Specifically, I have an SearchManager class, if in Application\User\Index, it will be injected with 2 params, but in Application\Post\Index, it will be injected 3 params.

froschdesign commented 5 years ago

If I understand your request correctly, then please have a look at method build of the Zend\ServiceManager\ServiceManager class. All informations can be found in the documentation (direct link).

khuyennguyen7007 commented 5 years ago

Sorry but it seem like your docs is not what I need. In factory, I can not determine the context where build method invoked (eg: what controller, what module,...). Morever, it seem like factory just allow pass an array options. I need automatic dependency injection on constructor like this:

__construct(ClassOne $one, ClassTwo $two) {}
froschdesign commented 5 years ago

I need automatic dependency injection on constructor…

Then look at the reflection based factory: https://docs.zendframework.com/zend-servicemanager/reflection-abstract-factory/

khuyennguyen7007 commented 5 years ago

Like i said, i need two things:

  1. Auto dependencies injection. Yes, I can use reflection abstract factory.
  2. Context, where class will be got. How do you determine the context to inject an exact number of params.
froschdesign commented 5 years ago

@khuyennguyen7007 I will provide a code example.

froschdesign commented 5 years ago

@khuyennguyen7007 The context is provided by the parameter $requestedName. The example shows the usage of the parameter in the factory for the controllers.

// Factory for controllers
$controllerFactory = static function (
    Psr\Container\ContainerInterface $container,
    $requestedName,
    array $options = null
) {
    switch ($requestedName) {
        case UserController::class:
            $options = [
                'parameter_1',
                'parameter_2',
            ];
            break;

        case PostController::class:
            $options = [
                'parameter_1',
                'parameter_2',
                'parameter_3',
            ];
            break;

        default:
            $options = [];
            break;
    }

    $container->build(SearchManager::class, $options);
};

// Factory for search-manager
$searchManagerFactory = static function (
    Psr\Container\ContainerInterface $container,
    $requestedName,
    array $options = null
) {
    return new class (...$options) {
        public function __construct($param1 = null, $param2 = null, $param3 = null)
        {
            var_dump($param1);
            var_dump($param2);
            var_dump($param3);
        }
    };
};

// Service manager
$serviceManager = new Zend\ServiceManager\ServiceManager(
    [
        'factories' => [
            SearchManager::class  => $searchManagerFactory,
            UserController::class => $controllerFactory,
            PostController::class => $controllerFactory,
        ],
    ]
);

// Get controllers
$serviceManager->get(UserController::class); // string(11) "parameter_1", string(11) "parameter_2", NULL
$serviceManager->get(PostController::class); // string(11) "parameter_1", string(11) "parameter_2", string(11) "parameter_3"

Please do not use this code in production, it's far away from perfect and it's only illustrative material for this issue report.

khuyennguyen7007 commented 5 years ago

Thank you very much for your sample code. But i'm still stuck in this:

  1. $serviceManager->get(UserController::class) not return object instance, it's return null value.
  2. How to combined it with ReflectionBasedAbstractFactory. Eg:
    
    class Service {}

class UserController { public function __construct(SearchManager $searchManager, Service $service) { var_dump($service); } }


When `$serviceManager->get(UserController::class);`, it's will automatic injected dependencies and build `SearchManager` as above.
michalbundyra commented 5 years ago

@khuyennguyen7007

$serviceManager->get(UserController::class) not return object instance, it's return null value.

There is missing return statement before $container->build(SearchManager::class, $options); in the first function provided by @froschdesign.

How to combined it with ReflectionBasedAbstractFactory. Eg...

I guess you want to use: https://github.com/zendframework/zend-di

froschdesign commented 5 years ago

@khuyennguyen7007

  1. $serviceManager->get(UserController::class) not return object instance, it's return null value.

Right, but this is not needed in this example. Only the output is interesting. (See the notice under the code example.)

khuyennguyen7007 commented 5 years ago

Sorry but it's very weird to me. I'm still not understand.

  1. $serviceManager->get(UserController::class) must be return UserController instance for using in my case. Why is it not needed?
  2. I discovered an issue in the example above is that build method is just return object in runtime, not save into manager. With this example:
    
    class Service {}

class UserController { public function __construct(SearchManager $searchManager, Service $service) { var_dump($searchManager); } }


`$searchManager` will be null in params.
froschdesign commented 5 years ago

@khuyennguyen7007

  1. $serviceManager->get(UserController::class) must be return UserController instance for using in my case. Why is it not needed?

It‘s not needed here on GitHub as example to illustrate the workflow. In real application you need return $requestedName(…). The important part of my example is the output from var_dump.

froschdesign commented 5 years ago

@khuyennguyen7007 My example was created for running without extra classes. Maybe that was not clear enough. Sorry for that!

In a real application you must change this line in the controller factory from:

$container->build(SearchManager::class, $options);

to

return new $requestedName($container->build(SearchManager::class, $options));
froschdesign commented 5 years ago

I must rewrite code in factory

The same with your factory: if ($requestedName == IndexController::class)

Btw. this won't work in your factory: return $container->get($requestedName); – the service-manager will call the factory again.

froschdesign commented 5 years ago

@khuyennguyen7007 I think you found a solution for your application. Can we close this request?

If you have further questions, please use the official forum.