详解PHP后期静态绑定分析与应用


Posted in Javascript onMarch 21, 2018

基础知识

1. 范围解析操作符 (::)

  • 可以用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法。
  • self,parent 和 static 这三个特殊的关键字是用于在类定义的内部对其属性或方法进行访问的。
  • parent用于调用父类中被覆盖的属性或方法(出现在哪里,就将解析为相应类的父类)。
  • self用于调用本类中的方法或属性(出现在哪里,就将解析为相应的类;注意与$this区别,$this指向当前实例化的对象)。
  • 当一个子类覆盖其父类中的方法时,PHP 不会调用父类中已被覆盖的方法。是否调用父类的方法取决于子类。

2. PHP内核将类的继承实现放在了"编译阶段"

<?php
class A{
 const H = 'A';

 const J = 'A';

 static function testSelf(){
  echo self::H; //在编译阶段就确定了 self解析为 A
 }
}

class B extends A{
 const H = "B";

 const J = 'B';

 static function testParent(){
  echo parent::J; //在编译阶段就确定了 parent解析为A
 }

 /* 若重写testSelf则能输出“B”, 且C::testSelf()也是输出“B”
 static function testSelf(){
  echo self::H;
 }
 */

}

class C extends B{
 const H = "C";

 const J = 'C';
}

B::testParent();
B::testSelf();

echo "\n";

C::testParent();
C::testSelf();

运行结果:

AA
AA

结论:

self::和parent::出现在某个类X的定义中,则将被解析为相应的类X,除非在子类中覆盖父类的方法。

3.Static(静态)关键字

作用:

- 在函数体内的修饰变量的static关键字用于定义静态局部变量。
- 用于修饰类成员函数和成员变量时用于声明静态成员。
- (PHP5.3之后)在作用域解析符(::)前又表示静态延迟绑定的特殊类。

例子:

定义静态局部变量(出现位置:局部函数中)

特征:静态变量仅在局部函数域中存在,但当程序执行离开此作用域时,其值并不丢失。

<?php
function test()
{
 static $count = 0;

 $count++;
 echo $count;
 if ($count < 10) {
  test();
 }
 $count--;
}

定义静态方法,静态属性

a)声明类属性或方法为静态,就可以不实例化类而直接访问。

b)静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)

c)如果没有指定访问控制,属性和方法默认为公有。

d)由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用。

e)静态属性不可以由对象通过 -> 操作符来访问。

f)用静态方式调用一个非静态方法会导致一个 E_STRICT 级别的错误。

g)就像其它所有的 PHP 静态变量一样,静态属性只能被初始化为文字或常量,不能使用表达式。所以可以把静态属性初始化为整数或数组,但不能初始化为另一个变量或函数返回值,也不能指向一个对象。

a.静态方法例子(出现位置: 类的方法定义)

<?php
class Foo {
 public static function aStaticMethod() {
  // ...
 }
}

Foo::aStaticMethod();
$classname = 'Foo';
$classname::aStaticMethod(); // 自PHP 5.3.0后,可以通过变量引用类
?>

b.静态属性例子(出现位置:类的属性定义)

<?php
class Foo
{
 public static $my_static = 'foo';

 public function staticValue() {
  return self::$my_static; //self 即 FOO类
 }
}

class Bar extends Foo
{
 public function fooStatic() {
  return parent::$my_static; //parent 即 FOO类
 }
}

print Foo::$my_static . "\n";

$foo = new Foo();
print $foo->staticValue() . "\n";
print $foo->my_static . "\n";  // Undefined "Property" my_static 

print $foo::$my_static . "\n";
$classname = 'Foo';
print $classname::$my_static . "\n"; // As of PHP 5.3.0

print Bar::$my_static . "\n";
$bar = new Bar();
print $bar->fooStatic() . "\n";
?>

c.用于后期静态绑定(出现位置: 类的方法中,用于修饰变量或方法)

下面详细分析

后期静态绑定(late static binding)

自 PHP 5.3.0 起,PHP 增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类。

1.转发调用与非转发调用

转发调用 :

指的是通过以下几种方式进行的静态调用:self::,parent::,static:: 以及 forward_static_call()。

非转发调用 :

明确指定类名的静态调用(例如Foo::foo())

非静态调用(例如$foo->foo())

2.后期静态绑定工作原理

原理:存储了在上一个“非转发调用”(non-forwarding call)中的类名。意思是当我们调用一个转发调用的静态调用时,实际调用的类是上一个非转发调用的类。

例子分析:

<?php
class A {
 public static function foo() {
  echo __CLASS__."\n";
  static::who();
 }

 public static function who() {
  echo __CLASS__."\n";
 }
}

class B extends A {
 public static function test() {
  echo "A::foo()\n";
  A::foo();
  echo "parent::foo()\n";
  parent::foo();
  echo "self::foo()\n";
  self::foo();
 }

