记录自己源码阅读的过程。
一、开篇
对于框架源码的阅读,千头万绪,不知道从哪里开始,那就从拜读作者的大作开始吧。先理解作者的思想,有理论的支撑,看起代码来,就很顺畅了。
1.laravel作者:
Taylor Otwell
2.作者出的书籍:
《From Apprentice To Artisan - Advanced Application Architecture With Laravel 4》 ,译作 《从学徒到工匠 - 基于 Laravel 4 进行高级应用架构》
二、关键概念:
作者说整个 Laravel 框架的基石是一个功能强大的 IoC 容器(控制反转容器)(java的spring也相似)
要先理解几个概念。依赖注入,服务容器,反射。
1.依赖注入篇
什么是依赖?A类执行依赖B类
什么是注入,注入什么?将B类对象注入到A类的构造方法中
先看官网的一个demo,看注释
//实现一个提醒用户该续费的功能
//定义一个支付接口
interface BillerInterface
{
public function bill(array $user, $amount);
}
//定义一个通知接口
interface BillingNotifierInterface
{
public function notify(array $user, $amount);
}
//付费接口的实现类:注意构造函数传入的是通知接口参数
class StripeBiller implements BillerInterface
{
public function __construct(BillingNotifierInterface $notifier)
{
$this->notifier = $notifier;
}
public function bill(array $user, $amount)
{
// todo bill业务,结束后发个通知给用户:这里思考是发短信还是邮件?
$this->notifier->notify($user, $amount);
}
}
// 这里就是依赖注入: 支付接口实现类StripeBiller的执行依赖于通知接口的实现类SmsNotifier。
// 这里设计好处是,支付实现不用考虑如何通知用户,我们直接传递给它一个具体通知实现类 SmsNotifier/EmailNotifier的实例即可。
$sms_biller = new StripeBiller(new SmsNotifier);
$email_biller = new StripeBiller(new EmailNotifier);
结合上面代码,再理解依赖注入。DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;(支付依赖通知)
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;(解耦,组件思维)
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;(通知对象注入支付实例)
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。(对象)
2.服务容器:(IoC 容器,控制反转容器):
(1)什么是服务容器?
java也有同样的概念。服务容器即“控制反转容器”。它不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
(2)如何理解好Ioc呢?
理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
举个通俗的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。
(3)那么IoC是如何做的呢?
有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
Laravel 的Application
类就是一个继承自 Container
的容器类,它就是整个 Laravel 应用的服务容器。
在 Laravel 应用中,可以通过 App
门面来访问服务容器,还可以通过辅助函数 app()
来访问,如果是在服务提供者(可以理解为一个专门用于绑定接口与实现到服务容器的地方)中,则一般通过 $this->app
来访问容器。
总之IoC 容器:控制反转容器让依赖注入更方便。
(4)如何将StripeBiller实现类绑定到容器?
接这上面的例子,前面我们定义了支付类接口BillerInterface
和通知类接口BillingNotifierInterface,并在StripeBiller 类中实现了支付功能。那如何将StripeBiller实现类绑定到容器?可以在服务提供者的 register()
方法中完成。
public function register()
{
$this->app->bind(BillerInterface::class, function ($app) {
// 初始化 BillingInterface 实现类时,额外需要一个BillingNotifierInterface 的实现
return new StripeBiller($app->make(BillingNotifierInterface::class));
});
}
// 定义接口的实现类 EmailBillingNotifier
namespace App\Services;
use App\Contracts\BillingNotifierInterface;
class EmailBillingNotifier implements BillingNotifierInterface
{
public function notify(array $user, $amount)
{
// TODO: Implement notify() method.
}
}
// 同样服务提供者中将其绑定到所实现的接口
$this->app->bind(BillingNotifierInterface::class, function ($app) {
return new EmailBillingNotifier();
});
3.反射解决方案
(1)什么是反射?看解析
// 程序运行中,动态获取类的方法属性等
$reflection = new ReflectionClass(\App\Services\StripeBiller::class);
dump($reflection->getMethods()); # 获取 StripeBiller 类中的所有方法
dump($reflection->getNamespaceName()); # 获取 StripeBiller 的命名空间
dump($reflection->getProperties()); # 获取 StripeBiller 上的所有属性
(2)如何通过反射来自动解析类的依赖?看代码
class UserController extends BaseController
{
public function __construct(StripBiller $biller)
{
$this->biller = $biller;
}
}
这个控制器的构造函数在形参里类型约束了一个 StripBiller
类,通过反射我们可以获取这个类型约束指定的类。
当 Laravel 的服务容器中没有某个显式绑定类的解析器时(即没有注册接口与对应实现的绑定),将会尝试使用反射来解析。程序流程类似于下面这样:
- 已经有一个
StripBiller
的解析器了吗? - 没有?那用反射来检查一下
StripBiller
吧,看看它有没有依赖。 - 解析
StripBiller
需要的所有依赖(递归处理)。 - 使用
ReflectionClass->newInstanceArgs()
来创建一个新的StripBiller
实例。(底层逻辑详见Illuminate\Container\Container
的resolve
方法)
修改一下控制器,看看改成这样会发生什么事:
class UserController extends BaseController
{
public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}
}
假设我们没有为 BillerInterface
显式绑定过任何解析器,即没有在服务提供者中定义过下面这样的绑定代码:
$this->app->bind(BillerInterface::class, function ($app) {
return new StripeBiller($app->make(BillingNotifierInterface::class));
});
(3)容器该怎么知道要注入什么类呢?
要知道,接口(或抽象类)本身是不能被实例化的。如果我们没有告知容器更多信息的话,容器是无法实例化这个依赖的。我们需要明确指出哪个类是这个接口的默认实现(正如我们之前在服务提供者 AppServiceProvider
中所做的那样),这就需要用到 bind
方法:
$this->app->bind(BillerInterface::class, StripeBiller::class);
注:这种方式会在绑定时就会实例化 StripeBiller
,性能不及匿名函数。
这里,我们只传了一个字符串进去,而不是一个匿名函数。这个字符串告诉容器总是使用 StripBiller
类作为 BillerInterface
接口的默认实现类。
如果我们需要切换到余额支付作为我们的支付提供者,只需要新写一个 BalancedBiller
来实现 BillerInterface
接口
$this->app->bind(BillerInterface::class, BalancedBiller::class);
在绑定实现到接口时,你也可以使用 singleton
方法,这样容器在整个请求生命周期中只会实例化这个实现类一次,从而实现单例模式:
$this->app->singleton(BillerInterface::class, StripeBiller::class);
我们再看看框架已经实现好的服务是如何做的,如redis
<?php
namespace Illuminate\Redis;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
class RedisServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('redis', function ($app) {
$config = $app->make('config')->get('database.redis', []);
return new RedisManager($app, Arr::pull($config, 'client', 'phpredis'), $config);
});
$this->app->bind('redis.connection', function ($app) {
return $app['redis']->connection();
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['redis', 'redis.connection'];
}
}
我们看到,也是$this->app->singleton(‘字符串’, 匿名函数);
掌握容器:想了解更多关于容器的知识?去读源码吧!容器在底层只有一个类Illuminate\Container\Container
,读完了你就会对容器如何工作有更深的理解。
以上内容大部分都源自官网,只做摘抄,自己做笔记用,详细内容请移动原文:https://laravelacademy.org/books/laravel-from-appreciate-to-artisan