PHP凭借注入(DI)在PHP中的完毕

何以是爱戴注入?

IOC:英文全称:Inversion of
Control,粤语名称:控制反转,它还有个名字叫重视注入(Dependency
Injection,简称DI)。

当2个类的实例需求另二个类的实例帮助时,在价值观的次第设计进度中,常常由调用者来成立被调用者的实例。而选拔看重注入的形式,创设被调用者的办事不再由调用者来成功,因而叫控制反转,创设被调用者的实例的干活由IOC容器来形成,然后注入调用者,因而也号称珍贵注入。

举个简易的例子:

(1)原始社会里,大约从不社会分工。必要斧子的人(调用者)只可以协调去磨一把斧子(被调用者)。

(2)进入工业社会,工厂出现。斧子不再由普通人达成,而在工厂里被生产出来,此时亟需斧子的人(调用者)找到工厂,购买斧子,无须关注斧子的打造进度。

(3)进入“按需分配”社会,必要斧子的人不须求找到工厂,坐在家里爆发二个简便指令:必要斧子。斧子就自然出现在他前边。

先是种处境下,实例的调用者制造被调用的实例,必然须要被调用的类出以后调用者的代码里。不可以落到实处二者之间的松耦合。

第壹种状态下,调用者无须关怀被调用者具体落成进程,只必要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,那也是工厂格局大批量施用的缘故。但调用者要求自个儿定位工厂,调用者与一定工厂耦合在一块儿。

其两种情况下,调用者无须自身平素工厂,程序运转到需求被调用者时,看重注入容器自动提供被调用者实例。事实上,调用者和被调用者都处在正视注入容器的军事管制下,二者之间的依赖关系由依赖注入容器提供。因而调用者与被调用者的耦合度进一步下落,那使得应用越来越便于有限协理,那就是爱慕注入所要达到的目标。

用php已毕三个轻量的器重性注入容器

PHP,首先大家创制3个类,看起来是这么的:

<?php   
class Di
{
    protected $_service = [];
    public function set($name, $definition)
    {
        $this->_service[$name] = $definition;
    }
    public function get($name)
    {
        if (isset($this->_service[$name])) {
            $definition = $this->service[$name];
        } else {
            throw new Exception("Service '" . name . "' wasn't found in the dependency injection container");
        }

        if (is_object($definition)) {
            $instance = call_user_func($definition);
        }

        return $instance;
    }
}

现行我们已经有了三个简便的类,包括二个本性和五个法子。假若大家明天有八个类,redisDB和cache,redisDB提供贰个redis数据库的操作,cache负责缓存功用的贯彻并且正视于redisDB。

class redisDB
{
    protected $_di;

    protected $_options;

    public function __construct($options = null)
    {
        $this->_options = $options;
    }

    public function setDI($di)
    {
        $this->_di = $di;
    }

    public function find($key, $lifetime)
    {
        // code
    }

    public function save($key, $value, $lifetime)
    {
        // code
    }

    public function delete($key)
    {
        // code
    }
}

在这么些类中大家简要完结了redis的询问、保存和删除。你只怕会有疑难,此外壹个方式setDi是做什么样的。待小编延续为你讲解。另2个类和近年来以此类社团很像:

class cache
{
    protected $_di;

    protected $_options;

    protected $_connect;

    public function __construct($options = null)
    {
        $this->_options = $options;
    }

    public function setDI($di)
    {
        $this->_di = $di;
    }

    protected function _connect()
    {
        $options = $this->_options;
        if (isset($options['connect'])) {
            $service = $options['connect'];
        } else {
            $service = 'redis';
        }

        return $this->_di->get($service);
    }

    public function get($key, $lifetime)
    {
        $connect = $this->_connect;
        if (!is_object($connect)) {
            $connect = $this->_connect()
            $this->_connect = $connect;
        }
        // code
        ...
        return $connect->find($key, $lifetime);
    }

    public function save($key, $value, $lifetime)
    {
        $connect = $this->_connect;
        if (!is_object($connect)) {
            $connect = $this->_connect()
            $this->_connect = $connect;
        }
        // code
        ...
        return $connect->save($key, $lifetime);
    }

    public function delete($key)
    {
        $connect = $this->_connect;
        if (!is_object($connect)) {
            $connect = $this->_connect()
            $this->_connect = $connect;
        }
        // code
        ...
        $connect->delete($key, $lifetime);
    }
}

当今大家就当已经完成了redisDB和cache那七个零部件,具体的底细那里就先不做切磋了,来探望如何接纳应用啊。首先须求将多少个零件注入到容器中:

<?php
    $di = new Di();
    $di->set('redis', function() {
         return new redisDB([
             'host' => '127.0.0.1',
             'port' => 6379
         ]);
    });
    $di->set('cache', function() use ($di) {
        $cache = new cache([
            'connect' => 'redis'
        ]);
        $cache->setDi($di);
        return $cache;
    });


    // 然后在任何你想使用cache的地方
    $cache = $di->get('cache');
    $cache->get('key'); // 获取缓存数据
    $cache->save('key', 'value', 'lifetime'); // 保存数据
    $cache->delete('key'); // 删除数据

