我们在各种项目中,尤其是框架源码中,经常可以看到self::static::parent::等来调用静态变量、静态方法,本文就简单地说明这三种关键字有啥区别。 首先说明两个概念,转发调用非转发调用

  • 转发调用:进行静态调用时,代码中不显式指明调用的对象,而是在运行时来判断调用哪个对象。比如使用关键字selfstaticparentforward_static_call
  • 非转发调用:在进行方法、对象调用时,代码中显式指明调用的对象名称,如A::getName()a->getName()

这三个关键字会进行转发调用,但他们转发的对象不同:

  • self::转发给该语句定义所在的类
  • parent::转发给该语句定义所在类的父类
  • static::转发给调用该语句调用者所在类

听起来很晕,好在 PHP 也为我们提供了相应的函数、魔术变量来帮助获取相关信息,便于我们理解:

  • 魔术变量__CLASS__可用来获取定义当前方法所在的类。
  • 函数get_called_class()可用来获取调用者所在类。

先看如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

define('TEMPLATE', "%-12s %-5s %-12s %-12s\n");

class Base 
{
    protected static $value = 'Base';
}

class Car extends Base
{
    protected static $value = 'Car';

    public static function printSelf()
    {
        printf(TEMPLATE, __FUNCTION__, self::$value, get_called_class(), __CLASS__);
    }

    public static function printParent()
    {
        printf(TEMPLATE, __FUNCTION__, parent::$value, get_called_class(), __CLASS__);
    }

    public static function printStatic()
    {
        printf(TEMPLATE, __FUNCTION__, static::$value, get_called_class(), __CLASS__);
    }
}

class Benz extends Car
{
    protected static $value = 'Benz';

    public static function printSelf()
    {
        printf(TEMPLATE, __FUNCTION__, self::$value, get_called_class(), __CLASS__);
    }

    public static function printParent()
    {
        printf(TEMPLATE, __FUNCTION__, parent::$value, get_called_class(), __CLASS__);
    }
}

printf(TEMPLATE, 'function','value', 'called_class', 'defined_class');

Car::printSelf();
Car::printParent();
Car::printStatic();

Benz::printSelf();
Benz::printParent();
Benz::printStatic();

运行结果是:

1
2
3
4
5
6
7
function     value called_class defined_class
printSelf    Car   Car          Car         
printParent  Base  Car          Car         
printStatic  Car   Car          Car         
printSelf    Benz  Benz         Benz        
printParent  Car   Benz         Benz        
printStatic  Benz  Benz         Car 

输出结果每一列分别为为函数名、获取到的 value 值、调用者所在类、被调用函数定义所在类.

除去标题,输出结果一共六行,编号为结果 1 ~ 结果 6。 由结果 1 与结果 4 ,BenzCar类中均有printSelf()方法,并都使用了self关键字,因此他们各自会指向各自的定义函数所在类:BenzCar。 由结果 2 与结果 5,BenzCar类中均有printParent()方法,并都使用了parent关键字,因此他们各自会指向各自的定义函数所在类的父类:CarBase。 由结果 3 与结果 6,Benz类没有printStatic()方法,虽然调用的是Car类的方法,但调用方是Benz类,故static::$value输出为Benz类的$value;而Car类调用printStatic()方法,输出为Car类的$value

总结一下,self::parent::指向的是其定义所在函数的类(如 defined_class 列所示)的本身、的父类;而static::指向的是其调用方的类(如 called_class 列所示)本身。

再提供几个例子,以供练习,加深理解。

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
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

说了这么多,那这三个关键字有什么用呢? self一般用来获取静态属性或调用静态方法。 parent用来调用父类的方法或属性。 static则用来调用调用者的方法或属性。比如,可以在Base类中通过static关键字调用子类中的方法,从而实现不用修改父类,只需修改子类,就能实现不同的功能,拓展性强。这种方法经常在框架中用到,Base 类写在框架中,而子类可由用户去实现,定制化强:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

class Base
{
    public static function work()
    {
        static::drive();
    }
}

class Car extends Base
{
    protected static function drive()
    {
        print("begin to drive car\n");
    }
}

class Bicycle extends Base
{
    protected static function drive()
    {
        print("begin to take bicycle\n");
    }
}

Bicycle::work();
Car::work();

参考文献

PHP常见概念混淆(七)之self、static、parent的区别

PHP Manual:后期静态绑定

PHP中静态(static)调用非静态方法详解

PHP中静态(static)调用非静态方法详解