Pull to refresh

Реализуем DI-контейнер на PHP5 с помощью Reflections

Reading time5 min
Views1K
Этот топик для тех, кто представляет, что такое DI (Dependency Injection) но никогда не задумывался «как оно там унутре все работает».
Прочитать, что такое DI, можно например тут или тут

Не ставилось целью разработать свой Production DI-фреймворк. Хотелось разобраться как можно реализовать подобную функциональность наиболее удобно (Phemto, упомянутый выше, показался менее удобным, чем, например, способ от Microsoft Unity)

Реализованный вариант конфигурируется в коде (не через XML, как некоторые другие реализации, хотя это кому как удобнее).
Каждый используемый тип должен быть предварительно зарегистрирован, но не надо перечислять его аргументы, как например в Phemto — контейнер сам выяснит типы аргументов конструктора через Reflection.


Сама реализация (примеры ниже):
<?
 class PUnityException extends RuntimeException {
    
 }

 class PUnity {
    
    const PUNITY_SINGLETON = 2;
    const PUNITY_SIMPLE = 1;
    
    private $data;
    private $attributes;
    private $singletons;
    
    /**
    * Регистрируем тип
    *
    * @param string $type
    * @param string $concreteInstance
    * @param int $attr
    */
    public function RegisterType($type, $concreteInstance, $attr = PUnity::PUNITY_SIMPLE) {
     // To get exceptions if types are not exists
     $typeReflection = new ReflectionClass($type);    
     $concreteReflection = new ReflectionClass($concreteInstance);    
     
     $this->data[$type] = $concreteReflection;
     $this->attributes[$type] = $attr;
    } 
    
    /**
    * Получаем экземпляр типа
    *
    * @param string $type
    * @return sdtclass
    */
    public function Resolve($type) {
     
     if($this->attributes[$type] == PUnity::PUNITY_SINGLETON)
     {
        $typeReflection = $this->data[$type];
        try// May be class is taking care of it's instace by itself?
         $getInstance = $typeReflection->getMethod('getInstance'); // Yes, it's a hardcoding...
         return $getInstance->invoke(null);
        } catch(ReflectionException $e) { }
        
        if(isset($this->singletons[$type])) // Try get existing one
         return $this->singletons[$type];
     }
     
     $instance = $this->resolver($type); // Resolve type
     if($this->attributes[$type] == PUnity::PUNITY_SINGLETON) // Take care of storing the object instance
     {
        $this->singletons[$type] = $instance;
     }
     
     return $instance;
    }
    
    /**
    * Ресолвер типов
    *
    * @param string $type
    * @return stdclass
    */
    private function resolver($type) {
     $typeReflection = $this->data[$type];
     $ctr = $typeReflection->getConstructor();
     $args = array(); 
     if($ctr != null) // Constructor is defined
     {
        $ctrParams = $ctr->getParameters();
        foreach($ctrParams as $p) {
         $cls = $p->getClass();
         if(!isset($this->data[$cls->getName()])) // No nothing about needed type
            throw new PUnityException("Type {$cls->getName()} not registered. Use RegisterType first");
         else
            array_push($args, $this->Resolve($cls->getName()));
        }     
     }
     return count($args) ? $typeReflection->newInstanceArgs($args) : $typeReflection->newInstance();
    }
    
 }
?>


* This source code was highlighted with Source Code Highlighter.


Простой пример использования:
 interface ILogger {
    public function Logstr($str);
 }
 
 class MyLogger implements ILogger {
    public function Logstr($str) {
     echo "MyLogger: {$str}";
    }
 }
 
 class UsingLogger {
    public function __construct(ILogger $myLogger) {
     $myLogger->Logstr(" On the move...");
    }
 }
 
 $u = new PUnity();
 $u->RegisterType('ILogger', 'MyLogger');
 $u->RegisterType('UsingLogger', 'UsingLogger');
 
 $logger = $u->Resolve('UsingLogger');


* This source code was highlighted with Source Code Highlighter.


А вот так можно делать синглтоны:
<?php
 interface ILogger {
    public function Logstr($str);
 }
 
 class MyTrickyLogger implements ILogger {

    private $timeCreated;
    
    public function MyTrickyLogger() {
     $this->timeCreated = time(); 
    }
    
    public function Logstr($str) {
     echo "I created at ".date('d.m.Y H:i:s .', $this->timeCreated).'Message: '.$str."<br/>\n";
    }
 }
 
 class UsingLogger {
    public function __construct(ILogger $myLogger) {
     $myLogger->Logstr(" On the move...");
    }
 }
 
 $u = new PUnity();
 $u->RegisterType('ILogger', 'MyTrickyLogger', PUnity::PUNITY_SINGLETON);
 $u->RegisterType('UsingLogger', 'UsingLogger');
 
 $logger = $u->Resolve('UsingLogger');
 sleep(2);
 $logger2 = $u->Resolve('UsingLogger');
?>


* This source code was highlighted with Source Code Highlighter.

Tags:
Hubs:
0
Comments4

Articles

Change theme settings