Write a simple DI Container in PHP with service locators
So recently I’ve had an interview, and one of the questions was, “What is not possible to write in PHP without Reflection API?”
And I couldn’t come up with anything, I am not a huge fan of dynamic programming, it makes sense sometimes, but could backfire. I am a supporter of explicit code doing simple things while being easily understandable.
Now to make the situation worse, the hint was “It is always used to implement this in frameworks like Symfony and Laravel”
Now, I had some doubts, I had my ideas, but somehow I have learnt that you need to somehow not reply correctly but try to understand what is the interviewer expecting from you, and went with “a DI container”.
Correct.
Now the hint, Laravel has used a service locators based DI container, same with Symfony, up to version 3 we had $di->getContainer(‘some’) all over the controllers, and you can implement that without having to deal with reflections.
Let’s see how
<?php
// A container that holds an array of dependencies
class Container
{
public function __construct(
private array $objects = []
) {}
public function set(string $name, $dependency)
{
$this->objects[$name] = $dependency;
}
public function get(string $name)
{
if (isset($this->objects[$name])) {
if(is_callable($this->objects[$name])) {
return $this->objects[$name]($this);
}
return $this->objects[$name];
}
}
}
// A dependency
class Dependency
{
}
// Something that needs the dependency
class Client
{
protected Dependency $dep;
public function __construct(Container $container)
{
$this->dep = $container->get('dependency');
}
public function sayHello()
{
print 'Hello world!' . "\n";
}
}
// Something else needing a dependency directly injected
class Client2 extends Client
{
public function __construct(protected Dependency $dep) {}
}
// Create the container instance
$container = new Container();
// Set a dependency as object instance
$container->set('dependency', new Dependency());
// Set a dependency as a callback
$container->set('client', function (Container $container) {
return new Client($container);
});
$container->get('client')->sayHello();
$container->set('client2', function (Container $container) {
return new Client2($container->get('dependency'));
});
$container->get('client2')->sayHello();
So it’s possible to implement a DI container without reflections. Now, hear me, service locator is an anti-pattern, but it exists.
In the end, I would love for interviews to give the opportunity to interviewees to have a conversation about questions, because we can all miss something, I’ve always interviewed others trying to spark a conversation about point of views and possible solutions, rather than, ask, ensure the answer matches my point of view, move on.