reference : http://jaceju.net/2014-07-27-php-di-container/
why we need dependency injection ?
cause we want to remove dependency. Instead of creating object in the inner, we pass the object via parameter during construction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
class App { protected $auth = null; protected $session = null; public function __construct($dsn, $username, $password) { $this->auth = new Auth($dsn, $username, $password); $this->session = new Session(); } public function login($username, $password) { if ($this->auth->check($username, $password)) { $this->session->set('username', $username); return true; } return false; } } class Auth { public function __construct($dsn, $user, $pass) { echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; } public function check($username, $password) { echo "Checking username, password from database...\n"; return true; } } class Session { public function set($name, $value) { echo "Set session variable '$name' to '$value'."; } } $app = new App('mysql://localhost', 'username', 'password'); $username = 'jaceju'; if ($app->login($username, 'password')) { echo "$username just signed in.\n"; } |
if we want to change the way we auth or how session store, we may need to change class app too.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class App { protected $auth = null; protected $session = null; public function __construct(Auth $auth, Session $session) { $this->auth = $auth; $this->session = $session; } } $auth = new Auth('mysql://localhost', 'username', 'password'); $session = new Session(); $app = new App($auth, $session); |
much better now,and we want different ways to auth,we had better to change auth to interface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
interface Auth { public function check($username, $password); } class DbAuth implements Auth { public function __construct($dsn, $user, $pass) { echo "Connecting to '$dsn' with '$user'/'$pass'...\n"; } public function check($username, $password) { echo "Checking username, password from database...\n"; return true; } } class HttpAuth implements Auth { public function check($username, $password) { echo "Checking username, password from HTTP Authentication...\n"; return true; } } $auth = new HttpAuth(); $session = new Session(); $app = new App($auth, $session); |
looks great, then why we need the container?
sometimes we don’t know which way we want until we run the program.(http or db auth). we can register the class we want into a container,and retrieve later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Container { protected static $map = []; public static function register($name, $class, $args = null) { static::$map[$name] = [$class, $args]; } public static function get($name) { list($class, $args) = isset(static::$map[$name]) ? static::$map[$name] : [$name, null]; if (class_exists($class, true)) { $reflectionClass = new ReflectionClass($class); return !empty($args) ? $reflectionClass->newInstanceArgs($args) : new $class(); } return null; } } Container::register('Auth', 'DbAuth', ['mysql://localhost', 'username', 'password']); $auth = Container::get('Auth'); $session = new Session(); $app = new App($auth, $session); |
looks perfect,but every time we need to create an app object,we need to write all this code. it’s a wise choice to wrap them into a function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Container { // ... public static function inject() { $args = func_get_args(); $callback = array_pop($args); $injectArgs = []; foreach ($args as $name) { $injectArgs[] = Container::get($name); } return call_user_func_array($callback, $injectArgs); } } $app = Container::inject('Auth', 'Session', function ($auth, $session) { return new App($auth, $session); }); |
could we make it better?
yes,we can. Every time we create an app object, we have to inject the dependency into container. what if the container can auto load the dependency by itself, then we can free from providing the dependency. it’s time to present reflection class.
http://php.net/manual/en/class.reflectionclass.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Container { // ... public static function resolve($name) { if (!class_exists($name, true)) { return null; } $reflectionClass = new ReflectionClass($name); $reflectionConstructor = $reflectionClass->getConstructor(); $reflectionParams = $reflectionConstructor->getParameters(); $args = []; foreach ($reflectionParams as $param) { $class = $param->getClass()->getName(); $args[] = static::get($class); } return !empty($args) ? $reflectionClass->newInstanceArgs($args) : new $class(); } } $app = Container::resolve(App); |
holy shit,only one line code we got what we want.