 public static function who() {
  echo __CLASS__."\n";
 }
}

class C extends B {
 public static function who() {
  echo __CLASS__."\n";
 }
}

C::test();

/*
 * C::test(); //非转发调用 ,进入test()调用后,“上一次非转发调用”存储的类名为C
 *
 * //当前的“上一次非转发调用”存储的类名为C
 * public static function test() {
 *  A::foo(); //非转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为A,然后实际执行代码A::foo(), 转 0-0
 *  parent::foo(); //转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为C, 此处的parent解析为A ,转1-0
 *  self::foo(); //转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为C, 此处self解析为B, 转2-0
 * }
 *
 *
 * 0-0
 * //当前的“上一次非转发调用”存储的类名为A
 * public static function foo() {
 *  static::who(); //转发调用, 因为当前的“上一次非转发调用”存储的类名为A, 故实际执行代码A::who(),即static代表A,进入who()调用后,“上一次非转发调用”存储的类名依然为A,因此打印 “A”
 * }
 *
 * 1-0
 * //当前的“上一次非转发调用”存储的类名为C
 * public static function foo() {
 *  static::who(); //转发调用, 因为当前的“上一次非转发调用”存储的类名为C, 故实际执行代码C::who(),即static代表C,进入who()调用后,“上一次非转发调用”存储的类名依然为C,因此打印 “C”

 * }
 *
 * 2-0
 * //当前的“上一次非转发调用”存储的类名为C
 * public static function foo() {
 *  static::who(); //转发调用, 因为当前的“上一次非转发调用”存储的类名为C, 故实际执行代码C::who(),即static代表C,进入who()调用后,“上一次非转发调用”存储的类名依然为C,因此打印 “C”
 * }
 */


故最终结果为:
A::foo()
A
A
parent::foo()
A
C
self::foo()
A
C

3.更多静态后期静态绑定的例子

a)Self, Parent 和 Static的对比

<?php
class Mango {
 function classname(){
  return __CLASS__;
 }

 function selfname(){
  return self::classname();
 }

 function staticname(){
  return static::classname();
 }
}

class Orange extends Mango {
 function parentname(){
  return parent::classname();
 }

 function classname(){
  return __CLASS__;
 }
}

class Apple extends Orange {
 function parentname(){
  return parent::classname();
 }

 function classname(){
  return __CLASS__;
 }
}

$apple = new Apple();
echo $apple->selfname() . "\n";
echo $apple->parentname() . "\n";
echo $apple->staticname();

?>

运行结果:
Mango
Orange
Apple

b)使用forward_static_call()

<?php
class Mango
{
 const NAME = 'Mango is';
 public static function fruit() {
  $args = func_get_args();
  echo static::NAME, " " . join(' ', $args) . "\n";
 }
}

class Orange extends Mango
{
 const NAME = 'Orange is';

 public static function fruit() {
  echo self::NAME, "\n";

  forward_static_call(array('Mango', 'fruit'), 'my', 'favorite', 'fruit');
  forward_static_call('fruit', 'my', 'father\'s', 'favorite', 'fruit');
 }
}

Orange::fruit('NO');

function fruit() {
 $args = func_get_args();
 echo "Apple is " . join(' ', $args). "\n";
}

?>


运行结果:
Orange is
Orange is my favorite fruit
Apple is my father's favorite fruit

c)使用get_called_class()

<?php


class Mango {
 static public function fruit() {
  echo get_called_class() . "\n";
 }
}

class Orange extends Mango {
 //
}

Mango::fruit();
Orange::fruit();

?>



运行结果:
Mango
Orange

应用

前面已经提到过了,引入后期静态绑定的目的是:用于在继承范围内引用静态调用的类。
所以, 可以用后期静态绑定的办法解决单例继承问题。

先看一下使用self是一个什么样的情况:

<?php
// new self 得到的单例都为A。
class A
{
 protected static $_instance = null;

 protected function __construct()
 {
  //disallow new instance
 }

 protected function __clone(){
  //disallow clone
 }

 static public function getInstance()
 {
  if (self::$_instance === null) {
   self::$_instance = new self();
  }
  return self::$_instance;
 }
}

class B extends A
{
 protected static $_instance = null;
}

class C extends A{
 protected static $_instance = null;
}

$a = A::getInstance();
$b = B::getInstance();
$c = C::getInstance();

var_dump($a);
var_dump($b);
var_dump($c);




运行结果:
E:\code\php_test\apply\self.php:37:
class A#1 (0) {
}
E:\code\php_test\apply\self.php:38:
class A#1 (0) {
}
E:\code\php_test\apply\self.php:39:
class A#1 (0) {
}

通过上面的例子可以看到,使用self,实例化得到的都是类A的同一个对象

再来看看使用static会得到什么样的结果

<?php
// new static 得到的单例分别为D,E和F。
class D
{
 protected static $_instance = null;

 protected function __construct(){}
 protected function __clone()
 {
  //disallow clone
 }

