laravel中路由、缓存等常常有Rote::get()、Cache::get()这样的写法,引入对应门面类就可以使用所定义方法,然而打开所引用门面类,并没有这些方法呀,那么它是怎样实现的呢?以及为何要这么写呢?

涉及知识点:重载、延迟静态绑定、外观模式。

我以非常常用的Cache门面为例追一下代码。

打开Cache门面类,发现只有一个方法,返回了一个名字。

class Cache extends Facade
{/*** Get the registered name of the component.** @return string*/protected static function getFacadeAccessor(){return 'cache';}
}

不知所云吗?别急,它继承了Facade,打开看看,翻一下发现有魔术方法

    public static function __callStatic($method, $args){$instance = static::getFacadeRoot();if (! $instance) {throw new RuntimeException('A facade root has not been set.');}return $instance->$method(...$args);}

当我们调用Cache::get(),很显然Cache门面和Facade都不存在静态的get方法,由此此魔术方法被触发,这是非常经典的使用场景,static::又是延迟静态绑定的写法,是要用调用域的getFacadeRoot()方法,追一下。

    public static function getFacadeRoot(){return static::resolveFacadeInstance(static::getFacadeAccessor());}

发现它用到了static::getFacadeAccessor(),也是延迟静态绑定的用法,咱们现在是Cache门面,返回的是cache,可以查看一下路由门面返回的是router。

    protected static function resolveFacadeInstance($name){if (is_object($name)) {return $name;}if (isset(static::$resolvedInstance[$name])) {return static::$resolvedInstance[$name];}if (static::$app) {return static::$resolvedInstance[$name] = static::$app[$name];}}

Facade类定义的属性包括:

    /*** The application instance being facaded.** @var \Illuminate\Contracts\Foundation\Application*/protected static $app;/*** The resolved object instances.** @var array*/protected static $resolvedInstance;

这种统筹管理实例的方式使用了注册树模式,laravel服务容器也用到了注册树模式。

再回过头就清楚了,Cache等具体门面返回的是服务容器中核心类的别名,门面类中重载方法是去对应核心类中调用指定方法,所传参数直接传递。还拿Cache举例

