最近项目用 Lumen 来做接口开发,主要看重 Lumen 比 Laravel 更轻量,更适合做接口开发。
Laravel 的源代码平时用的时候也经常去翻,但总有种云里雾里的感觉,主要是 Laravel 运用了很多设计模式,还有许多 PHP 的 OOP 特性,所以看起来略显繁复。
刚好趁着应用 Lumen,就以 Lumen 来分析一下其所用的门面模式(Facade pattern)。
Lumen 的代码比 Laravel 简洁很多,砍掉了很多自定义配置,还替换了路由的包,以此来获得跟快的运行速度。
从入口文件开始。
public/index.php
1 2 3
| $app = require __DIR__.'/../bootstrap/app.php';
$app->run();
|
可以看到,入口文件只是引用了 bootstrap 下的 app.php 文件,通过返回值来看,是返回了一个对象,如此,下面就直接运行了这个对象 run 方法。我们再来看看 bootstrap/app.php 文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| require_once __DIR__.'/../vendor/autoload.php';
try { (new Dotenv\Dotenv(__DIR__.'/../'))->load(); } catch (Dotenv\Exception\InvalidPathException $e) { }
$app = new Laravel\Lumen\Application( realpath(__DIR__.'/../') );
$app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class );
$app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class );
$app->group(['namespace' => 'App\Http\Controllers'], function ($app) { require __DIR__.'/../routes/web.php'; });
return $app;
|
大致介绍了一下 Lumen 的加载过程,本次我们主要看 Lumen 里的门面模式
就是下面这句代码:
我们可以跟进去看看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public function withFacades($aliases = true, $userAliases = []) { Facade::setFacadeApplication($this);
if ($aliases) { $this->withAliases($userAliases); } }
|
可以看到,此方法做了两件事:
- 把 Application 对象注入 Facade 类
- 执行 withAliases() 方法
我们再来看看 withAliases() 干了啥事
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public function withAliases($userAliases = []) { $defaults = [ 'Illuminate\Support\Facades\Auth' => 'Auth', 'Illuminate\Support\Facades\Cache' => 'Cache', 'Illuminate\Support\Facades\DB' => 'DB', 'Illuminate\Support\Facades\Event' => 'Event', 'Illuminate\Support\Facades\Gate' => 'Gate', 'Illuminate\Support\Facades\Log' => 'Log', 'Illuminate\Support\Facades\Queue' => 'Queue', 'Illuminate\Support\Facades\Schema' => 'Schema', 'Illuminate\Support\Facades\URL' => 'URL', 'Illuminate\Support\Facades\Validator' => 'Validator', ];
if (! static::$aliasesRegistered) { static::$aliasesRegistered = true;
$merged = array_merge($defaults, $userAliases);
foreach ($merged as $original => $alias) { class_alias($original, $alias); } } }
|
哦,原来是定义了一些别名,门面模式的目的就是简化、方便使用。
这里就定义了一个门面列表,有常用的 DB、Schema、Log 等。
到这里肯定是还不够的,我们只是看到了表象,还不知道其内部到底是如何实现的,我们继续翻源码。
先拿DB来举例,通过这里的别名列表我们知道 DB 实际上映射到了 Illuminate\Support\Facades\DB。
打开这个文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
namespace Illuminate\Support\Facades;
class DB extends Facade {
protected static function getFacadeAccessor() { return 'db'; } }
|
瞬间懵了,什么鬼,就一个方法,还是只返回一个 db
字符串的方法,接着找。
我们看到这个类继承了 Facade 类,so,我们继续翻看 Facade 的代码。
Facade 类里也没有 DB::table() 这种方法,但是,我们找到了魔术方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static function __callStatic($method, $args) { $instance = static::getFacadeRoot();
if (! $instance) { throw new RuntimeException('A facade root has not been set.'); }
switch (count($args)) { case 0: return $instance->$method(); case 1: return $instance->$method($args[0]); case 2: return $instance->$method($args[0], $args[1]); case 3: return $instance->$method($args[0], $args[1], $args[2]); case 4: return $instance->$method($args[0], $args[1], $args[2], $args[3]); default: return call_user_func_array([$instance, $method], $args); } }
|
这个魔术方法会调用 getFacadeRoot() 获得实例,我们跟进去看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); }
protected static function getFacadeAccessor() { throw new RuntimeException('Facade does not implement getFacadeAccessor method.'); }
protected static function resolveFacadeInstance($name) { if (is_object($name)) { return $name; }
if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; }
return static::$resolvedInstance[$name] = static::$app[$name]; }
|
重点就是这三个方法了,首先调用 getFacadeAccessor(),我们看到在这里是抛出异常,但是这个方法我们在 DB 里看到被重写了,返回了要给 db
字符串。
然后调用 resolveFacadeInstance(),传进来一个db
字符串,我们看到代码里,首先看 Facade 里有没有存储这个实例,没有的话就去 Application 对象里去拿实例,so,这里也可以知道 Application 已经保存了所有 Facade 需要的实例。
返回实例以后,看上面的魔术方法,就会去调用此实例相应的方法,到这里,门面模式的运行过程就剖析完了。
我们也得出个结论,Application 类是 Lumen 框架的核心,保存了所有用过的实例,这样我们就不用每次都去 new Class(),只需要 app() app()->make(),获得实例即可。
这里也看得出来,启用了门面模式,用起来比较方便,稍微牺牲了一点性能,具体性能却没有测试过,这就看开发的时候如何取舍了。
本文标题:Lumen 源码分析之门面模式
文章作者:郭大侠
发布时间:2017-07-18
最后更新:2025-03-05
原始链接:https://www.guozhenyi.com/post/2017/07/18/facade-pattern-in-lumen-framework.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!