Witam!
Piszę niewielką aplikację PHP bez wykorzystania frameworka (na początku go nie dodano a teraz sporo przepisywania). Więc naszła mnie chęć napisania na szybko kontenera DI. Okazało się że argumenty funkcji w PHP nie mogą przyjmować zmiennych jako wartości domyślnych co skomplikowało zadanie. Jednak coś tam wyskrobałem :
<?php
class DependencyInjectionContainer
{
private static $objects = [];
private static function register()
{
return Array(
'PDO_',
'UserRepository',
'User'
);
}
public static function boot()
{
$register = self::register();
foreach ($register as $item)
self::$objects[$item] = 0;
}
public static function setInstance($class, $object)
{
if (! isset(self::$objects[$class]))
throw new Exception('There is no class "'.$class.'" in DI container');
self::$objects[$class] = $object;
}
public static function Inject()
{
$function_parameters = [];
$response = [];
$caller = self::caller();
$parameters = null;
if (isset($caller['class']))
$parameters = self::getReflectionParameters($caller['class'], $caller['function']);
else
$parameters = self::getReflectionParametersFunction($caller['function']);
$i=0;
foreach($parameters as $parameter)
{
$class = $parameter->getClass()->name;
$value = isset($caller['args'][$i]) ? $caller['args'][$i] : null;
$name = $parameter->getName();
$function_parameters[$i]['name'] = $name;
$function_parameters[$i]['class'] = $class;
$function_parameters[$i]['value'] = $value;
$i++;
}
foreach ($function_parameters as $parameter)
{
if (isset($parameter['class']) && self::is_registered_class($parameter['class']))
if (is_null($parameter['value']))
$response[$parameter['name']] = self::getInstance($parameter['class']);
}
return $response;
}
private static function getInstance($class)
{
if(!self::$objects[$class])
self::$objects[$class] = new $class;
return self::$objects[$class];
}
private static function is_registered_class($_key)
{
foreach (self::$objects as $key => $value)
{
if (strtolower($key) == strtolower($_key))
return true;
}
return false;
}
private static function caller($level = 1)
{
$dbt=debug_backtrace(null,$level+2);
return $caller = isset($dbt[$level+1]) ? $dbt[$level+1] : null;
}
private static function getReflectionParameters($class, $method)
{
$reflection = new ReflectionMethod($class, $method);
return $reflection->getParameters();
}
private static function getReflectionParametersFunction($function)
{
$reflection = new ReflectionFunction($function);
return $reflection->getParameters();
}
}
Przykład użycia:
<?php
class PDO_{}
class UserRepository{
public function __construct(PDO_ $pdo=null)
{
extract( DependencyInjectionContainer::Inject() ); // Ta linia jest wymagana wszędzie gdzie chcemy korzystać z DI
}
public function getUser($id){
return Array(
'id' => 1,
'name' => 'Karol'
);
}
}
class User{
public function __construct(UserRepository $ur = null)
{
extract( DependencyInjectionContainer::Inject() );
$this->ur = $ur;
}
public function showUser()
{
$user = $this->ur->getUser(1);
var_dump($user);
}
}
// Budowanie aplikacji
include 'DependencyInjectionContainer.php';
DependencyInjectionContainer::boot();
// jakieś tam pdo tworzone gdzieś w aplikacji
$pdo = new PDO_();
DependencyInjectionContainer::setInstance('PDO_', $pdo);
function Controller(User $user = null)
{
extract( DependencyInjectionContainer::Inject() );
$user->showUser();
}
Controller();
Do każdej funkcji zawierającej linie:
extract( DependencyInjectionContainer::Inject() );
kontener automatycznie wstrzykuje zdefiniowane w register()
zależności.
Jeżeli chcemy wstrzykiwać jakąś konkretną instancje możemy użyć serInstance()
Chciałem jeszcze pozbyć się tej funkcji extract (wciągnąć ją do środka Inject) ale znalazłem tylko coś takiego
you can modify the "parent scope" by changing the global EG(active_symbol_table)
Klasa jest statyczna ponieważ chciałem aby był do niej łatwy dostęp w funkcjach. Może jest jakiś lepszy sposób?
**Teraz pytanie do bardziej doświadczonych kolegów ;) Jak to wygląda waszym zdaniem? Gra warta świeczki? Może jakieś kardynalne błędy...
Czy odpuścić sobie i pobrać gotowy kontener, jakie dodatkowe zalety będzie miał? **