为什么 laravel 用起来那么爽
两年前开始使用 laravel ,那个时候还只是吧 laravel 当做另一个 MVC 框架,受 ASP.NET 的影响对齐代码风格和事件模型比较钟爱。但是了解越多越发现 laravel 的优点不止于此。
有时候也会想 laravel 为什么用起来那么爽,用个框架都会有优越感?
上帝说要有光,于是便有了光
平时写代码的时候,有时为了了解某一个函数具体实现会去定位到相关位置去看一眼,但是整体了解整个框架结构的事儿虽然也看过,但大多半途而废。更多的试看看官方文档,然后在自己的应用里拿去用。终于拿出时间来思考这个事情,我觉得这个答案应该就是 “直接去用就好了,不用关心底层实现”。
业务模型与数据库表结构
你需要知道的东西
你需要使用过 laravel 框架,并对其有个基本的了解
文档都看过并对该框架的一些基本概念有所了解,比如 IOC
想更深入的了解框架的实现,以及项目代码的执行过程
入口
laravel 的启动入口一般情况无外乎 web 访问,artisan 命令,phpunit 单元测试。
先看 web 访问,入口在 public/index.php
这个文件,相关代码
require __DIR__.'/../bootstrap/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
artisan 命令入口是 根目录下的 artisan 文件,相关代码
require __DIR__.'/bootstrap/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
$kernel->terminate($input, $status);
exit($status);
phpunit 启动入口是 phpunit.xml 这个配置文件中
./tests
./app
从这个配置文件里可以看出,先加载 bootstrap,然后去 tests 目录下执行找 Test.php 后缀的文件执行测试,tests 目录下有一个 ExampleTest.php 刚好符合这个规则,ExampleTest,文件里只有一个简单的测试用例,而且又继承自 TestCase 这个类,TestCase 有这样一个方法
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
return $app;
}
纵观来看三个入口都做了类似的行为
- require autoload
- require bootstrap/app 启动 aplication
- make(Kernel) 并 启动起来
但是 web 和 artisan 的入口 make kernel 后有handle 的行为,phpunit 的却没有,web 入口make 的 kernel 是 Illuminate\Contracts\Http\Kernel::class
, 其余两个的 kernel 是Illuminate\Contracts\Console\Kernel::class
,这又是为什么?,先留个问题在这暂不解决。
启动 application
把上面的问题放下先去 ‘bootstrap/app.php’ 看一眼返回 app这个 application 的时候都干了些什么。
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
前三行把 Illuminate\Foundation\Application
实例化成了一个对象 $app,接下来执行了三次 $app->singleton() 操作。
singleton 方法看起来是生成一个单例对象的意思,但是不是呢?还是先看一眼 Illuminate\Foundation\Application
这个 class 吧。
打开 Illuminate\Foundation\Application这个 class 对应的文件,从上往下拉了一下,1千多行代码,“卧槽,太长不看!”,而且该 class 中也没有 singleton 这个方法的定义,很多时候我都在这个地方半途而废了,不是迷失在各种方法实现的细节中就是陷于各种调用中无法自拔。
现在再次梳理来到这个文件的目的:1. new Application 的时候都干了什么,2 $app上的 singleton方法什么意思。
先看 Application 的构造函数
public function __construct($basePath = null)
{
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
if ($basePath) {
$this->setBasePath($basePath);
}
}
// registerBaseBindings 的实现
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance('Illuminate\Container\Container', $this);
}
// registerBaseBindings 的实现
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
public function register($provider, $options = [], $force = false)
{
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
if (is_string($provider)) {
$provider = $this->resolveProviderClass($provider);
}
if (method_exists($provider, 'register')) {
$provider->register();
}
// Once we have registered the service we will iterate through the options
// and set each of them on the application so they will be available on
// the actual loading of the service objects and for developer usage.
foreach ($options as $key => $value) {
$this[$key] = $value;
}
$this->markAsRegistered($provider);
// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by the developer's application logics.
if ($this->booted) {
$this->bootProvider($provider);
}
return $provider;
}
OK,从构造函数里可以看出来一共做了四件事情,
- 注册基础绑定
- 注册基础服务 provider(events,router)
- 注册容器核心别名
- 设置项目根目录
这些基本可以根据方法名猜测到都做了些什么,猜不到的也可以点进去看一下函数的具体实现,比如 register 这个方法如果初读就会有一些疑问:这个方法是干什么的啊,它是怎么实现的啊,这个时候如果大概知道是做什么的就行不要继续深入去了解细节,毕竟我们更关系整个框架是怎么运行的。
到这个地方,我们已经搁置了好几个问题没有解决了,捡几个重要的拾回来
- singleton 是干什么的,怎么用?以及上文的 instance,和 self::setInstance,都分别什么意思?
-
class Application extends Container implements ApplicationContract, HttpKernelInterface
, 要去看一下 Container 怎么实现的么? - baseBinding绑的对象都是$this,这应该没什么异议,但BaseProvider为什么是 Event 和 Routing.
这个时候如果上面的问题你都有答案,那么就可以跳出这块继续去看make kernel并 handle 的部分了如果不甚明白,那就去看看文档吧,毕竟文档里说明你是框架开发者希望你正确使用的方式
[1] 核心概念- 服务容器
[2] 核心概念- 服务容器提供者
[3] Laravel深入学习2 - 控制反转容器
[4] Laravel 依赖注入思想