php中的静态关键字-static
静态关键字 static
php中static
关键字的用法有多种:
- 定义静态变量;
- 静态匿名函数;
- 在类中定义静态方法和属性;
- 后期静态绑定;
定义静态变量
静态变量仅在局部函数域中存在,但当程序执行离开此作用域时,其值并不丢失。
一般情况下的例子:
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
来引用,要使用关键字self
、parent
、static
、类名和实例类加范围解析操作符来引用,否则报错,具体可以看后面的例子。
注意:以静态方式访问类的非静态成员(即使用范围解析操作符访问类的非静态成员)将会抛出一个 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/>';
例子中虽然创建了两个实例,但是类的静态属性的值是依次递增的,说明类的静态属性没有重新分配给各个实例,各个实例访问的是同一个。
类的静态成员前面也可以加
public
,private
,protected
控制访问关键字。
我们知道,声明为public
的类成员可以在类的外部调用,声明为private
和protected
的类成员只能在类的内部调用(类的内部是指定义类时类的大括号范围内)。
加了访问控制的类的静态成员同样适用。
后期静态绑定
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
是哪个类的(个人猜测):
首先要知道,虽然self
,parent
和static
调用静态方法的写法相同,但是static
在确定调用类的时候与前两者是完全不同的机制。
self
,parent
是按照代码的正常执行流程确定的。也就是说代码在执行过程中遇到self
,parent
,那么就在当前上下文中确定其指代的类。
而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的解释:
C::test()
是非转发调用,调用类是C
。- 在
test()
函数中A::foo()
也是一个非转发调用,调用类是A
,在foo
函数内遇到了static::
开始倒推调用栈中最近一次的非转发调用,找到A::FOO()
,那么将这里的后期静态绑定static
解析为A
。 - 代码执行到
parent::foo()
,此时parent
解析为A
,然后继续向下执行foo()
,在foo
函数内遇到了static::
开始倒推调用栈中最近一次的非转发调用,由于parent::foo()
是转发调用,继续倒推,找到C::test()
,parent::foo()
将类C
转发给static::who()
,所以此时static
解析为C
。 - 代码执行到
self::foo()
,解析流程同第3步。
new static
或者new static()
也是后期静态绑定的一种用法,解析规则相同。
参考文章:对 PHP 后期静态绑定的理解