PHP控制反转(IOC)和依赖注入(DI)(转)

IOC(inversion of control)控制反转模式;控制反转是将组件间的依赖关系从程序内部提到外部来管理;
DI(dependency injection)依赖注入模式;依赖注入是指将组件的依赖通过外部以参数或其他形式注入;
两个说法本质上是一个意思。
例如:

class DbMysql
{
    public function query(){}
}
class Controller
{
    public $db;
    public function __construct()
    {
        $this->db = new DbMysql();
    }
    public function action()
    {
        $this->db->query();
    }
}
$c = new Controller();
$c->action();

Controller类中的action方法需要用到DbMysql类中的query方法,所以Controller类就对DbMysql类产生了依赖,Controller类和DbMysql类之间的耦合度就比较高,因为当DbMysql类的构造函数发生改变的时候,比如由现在的没有参数变成有参数了,参数数量改变了,那么Controller类中的代码都要做出相应改变。

或者说我们现在需要将DbMysql类换成另一个DbOracle类,Controller类中要做出的改变甚至更大

看下面的另一种写法:

class DbMysql
{
    public function query(){}
}
class Controller
{
    public $db;
    public function __construct($dbMysql)
    {
        $this->db = $dbMysql;
    }
    public function action()
    {
        $this->db->query();
    }
}
$db = new DbMysql();
$c = new Controller($db);
$c->action();

Controller类中不需要实例化DbMysql,而是将DbMysql类的实例作为参数传递(或者单独写一个接收实例的方法处理),这样Controller类就完全不用管DbMysql是怎么样实例的,而是仅仅调用DbMysql中的query方法就行了。这种模式就是依赖注入。

第一个例子中Controller类负责实例DbMysql,也就是说Controller类控制着实例DbMysql类的主动权,而第二个例子中将这个主动权提出到Controller类的外面,所以也叫做控制反转。

这样看起来还不错,但是如果我们需要很多类,而且需要自己写的时候弄清楚,每一个类依赖什么类,这样太烦,如果有一个类能够帮我们搞定这个动作那就太爽了。而事实上有这个类,这个类就叫做IOC容器。

下面通过实例与大家分析分析

示例一

class DbMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class DbRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct()
    {
        $this->mysql = new DbMysql('host', 'name', 'pwd');
        $this->redis = new DbRedis('host', 'name', 'pwd');
    }

    public function action()
    {
        $this->mysql->query();
        $this->redis->set();
    }
}

$c = new Controller();
$c->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

普通的实现方式,耦合度高。

示例二

class DbMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class DbRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct($mysql, $redis)
    {
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        $this->mysql->query();
        $this->redis->set();
    }
}

$mysql = new DbMysql('host', 'name', 'pwd');
$redis = new DbRedis('host', 'name', 'pwd');
$c = new Controller($mysql, $redis);
$c->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

实现了依赖注入和控制反转,但是没有使用容器类。

示例三

class DbMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class DbRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct($mysql, $redis)
    {
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        $this->mysql->query();
        $this->redis->set();
    }
}

class Container
{

    public $bindings = [];

    public function bind($key, Closure $value)
    {
        $this->bindings[$key] = $value;
    }

    public function make($key)
    {
        $new = $this->bindings[$key];
        return $new();
    }

}

$app = new Container();
$app->bind('mysql', function () {
    return new DbMysql('host', 'name', 'pwd');
});
$app->bind('redis', function () {
    return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', function () use ($app) {
    return new Controller($app->make('mysql'), $app->make('redis'));
});
$controller = $app->make('controller');
$controller->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

实现了基本的容器类,容器类中有两个方法,bind和make,一个是绑定操作,一个是实例化操作。将每一个需要使用到的类使用关键字绑定到容器类中去,但是每一个类仍然需要手动去实例化,这里引入了闭包函数,主要作用是在调用的时候才真正去实例化,而如果仅仅是绑定了一个类,是不会实例化这个类的。

示例四

class T
{
    public $t;
}

class X
{
    public $x;

    private function __construct()
    {
    }
}

class Y
{
    public $x;

    public function __construct()
    {
    }
}

interface Is
{
}

class Sis implements Is
{

}

class S
{
    public $s;

    public function __construct(string $s, int $i, array $a, Is $object)
    {
        $this->s = $s;
    }
}

