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

    Этот топик для тех, кто представляет, что такое 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.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 4

      +2
      константы классов приятнее чем глобальные
        –2
        Поставил минус, %username%? Отпишись в каментах!
            0
            Начало неплохое, но тут слишком мало функции для DI-контейнер

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое