详解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 相关文章推荐
弹出广告特效代码(一个IP只弹出一次)
May 11 Javascript
EasySlider 基于jQuery功能强大简单易用的滑动门插件
Jun 11 Javascript
js Event对象的5种坐标
Sep 12 Javascript
jquery实现鼠标滑过小图时显示大图的方法
Jan 14 Javascript
javascript限制文本框输入值类型的方法
May 07 Javascript
使用BootStrap实现用户登录界面UI
Aug 10 Javascript
详解Javascript ES6中的箭头函数(Arrow Functions)
Aug 24 Javascript
JavaScript实现输入框与清空按钮联动效果
Sep 09 Javascript
AngularJS出现$http异步后台无法获取请求参数问题的解决方法
Nov 03 Javascript
vue项目首屏加载时间优化实战
Apr 23 Javascript
vue设置动态请求地址的例子
Nov 01 Javascript
Javascript中的解构赋值语法详解
Apr 02 Javascript
在 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
ThinkPHP的模版中调用session数据的方法
2014/07/01 PHP
PHP基于php_imagick_st-Q8.dll实现JPG合成GIF图片的方法
2014/07/11 PHP
php+mysqli实现批量执行插入、更新及删除数据的方法
2015/01/29 PHP
php 数组字符串搜索array_search技巧
2016/07/05 PHP
PHP使用Redis替代文件存储Session的方法
2017/02/15 PHP
PHP编程实现多维数组按照某个键值排序的方法小结【2种方法】
2017/04/27 PHP
php实现算术验证码功能
2018/12/05 PHP
Laravel5.1 框架路由基础详解
2020/01/04 PHP
JS处理VBArray的函数使用说明
2008/05/11 Javascript
js下获取div中的数据的原理分析
2010/04/07 Javascript
JS生成随机字符串的多种方法
2014/06/10 Javascript
jquery中ajax跨域方法实例分析
2015/12/18 Javascript
浅谈js图片前端预览之filereader和window.URL.createObjectURL
2016/06/30 Javascript
jQuery右下角悬浮广告实例
2016/10/17 Javascript
Node.js实现文件上传的示例
2017/06/28 Javascript
在Vue组件化中利用axios处理ajax请求的使用方法
2017/08/25 Javascript
利用nginx + node在阿里云部署https的步骤详解
2017/12/19 Javascript
动态加载JavaScript文件的3种方式
2018/05/05 Javascript
JS实现音乐钢琴特效
2020/01/06 Javascript
React中使用Vditor自定义图片详解
2020/12/25 Javascript
使用Python编写Linux系统守护进程实例
2015/02/03 Python
python MySQLdb Windows下安装教程及问题解决方法
2015/05/09 Python
Python reduce()函数的用法小结
2017/11/15 Python
python数组循环处理方法
2019/08/26 Python
Python OrderedDict的使用案例解析
2019/10/25 Python
如何用OpenCV -python3实现视频物体追踪
2019/12/04 Python
Cotton On美国网站:澳洲时装连锁品牌
2016/10/25 全球购物
微软中国官方旗舰店:销售Surface、Xbox One、笔记本电脑、Office
2018/07/23 全球购物
小米俄罗斯授权商店:Xiaomi俄罗斯
2019/12/08 全球购物
人事部专员岗位职责
2014/03/04 职场文书
科技活动周标语
2014/10/08 职场文书
预备党员群众路线思想汇报2014
2014/10/25 职场文书
硕士学位论文评语
2014/12/31 职场文书
大学生自荐信范文
2015/03/05 职场文书
Nginx解决前端访问资源跨域问题的方法详解
2021/03/31 Servers
python中的3种定义类方法
2021/11/27 Python