php中的静态关键字-static

静态关键字 static

php中static关键字的用法有多种:

  1. 定义静态变量;
  2. 静态匿名函数;
  3. 在类中定义静态方法和属性;
  4. 后期静态绑定;

定义静态变量

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

一般情况下的例子:

function Test()
{
    $a = 0;
    echo $a;
    $a++;
}

每次调用函数Test()变量$a的值都会输出0。语句$a++没什么用。

看看在函数中使用静态变量的例子:

function test()
{
    static $a = 0;
    echo $a;
    $a++;
}
test(); // 输出 0
test(); // 输出 1
test(); // 输出 2

将变量$a声明为静态的,每次调用test()函数,$a的值都会在前一次的结果上加1。

静态变量的值是在编译时解析,所以与const声明常量类似,常量表达式的结果可以赋值给静态变量,但是动态表达式(比如函数调用)会导致解析错误。

function foo(){
    static $int = 0;          // 正确
    static $int = 1+2;        // 正确
    static $int = sqrt(121);  // 错误(因为它是函数)

    $int++;
    echo $int;
}

从 PHP 8.1.0 开始,当继承(不是覆盖)使用有静态变量的方法时,继承的方法将会跟父级方法共享静态变量。
这意味着方法中的静态变量现在跟静态属性有相同的行为。

class Foo {
    public static function counter() {
        static $counter = 0;
        $counter++;
        return $counter;
    }
}
class Bar extends Foo {}
var_dump(Foo::counter()); // int(1)
var_dump(Foo::counter()); // int(2)
var_dump(Bar::counter()); // int(3),PHP 8.1.0 之前 int(1)
var_dump(Bar::counter()); // int(4),PHP 8.1.0 之前 int(2)

静态匿名函数

php中的匿名函数又叫闭包。

静态闭包不能有绑定的对象,换句话说就是静态闭包里不存在$this。不过仍然可以用 bindTo 方法来改变它们的类作用域

例子:

class Foo
{
    static $tt = 'sssss';
    function __construct()
    {
        $func = static function() {
            var_dump($this);
        };
        $func();
    }
};
// 会有提示:Notice: Undefined variable: this
$f00 = new Foo(); // 输出 NULL

如果使用bindTo函数强行给静态闭包绑定$this会报错:

class Foo
{
    static $tt = 'sssss';
    function __construct()
    {
        $func = static function() {
            echo self::$tt;
        };
        $func();
    }
};
$f00 = new Foo();// sssss

$func = static function () {
    var_dump($this);
};
$func_1 = $func->bindTo($f00); // Warning: Cannot bind an instance to a static closure

https://stackoverflow.com/questions/19899468/php-closures-why-the-static-in-the-anonymous-function-declaration-when-bindin
https://stackoverflow.com/questions/69742245/what-exactly-is-the-difference-between-an-anonymous-function-and-a-static-anonym

在类中定义静态方法和属性

声明类属性或方法为静态,就可以不实例化类而直接访问。可以在实例化的类对象中通过静态访问。

类的静态成员使用 范围解析操作符 ( :: )访问,不能通过对象操作符( -> )访问。

在访问类的静态成员时不能使用$this来引用,要使用关键字selfparentstatic、类名和实例类加范围解析操作符来引用,否则报错,具体可以看后面的例子。

注意:以静态方式访问类的非静态成员(即使用范围解析操作符访问类的非静态成员)将会抛出一个 Error。 在 PHP 8.0.0 之前版本中,将会产生一个废弃通知,同时 $this 将会被声明为未定义。在 PHP 8 中会抛出致命错误(Fatal Error),中断程序执行。下面看例子。

php 8中,在类的外部以静态方式访问类的非静态成员会报错:

class A
{
    function __construct() {
        echo 'AAAAAAAA';
    }
    function foo()
    {
    	var_dump($this);
        if (isset($this)) {
            echo '$this is defined (';
            echo get_class($this);
            echo ")\n";
        } else {
            echo "\$this is not defined.\n";
        }
    }
}
A::foo();