 static public function getInstance()
 {
  if (static::$_instance === null) {
   static::$_instance = new static();
  }
  return static::$_instance;
 }
}

class E extends D
{
 protected static $_instance = null;
}

class F extends D{
 protected static $_instance = null;
}

$d = D::getInstance();
$e = E::getInstance();
$f = F::getInstance();

var_dump($d);
var_dump($e);
var_dump($f);




运行结果:
E:\code\php_test\apply\static.php:35:
class D#1 (0) {
}
E:\code\php_test\apply\static.php:36:
class E#2 (0) {
}
E:\code\php_test\apply\static.php:37:
class F#3 (0) {
}

可以看到,使用static可以解决self时出现的单例继承问题。

Javascript 相关文章推荐
用正则xmlHttp实现的偷(转)
Jan 22 Javascript
JavaScript编程开发中的五个实用小技巧
Jul 22 Javascript
struts2+jquery+json实现异步加载数据(自写)
Jun 24 Javascript
用js代码改变单选框选中状态的简单实例
Dec 18 Javascript
Js保留小数点的4种效果实现代码分享
Apr 12 Javascript
Javascript学习笔记之 函数篇(二) : this 的工作机制
Jun 24 Javascript
详解jQuery中的元素的属性和相关操作
Aug 14 Javascript
Javascript类型转换的规则实例解析
Feb 23 Javascript
遍历js中对象的属性和值的实例
Nov 21 Javascript
基于react框架使用的一些细节要点的思考
May 31 Javascript
微信小程序地图(map)组件点击(tap)获取经纬度的方法
Jan 10 Javascript
jquery实现简单每周轮换的日历
Sep 10 jQuery
在 Linux/Unix 中不重启 Vim 而重新加载 .vimrc 文件的流程
Mar 21 #Javascript
用p5.js制作烟花特效的示例代码
Mar 21 #Javascript
AngularJS监听ng-repeat渲染完成的方法
Mar 20 #Javascript
vue对storejs获取的数据进行处理时遇到的几种问题小结
Mar 20 #Javascript
webpack本地开发环境无法用IP访问的解决方法
Mar 20 #Javascript
解决vue-router中的query动态传参问题
Mar 20 #Javascript
vue数据传递--我有特殊的实现技巧
Mar 20 #Javascript
You might like
PHP 上传文件大小限制
2009/07/05 PHP
PHP curl_setopt()函数实例代码与参数分析
2011/06/02 PHP
用 Composer构建自己的 PHP 框架之构建路由
2014/10/30 PHP
php版微信数据统计接口用法示例
2016/10/12 PHP
PHP+jQuery实现即点即改功能示例
2019/02/21 PHP
常用的javascript function代码
2008/05/23 Javascript
iframe 父窗口和子窗口相互的调用方法集锦
2010/12/15 Javascript
JavaScript中valueOf函数与toString方法深入理解
2012/12/02 Javascript
Windows8下搭建Node.js开发环境教程
2014/09/03 Javascript
jQuery内容过滤选择器用法分析
2015/02/10 Javascript
详解javascript遍历方式
2015/11/11 Javascript
原生node.js案例--前后台交互
2017/02/20 Javascript
ReactNative实现图片上传功能的示例代码
2017/07/11 Javascript
用ES6的class模仿Vue写一个双向绑定的示例代码
2018/04/20 Javascript
浅谈vue同一页面中拥有两个表单时,的验证问题
2018/09/18 Javascript
vue 使某个组件不被 keep-alive 缓存的方法
2018/09/21 Javascript
ng-repeat指令在迭代对象时的去重方法
2018/10/02 Javascript
仿iPhone通讯录制作小程序自定义选择组件的实现
2019/05/23 Javascript
详解Vue中组件传值的多重实现方式
2019/08/16 Javascript
Python使用中文正则表达式匹配指定中文字符串的方法示例
2017/01/20 Python
Django中使用第三方登录的示例代码
2018/08/20 Python
python获取时间及时间格式转换问题实例代码详解
2018/12/06 Python
Python实现E-Mail收集插件实例教程
2019/02/06 Python
用Q-learning算法实现自动走迷宫机器人的方法示例
2019/06/03 Python
Keras 使用 Lambda层详解
2020/06/10 Python
python爬虫分布式获取数据的实例方法
2020/11/26 Python
Pandas的数据过滤实现
2021/01/15 Python
自主招生自荐书
2013/11/29 职场文书
经营管理策划方案
2014/05/22 职场文书
论群众路线学习笔记
2014/11/06 职场文书
2015建军节87周年演讲稿
2015/03/19 职场文书
大学生违纪检讨书范文
2015/05/07 职场文书
廉洁自律证明
2015/06/24 职场文书
篮球赛新闻稿
2015/07/17 职场文书
小学教师教学反思
2016/02/24 职场文书
python实现批量移动文件
2021/04/05 Python