【问题标题】:Accessing outer variable in PHP 7 anonymous class在 PHP 7 匿名类中访问外部变量
【发布时间】:2018-08-07 16:23:28
【问题描述】:

PHP 7 添加了对anonymous classes 的支持,但是我似乎找不到任何有关相关范围问题的信息。我知道我可以将 use 关键字与 callables/closures 一起使用来访问外部作用域变量(如 function() use ($outer) { // do work with $outer }),有没有办法使用匿名类来做到这一点?

我希望能够在不依赖匿名类构造函数参数的情况下做到这一点,并且不需要添加 setter 方法或公共属性来存储实例化后的值。

这是一个例子:

$outer = 'something';

$instance = new class {
    public function testing() {
        var_dump($outer); // would like this to dump the string 'something'
    }
};

【问题讨论】:

  • 将其传递给方法?
  • 所以你没有使用属性,你没有使用构造函数?那你可以使用匿名函数吗?在不知道这个用例是什么的情况下,我们只能推测。
  • @JonStirling 我目前正在使用一个匿名函数,但我想使用一个带有接口的匿名类,这样我就可以推断出使用该可调用对象的更改
  • @AbraCadaver 只是考虑像 Java 这样的语言特性,例如,您可以在匿名类中访问外部作用域变量 - 只是想知道 PHP 是否有一些我需要使用的特殊语法来访问这些变量(如可调用/闭包的 use 关键字)
  • 我希望new class use ($outer) { .. } 能工作

标签: php


【解决方案1】:

另一种解决方案可能是

$outer = 'something';

$instance = new class($outer) {

    private $outer;

    public function __construct($outer) {
        $this->outer = $outer
    }

    public function testing() {
        var_dump($this->outer); 
    }
};

【讨论】:

  • 我认为这是目前可用的“最佳”选项,所以我将其标记为答案
  • 我认为这是做到这一点的完美方法。谢谢! :)
  • 这太烦人了,为什么 PHP 不像普通语言那样提供对闭包中外部范围变量的直接访问?
  • @AntoineMarques 他们忙于改变生活的进步,比如弃用“”标记来解决与 XML 语法的冲突,众所周知,XML 语法每天都会让世界各地的开发人员感到痛苦。箭头函数、简洁的外部范围变量访问或 21 世纪的打字方案等不太重要的功能并不那么重要。
  • @obe 我发誓我在 php 中编程超过 10 年,专业近 9 年,我从未遇到过 vs xml 有效性问题
【解决方案2】:

在这种情况下访问外部变量的唯一方法是使用 $ _GLOBAL(我不推荐)。如果您不想使用构造函数或setter方法,我的建议是在匿名类内部使用一个静态变量,并将属性后的值设置为包含匿名类实例的变量(无法定义静态值之前,因为该类是匿名的..)。这样做,你有一个更好的控制和一个静态变量,但在某些方面这不是很常见,每次当你创建一个新的匿名类时,实例及其值都属于接收“新对象”的 VARIABLE,也许更适合你创建一个真实的类。但请以静态值和匿名类为例:

$i = new class {

    public static $foo;
};

var_dump($i::$foo); //No value

$i::$foo = "Some value";

var_dump($i::$foo); //Has value

希望对你有帮助!

【讨论】:

  • 完美,谢谢!解决了我的动态扩展类的问题,该类在框架中使用了一些关键的静态调用。
  • 这里要注意的是,当您使用带有use($someOuterVar) 语句的匿名函数时,您也在那里分配了一个静态变量。就像您要在函数中(是的,您可以这样做)或在类定义中使用 static 关键字一样。也就是说,我认为这应该是公认的答案,它最符合闭包建立的约定。
【解决方案3】:

http://php.net/manual/en/language.variables.scope.php

php 变量作用域文档中有一些说明。

此脚本不会产生任何输出,因为 echo 语句引用了 $a 变量的本地版本,并且尚未在此范围内为其分配值。您可能会注意到这与 C 语言有点不同,因为 C 中的全局变量自动可用于函数,除非被局部定义特别覆盖。这可能会导致一些问题,因为人们可能会无意中更改全局变量。在 PHP 中,如果要在函数中使用全局变量,则必须在函数中声明为全局变量。

在php中,类内部的方法可以访问的范围被限制在整个类内部,不能被其他范围访问。所以我认为你想要的效果并没有在php中实现,至少在PHP GROUP决定改变PHP的默认行为之前。

当然,您仍然可以通过将变量声明为全局变量来使用它。

【讨论】:

    【解决方案4】:

    尽管 OP 确实声明他们希望避免使用 public propertiesanonymous class constructor arguments,但公认的答案正是如此,所以这里有一个使用公共属性的示例,可以通过私有属性和setter 来维护封装:

    class Foo {
        public function executionMethod() {
            return "Internal Logic";
        }
    }
    
    $foo = new Foo();
    var_dump("Foo's execution method returns: " . $foo->executionMethod());
    
    $bar = new class extends Foo {
        public $barVal;
        public function executionMethod() {
            return $this->barVal;
        }
    };
    $bar->barVal = "External Logic";
    var_dump("Bar's execution method returns: " . $bar->executionMethod());
    

    如果您无法覆盖继承类的构造函数,我发现这很有用。

    这将输出:

    string(46) "Foo's execution method returns: Internal Logic"
    string(46) "Bar's execution method returns: External Logic"
    

    【讨论】:

      【解决方案5】:

      如果您希望匿名类能够访问受保护或私有的外部属性和方法,您可以利用闭包继承它们定义的范围这一事实,并将它们绑定到一些神奇的方法中以实现无缝连接行为。

      这是未经测试的,但我很确定它会起作用:

      $class = new class {
          /**
           * @var \Closure
           */
          public static $outerScopeCall;
      
          /**
           * @var \Closure
           */
          public static $outerScopeGet;
      
          /**
           * @param string $name
           * @param array $arguments
           * @return mixed
           */
          public function __call(string $name, array $arguments = [])
          {
              $closure = static::$outerScopeCall;
              return $closure($name, $arguments);
          }
      
          /**
           * @param string $name
           * @param array $arguments
           * @return mixed
           */
          public function __get(string $name)
          {
              $closure = static::$outerScopeGet;
              return $closure($name);
          }
      };
      
      $class::$outerScopeCall = function (string $name, array $arguments = []) {
          return $this->$name(...$arguments);
      };
      
      $class::$outerScopeGet = function (string $name) {
          return $this->$name;
      };
      

      $call 闭包将可以访问 $this 的定义,而不是从哪里定义

      【讨论】:

        最近更新 更多