Интерфейс DI, нужно авторитетное мнение

t43

Новичок
Привет всем!
Вообщем хочу реализовать свой DI. Так сказать в учебных целях)
На данный момент реализовал первую версию, но хочется сделать, что-то более удобное и быстрое.
Для новой версии пока что сделал вот такой вот интерфейс.
Хочется у слушать мнение опытных людей, чего не хватает, что лишнее и т.д.

PHP:
<?php

interface IDI {

    /**
    * Возвращает сервис
    *
    * @param $service (имя запрашиваемого серсвиса)
    * @param $arguments (аргументы)
    * @return mixed
    */
    public function get($service, array $arguments=null);


    /**
    * Связывает интерфейс с конкретным сервисом.
    *
    * @param $interface (имя интерфейса)
    * @param $service (имя соответсвующего сервиса)
    * @param $share (использовать как синглетон)
    * @return mixed
    */
    public function bind($interface, $service, $share=false);


    /**
    * Связывает интерфейс с конкретным сервисом, для конкретного сервиса.
    *
    * @param $service
    * @param $interface
    * @param $dependencyService
    * @param $share
    * @return mixed
    */
    public function bindFor($service, $interface, $dependencyService, $share=false);


    /**
    * Все сервисы реализующие этот интерфейс будут синглетонами.
    *
    * @param $interface
    * @return mixed
    */
    public function shareInterface($interface);


    /**
    * Задаёт аргументы для определённого сервиса
    *
    * @param $service
    * @param array $arguments
    * @return mixed
    */
    public function setArgumentFor($service, array $arguments);


    /**
    * Если не используется типизация.
    *
    * Связывает аргумент сервиса с определённой зависимостью.
    *
    * @param $service
    * @param $argument
    * @param $dependencyService
    * @return mixed
    */
    public function bindArgumentAsDependency($service, $argument, $dependencyService);


    /**
    * Аналогичен bindArgumentAsDependency, но второй параметр массив вида:
    *
    * [
    *  'argumentName'=>'dependency'
    * ]
    *
    * @param $service
    * @param $dependency
    * @return mixed
    */
    public function bindArgumentsAsDependency($service, array $dependency);


    /**
    * Если true, то будут генерирвоаться прокси-класс, и передаваться не сами зависимости, а прокси.
    *
    * @param $bool
    * @return mixed
    */
    public function useLazyLoad($bool);


    /**
    * Если true, то будет происходить кэширование карты зависимостей.
    *
    * @param $bool
    * @return mixed
    */
    public function useCompilation($bool);
}
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
не хватает класса который имплементирует этот интерфейс или, что даже интереснее, класса который работает с обьектами типа IDI.
 

hell0w0rd

Продвинутый новичок
Идеальный DI - это тот, которого не видно:) В противном случае это service locator
 

t43

Новичок
Идеальный DI - это тот, которого не видно:) В противном случае это service locator
не видно), но связи между интерфейсом и его имплементацией (конкретным сервисом), всё равно нужно установить.

PHP:
<?php
class newDI implements IDI{

    private $_useLazyLoad = false;

    private $_useCompilation = false;

    private $_map;

    private $_association;

    private $_specialAssociation;

    private $_serviceArg;

    private $_singletons = [];

    private $_singletonsInterface = [];

    private function isSingleton($service)
    {
        $ref = new ReflectionClass($service);
        $interfaces = $ref->getInterfaces();

        foreach($interfaces as $interface){
            if(array_search($interface->name, $this->_singletonsInterface) !== false){//?
                return true;
            }
        }
        if(array_search($service, $this->_singletonsInterface)){
            return true;
        }

        return false;
    }

    private function setSingleton($service, $object){
        $this->_singletons[$service] = $object;
    }

    private function getSingleton($service){
        if(!empty($this->_singletons[$service])){
            return $this->_singletons[$service];
        }else{
            $object = $this->getService($service);
            $this->setSingleton($service, $object);
            return $object;

        }
    }


    private function getMap($service)
    {
        if(isset($this->_map[$service])){
            return $this->_map[$service];
        }else{
            return $this->_map[$service] = $this->createMap($service);
        }
    }

    private function createMap($service)
    {
        $map = [];

        $reflection = new ReflectionClass($service);
        if($constructor = $reflection->getConstructor()){
            $parameters = $constructor->getParameters();
            if($parameters){
                $map['arguments'];
                foreach($parameters as $parameter){
                    if($dependencyClass =  $parameter->getClass()){
                        if($dependencyClass->isInterface()){
                            $type = 'interface';
                        }elseif($dependencyClass->isAbstract()){
                            $type = 'abstract';
                        }else{
                            $type = 'class';
                        }
                        $map['arguments'][]['dependency'][$dependencyClass->name] = $type;
                    }else{
                        if($parameter->isDefaultValueAvailable()){
                            $map['arguments'][]['parameter'][$parameter->name] = ['defaultValue'=>$parameter->getDefaultValue()];
                        }else{
                            $map['arguments'][]['parameter'][$parameter->name] = null;
                        }
                    }

                }
            }
        }
        $map['implements'] = $reflection->getInterfaceNames();
        $map['extend'] = $reflection->getParentClass();
        if($reflection->isInterface()){
            $map['type'] = 'interface';
        }elseif($reflection->isAbstract()){
            $map['type'] = 'abstract';
        }else{
            $map['type'] = 'class';
        }

        $map['isService'] = $reflection->isInstantiable();
        return $map;
    }

