对 PHP 后期静态绑定的理解
什么是后期静态绑定
在看一些框架源码或者是某个项目的代码时,经常能看到后期静态绑定的用法
。比如下面这段:
public static function getInstance()
{
if (is_null(static::$instance)) {
static::$instance = new static;
}
return static::$instance;
}
这里用到的就是后期静态绑定。那么,什么是后期静态绑定?
“后期绑定”的意思是说,static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的。
这里要先说两个概念,一个是转发调用,另一个是非转发调用。
-
转发调用
所谓的“转发调用”(forwarding call)指的是通过以下几种方式进行的静态调用:self::, parent::, static:: 以及 forward_ static _call()。即在进行静态调用时未指名类名的调用属于转发调用。 - 非转发调用
非转发调用其实就是明确指定类名的静态调用(foo::bar())和非静态调用($foo->bar())。即明确地指定类名的静态调用和非静态调用。
顾名思义,非转发调用前面有类名所以调用的函数一定是属于“这个类的”,不需要转到别的类。转发调用就是由于前期的静态绑定导致在后面调用静态方法时可能“转发到其他的类”
在PHP的官方文档里,对于后期静态绑定是这样说的:后期静态绑定工作原理是存储了在上一个“非转发调用”(non-forwarding call)中的类名。意思是当我们调用一个转发调用的静态调用时,实际调用的类是上一个非转发调用的类。
来看两个例子:
例1:
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // 后期静态绑定从这里开始
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
以上代码会输出
B
例2:
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo();
self::foo();
}
public static function who() {
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __CLASS__."\n";
}
}
C::test();
以上代码会输出
A
C
C
在这里主要分析下例2。
1.C::test(),这是一个非转发调用,因为::前面有类名C。
2.进入test()方法,有三个静态调用 A::foo(),parent::foo(),self::foo(),对于这三个静态调用来说,他们的非转发调用类就是 C。
3.现在执行A::foo(),这是一个非转发调用。A::foo()中的代码是 static::who(),这是一个转发调用,对于这个转发调用来说他的非转发调用类就是不再是C而是A(因为之前执行了A::foo())。因此执行的结果为A
4.现在执行 parent::foo(),这是一个转发调用,转发到哪里呢?就是它的上一个非转发调用的类,也就是类C(在步骤2中提到的)。在这里一定要注意虽然在这之前执行了 A::foo(),但是parent::foo()的上一个非转发调用的类任然是类C。因此执行的结果是 C.
5.现在执行 self::foo(),这个和 parent::foo()一样都是转发调用,因此也输出 C。
使用后期静态绑定的好处
后期静态绑定目前我看到较多的是用于对象实例化中,在实例化对象时,static 会根据运行时调用的类来决定实例化对象,而 self 则是根据所在位置的类来决定实例化对象。当我们只想实例化子类,并且不希望后续在对子类的使用中由于父类的变化对子类产生影响时,后期静态绑定就能发挥它的作用了。
参考文献:
https://learnku.com/articles/8964/understanding-of-static-binding-at-the-later-stage-of-php
- 图解闭包
- 【web必知必会】——图解HTTP(上)
- 【web必知必会】—— 图解HTTP(下)
- 【Spring实战】—— 14 传统的JDBC实现的DAO插入和读取
- 【AngularJS】—— 4 表达式
- 【AngularJS】—— 5 表单
- 【AngularJS】—— 1 初识AngularJs
- 【AngularJS】—— 2 初识AngularJs(续)
- 【AngularJS】—— 3 我的第一个AngularJS小程序
- 【Spring实战】—— 2 构造注入
- 共享单车运维“朋友圈”越来越宽,乱停乱放现象有望改善吗?
- 【Spring实战】—— 15 Spring JDBC模板使用
- 前端开发总览
- 【Spring实战】—— 16 基于JDBC持久化的事务管理
- php概述
- php教程
- php环境搭建
- PHP书写格式
- php变量
- php常量
- PHP注释
- php数组
- php字符串 string
- PHP整型 integer
- PHP浮点型 float
- php布尔型
- php数据类型之数组
- php数据类型之对象
- php数据类型之null
- php数据类型之间的转换
- php运算符
- php表达式
- PHP循环控制
- PHP流程控制
- php函数
- php全局变量
- PHP魔术变量
- php命名空间
- php 日期
- PHP包含文件
- php文件
- PHP 文件上传
- php Cookies
- php Sessions
- php email
- php安全email
- php错误处理
- PHP异常处理
- php过滤器
- PHP 高级过滤器
- php json
- php 表单
- PHP MySQL 简介
- PHP 连接 MySQL
- php创建数据库
- php 创建表
- php mysq 插入数据
- PHP MySQL 插入多条数据
- PHP MySQL 预处理语句
- php mysql 读取数据
- php mysql where
- PHP MySQL Order By
- PHP MySQL Update
- PHP MySQL Delete
- php ODBC
- 「MoreThanJava」Day 5:面向对象进阶—继承详解
- 要点1:指针、数组和复合字面量
- akka-typed(9) - 业务分片、整合,谈谈lagom, 需要吗?
- com-IFileDlg 进行文件的 打开或者保存
- 哆啦A梦?不好记!安利一下Prometheus这款开源的企业监控报警平台
- Kotlin:03-变量、常量、数据类型
- nginx工程师,需要上承天命,下召九幽
- 新版视频流媒体平台EasyNVR如何在前端显示当前页面所在位置?
- Kotlin:04-基本数据类型详细介绍
- Kotlin:05-控制流 if、when、for、while
- android: API24 及以上版本调用系统相机时报:FileUriExposedException 的解决
- Typora 完美结合 PicGo,写作体验更佳!
- 视频流媒体服务器EasyNVR在CentOS6.5上编译报 No such file or directory错误
- Flink Timer(定时器)机制及实现详解
- android:运行时权限工具类的封装