public function registerCoreContainerAliases(){foreach (['app'                  => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],...'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],...] as $key => $aliases) {foreach ($aliases as $alias) {$this->alias($key, $alias);}}}

cache核心类别名注册了\Illuminate\Cache\CacheManager::class()它对应的核心类和\Illuminate\Contracts\Cache\Factory::class(它对应的契约),所以继承了\Illuminate\Contracts\Cache\Factory::class的也都可以用Cache门面,包括Lock、store等,当然实际使用的时候不用一个一个去找,对应门面有所代理类和方法的注释。

这种使用门面来代理一大堆关联类,而不用一个一个引入的方式可以使代码非常简洁,这就是外观模式。不仅如此,门面类引入了Mockery,可以模拟无法复现的条件以方便做单元测试。

门面缺点

但是,过多的门面可能造成类范围蠕变,过多调用门面可能让使用者在不知不觉中已经调用了过多的类,使得当前类变得膨胀,使用依赖注入会让用户清晰地感知到类变大的过程从而更注意此类问题。

重载

php重载是指动态地创建类属性和方法,换一种说法,php的重载是指访问/设置一些原本没有权限看见或不存在的属性或方法时,去现场创建一个的过程。这与大多数语言的重载实现多态是完全不一样的。

重载需要借助这些魔术方法:

__call()、__callStatic()用以做方法重载,其他几个用以做属性重载。

__call() 在对象中调用一个不可访问方法时调用
__callStatic() 在静态上下文中调用一个不可访问方法时调用
__get() 读取不可访问属性的值时调用,以获取私有属性的值
__set() 设置不可访问属性的值时调用,以设置私有属性的值,__set() 的返回值会被忽略
__isset() 对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用以检测私有属性是否被设定。
 __unset() 对不可访问属性调用 unset() 时,__unset() 会被调用以删除私有属性

表格中的不可访问包含:不存在、访问范围不可见两种情况。

例1:__get()访问未设置/不可见属性

class Test{private $year = '17';public function __get($key){return $key.' is empty';}}//不存在
echo (new Test)->name."</br>";
//不可见
echo (new Test)->year;

例2:__set() 设置 未设置/不可见属性并访问

class Test{//private $year = '17';public function __get($key){return $key." is empty </br>";}public function __set($key,$value){$this->$key = $value;}public function index(){echo $this->year."</br>";}}$a = new Test;
//未设置前访问
echo $a->year;
//设置
$a->year = '2020';
//设置后访问
$a->index();
//设置后再通过私有属性访问
echo $a->year;

解开private $year=17注释:

在private $year=17被注释后,set不仅给year赋值,还把其访问范围设为了public,在打开注释后,set依然能赋值,只是不能把private改为public。在正常使用时一般是会预先定义私有属性的。

例3.isset() 可以动态地检测一个属性是否被定义

class Test{private $year = '17';public function __set($key,$value){$this->$key = $value;}public function __isset($key){echo $key."设置了吗:".isset($this->$key);}}$a = new Test;
$a->year = '2020';
echo isset($a->year);

接例2,当private $year = '17',此时$a->year = '2020'只是赋值,$year依然是私有,isset($a->year)需要走Test类的__isset()方法,并且因为在类中有year属性,所以输出

private $year=17被注释后:$a->year被设置为了2020,且默认是public,当前是可以访问的,不需要调用__isset()

例4:__unset()动态销毁属性

class Test{private $year = '17';public function __get($key){echo $key." is empty </br>";}public function __set($key,$value){$this->$key = $value;echo "set a key </br>";}public function __unset($key){echo "I will unset it </br>";unset($this->$key);$this->$key;}}$a = new Test;
$a->year = '2020';
unset($a->year);

由于Test{}中定义了private $year,因此类外给其赋值触发了__set(),又因为类外无法访问$year,因此unset()触发了__unset()。unset后,在类内部访问该属性,也会触发__get()

例5:__call() __callStatic()重载方法、重载静态调用的方法

class Test{private function mySecret($arg){var_dump($arg);}public function __call($name,$arg){echo 'my secret:';return $this->mySecret(...$arg);}public static function __callStatic($name,$arg){echo 'my static secret:';$data = new Test();return $data->mySecret(...$arg);}}$a = new Test;
echo '<pre>';
$a->mySecret(['k1','k2']);
$a::mySecret(['k3','k4']);

需要注意的是 __callStatic()方法本身需要是静态方法,而它内部不限于调用静态方法或属性。

静态方法和普通方法区别

静态方法和普通方法有何区别?laravel为什么要用__callStatic()而不用__call()实现重载?

静态方法属于当前类,它不需要实例化就可以直接通过类调用,也可以通过类的对象使用,它不能自动销毁,它被创建后就使用同一块内存,类的所有实例也共享此静态方法。

laravel用静态方式实现门面,个人认为是为了减少内存开销。

参考:

https://www.php.net/manual/zh/language.oop5.overloading.php

Facades外观模式背后实现原理 |《深入 Laravel 核心 5.5》| Laravel China 社区

PHP: 魔术方法 - Manual

【laravel】门面:重载、延迟静态绑定、注册树模式、外观模式、Mockery相关推荐

  1. php门面理解,php 门面模式(外观模式)

    header("content-type:text/html;charset=utf-8"); // ==================php 门面模式(外观模式) ====== ...

  2. php 外观模式,外观模式的作用

    外观模式(门面模式) 外观模式是指通过外观的包装,使应用程序只能看到外观对象,而不会看到具体的细节对象,这样无疑会降低应用程序的复杂度,并且提高了程序的可维护性. 门面模式的优点 1.它对客户屏蔽了子 ...

  3. 《设计模式详解》结构型模式 - 外观模式

    外观模式 5.5 外观模式 5.5.1 概述 5.5.2 结构 5.5.3 案例 5.5.4 使用场景 5.5.5 Tomcat 源码 完整的笔记目录:<设计模式详解>笔记目录,欢迎指点! ...

  4. 设计模式(装饰者模式外观模式代理模式)

    目录 装饰者模式 介绍 实现 外观模式 介绍 实现 代理模式 介绍 实现 装饰者模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设 ...

  5. 设计模式 -结构型模式_门面模式(外观模式) Facade Pattern 在开源软件中的应用

    文章目录 Pre 定义 案例 Demo V1.0 Boss直接管理 V2.0 引入外观类 PmManagerFacade 何时使用Facade Tomcat Mybatis Pre 我们先来回顾下门面 ...

  6. Log4j、Logback的使用以及日志门面模式(外观模式)

    常用日志框架 j.u.l:  j.u.l是java.util.logging包的简称,是JDK在1.4版本中引入的Java原生日志框架.Java Logging API提供了七个日志级别用来控制输出 ...

  7. 设计模式 结构型模式 外观模式(Facade Pattern)

    在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化. 这时为了将复杂系统的内部子系统与客户端之间的依赖解耦,从而就有了外观模式,也称作 &quo ...

  8. 结构型模式—外观模式

    原文作者:Fina1ly 原文地址:java设计模式之外观模式(门面模式) 目录 一.概念介绍 二.角色及使用场景 三.实例 四.优点 - 松散耦合 - 简单易用 - 更好的划分访问层次 一.概念介绍 ...

  9. 12、设计模式-结构型模式-外观模式

    外观模式 定义: 为子系统中的一组接口提供一个统一的入口.外观模式定义 了一个高层接口,这个接口使得这一子系统更加容易使用. 外观模式又称为门面模式,它是一种对象结构型模式. 外观模式是迪米特法则的一 ...

  10. 结构型模式-外观模式

    1.概述 有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个好帮手,支付宝里就有许多的基金 ...

最新文章

  1. JavaScript和HTML实现的简单计算机
  2. 查询qt中的数据_EXCEL在多表中查询数据(函数中引用工作表的办法)
  3. anjularjs 路由
  4. Dora.Interception: 一个为.NET Core度身定制的AOP框架
  5. 大一c语言操作题期末考答案,大一期末考试c语言操作题答案
  6. Linux as4开启telnet,Red hat AS4开启telnet过程
  7. (转)javascript异步编程的四种方法
  8. Java课程设计【学生信息管理系统】
  9. 强化学习读书笔记 - 03 - 有限马尔科夫决策过程
  10. 对应node版本_Node.js 应用故障排查手册 —— Node.js 性能平台使用指南
  11. 2000G视频资料送带资源账号
  12. 修改网站背景图html代码,网站首页顶部添加背景图片的修改代码
  13. 启用计算机来宾账号,开启Windows 10来宾账户
  14. 快速通过论文相似度检测
  15. 基于python和Opencv将多张图片结合为一张图片的办法
  16. 质因数算法(C/C++)
  17. PHP移动互联网开发笔记(1)——环境搭建及配置
  18. 2017 ACM-ICPC 亚洲区(西安赛区)网络赛 E Maximum Flow
  19. 关于Windows日志
  20. 运动,今晚去跑步了。贵在坚持

热门文章

  1. 刘韧文章修改经验共享:写作是复述的改写
  2. 比羊了个羊更时间刺客的,原来是它?
  3. Flutter Widgets 之 Expanded和Flexible
  4. [20][04][10] Fortify Static Code Analyzer 详解
  5. 低延时的RTMP网络直播
  6. Kylin 在满帮集团千亿级用户访问行为分析中的应用
  7. 天锐绿盾终端安全管理系统
  8. 腾讯发布首个全自研机器狗Max,有腿又有轮,会“拜年讨红包”
  9. Spring框架的设计目标,设计理念,和核心是什么
  10. Ubuntu源(apt)更换网络源