前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Laravel 底层原理:门面(Facades)

Laravel 底层原理:门面(Facades)

作者头像
全栈程序员站长
发布2022-07-12 21:50:44
1.3K0
发布2022-07-12 21:50:44
举报

大家好,又见面了,我是全栈君。

简介

Facades 为应用服务容器中的绑定类提供了一个“静态”接口。

Laravel 内置了很多 Facades ,可以访问绝大部分 Laravel 的功能。

Laravel 的门面作为服务容器中底层类的“静态代理”,相比于传统静态方法,在维护时能够提供更加易于测试、更加灵活、简明优雅的语法。

Laravel 的所有门面都定义在 Illuminate\Support\Facades 命名空间下。

我们可以轻松访问到门面:

代码语言:javascript
复制
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
    return Cache::get('key');
});

在整个 Laravel 文档中,很多例子使用了门面来演示框架的各种功能特性。

何时使用 Facades

门面有诸多优点,其提供了简单、易记的语法,让我们无需记住长长的类名即可使用 Laravel 提供的功能特性,此外,由于他们对 PHP 动态方法的独到用法,使得它们很容易测试。

在使用 Facades 时,有些地方还需要特别注意。

使用 Facades 最主要的风险就是会引起类作用范围的膨胀。

因为 Facades 使用起来非常简单而且不需要注入,就会使得我们在不经意间在单个类中使用许多 Facades,从而导致类变的越来越大。

而使用依赖注入的时候,使用的类越多,构造方法就会越长,在视觉上就会引起注意,提醒你这个类有点庞大了。因此在使用 Facades 的时候,要特别注意控制好类的大小,让类的作用范围保持短小。

在开发与 Laravel 进行交互的第三方扩展包时,建议最好选择注入 Laravel 契约 ,而不是使用 Facades 的方式来使用类。因为扩展包是在 Laravel 本身之外构建,所以你无法使用 Laravel Facades 测试辅助函数。

Facades Vs. 依赖注入

依赖注入的主要优点之一是切换注入类的实现的能力。这在测试的时候很有用,因为你可以注入一个 mock 或者 stub ,并断言在 stub 上调用的各种方法。

通常,真正的静态方法是不可能被 mock 或者 stub。但是,因为 Facades 使用动态方法来代理从服务容器解析的对象的方法调用,我们可以像测试注入的类实例一样来测试 Facades。例如,像下面的路由:

代码语言:javascript
复制
use Illuminate\Support\Facades\Cache;
 
Route::get('/cache', function () {
    return Cache::get('key');
})

我们可以这样编写测试来验证 Cache::get 方法以我们期望的方式被调用:

代码语言:javascript
复制
use Illuminate\Support\Facades\Cache;
/**
 * 一个基础功能的测试用例。
 *
 * @return void
 */
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}

Facades Vs. 辅助函数

除了 Facades, Laravel 还包含各种「辅助函数」来实现一些常用的功能,比如生成视图、触发事件、调度任务或者发送 HTTP 响应。

许多辅助函数的功能都有与之对应的 Facade。例如,下面这个 Facade 的调用和辅助函数的作用是一样的:

代码语言:javascript
复制
return View::make('profile');
return view('profile');

这里的 Facades 和辅助函数之间没有实际的区别。当你使用辅助函数时,你可以使用对应的 Facade 进行测试。例如,下面的路由:

代码语言:javascript
复制
Route::get('/cache', function () {
return cache('key');
});

在底层,辅助函数 cache 实际上是调用了 Cache facade 中的 get 方法。

因此,尽管我们使用的是辅助函数,我们依然可以编写以下测试来验证该方法是否使用我们预期的参数来调用:

代码语言:javascript
复制
use Illuminate\Support\Facades\Cache;
/**
 * 一个基础功能的测试用例。
 *
 * @return void
 */
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}

Facades 工作原理

在 Laravel 应用中,门面就是一个为容器中的对象提供访问方式的类。该机制的原理由 Facade 类实现。

不管是 Laravel 自带的 Facades,还是用户自定义的 Facades ,都继承自 Illuminate\Support\Facades\Facade 类。

门面类只需要实现一个方法:getFacadeAccessor。正是 getFacadeAccessor 方法定义了从容器中解析什么,然后 Facade 基类使用魔术方法 __callStatic() 从你的门面中调用解析对象。

在下面的例子中,调用了 Laravel 的缓存系统。通过浏览这段代码,可以假定在 Cache 类中调用了静态方法 get:

代码语言:javascript
复制
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
     * 显示给定用户的信息。
     *
     * @param  int  $id
     * @return Response
     */
public function showProfile($id)
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}

在上面这段代码中,我们「导入」了 Cache Facade 。这个 Facade 作为访问 Illuminate\Contracts\Cache\Factory 接口底层实现的代理。我们使用 Facade 进行的任何调用都将传递给 Laravel 缓存服务的底层实例。