到此地你只怕会认为这么来说反而有些麻烦了。cache和redisDB的结构如此之像,完全可以把redis写到cache中而没要求单独分离出来?不过你想过没有,某个数据及时性没那么高而且数量相比大,用redis有点不合适,mongodb是更好的抉择;有些数据更新频率更慢,对查询速度也没须要,直接写入文件保留到硬盘恐怕越来越贴切;再或许,你的客户认为redis运营难度有点大,让您给他换到memcache…
那就是为何把它分离出来了。然后,继续改善代码:

interface BackendInterface {
    public function find($key, $lifetime);
    public function save($key, $value, $lifetime);
    public function delete($key);
}

class redisDB implements BackendInterface
{
    public function find($key, $lifetime) { }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
}

class mongoDB implements BackendInterface
{
    public function find($key, $lifetime) { }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
}

class file implements BackendInterface
{
    public function find($key, $lifetime) { }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
}

$di = new Di();
//  redis
$di->set('redis', function() {
     return new redisDB([
         'host' => '127.0.0.1',
         'port' => 6379
     ]);
});
// mongodb
$di->set('mongo', function() {
     return new mongoDB([
         'host' => '127.0.0.1',
         'port' => 12707
     ]);
});
// file
$di->set('file', function() {
     return new file([
         'path' => 'path'
     ]);
});
// save at redis
$di->set('fastCache', function() use ($di) {
     $cache = new cache([
         'connect' => 'redis'
     ]);
     $cache->setDi($di);
     return $cache;
});
// save at mongodb
$di->set('cache', function() use ($di) {
     $cache = new cache([
         'connect' => 'mongo'
     ]);
     $cache->setDi($di);
     return $cache;
});
// save at file
$di->set('slowCache', function() use ($di) {
     $cache = new cache([
         'connect' => 'file'
     ]);
     $cache->setDi($di);
     return $cache;
});

// 然后在任何你想使用cache的地方 
$cache = $di->get('cache');

咱俩新增添了贰个接口BackendInterface,规定了redisDB,mongoDB,file那两个类必须贯彻那几个接口所要求的成效,至于其余为虎添翼的效应,随你怎么表述。而cache的代码,好像从没变,因为cache不要求关切数据是怎么存入数据库只怕文件中。而cache的调用者,也不需求关爱cache具体是怎么落到实处的,只要依照接口已毕相应的不二法门就行了。多人搭档你会越加收益,你们只须要订立好接口,然后分别落成就行了。

那就是依靠注入的吸引力所在了,固然看似这么简约。

上述代码仍可以一连革新,直到你认为不错截止。比如,redis服务在一个呼吁中或者会调用数十四回,而每一趟调用都会另行创立,那将有损品质。只需伸张一下DI容器就好扩张3个参数或充实三个办法,随你。

class Di
{
    protected $_service = [];
    protected $_sharedService = [];
    public function set($name, $definition, $shared = false)
    {
        if ($shared) {
            $this->_sharedService[$name] = $definition;
        } else {
            $this->_service[$name] = $definition;
        }
    }
    public function get($name) {
        if (isset($this->_service[$name])) {
            $definition = $this->service[$name];
        } else if ($this->_sharedService[$name]) {
             $definition = $this->_sharedService[$name];
        } else {
            throw new Exception("Service '" . name . "' wasn't found in the dependency injection container");
        }
        ...
    }

这么来说,如若有个别服务在五遍呼吁中要调用数十次,你就足以将shared属性设置为true,以减掉不要求的浪费。假使你以为每一回在注入时都要setDi有点繁琐,想让她活动setDi,那可以那样做:

interface DiAwareInterface
{
    public function setDI($di);
    public function getDI();
}

class Di
{
    protected $service;

    public function set($name, $definition)
    {
        $this->service[$name] = $definition;
    }

    public function get($name)
    {
        ...
        if (is_object($definition)) {
            $instance = call_user_func($definition);
        }

        // 如果实现了DiAwareInterface这个接口,自动注入
        if (is_object($instance)) {
            if ($instance instanceof DiAwareInterface) {
                $instance->setDI($this);
            }
        }

        return $instance;
    }
}

class redisDB implements BackendInterface, DiAwareInterface
{
    public function find($key, $lifetime) { }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
}

下一场,就足以这么:

$di->set('cache', function() {
    return new cache([
        'connect' => 'mongo'
    ]);
});

我们未来所完毕的这么些DI容器还很简陋,还不扶助复杂的流入,你可以屡次三番完善它。

唯独,通过这么些代码你已经精晓怎么是依靠在注入了,你可以将那种思维应用到您的系列中,只怕起首开发你自身的框架。如若想继承深远学习的话,烦请
click
Scene

完。

相关文章