PHP 静态属性和静态方法

基本使用

前面介绍的 PHP 类属性和方法都要在类实例化后才能调用(常量属性除外),除此之外,PHP 还提供了静态属性和静态方法,所谓「静态」指的是无需对类进行实例化,就可以直接调用这些属性和方法,这么生讲有点硬,我们举个例子一看就明白了。

静态属性和方法的定义和调用

php_learning/oop 目录下新建一个 static.php 文件,编写一段测试代码如下:

<?php

class Car
{
    public static $WHEELS = 4;

    public static function getWheels()
    {
        return self::$WHEELS;
    }
}

在 PHP 中,我们通过 static 关键字来修饰静态属性和方法,这里我们定义了一个静态属性$WHEELS 和静态方法 getWheels,由于静态属性和方法可以直接通过类引用,所以又被称作类属性和类方法(相应的,非静态属性和非静态方法需要实例化后通过对象引用,因此被称作对象属性和对象方法),静态属性和方法可以通过 类名::属性/方法 的方式调用:

echo "WHEELS:" . Car::$WHEELS . PHP_EOL;
echo "getWheels:" . Car::getWheels() . PHP_EOL;

如果是在类内部方法中,需要通过 self:: 引用当前类的静态属性和方法,就像常量一样,因为静态属性和方法无需实例化类即可使用,而没有实例化的情况下,$this 指针指向的是空对象,所以不能动过它引用静态属性和方法:

-w670

同理,我们也不能在静态方法中通过 $this 引用对象属性和方法。

静态属性支持动态修改

为了以示区别,这里通过了大写字母设置静态属性变量名,这不是强制的,因为静态属性名和常量不同,可以在运行时进行修改,只是它的作用域是整个类,而不是某个对象:

Car::$WHEELS = 8;
echo "getWheels:" . Car::getWheels() . PHP_EOL;

上述代码的打印结果是:

getWheels:8

并且静态属性和方法与对象属性和方法一样,支持设置 privateprotectedpublic 三种可见性级别。

调用另一个类的静态属性/方法

如果在一个类中调用其他类的静态属性和方法,需要通过 完整类名:: 进行引用:

<?php

class Gas
{
    public static $POWER = '汽油';
}

class Car
{
    protected static $WHEELS = 4;

    public static function getWheels()
    {
        return self::$WHEELS;
    }

    public static function printCar()
    {
        printf("这辆车有 %d 个轮子,使用 %s 作为动力来源\n", self::$WHEELS, Gas::$POWER);
    }
}

Car::printCar();

这里我们通过 Gas::$POWER 引用了 Gas 中的静态属性 $POWER,静态方法也是类似,上述代码打印结果如下:

-w557

在非静态方法中调用静态属性/方法

另外,我们前面提到不能在静态方法中通过 $this 调用非静态属性/方法,但是在非静态方法中可以通过 self:: 调用静态属性/方法:

class Car
{
    ...

    public static function printCar()
    {
        return sprintf("这辆车有 %d 个轮子,使用 %s 作为动力来源\n", self::$WHEELS, Gas::$POWER);
    }

    public function __toString()
    {
        return self::printCar();
    }
}

$car = new Car();
echo $car;

这很好理解,因为前者是因为没有实例化就可以调用,后者实例化后不影响类方法的调用,在上述代码中,我们将静态方法 printCar 调整为通过 sprintf 函数返回格式化字符串,然后在魔术方法 __toString 方法中调用,作为该非静态方法的返回值。上述代码的打印结果是:

-w575

完全可以正常运行。

进阶功能

静态方法的继承和重写

和非静态属性/方法一样,静态属性和方法也可以被子类继承,静态方法还可以被子类重写:

class Car
{
    ...

    public static function getClassName()
    {
        return __CLASS__;
    }

    public static function who()
    {
        echo self::getClassName() . PHP_EOL;
    }
}

class LynkCo01 extends Car
{
    public static function getClassName()
    {
        return __CLASS__;
    }
}

通过 __CLASS__ 可以获取当前类的类名,我们分别调用两个类的 getClassName 方法:

echo Car::getClassName() . PHP_EOL;
echo LynkCo01::getClassName() . PHP_EOL;

打印结果如下:

-w532

说明子类重写了父类的同名静态方法,同样我们在子类上也可以调用父类中的 who 方法:

Car::who();
LynkCo01::who();

上述代码的打印结果是:

Car
Car

咦?为什么第二个打印的结果是父类名 Car 而不是子类名 LynkCo01?这是因为,和 $this 指针始终指向持有它的引用对象不同,self 指向的是定义时持有它的类而不是调用时的,为了解决这个问题,从 PHP 5.3 开始,新增了一个叫做后期静态绑定的特性。

后期静态绑定

后期静态绑定(Late Static Bindings)针对的是静态方法的调用,使用该特性时不再通过 self:: 引用静态方法,而是通过 static::,如果是在定义它的类中调用,则指向当前类,此时和 self 功能一样,如果是在子类或者其他类中调用,则指向调用该方法所在的类,我们通过后期静态绑定改写上述代码:

class Car
{
    ...

    public static function getClassName()
    {
        return __CLASS__;
    }

    public static function who()
    {
        echo static::getClassName() . PHP_EOL;
    }
}

class LynkCo01 extends Car
{
    public static function getClassName()
    {
        return __CLASS__;
    }
}

...

Car::who();
LynkCo01::who();

再次执行,打印结果如下:

Car
LynkCo01

表明后期静态绑定生效,即 static 指向的是调用它的方法所在的类,而不是定义时,所以称之为后期静态绑定。

此外,还可以通过 static::class 来指向当前调用类的类名,例如我们可以通过它来替代 __CLASS__,这样上述子类就没有必要重写 getClassName 方法了:

class Car
{
    ...

    public static function getClassName()
    {
        return static::class;
    }

    public static function who()
    {
        echo static::getClassName() . PHP_EOL;
    }
}

class LynkCo01 extends Car
{

}

代码执行结果和之前一样。

同理,self::class 则始终指向的是定义它的类,感兴趣的同学可以自行测试,这里不再演示了。selfstatic 各有其使用场景,后面我们会看到其实际的使用场景。

关于 PHP 静态属性和方法的使用就简单介绍到这里,明天,学院君将给大家介绍下 PHP 类中常见的魔术方法。

上一篇: 通过 Trait 水平扩展 PHP 类功能

下一篇: PHP 魔术方法、序列化与对象复制