How to place a class that requires a variable parameter into Dependency Injection container?

1k views Asked by At

How to use Dependency Injection container when I have a variable non-static parameter that I need to supply?

What I want in my code is this:

$staff = $container->get(Staff::class);

What I have now is this:

$staff = new Staff("my_great_username");

Note that username can change and is supplied at run-time.

I cannot seem to put Staff into my DI Container, because there is no way to specify a variable parameter there.

My Problem is ...

I am using Factory-based container, namely Zend\ServiceManager\ServiceManager.This is a factory that I use to hide instantiation details:

class StaffFactory
{
    function __invoke(ContainerInterface $container): Staff
    {
        /*
         * I do not seem to know how to get my username here
         * nor if it is the place to do so here
         */
        $staff = new Staff(????????);
        return $staff;
    }
}

The way I set up the container in my config is this:

'factories' => [
    Staff::class => StaffFactory::class
]

Note: even though the parameter is a "variable", I want Staff to be immutable. Namely, once it is created, it stays that way. So I do not particularly wish to make a setter method for the username as that will imply that the class is mutable, when it is not.

What do you suggest?

1

There are 1 answers

0
Dennis On

My problem was that I had a variable parameter that was being passed to the constructor of my Staff class.

The solution is to create a StaffCreator class that does not have variable parameters in its constructor, and then write a StaffCreator::create method, which takes the variable parameter. Then, instead of injecting Staff into whatever class where Staff is needed, inject StaffCreator instead, and then use it to create a Staff instance.

i.e.

//Inject this wherever you need Staff
$staffCreator = $container->get(StaffCreator::class);

//use it:
$staff = $this->staffCreator->create("my_great_username");

//code:
class StaffCreatorFactory
{    
    function __invoke(ContainerInterface $container)
    {
        return new StaffCreator();
    }
}

class StaffCreator
{
    function __construct()
    {
        //additional creation parameters possible here
    }

    function create(string $username): Staff
    {
        return new Staff($username);
    }
}

with credit to Steve

Note: You can create and add Interfaces to the above code to make DI reusable. i.e. StaffCreatorInterface, StaffInterface. In my case I kept it simple, as I do not (yet) have a strong use case for reuse of the interface.