function reflectionClass($className, array $inParams = [])
{
    $reflection = new ReflectionClass($className);
    // isInstantiable() 方法判断类是否可以实例化
    $isInstantiable = $reflection->isInstantiable();
    if ($isInstantiable) {
        // getConstructor() 方法获取类的构造函数,为NULL没有构造函数
        $constructor = $reflection->getConstructor();
        if (is_null($constructor)) {
            // 没有构造函数直接实例化对象返回
            return new $className;
        } else {
            // 有构造函数
            $params = $constructor->getParameters();
            if (empty($params)) {
                // 构造函数没有参数,直接实例化对象返回
                return new $className;
            } else {
                // 构造函数有参数,将$inParams传入实例化对象返回
                return $reflection->newInstanceArgs($inParams);
            }
        }
    }
    return null;
}

$t = reflectionClass('T');
var_dump($t instanceof T);
$x = reflectionClass('X');
var_dump($x instanceof X);
$x = reflectionClass('Y');
var_dump($x instanceof Y);
$s = reflectionClass('S', ['asdf', 123, [1, 2], (new Sis)]);
var_dump($s instanceof S);
/**
 * 输出:
 * bool(true)
 * bool(false)
 * bool(true)
 * bool(true)
 */

引入反射类,他的作用是可以实例化一个类,和new操作一样。但是实例化一个类所需要的参数,他都能自动检测出来。并且能够检测出来这个参数是不是一个继承了接口的类。上面说一个类依赖另一个类,然后将另一个类作为参数注入,这个反射能够检测出一个类实例化的时候需要什么样的类,好像有点眉目了是吧。

示例五

class DbMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class DbRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct($mysql, $redis)
    {
        var_dump($mysql);var_dump($redis);
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        is_object($this->mysql) && $this->mysql->query();
        is_object($this->redis) && $this->redis->set();
    }
}

class Container
{

    public $bindings = [];

    public function bind($key, $value)
    {
        if (!$value instanceof Closure) {
            $this->bindings[$key] = $this->getClosure($value);
        }
        else{
            $this->bindings[$key] = $value;
        }
    }

    public function getClosure($value)
    {
        return function () use ($value) {
            return $this->build($value);
        };
    }

    public function make($key)
    {
        if (isset($this->bindings[$key])) {
            return $this->build($this->bindings[$key]);
        }
        return $this->build($key);
    }

    public function build($value)
    {
        if ($value instanceof Closure) {
            return $value();
        }
        // 实例化反射类
        $reflection = new ReflectionClass($value);
        // isInstantiable() 方法判断类是否可以实例化
        $isInstantiable = $reflection->isInstantiable();
        if ($isInstantiable) {
            // getConstructor() 方法获取类的构造函数,为NULL没有构造函数
            $constructor = $reflection->getConstructor();
            if (is_null($constructor)) {
                // 没有构造函数直接实例化对象返回
                return new $value;
            } else {
                // 有构造函数
                $params = $constructor->getParameters();
                if (empty($params)) {
                    // 构造函数没有参数,直接实例化对象返回
                    return new $value;
                } else {
                    $dependencies = [];
                    // 构造函数有参数
                    foreach ($params as $param) {
                        $dependency = $param->getClass();
                        if (is_null($dependency)) {
                            // 构造函数参数不为class,返回NULL
                            $dependencies[] = NULL;
                        } else {
                            // 类存在创建类实例
                            $dependencies[] = $this->make($param->getClass()->name);
                        }
                    }
                    return $reflection->newInstanceArgs($dependencies);
                }
            }
        }
        return null;
    }

}