    private function createService($service, $arguments=null)
    {
        if(null === $arguments){
            return new $service;
        }else{
            //switch-case or Reflection
        }
    }

    private function checkRelation($interface, $class)
    {
        if(isset($this->_map[$interface])){
            $mapInterface = $this->_map[$interface];
        }else{
            $mapInterface = ($this->_map[$interface] = $this->createMap($interface));
        }

        if(isset($this->_map[$class])){
            $mapClass = $this->_map[$class];
        }else{
            $mapClass = ($this->_map[$class] = $this->createMap($class));
        }

        if(array_search($interface, $mapClass['implements']) !== false){
            return true;
        }
        if($mapInterface === $mapClass['extend']){
            return true;
        }
        return false;
    }

    public function get($service, array $arguments=null)
    {

        if($this->isSingleton($service)){
            return $this->getSingleton($service);
        }else{
            return $this->getService($service, $arguments);
        }

    }


public function bindSingletonInterface($interface)
    {
        $this->_singletonsInterface[] = $interface;
    }

    private function getService($serviceName, $args=null)
    {
        $map = $this->getMap($serviceName);

        if(!empty($map['arguments'])){
            $injectProperty = [];
            foreach($map['arguments'] as $argument){
                if(isset($argument['dependency'])){
                    $dependencyName = key($argument['dependency']);
                    $dependencyType = $argument['dependency'][$dependencyName];

                    if(isset($this->_specialAssociation[$serviceName][$dependencyName])){
                        $injectProperty[] = $this->get($this->_specialAssociation[$serviceName][$dependencyName]);
                    }elseif(isset($this->_association[$dependencyName])){
                        $injectProperty[] = $this->get($this->_association[$dependencyName]);
                    }elseif($dependencyName === __CLASS__){
                        $injectProperty[] = $this;
                    }elseif($dependencyType === 'class'){
                        $injectProperty[] = $this->get($dependencyName);
                    }else{
                        throw new Exception('Нет ассоциации для '.$dependencyType.' '.$dependencyName);
                    }
                }else{
                    $propertyName = key($argument['parameter']);
                    if(!empty($args[$propertyName])){
                        $injectProperty[] = $args[$propertyName];
                    }elseif(!empty($this->_serviceArg[$serviceName][$propertyName])){
                        $injectProperty[] = $this->_serviceArg[$serviceName][$propertyName];
                    }elseif(!empty($argument['parameter'][$propertyName]['defaultValue'])){
                        $injectProperty[] = $argument['parameter'][$propertyName]['defaultValue'];
                    }else{
                        throw new Exception('Нет аргумента '.$propertyName.' для класс сервиса '.$serviceName);
                    }
                }
            }
            return (new ReflectionClass($serviceName))->newInstanceArgs($injectProperty);

        }else{
            return new $serviceName;

        }
    }

    private function isService($service){
        if(isset($this->_map[$service])){
            return $this->_map[$service]['isService'];
        }else{
            $this->_map[$service] = $this->createMap($service);
            return $this->_map[$service]['isService'];
        }

    }

    public function bind($interface, $service, $share = false)
    {
        if($this->checkRelation($interface, $service)){
            if($this->isService($service)){
                $this->_association[$interface] = $service;
                (!$share)?:$this->_singletonsInterface[] = $service;
            }else{
                throw new Exception('Сервис не является классом');
            }
        }
    }

    public function bindFor($service, $interface, $dependencyService)
    {
        if($this->checkRelation($interface, $service)){
            if($this->isService($service))
            $this->_specialAssociation[$service] = [$interface=>$service];
        }else{
            throw new Exception('Сервис не является классом');
        }
    }

    public function shareInterface($interface)
    {
        $this->_singletonsInterface[] = $interface;
    }

    public function setArgumentFor($service, array $arguments)
    {
        $this->_serviceArg[$service] = $arguments;
    }

    public function bindArgumentAsDependency($service, $argument, $dependencyService)
    {
        // TODO: Implement bindArgumentAsDependency() method.
    }

    public function bindArgumentsAsDependency($service, array $dependency)
    {
        // TODO: Implement bindArgumentsAsDependency() method.
    }

    public function useLazyLoad($bool=true)
    {
        // TODO: Implement useLazyLoad() method.
    }

    public function useCompilation($bool=true)
    {
        // TODO: Implement useCompilation() method.
    }

}
 
Последнее редактирование:

