Understanding Dependency Injection in PHP with Example Implementations
This article explains the concept of dependency injection in PHP, demonstrates simple and multi‑class injection examples, and shows how to build both a dynamic IoC container and a static container with autowiring support for flexible backend development.
Dependency injection (DI) separates a class's immutable parts from replaceable parts, allowing decoupling through injection.
1. Simple DI implementation: a Mysql class holds connection details, a MysqlConfiguration class provides those details, and a Mysql1 class receives the configuration via its constructor.
<?php
/**
* A database connection class
*/
class Mysql{
private $host;
private $port;
private $username;
private $password;
private $db_name;
public function __construct(){
$this->host = '127.0.0.1';
$this->port = '3306';
$this->username = 'root';
$this->password = 'root';
$this->db_name = 'laravel5';
}
public function connect() {
return mysqli_connect($this->host,$this->username ,$this->password,$this->db_name,$this->port);
}
}
// usage
$db = new Mysql();
$con = $db->connect();
// configuration class
class MysqlConfiguration{
private $host;
private $port;
private $username;
private $password;
private $db_name;
public function __construct($host,$port,$username,$password,$db_name){
$this->host = $host;
$this->port = $port;
$this->username = $username;
$this->password = $password;
$this->db_name = $db_name;
}
public function getHost(){ return $this->host; }
public function getPort(){ return $this->port; }
public function getUsername(){ return $this->username; }
public function getPassword(){ return $this->password; }
public function getDbName(){ return $this->db_name; }
}
class Mysql1{
private $configuration;
public function __construct($config){
$this->configuration = $config;
}
public function connect(){
return mysqli_connect(
$this->configuration->getHost(),
$this->configuration->getUsername(),
$this->configuration->getPassword(),
$this->configuration->getDbName(),
$this->configuration->getPort()
);
}
}
$config = new MysqlConfiguration('127.0.0.1','root','', 'my_db', 22);
$db = new Mysql1($config);
$con = $db->connect(); // $config is injected into Mysql12. Three‑class injection example: manual injection of Bim , Bar , and Foo demonstrates how each class depends on the previous one.
<?php
class Bim{
public function doSomething(){ echo __METHOD__, '|'; }
}
class Bar{
private $bim;
public function __construct(Bim $bim){ $this->bim = $bim; }
public function doSomething(){
$this->bim->doSomething();
echo __METHOD__, '|';
}
}
class Foo{
private $bar;
public function __construct(Bar $bar){ $this->bar = $bar; }
public function doSomething(){
$this->bar->doSomething();
echo __METHOD__;
}
}
$foo = new Foo(new Bar(new Bim()));
$foo->doSomething(); // outputs Bim::doSomething|Bar::doSomething|Foo::doSomethingThis pattern is an example of Inversion of Control (IoC), where the control of dependencies is moved to the composition root.
3. Dynamic IoC container using magic methods: the container stores resolvers in an internal array and creates objects on demand.
<?php
class Container{
private $s = array();
function __set($k, $c){ $this->s[$k] = $c; }
function __get($k){ return $this->s[$k]($this); }
}
$c = new Container();
$c->bim = function(){ return new Bim(); };
$c->bar = function($c){ return new Bar($c->bim); };
$c->foo = function($c){ return new Foo($c->bar); };
$foo = $c->foo; // resolves Bim → Bar → Foo
$foo->doSomething();4. Static container implementation (class Ioc ): provides static bind and make methods for registering and retrieving services.
<?php
class Ioc{
protected static $registry = [];
public static function bind($name, $resolver){ static::$registry[$name] = $resolver; }
public static function make($name){
if(isset(self::$registry[$name])){
$resolver = self::$registry[$name];
return $resolver();
}
throw new Exception('Container does not have this class');
}
}
Ioc::bind('bim', function(){ return new Bim(); });
Ioc::bind('bar', function(){ return new Bar(Ioc::make('bim')); });
Ioc::bind('foo', function(){ return new Foo(Ioc::make('bar')); });
$foo = Ioc::make('foo');
$foo->doSomething();5. Autowiring container with reflection: the container can automatically resolve class constructors, handle scalar parameters with defaults, and recursively build dependencies.
<?php
class Container{
private $s = [];
public function __set($k, $c){ $this->s[$k] = $c; }
public function __get($k){ return $this->build($this->s[$k]); }
public function build($className){
if($className instanceof Closure){ return $className($this); }
$reflector = new ReflectionClass($className);
$constructor = $reflector->getConstructor();
if(is_null($constructor)) return new $className;
$parameters = $constructor->getParameters();
$dependencies = $this->getDependencies($parameters);
return $reflector->newInstanceArgs($dependencies);
}
public function getDependencies($parameters){
$deps = [];
foreach($parameters as $parameter){
$dependency = $parameter->getClass();
if(is_null($dependency)){
$deps[] = $this->resolveNonClass($parameter);
} else {
$deps[] = $this->build($dependency->name);
}
}
return $deps;
}
public function resolveNonClass($parameter){
if($parameter->isDefaultValueAvailable()) return $parameter->getDefaultValue();
throw new Exception('No default value');
}
}
$c = new Container();
$c->bar = 'Bar';
$c->foo = function($c){ return new Foo($c->bar); };
$foo = $c->foo; // automatically builds Bar (and its Bim dependency)
$foo->doSomething();All these examples illustrate how a DI container can centralize object creation, make dependencies explicit, and enable easy swapping of implementations (e.g., replacing a Memcache client with Redis) without modifying the dependent classes.
Laravel Tech Community
Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.