上面代码在php 8中运行会报错(测试环境是:https://onlinephp.io/):

Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically in /home/user/scripts/code.php:19
Stack trace:
#0 {main}
  thrown in /home/user/scripts/code.php on line 19

php 8中,在类的内部以静态方式访问类的非静态成员程序会正常运行:

class A
{
    function __construct() {
        echo 'AAAAAAAA';
    }
    function foo()
    {
    	var_dump($this);
        if (isset($this)) {
            echo '$this is defined (';
            echo get_class($this);
            echo ")\n";
        } else {
            echo "\$this is not defined.\n";
        }
        $this->foo();
    }
}

class B extends A
{
    function __construct() {
        echo 'BBBBBBBBB <br/>';
    }
    function bar()
    {
        A::foo();
    }
    
    function foo() {
        echo 'b->foo';
    }
}


$b = new B();
$b->bar();

上面代码在php 8中正常运行,注意$this也被正常解析(测试环境是:https://onlinephp.io/):

BBBBBBBBB <br/>object(B)#1 (0) {
}
$this is defined (B)
b->foo

也可以用对象实例或者$this->访问public声明的静态方法,但是不推荐这样做。

类的静态方法

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

class Foo {
    public static function aStaticMethod() {
        // ...
        var_dump($this); // 报错 Notice: Undefined variable: this
    }
}

Foo::aStaticMethod();
$classname = 'Foo';
$classname::aStaticMethod();

类的静态属性

静态属性的例子,注意静态属性的访问方式:

class Foo
{
    public static $my_static = 'foo';

    public function staticValue() {
        return self::$my_static;
    }
}

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


print Foo::$my_static . "<br/>"; // foo

$foo = new Foo();
print $foo->staticValue() . "<br/>"; // foo

// 报错
// Strict Standards: Accessing static property Foo::$my_static as non static 
// Notice: Undefined property: Foo::$my_static
print $foo->my_static . "<br/>";      // 报错 未定义的 "属性" my_static

print $foo::$my_static . "<br/>"; // foo
$classname = 'Foo';
print $classname::$my_static . "<br/>"; // foo

print Bar::$my_static . "<br/>"; // foo
$bar = new Bar();
print $bar->fooStatic() . "<br/>"; // foo

类的静态成员是共享给所有实例的。下面看一个统计网站访问数量的例子:

class Visitor
{
    private static $visitors = 0;

    function __construct()
    {
        self::$visitors++;
    }

    static function getVisitors()
    {
        return self::$visitors;
    }
} 

$visits = new Visitor();
echo Visitor::getVisitors().'<br/>';

$visits2 = new Visitor();
echo Visitor::getVisitors().'<br/>';

// 下面两句报错,因为静态成员`visitors`是私有的,不能在类的外部调用
echo Visitor::$visitors.'<br/>';
echo $visits::$visitors.'<br/>';

例子中虽然创建了两个实例,但是类的静态属性的值是依次递增的,说明类的静态属性没有重新分配给各个实例,各个实例访问的是同一个。

类的静态成员前面也可以加publicprivateprotected控制访问关键字。
我们知道,声明为public的类成员可以在类的外部调用,声明为privateprotected的类成员只能在类的内部调用(类的内部是指定义类时类的大括号范围内)。
加了访问控制的类的静态成员同样适用。

后期静态绑定

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

这一句话限制了后期静态绑定的使用范围: 类的继承和类的静态方法的调用,在这两个条件的前提下使用后期静态绑定才有意义。

“后期绑定”的意思是说,static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的。
也可以称之为“静态绑定”,因为它可以用于(也可以用于调用非静态方法,但是不推荐)静态方法的调用。

先学习两个概念:转发调用和非转发调用。

转发调用(forwarding call)指的是通过以下几种方式进行的静态调用:self::, parent::, static::以及forward_ static _call ()。即在进行静态调用时未指名类名的调用。

非转发调用(non-forwarding call)其实就是明确指定类名的静态调用(foo::bar ())和非静态调用 ($foo->bar ())。

后期静态绑定工作原理是存储了上一次非转发调用的类名(假设类名是A),当进行静态方法调用(假设是继承A类的子类B调用了静态方法)时,该非转发调用类名(也就是A)就是调用静态方法的类;当进行非静态方法调用时,即为该对象所属的类(可以看例子3中的$c->ttt())。

self在时候用上有限制:使用 self:: 或者 __CLASS__ 对当前类的静态引用,取决于定义当前方法所在的类:

// 例子1
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}
// self 和 __CLASS__ 所在的方法在哪个类里定义,self 和 __CLASS__就指代哪个类(编译时确定)
B::test();// A
B::who(); // B

后期静态绑定的简单用法:

// 例子2
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() 是一个非转发调用,所以test函数里的static::who()在运行时解析时,static被解析为类B,所以最终输出B
B::test();// B

这里说一下后期静态绑定是如何确定static是哪个类的(个人猜测):

首先要知道,虽然selfparentstatic调用静态方法的写法相同,但是static在确定调用类的时候与前两者是完全不同的机制。

selfparent是按照代码的正常执行流程确定的。也就是说代码在执行过程中遇到selfparent,那么就在当前上下文中确定其指代的类。

static是倒推的。就是说,代码在执行时,并不知道之后会不会有后期静态绑定,也就是static::(后面还有new static也属于后期静态绑定),在没有遇到static::的时候都是正常的机制,当遇到static::的时候,php内部会向前倒推,在当前上下文中寻找最近一次的非转发调用,然后取本次非转发调用的类赋给static::

这是官方手册的原话:
后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。另一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。

转发调用和非转发调用的例子:

// 例子3
class A {
    public static function foo() {
        static::who();
    }

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

    public function ttt() {
        static::who();
    }

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

class B extends A {
    public static function test() {
        A::foo();
        parent::foo();
        self::foo();

        // self::fff() // 输出 A 因为fff函数是在A类中定义的
    }

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

C::test(); //  输出 A  C  C
echo '<br />';
$c = new C();
$c->ttt(); // 输出 C

对于例子3的解释:

  1. C::test()是非转发调用,调用类是C
  2. test()函数中A::foo()也是一个非转发调用,调用类是A,在foo函数内遇到了static::开始倒推调用栈中最近一次的非转发调用,找到A::FOO(),那么将这里的后期静态绑定static解析为A
  3. 代码执行到parent::foo(),此时parent解析为A,然后继续向下执行foo(),在foo函数内遇到了static::开始倒推调用栈中最近一次的非转发调用,由于parent::foo()是转发调用,继续倒推,找到C::test()parent::foo()将类C转发给static::who(),所以此时static解析为C
  4. 代码执行到self::foo(),解析流程同第3步。

new static或者new static()也是后期静态绑定的一种用法,解析规则相同。

参考文章:对 PHP 后期静态绑定的理解

posted @ 2024-01-03 11:11  Fogwind  阅读(65)  评论(0编辑  收藏  举报