t43

Новичок
А и ещё вопрос, стоит ли вообще создавать прокси классы, будет ли от этого хоть какой то выигрыш в скорости?
И да, как лучше всего хранить карту, просто серелизировать или есть что-то более быстрое?
 

HraKK

Мудак
Команда форума
Ужас.

Ну да ладно, можно попросить об одном? Избавьте мир уже от _ перед переменными, а?
 

ghost9

Новичок
Идеальный DI - это тот, которого не видно:) В противном случае это service locator
Вот этого два поддерживаю. Особенно в классическом DI меня бесит отсутствие подсказок в IDE при обращении к элементам. Черный ящик какой-то. Везде @var прописывать можно, но долго и вообще лениво. А вот пример из моего service locator велосипеда:
Код:
* Class AppSL
* @property  MongoClient $mongo                                  Соединение с mongodb
* @method    string      render($template, array $args = [])     Отрендерить вьюшку
*/
class AppSL extends ServiceLocator
{

}
 

t43

Новичок
Ужас.

Ну да ладно, можно попросить об одном? Избавьте мир уже от _ перед переменными, а?
Ну что по ужас я согласен, уж очень много берёт на себя этот класс.
По поводу подчёркиваний, ну это как с табами и пробелами, кто-то делает так кто так...
 

HraKK

Мудак
Команда форума
да табы с пробелами это одно, а засорять _ не нужно. Сейчас же нормальное IDE все тебе само подскажет. А пошла эта привычка с пхп3-4 когда не было уровней доступа и приходилось вот такими условными обозначениями фигачить. Сейчас то на дворе 2014 год можно сказать, а в коде 86 :)
 

t43

Новичок
да табы с пробелами это одно, а засорять _ не нужно. Сейчас же нормальное IDE все тебе само подскажет. А пошла эта привычка с пхп3-4 когда не было уровней доступа и приходилось вот такими условными обозначениями фигачить. Сейчас то на дворе 2014 год можно сказать, а в коде 86 :)
Ну может быть)
 

artoodetoo

великий и ужасный
Фигня это. Подчерки вовсе не в PHP родились и умирать не собираются. Это кодинг стадарт, он в разных командах может быть разный. Не стоит так нервничать по пустякам.
 
  • Like
Реакции: WMix

t43

Новичок
вот пример настройки, чуть позже скину пример использования

PHP:
$di = new \Components\DependencyInjection\DiC();
/**
* Получаем конфигуратор
*/
$diConfigurator = $di->getConfigurator();

/**
* "Привязываем" к интерфейсу interface сервис serviceName
*/
$diConfigurator->getConfig('interfaceA')->bind('serviceA')->share();

/**
* Привязываем для dependencyService у которого зависемость interfaceA serviceB
*/
$diConfigurator->getConfig('interfaceA')->bindFor('dependencyService','serviceB');

/**
* Singleton
*/
$diConfigurator->getConfig('serviceB')->share();

/**
* Установка параметров
*/
$diConfigurator->getConfig('DataDriver')->bind('Mongo')->setArguments(['server'=>'localhost']);
 

Вурдалак

Продвинутый новичок
Связывать сервис с интерфейсом — это вообще странная затея. У тебя что, в приложении не может быть 2 инстанса разных классов, но с одним интерфейсом?
 

t43

Новичок
Связывать сервис с интерфейсом — это вообще странная затея. У тебя что, в приложении не может быть 2 инстанса разных классов, но с одним интерфейсом?
Может, для этого и связываю
PHP:
class a
{
    private $cache;
    public function __construct(ICache $cache)
    {
        $this->cahe = $cache;
    }
}

class b
{
    private $cache;
    public function __construct(ICache $cache)
    {
        $this->cahe = $cache;
    }
}

class MemCached implements ICache{}
class FileCache implements ICache{}
 

Вурдалак

Продвинутый новичок
t43, не, не 2 класса, а 2 инстанса. Например, тебе понадобилось юзать 2 разных ICache: где-то memcached, а где-то — файловый. Как ты это сделаешь?
 

t43

Новичок
Если понял, то так:
PHP:
class a//Хочет FileCache
{
    private $cache;
    public function __construct(ICache $cache)
    {
        $this->cahe = $cache;
    }
}

class b//Хочет MemCached
{
    private $cache;
    public function __construct(ICache $cache)
    {
        $this->cahe = $cache;
    }
}

class MemCached implements ICache{}
class FileCache implements ICache{}

$di = new DiC();
$di->getConfigurator()->getConfig('ICache')->bindFor('a','FileCache')->bindFor('b','MemCached');
а тупанул)
 

t43

Новичок
стоп)
если в одном месте у Объекта класс а, должен быть FileCache, а в другом, у другого объекта класса а, MemCached нужно при вызове DI, это оговаривать: $di->get('a',['*cache'=>'MemCached ']) и $di->get('a',['*cache'=>'FileCache'])
 
Сверху