如果我们看一下 Illuminate\Support\Facades\Cache 这个类,你会发现类中根本没有 get 这个静态方法:

代码语言:javascript
复制
class Cache extends Facade
{
/**
     * 获取组件的注册名称。
     *
     * @return string
     */
protected static function getFacadeAccessor() { return 'cache'; }
}

Cache Facade 继承了 Facade 的基类,并定义了 getFacadeAccessor() 方法。这个方法的作用是返回服务容器绑定的类的名称。

当用户调用 Cache Facade 中的任何静态方法时, Laravel 会从 服务容器 中解析 cache 绑定,然后在解析出的对象上调用所有的请求方法(本例中是 get)。

实时门面

使用实时门面,可以将应用中的任意类当做门面来使用。

为了说明如何使用这个功能,我们先看一个替代方案。例如我们假设 Podcast 模型有一个 publish 方法,尽管如此,为了发布博客,我们需要注入 Publisher 实例:

代码语言:javascript
复制
<?php
namespace App;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
     * Publish the podcast.
     *
     * @param  Publisher  $publisher
     * @return void
     */
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}

因为可以模拟注入的发布服务,所以注入发布实例到该方法后允许我们轻松在隔离状态下测试该方法。不过,这要求我们每次调用 publish 方法时,都要传递一个发布服务实例。

使用实时门面,我们可以在维持这种易于测试的前提下不必显式传递 Publisher 实例。要生成一个实时门面,在导入类前面加上 Facades 命名空间前缀即可:

代码语言:javascript
复制
<?php
namespace App;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
     * Publish the podcast.
     *
     * @return void
     */
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}

使用实时门面后,发布服务实例将会通过使用 Facades 前缀后的接口或类名在服务容器中解析。

在测试的时候,我们可以使用 Laravel 自带的门面测试辅助函数来模拟这个方法调用。

代码语言:javascript
复制
<?php
namespace Tests\Feature;
use App\Podcast;
use Tests\TestCase;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
     * A test example.
     *
     * @return void
     */
public function test_podcast_can_be_published()
{
$podcast = factory(Podcast::class)->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}

Facade 类参考

下面列出了每个门面及其对应的底层类。

这是一个查找给定 Facade 类 API 文档的工具。服务容器绑定的可用键值也包含在内。

Facade

服务容器绑定

App

Illuminate\Foundation\Application

app

Artisan

Illuminate\Contracts\Console\Kernel

artisan

Auth

Illuminate\Auth\AuthManager

auth

Blade

Illuminate\View\Compilers\BladeCompiler

blade.compiler

Bus

Illuminate\Contracts\Bus\Dispatcher

Cache

Illuminate\Cache\Repository

cache

Config

Illuminate\Config\Repository

config

Cookie

Illuminate\Cookie\CookieJar

cookie

Crypt

Illuminate\Encryption\Encrypter

encrypter

DB

Illuminate\Database\DatabaseManager

db

DB (Instance)

Illuminate\Database\Connection

Event

Illuminate\Events\Dispatcher

events

File

Illuminate\Filesystem\Filesystem

files

Gate

Illuminate\Contracts\Auth\Access\Gate

Hash

Illuminate\Contracts\Hashing\Hasher

hash

Lang

Illuminate\Translation\Translator

translator

Log

Illuminate\Log\Writer

log

Mail

Illuminate\Mail\Mailer

mailer

Notification

Illuminate\Notifications\ChannelManager

Password

Illuminate\Auth\Passwords\PasswordBrokerManager

auth.password

Queue

Illuminate\Queue\QueueManager

queue

Queue (Instance)

Illuminate\Contracts\Queue\Queue

queue

Queue (Base Class)

Illuminate\Queue\Queue

Redirect

Illuminate\Routing\Redirector

redirect

Redis

Illuminate\Redis\Database

redis

Request

Illuminate\Http\Request

request

Response

Illuminate\Contracts\Routing\ResponseFactory

Route

Illuminate\Routing\Router

router

Schema

Illuminate\Database\Schema\Blueprint

Session

Illuminate\Session\SessionManager

session

Session (Instance)

Illuminate\Session\Store

Storage

Illuminate\Contracts\Filesystem\Factory

filesystem

URL

Illuminate\Routing\UrlGenerator

url

Validator

Illuminate\Validation\Factory

validator

Validator (Instance)

Illuminate\Validation\Validator

View

Illuminate\View\Factory

view

View (Instance)

Illuminate\View\View

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/111724.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022年2月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 何时使用 Facades
    • Facades Vs. 依赖注入
      • Facades Vs. 辅助函数
      • Facades 工作原理
      • 实时门面
      • Facade 类参考
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档