PHP中函数内的静态变量是否跨实例全局?

6
如果我的代码使用static变量来进行缓存,就像这样:
class BossParty
{
    // ...

    public function getTemplate()
    {
        static $template;

        if ($template == null)
        {
            $template = BossTemplate::get($this->templateID);
        }

        return $template;
    }

    // ...
}
< p > $template 在不同的 BossParty 实例之间是否会保留?我已经尝试查看php.net,但是我只找到有关静态类变量的信息。


考虑在类主体中使用类变量而不是实例变量:private static $template; - NikiC
3个回答

8
是的,静态变量将在类的多个实例之间保持持久性。
例如:
<?php

class Test {
    public function __construct() {
        static $foo;

        if ($foo) {
            echo 'found';
        } else {
            $foo = 'foobar';
        }
    }
}

$test1 = new Test();
$test2 = new Test();
// output "found"

请注意,这也适用于子类。如果我们有一个扩展了Test的Child类,调用parent::__construct(无论是显式还是隐式)将使用相同的$foo值。

2
"@lonesomeday,那似乎大部分是正确的。然而,特别是关于您的后续评论涉及继承的部分,函数内静态变量作用域的行为似乎更加复杂。我在所有示例中都使用PHP 5.3.16。

总结: 当在实例函数中使用static关键字来定义变量时,其行为似乎会根据继承和函数调用堆栈中的位置而有所不同。

以下是一些示例。

首先,这类似于您的初始示例:在类的__construct()方法中实例化一个静态变量,创建两个该类的实例,并查看该变量的行为。

"
<?php
// Example 1
class A {
  public function __construct() {
    static $i = 0;
    $i++;
    $this->i = $i;
  }
}
$o1 = new A();
$o2 = new A();
printf("\$o1->i: %d\n", $o1->i); // outputs "$o1->i: 1"
printf("\$o2->i: %d\n", $o2->i); // outputs "$o2->i: 2"
?>

到目前为止,没有什么意外。该变量被实例化一次(为0),然后每个单独的实例都会递增。
如果您将类A扩展为B,并实例化其中一个(其他所有内容保持不变),则会得到相同的行为:
<?php
// Example 2
class A {
  public function __construct() {
    static $i = 0;
    $i++;
    $this->i = $i;
  }
}
class B extends A {}
$o1 = new A();
$o2 = new B();
printf("\$o1->i: %d\n", $o1->i); // outputs "$o1->i: 1"
printf("\$o2->i: %d\n", $o2->i); // outputs "$o2->i: 2"
?>

再一次,没有惊喜。让我们回到第一个例子并稍微修改一下。首先,我们将静态变量的实例化/递增调用移动到成员方法中。请注意,我在这个例子中删除了类B。
<?php
// Example 3
class A {
  public function __construct() {
    $this->setI();
  }

  public function setI() {
    static $i = 0;
    $i++;
    $this->i = $i;
  }
}
$o1 = new A();
$o2 = new A();
printf("\$o1->i: %d\n", $o1->i); // outputs "$o1->i: 1"
printf("\$o2->i: %d\n", $o2->i); // outputs "$o2->i: 2"
?>

仍然是相同的输出。
然而,当你将 A 扩展为 B,并保持相同的结构时:
<?php
// Example 4
class A {
  public function __construct() {
    $this->setI();
  }

  public function setI() {
    static $i = 0;
    $i++;
    $this->i = $i;
  }
}
class B extends A {}
$o1 = new A();
$o2 = new B();
printf("\$o1->i: %d\n", $o1->i); // outputs "$o1->i: 1"
printf("\$o2->i: %d\n", $o2->i); // outputs "$o2->i: 1"
?>

注意输出结果:在所有其他情况下,$o2->i 变成了 2,除了这个情况。
因此,如果我们扩展 A 并将静态实例化移动到实例方法中,似乎我们现在引入了一个新的作用域来保存 $i 变量,而在所有其他情况下,实例共享该静态变量的作用域。
更令人困惑的是,考虑这个例子;它与前一个例子相同,但在这种情况下,类 B 得到了自己的 setI() 实现,它只是简单地调用其父类的实现。
<?php
// Example 5
class A {
  public function __construct() {
    $this->setI();
  }
  public function setI() {
    static $i = 0;
    $i++;
    $this->i = $i;
  }
}
class B extends A {
  public function setI() {
    parent::setI();
  }
}
$o1 = new A();
$o2 = new B();
printf("\$o1->i: %d\n", $o1->i); // outputs "$o1->i: 1"
printf("\$o2->i: %d\n", $o2->i); // outputs "$o2->i: 2"
?>

正如您所看到的,$o2->i现在又被设置为2了,这几乎是我们得到的所有结果(除了示例#4)。
对我来说,这似乎非常违反直觉。我希望不同的实例为该变量获取自己的作用域,或者所有实例(包括扩展类的实例)共享相同的作用域。
我无法确定这是否是PHP中的一个错误,还是预期行为。 PHP文档中关于静态变量作用域的说明说:
“静态变量仅存在于局部函数作用域中,但当程序执行离开此作用域时,它不会失去其值。”
他们没有详细说明在对象实例的上下文中如何定义“函数作用域”,因此我不确定示例#4中的边缘情况是否是预期的。

这正是我经历的。感谢解释,我以为我做错了什么。 - Savas Vedova

1

是的,它会生效,而且在文档中有记录,但它在变量作用域部分而不是静态关键字部分,这可能是你没有找到它的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接