该篇属于《Laravel底层核心技术实战揭秘》这一课程《laravel底层核心概念解析》这一章的扩展阅读。由于要真正学好laravel底层,有些PHP相关的知识必须得了解,考虑到学员们的基础差异,为了避免视频当中过于详细而连篇累牍,故将一些laravel底层实现相关的PHP知识点以文章形式呈现,供大家预习和随时查阅。

php传参类型声明(type declaration)

今天,我们不在laravel里,自己搞个PHP文件测试:

<?php
class Bar {}

function injection($bar)
{
	var_dump($bar);
}

injection();

?>

我们想的是把Class Bar的一个实例传进去,但是这样肯定会报错:

Fatal error: Uncaught ArgumentCountError: Too few arguments to function injection(), 0 passed in

好吧,那就给你传个参数进去:

$bar = new Bar;
injection($bar);

这样就可以了:

object(Bar)[1]

问题是,这个时候,我怎么确保你传递进去的就是Class Bar的实例呢?我们完全可以传个别的进去啊:

class Foo {}

$bar = new Foo;
injection($bar);

这个时候也不会报错:

object(Foo)[1]

由于这个时候我们的function injection($bar)期望传入的只是一个变量$bar,所以如果我们需要根据这个变量$bar的类型来操作,我们就得这样:

<?php
class Bar {}
class Foo {}

function injection($bar)
{
	if($bar instanceof Bar){
		echo 'Bar type';
	}elseif($bar instanceof Foo){
		echo 'Foo type';
	}
}

$bar = new Foo;
injection($bar);

?>

这样的话就会输出:

Foo type

看到这儿,相信你应该能想起我们在《Laravel底层核心技术实战揭秘》自定义常见的系统Exception时用过:

public function render($request, Exception $e)
    {
        if($e instanceof TokenMismatchException){
            ...
        }

        if($e instanceof ModelNotFoundException){
           ...
        }

        if($e instanceof FatalErrorException){
            ...
        }

        ...
    }

但是,更经常的是,我们在往一个方法里传参的时候,我们只期望是某一种类型(type),比如你传进来的必须是一个UserModel的实例,传其他的进来就没有意义,这个时候就需要用到PHP的类型声明(type declaration),允许函数在调用时要求参数为特定类型。 如果给出的值类型不对,那么将会产生一个错误: 在PHP 5中,这将是一个可恢复的致命错误,而在PHP 7中将会抛出一个TypeError异常。

好吧,这没什么稀奇的,相信很多人都已经知道了,所以之前的例子现在就可以写成:

<?php
class Bar {}
class Foo {}

function injection(Bar $bar)
{
	echo get_class($bar);
}

$bar = new Foo;
injection($bar);

?>

此时就会输出:

Fatal error: Uncaught TypeError: Argument 1 passed to injection() must be an instance of Bar, instance of Foo given,

如果我们将$bar = new Foo;改成$bar = new Bar;就会输出:

Bar

这样我们就限定了依赖注入(dependency injection)的类型(type hinted),嗯,没啥高科技。

常见的依赖注入(dependency injection)——方法注入(method injection)

这里需要注意的是,我们在执行injection($bar);前,手动实例化了一个BarClass,所以这个时候相当于我们执行的是:injection(new Bar);,如果这个injection()方法是在一个class当中,因为这样更符合实际情况,例如:

<?php
class Bar {}
class Foo {}

class Baz {
	function injection(Bar $bar)
	{
		echo get_class($bar);
	}
}

$baz = new Baz;
$bar = new Bar;
$result = $baz->injection($bar);

//相当于$result = (new Baz)->injection(new Bar);
?>

这就是一个(单一)方法注入(method injection)

常见的依赖注入(dependency injection)——构造函数依赖注入(constructor injection)

如果这个object参数还经常用在其他同类(class)方法里,那么我们通常放在一个构造函数(constructor)里,就成了这样子:

class Baz {

	protected $bar;
	public function __construct(Bar $bar)
	{
		$this->bar = $bar;
	}

	public function check()
	{
		echo get_class($this->bar);
	}
}

这个时候我们就不能直接$baz = new Baz;了,得是这样调用:

$baz = new Baz(new Bar);
$result = $baz->check();

看上去没错,挺简单,这就是构造函数依赖注入(constructor injection),这些我们laravel实战优雅入门:任务管理系统(一)都讲烂了。

层层依赖的问题

但如果Class Bar还有它自己的依赖呢?比如说:

class Foo {}
class Bar {
	protected $foo;
	public function __construct(Foo $foo)
	{
		$this->foo = $foo;
	}
}

稍微复杂点的类有几个依赖很正常,那么现在我们的调用就得这样了:

$baz = new Baz(new Bar(new Foo));
$result = $baz->check();

看出问题来了吗?也就是说现在我们还只是个简单的示例,实际当中,当你的依赖逐渐增多,这个class依赖那个,那个又依赖另一个的时候,层层依赖,稍微大点的系统很常见的事儿。

但是如果我们每次实例化一个对象,就得手动去传递或者说注入一大堆依赖,就得不断地去newnew去,那还了得?

如何自动地去引入相应地依赖,不论我们的依赖有多少层?

或者说,Laravel能不能做到这一点呢?都说Laravel IOC Container,又称Service Container(服务容器),很强大,那么是否强大到自动给我们构建依赖呢?

限于篇幅,我们下一节讲~