$app = new Container();
$app->bind('mysql', function () {
    return new DbMysql('host', 'name', 'pwd');
});
$app->bind('redis', function () {
    return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', 'controller');
$controller = $app->make('controller');
$controller->action();
/**
 * 输出:
 * NULL
 * NULL
 */

容器类中引入反射,容器类bind方法升级,不仅仅支持闭包绑定,而且支持类名绑定。示例三中的bind方法仅仅支持绑定一个关键字为闭包,而类的实例操作,需要写到闭包函数中去。现在有了反射,可以直接使用类名,反射会根据类名自动去实例化这个类。

但是这个例子中输出两个NULL,Controller类的两个参数均为NULL,反射类并没有自动去找到Controller依赖的DbMysql和DbRedis去实例化。这是为什么呢?现在就需要引入另一个东西,针对接口编程。

这个例子中我们知道Controller类的两个参数是类,但是我们定义的构造函数中并没有声明,现在这种定义方式的两个参数,是类,是字符串,是整型,完全没区别的,反射类无法检测出这两个参数是类,我们的反射方法里面如果检测到构造函数的参数不是类直接返回NULL,所以这里输出了两个NULL。

示例六

interface SMysql
{
    public function query();
}

class DbMysql implements SMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

interface SRedis
{
    public function set();
}

class DbRedis implements SRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct(SMysql $mysql, SRedis $redis)
    {
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        is_object($this->mysql) && $this->mysql->query();
        is_object($this->redis) && $this->redis->set();
    }
}

class Container
{

    public $bindings = [];

    public function bind($key, $value)
    {
        if (!$value instanceof Closure) {
            $this->bindings[$key] = $this->getClosure($value);
        } else {
            $this->bindings[$key] = $value;
        }
    }

    public function getClosure($value)
    {
        return function () use ($value) {
            return $this->build($value);
        };
    }

    public function make($key)
    {
        if (isset($this->bindings[$key])) {
            return $this->build($this->bindings[$key]);
        }
        return $this->build($key);
    }

    public function build($value)
    {
        if ($value instanceof Closure) {
            return $value();
        }
        // 实例化反射类
        $reflection = new ReflectionClass($value);
        // isInstantiable() 方法判断类是否可以实例化
        $isInstantiable = $reflection->isInstantiable();
        if ($isInstantiable) {
            // getConstructor() 方法获取类的构造函数,为NULL没有构造函数
            $constructor = $reflection->getConstructor();
            if (is_null($constructor)) {
                // 没有构造函数直接实例化对象返回
                return new $value;
            } else {
                // 有构造函数
                $params = $constructor->getParameters();
                if (empty($params)) {
                    // 构造函数没有参数,直接实例化对象返回
                    return new $value;
                } else {
                    $dependencies = [];
                    // 构造函数有参数
                    foreach ($params as $param) {
                        $dependency = $param->getClass();
                        if (is_null($dependency)) {
                            // 构造函数参数不为class,返回NULL
                            $dependencies[] = NULL;
                        } else {
                            // 类存在创建类实例
                            $dependencies[] = $this->make($param->getClass()->name);
                        }
                    }
                    return $reflection->newInstanceArgs($dependencies);
                }
            }
        }
        return null;
    }

}

$app = new Container();
$app->bind('SMysql', function () {
    return new DbMysql('host', 'name', 'pwd');
});
$app->bind('SRedis', function () {
    return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', 'controller');
$controller = $app->make('controller');
$controller->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

使用接口了以后脚本执行正常。

首先容器绑定了接口SMysql和SRedis分别对应的闭包,绑定的关键字为接口名称,也就是说在容器中多次绑定一个接口只会绑定一个闭包一个类的实现,controller类的构造函数中声明了需要两个分别继承自SMysql接口和SRedis接口的类。容器中已经有了这两个接口的实现方式,所以直接调用闭包函数实例化类,然后将结果提供给controller类进行实例化。

本例中,我们仅仅make了controller类,也就是说我们只需要实例化controller类,而controller类依赖的DbMysql类和DbRedis类,IOC容器会自动帮我们实例化并注入。

本例中controller类的实例化很简单:

$app->bind('controller', 'controller');
$controller = $app->make('controller');

但是DbMysql和DbRedis就比较丑了:

$app->bind('SMysql', function () {
    return new DbMysql('host', 'name', 'pwd');
});
$app->bind('SRedis', function () {
    return new DbRedis('host', 'name', 'pwd');
});

其实是这两个类写的有问题,因为这两个类不是面对接口编程。但是这种自定义闭包函数的绑定非常方便,完全满足任何类的自由实例化。我们要做是依赖注入,那就全部使用接口来实现,看下一个例子。

示例七

interface MConfig
{
    public function getConfig();
}

class MysqlConfig implements MConfig
{
    public function getConfig()
    {
        // 获取配置
        return ['host', 'name', 'pwd'];
    }
}

interface RConfig
{
    public function getConfig();
}

class RedisConfig implements RConfig
{
    public function getConfig()
    {
        // 获取配置
        return ['host', 'name', 'pwd'];
    }
}

interface SMysql
{
    public function query();
}

class DbMysql implements SMysql
{
    public $config;

    public function __construct(MConfig $config)
    {
        $this->config = $config->getConfig();
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

interface SRedis
{
    public function Set();
}

class DbRedis implements SRedis
{
    public function __construct(RConfig $config)
    {
        $this->config = $config->getConfig();
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class Controller
{
    public $mysql;
    public $redis;

    public function __construct(SMysql $mysql, SRedis $redis)
    {
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        is_object($this->mysql) && $this->mysql->query();
        is_object($this->redis) && $this->redis->set();
    }
}

class Container
{

    public $bindings = [];

    public function bind($key, $value)
    {
        if (!$value instanceof Closure) {
            $this->bindings[$key] = $this->getClosure($value);
        } else {
            $this->bindings[$key] = $value;
        }
    }

    public function getClosure($value)
    {
        return function () use ($value) {
            return $this->build($value);
        };
    }

    public function make($key)
    {
        if (isset($this->bindings[$key])) {
            return $this->build($this->bindings[$key]);
        }
        return $this->build($key);
    }

    public function build($value)
    {
        if ($value instanceof Closure) {
            return $value();
        }
        // 实例化反射类
        $reflection = new ReflectionClass($value);
        // isInstantiable() 方法判断类是否可以实例化
        $isInstantiable = $reflection->isInstantiable();
        if ($isInstantiable) {
            // getConstructor() 方法获取类的构造函数,为NULL没有构造函数
            $constructor = $reflection->getConstructor();
            if (is_null($constructor)) {
                // 没有构造函数直接实例化对象返回
                return new $value;
            } else {
                // 有构造函数
                $params = $constructor->getParameters();
                if (empty($params)) {
                    // 构造函数没有参数,直接实例化对象返回
                    return new $value;
                } else {
                    $dependencies = [];
                    // 构造函数有参数
                    foreach ($params as $param) {
                        $dependency = $param->getClass();
                        if (is_null($dependency)) {
                            // 构造函数参数不为class,返回NULL
                            $dependencies[] = NULL;
                        } else {
                            // 类存在创建类实例
                            $dependencies[] = $this->make($param->getClass()->name);
                        }
                    }
                    return $reflection->newInstanceArgs($dependencies);
                }
            }
        }
        return null;
    }

}

$app = new Container();
$app->bind('MConfig', 'MysqlConfig');
$app->bind('RConfig', 'RedisConfig');
$app->bind('SMysql', 'DbMysql');
$app->bind('SRedis', 'DbRedis');
$app->bind('controller', 'Controller');
$controller = $app->make('controller');
$controller->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

我们只需要实例化一个IOC容器,而后我们要使用到的所有类,它都能帮我们自动依赖注入。当然,这种实现是建立在组件针对接口编程上面的,否则就是示例六中的那种。

IOC(inversion of control)控制反转模式;控制反转是将组件间的依赖关系从程序内部提到外部来管理;
DI(dependency injection)依赖注入模式;依赖注入是指将组件的依赖通过外部以参数或其他形式注入;
两个说法本质上是一个意思。
例如:

class DbMysql
{
    public function query(){}
}
class Controller
{
    public $db;
    public function __construct()
    {
        $this->db = new DbMysql();
    }
    public function action()
    {
        $this->db->query();
    }
}
$c = new Controller();
$c->action();

Controller类中的action方法需要用到DbMysql类中的query方法,所以Controller类就对DbMysql类产生了依赖,Controller类和DbMysql类之间的耦合度就比较高,因为当DbMysql类的构造函数发生改变的时候,比如由现在的没有参数变成有参数了,参数数量改变了,那么Controller类中的代码都要做出相应改变。

或者说我们现在需要将DbMysql类换成另一个DbOracle类,Controller类中要做出的改变甚至更大

看下面的另一种写法:

class DbMysql
{
    public function query(){}
}
class Controller
{
    public $db;
    public function __construct($dbMysql)
    {
        $this->db = $dbMysql;
    }
    public function action()
    {
        $this->db->query();
    }
}
$db = new DbMysql();
$c = new Controller($db);
$c->action();

Controller类中不需要实例化DbMysql,而是将DbMysql类的实例作为参数传递(或者单独写一个接收实例的方法处理),这样Controller类就完全不用管DbMysql是怎么样实例的,而是仅仅调用DbMysql中的query方法就行了。这种模式就是依赖注入。

第一个例子中Controller类负责实例DbMysql,也就是说Controller类控制着实例DbMysql类的主动权,而第二个例子中将这个主动权提出到Controller类的外面,所以也叫做控制反转。

这样看起来还不错,但是如果我们需要很多类,而且需要自己写的时候弄清楚,每一个类依赖什么类,这样太烦,如果有一个类能够帮我们搞定这个动作那就太爽了。而事实上有这个类,这个类就叫做IOC容器。

下面通过实例与大家分析分析

示例一

class DbMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class DbRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct()
    {
        $this->mysql = new DbMysql('host', 'name', 'pwd');
        $this->redis = new DbRedis('host', 'name', 'pwd');
    }

    public function action()
    {
        $this->mysql->query();
        $this->redis->set();
    }
}

$c = new Controller();
$c->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

普通的实现方式,耦合度高。

示例二

class DbMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class DbRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct($mysql, $redis)
    {
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        $this->mysql->query();
        $this->redis->set();
    }
}

$mysql = new DbMysql('host', 'name', 'pwd');
$redis = new DbRedis('host', 'name', 'pwd');
$c = new Controller($mysql, $redis);
$c->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

实现了依赖注入和控制反转,但是没有使用容器类。

示例三

class DbMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class DbRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct($mysql, $redis)
    {
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        $this->mysql->query();
        $this->redis->set();
    }
}

class Container
{

    public $bindings = [];

    public function bind($key, Closure $value)
    {
        $this->bindings[$key] = $value;
    }

    public function make($key)
    {
        $new = $this->bindings[$key];
        return $new();
    }

}

$app = new Container();
$app->bind('mysql', function () {
    return new DbMysql('host', 'name', 'pwd');
});
$app->bind('redis', function () {
    return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', function () use ($app) {
    return new Controller($app->make('mysql'), $app->make('redis'));
});
$controller = $app->make('controller');
$controller->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

实现了基本的容器类,容器类中有两个方法,bind和make,一个是绑定操作,一个是实例化操作。将每一个需要使用到的类使用关键字绑定到容器类中去,但是每一个类仍然需要手动去实例化,这里引入了闭包函数,主要作用是在调用的时候才真正去实例化,而如果仅仅是绑定了一个类,是不会实例化这个类的。

示例四

class T
{
    public $t;
}

class X
{
    public $x;

    private function __construct()
    {
    }
}

class Y
{
    public $x;

    public function __construct()
    {
    }
}

interface Is
{
}

class Sis implements Is
{

}

class S
{
    public $s;

    public function __construct(string $s, int $i, array $a, Is $object)
    {
        $this->s = $s;
    }
}

function reflectionClass($className, array $inParams = [])
{
    $reflection = new ReflectionClass($className);
    // isInstantiable() 方法判断类是否可以实例化
    $isInstantiable = $reflection->isInstantiable();
    if ($isInstantiable) {
        // getConstructor() 方法获取类的构造函数,为NULL没有构造函数
        $constructor = $reflection->getConstructor();
        if (is_null($constructor)) {
            // 没有构造函数直接实例化对象返回
            return new $className;
        } else {
            // 有构造函数
            $params = $constructor->getParameters();
            if (empty($params)) {
                // 构造函数没有参数,直接实例化对象返回
                return new $className;
            } else {
                // 构造函数有参数,将$inParams传入实例化对象返回
                return $reflection->newInstanceArgs($inParams);
            }
        }
    }
    return null;
}

$t = reflectionClass('T');
var_dump($t instanceof T);
$x = reflectionClass('X');
var_dump($x instanceof X);
$x = reflectionClass('Y');
var_dump($x instanceof Y);
$s = reflectionClass('S', ['asdf', 123, [1, 2], (new Sis)]);
var_dump($s instanceof S);
/**
 * 输出:
 * bool(true)
 * bool(false)
 * bool(true)
 * bool(true)
 */

引入反射类,他的作用是可以实例化一个类,和new操作一样。但是实例化一个类所需要的参数,他都能自动检测出来。并且能够检测出来这个参数是不是一个继承了接口的类。上面说一个类依赖另一个类,然后将另一个类作为参数注入,这个反射能够检测出一个类实例化的时候需要什么样的类,好像有点眉目了是吧。

示例五

class DbMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class DbRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct($mysql, $redis)
    {
        var_dump($mysql);var_dump($redis);
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        is_object($this->mysql) && $this->mysql->query();
        is_object($this->redis) && $this->redis->set();
    }
}

class Container
{

    public $bindings = [];

    public function bind($key, $value)
    {
        if (!$value instanceof Closure) {
            $this->bindings[$key] = $this->getClosure($value);
        }
        else{
            $this->bindings[$key] = $value;
        }
    }

    public function getClosure($value)
    {
        return function () use ($value) {
            return $this->build($value);
        };
    }

    public function make($key)
    {
        if (isset($this->bindings[$key])) {
            return $this->build($this->bindings[$key]);
        }
        return $this->build($key);
    }

    public function build($value)
    {
        if ($value instanceof Closure) {
            return $value();
        }
        // 实例化反射类
        $reflection = new ReflectionClass($value);
        // isInstantiable() 方法判断类是否可以实例化
        $isInstantiable = $reflection->isInstantiable();
        if ($isInstantiable) {
            // getConstructor() 方法获取类的构造函数,为NULL没有构造函数
            $constructor = $reflection->getConstructor();
            if (is_null($constructor)) {
                // 没有构造函数直接实例化对象返回
                return new $value;
            } else {
                // 有构造函数
                $params = $constructor->getParameters();
                if (empty($params)) {
                    // 构造函数没有参数,直接实例化对象返回
                    return new $value;
                } else {
                    $dependencies = [];
                    // 构造函数有参数
                    foreach ($params as $param) {
                        $dependency = $param->getClass();
                        if (is_null($dependency)) {
                            // 构造函数参数不为class,返回NULL
                            $dependencies[] = NULL;
                        } else {
                            // 类存在创建类实例
                            $dependencies[] = $this->make($param->getClass()->name);
                        }
                    }
                    return $reflection->newInstanceArgs($dependencies);
                }
            }
        }
        return null;
    }

}

$app = new Container();
$app->bind('mysql', function () {
    return new DbMysql('host', 'name', 'pwd');
});
$app->bind('redis', function () {
    return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', 'controller');
$controller = $app->make('controller');
$controller->action();
/**
 * 输出:
 * NULL
 * NULL
 */

容器类中引入反射,容器类bind方法升级,不仅仅支持闭包绑定,而且支持类名绑定。示例三中的bind方法仅仅支持绑定一个关键字为闭包,而类的实例操作,需要写到闭包函数中去。现在有了反射,可以直接使用类名,反射会根据类名自动去实例化这个类。

但是这个例子中输出两个NULL,Controller类的两个参数均为NULL,反射类并没有自动去找到Controller依赖的DbMysql和DbRedis去实例化。这是为什么呢?现在就需要引入另一个东西,针对接口编程。

这个例子中我们知道Controller类的两个参数是类,但是我们定义的构造函数中并没有声明,现在这种定义方式的两个参数,是类,是字符串,是整型,完全没区别的,反射类无法检测出这两个参数是类,我们的反射方法里面如果检测到构造函数的参数不是类直接返回NULL,所以这里输出了两个NULL。

示例六

interface SMysql
{
    public function query();
}

class DbMysql implements SMysql
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

interface SRedis
{
    public function set();
}

class DbRedis implements SRedis
{
    public function __construct($host, $name, $pwd)
    {
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class controller
{
    public $mysql;
    public $redis;

    public function __construct(SMysql $mysql, SRedis $redis)
    {
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        is_object($this->mysql) && $this->mysql->query();
        is_object($this->redis) && $this->redis->set();
    }
}

class Container
{

    public $bindings = [];

    public function bind($key, $value)
    {
        if (!$value instanceof Closure) {
            $this->bindings[$key] = $this->getClosure($value);
        } else {
            $this->bindings[$key] = $value;
        }
    }

    public function getClosure($value)
    {
        return function () use ($value) {
            return $this->build($value);
        };
    }

    public function make($key)
    {
        if (isset($this->bindings[$key])) {
            return $this->build($this->bindings[$key]);
        }
        return $this->build($key);
    }

    public function build($value)
    {
        if ($value instanceof Closure) {
            return $value();
        }
        // 实例化反射类
        $reflection = new ReflectionClass($value);
        // isInstantiable() 方法判断类是否可以实例化
        $isInstantiable = $reflection->isInstantiable();
        if ($isInstantiable) {
            // getConstructor() 方法获取类的构造函数,为NULL没有构造函数
            $constructor = $reflection->getConstructor();
            if (is_null($constructor)) {
                // 没有构造函数直接实例化对象返回
                return new $value;
            } else {
                // 有构造函数
                $params = $constructor->getParameters();
                if (empty($params)) {
                    // 构造函数没有参数,直接实例化对象返回
                    return new $value;
                } else {
                    $dependencies = [];
                    // 构造函数有参数
                    foreach ($params as $param) {
                        $dependency = $param->getClass();
                        if (is_null($dependency)) {
                            // 构造函数参数不为class,返回NULL
                            $dependencies[] = NULL;
                        } else {
                            // 类存在创建类实例
                            $dependencies[] = $this->make($param->getClass()->name);
                        }
                    }
                    return $reflection->newInstanceArgs($dependencies);
                }
            }
        }
        return null;
    }

}

$app = new Container();
$app->bind('SMysql', function () {
    return new DbMysql('host', 'name', 'pwd');
});
$app->bind('SRedis', function () {
    return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', 'controller');
$controller = $app->make('controller');
$controller->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

使用接口了以后脚本执行正常。

首先容器绑定了接口SMysql和SRedis分别对应的闭包,绑定的关键字为接口名称,也就是说在容器中多次绑定一个接口只会绑定一个闭包一个类的实现,controller类的构造函数中声明了需要两个分别继承自SMysql接口和SRedis接口的类。容器中已经有了这两个接口的实现方式,所以直接调用闭包函数实例化类,然后将结果提供给controller类进行实例化。

本例中,我们仅仅make了controller类,也就是说我们只需要实例化controller类,而controller类依赖的DbMysql类和DbRedis类,IOC容器会自动帮我们实例化并注入。

本例中controller类的实例化很简单:

$app->bind('controller', 'controller');
$controller = $app->make('controller');

但是DbMysql和DbRedis就比较丑了:

$app->bind('SMysql', function () {
    return new DbMysql('host', 'name', 'pwd');
});
$app->bind('SRedis', function () {
    return new DbRedis('host', 'name', 'pwd');
});

其实是这两个类写的有问题,因为这两个类不是面对接口编程。但是这种自定义闭包函数的绑定非常方便,完全满足任何类的自由实例化。我们要做是依赖注入,那就全部使用接口来实现,看下一个例子。

示例七

interface MConfig
{
    public function getConfig();
}

class MysqlConfig implements MConfig
{
    public function getConfig()
    {
        // 获取配置
        return ['host', 'name', 'pwd'];
    }
}

interface RConfig
{
    public function getConfig();
}

class RedisConfig implements RConfig
{
    public function getConfig()
    {
        // 获取配置
        return ['host', 'name', 'pwd'];
    }
}

interface SMysql
{
    public function query();
}

class DbMysql implements SMysql
{
    public $config;

    public function __construct(MConfig $config)
    {
        $this->config = $config->getConfig();
        // do something
    }

    public function query()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

interface SRedis
{
    public function Set();
}

class DbRedis implements SRedis
{
    public function __construct(RConfig $config)
    {
        $this->config = $config->getConfig();
        // do something
    }

    public function set()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

class Controller
{
    public $mysql;
    public $redis;

    public function __construct(SMysql $mysql, SRedis $redis)
    {
        $this->mysql = $mysql;
        $this->redis = $redis;
    }

    public function action()
    {
        is_object($this->mysql) && $this->mysql->query();
        is_object($this->redis) && $this->redis->set();
    }
}

class Container
{

    public $bindings = [];

    public function bind($key, $value)
    {
        if (!$value instanceof Closure) {
            $this->bindings[$key] = $this->getClosure($value);
        } else {
            $this->bindings[$key] = $value;
        }
    }

    public function getClosure($value)
    {
        return function () use ($value) {
            return $this->build($value);
        };
    }

    public function make($key)
    {
        if (isset($this->bindings[$key])) {
            return $this->build($this->bindings[$key]);
        }
        return $this->build($key);
    }

    public function build($value)
    {
        if ($value instanceof Closure) {
            return $value();
        }
        // 实例化反射类
        $reflection = new ReflectionClass($value);
        // isInstantiable() 方法判断类是否可以实例化
        $isInstantiable = $reflection->isInstantiable();
        if ($isInstantiable) {
            // getConstructor() 方法获取类的构造函数,为NULL没有构造函数
            $constructor = $reflection->getConstructor();
            if (is_null($constructor)) {
                // 没有构造函数直接实例化对象返回
                return new $value;
            } else {
                // 有构造函数
                $params = $constructor->getParameters();
                if (empty($params)) {
                    // 构造函数没有参数,直接实例化对象返回
                    return new $value;
                } else {
                    $dependencies = [];
                    // 构造函数有参数
                    foreach ($params as $param) {
                        $dependency = $param->getClass();
                        if (is_null($dependency)) {
                            // 构造函数参数不为class,返回NULL
                            $dependencies[] = NULL;
                        } else {
                            // 类存在创建类实例
                            $dependencies[] = $this->make($param->getClass()->name);
                        }
                    }
                    return $reflection->newInstanceArgs($dependencies);
                }
            }
        }
        return null;
    }

}

$app = new Container();
$app->bind('MConfig', 'MysqlConfig');
$app->bind('RConfig', 'RedisConfig');
$app->bind('SMysql', 'DbMysql');
$app->bind('SRedis', 'DbRedis');
$app->bind('controller', 'Controller');
$controller = $app->make('controller');
$controller->action();
/**
 * 输出:
 * DbMysql::query
 * DbRedis::set
 */

我们只需要实例化一个IOC容器,而后我们要使用到的所有类,它都能帮我们自动依赖注入。当然,这种实现是建立在组件针对接口编程上面的,否则就是示例六中的那种。

转载说明

文章来自知乎:https://zhuanlan.zhihu.com/p/93270244

作者:PHP进阶架构师

原创文章,作者:Zeyu,如若转载,请注明出处:https://jinzhijun.cn/develop/944

赞 (0)
ZeyuZeyu
上一篇 2021年12月18日 下午4:31
下一篇 2021年12月30日 下午10:29

相关推荐

  • 红米8A刷机root流程记录

    自从安卓版本升级以后,app抓包https就成了问题,所以上周就在咸鱼上买了一个红米8a,计划着root手机来做开发测试使用。在绑了小米账号7天后,终于可以解除bl锁,然后把手机r…

    2023年3月5日
    7.1K
  • docker的备份和迁移

    1.查看容器 2.容器保存为镜像 php是容器名称 myphp是新的镜像名称 此镜像的内容就是你当前容器的内容,接下来你可以用此镜像再次运行新的容器 然后查询镜像,看一下结果 3.…

    2020年11月17日
    1.8K
  • PHP代码审计一条龙思路(转)

    00×0 前言 最近也是边挖src边审计代码,总结下最近的php代码审计的一些思路,我一般按照顺序往下做,限于能力水平,可能会有不对或者欠缺的地方,希望各位师傅能够指导。 00×1…

    2022年5月31日
    1.8K
  • PHP获取客户端相关访问信息

    (一)PHP通过IP获取客户端相关访问信息 (二)PHP解析HTTP_USER_AGENT 获取客户端操作系统 获取客户端操作系统  Windows NT 4.0 = N…

    2020年11月3日
    42.6K
  • Centos安装elasticsearch教程

    最近在部署一个项目是用 laravel开发,项目用到了了 elasticsearch,通过自己的搜索和学习能力不算很费力解决了。下面就整理一下安装elasticsearch教程,服…

    2020年11月26日
    2.0K
  • 常见的Web攻击手段(转)

    午饭期间,读者小李与我闲聊,谈到上周去面试的过程。经典的高开低走,面试初期答得还可以,但是到后面却不尽人意。其中有个面试问题引起了我的注意,面试官当时问小李:你知道有哪几种常见的&…

    2021年10月10日
    1.6K
  • 阿里云SLB(负载均衡)访问远程端口

    最近公司在搞互联网医院三级等保的问题,然后广东卫生健康委办公室的接入通知里有一段话,说是用于互联网医院运行的服务器不少于2套(这里的单位是套),我们不知道这里的意思是指,服务器要有…

    2020年12月19日
    2.4K
  • 记录一下宝塔搭建Redis遇到的一个小问题

    最近在对接广东省互联网医院监管平台时,发现广东这边是要求平台必须要有医生和患者要有视频通话功能。因为原本的消息通话是用的网易IM,所以音视频也选的网易。网易音视频能实现通话视频云录…

    2021年6月29日
    1.9K
  • JS定时触发—–新订单提醒展示效果并提示音效(转)

    展示效果 php代码 版权声明:本文为lixing1359199697原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本文链…

    前端 2022年6月28日
    2.1K
  • linux 测试端口通不通(四种方法)(转)

    针对Linux系统:有1、2、3、4四种方法针对Windows系统:有2、5两种通用方法 一般情况下使用”telnet ip port”判断端口通不通,其实…

    2022年3月13日
    2.6K

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注