PHP变量引用:数组、整数、对象及引用传递示例

PHP问题:变量引用

一、问题

以下代码会输出什么?

  
    function arrayPrint($array)
    {
       echo implode(' ', $array);
    }

    $arrayA = [1, 2, 3];
    $arrayB = $arrayA;
    $arrayB[1] = 0;
    arrayPrint($arrayA);
  

二、答案

这个问题的答案是,代码会输出 "1 2 3"。

原因是在PHP中,当你将一个变量赋值给另一个变量时,你会得到原始变量的一个副本。之后我们对新变量执行的任何操作都不会影响原始变量。

在上述示例中,属性 $arrayB 中包含的值现在是 "1 0 3",因为我们将索引为1的数组项设置为0。除非另有指定,PHP中的所有数组都从索引0开始,所以数组中的“第二个”项的键为1。

我们可以使用以下示例用整数来演示变量赋值时的复制操作。

  
    $integerA = 1;
    $integerB = $integerA;
    $integerB = 2;
    echo $integerA; // 输出 "1"。
    echo $integerB; // 输出 "2"。
  

在上述代码中,更改 $integerB 不会影响 $integerA 的值。这本质上与原始问题是相同的示例,但在这种情况下,我们改变的是单个变量,所以更容易看清发生了什么。

我们可以通过请求赋值 $integerA 的引用 来改变赋值运算符的行为。这可以通过在变量前面加上 "&" 符号并使用引用赋值来实现。

下面是考虑到这一点重写的单个变量示例。

  
    $integerA = 1;
    $integerB = &$integerA;
    $integerB = 2;
    echo $integerA; // 输出 "2"。
    echo $integerB; // 输出 "2"。
  

当我们将 &$integerA 赋值给 $integerB 时,实际上是让 $integerB 成为变量 $integerA引用,这意味着我们对任何一个变量所做的任何更改都会同时应用到两个变量上。所以,在上述示例中,将 $integerB 的值设置为 "2",实际上就是将 $integerA 的值设置为 "2",因为 $integerB 是另一个变量的引用。

三、对象是引用

在PHP中,这个规则有一个例外,那就是当我们处理对象时。在PHP中,所有对象变量都是指向原始对象的指针。创建对象时使用的 "new" 运算符会自动返回一个引用。

这意味着如果我们将对象赋值给不同的变量,我们就创建了指向同一个对象的两个指针。我们不需要使用 "&" 符号来实现这一点。

作为一个示例,让我们来看一个简单的类,它有一个公共可访问的属性(默认值为0)和一个打印类属性的方法。

  
    class SomeClass
    {
        public $someProperty = 0;

        public function __toString()
        {
            return 'someProperty: ' . $this->someProperty;
        }
    }
  

然后我们在 $objectA 变量中创建这个类的一个实例,然后将其赋值给 $objectB 变量。之后,使用 $objectB 变量将属性设置为值1。

  
    $objectA = new SomeClass();
    $objectB = $objectA;
    $objectB->someProperty = 1;
  

当我们(借助 __toString() 方法)打印对象时,我们会看到两个对象的属性都被设置为1。

  
    echo $objectA; // 输出 "someProperty: 1"
    echo $objectB; // 输出 "someProperty: 1"
  

这是因为我们告诉PHP,变量 $objectA$objectB 都指向内存中的同一个位置。所以,当我们设置一个变量的属性时,实际上是在更改同一个对象。

如果我们想将对象复制到不同的变量中,那么我们需要使用 clone 关键字。使用这个关键字会让PHP复制 $objectA 中的原始对象,并让属性 $objectB 指向这个新对象。

  
    $objectA = new SomeClass();
    $objectB = clone $objectA;
    $objectB->someProperty = 1;
    echo $objectA; // 输出 "someProperty: 0"
    echo $objectB; // 输出 "someProperty: 1"
  

通过这个简单的更改,这两个变量就表现为不同的对象了。在Drupal开发中,理解对象引用的这些特性有助于进行Drupal模块开发Drupal升级等操作,特别是在处理Drupal11的复杂业务逻辑时,对对象引用的准确把握能避免很多潜在的问题。

四、按引用传递

在将变量传递给函数时,PHP中变量的引用也是一个需要重要考虑的因素。在这种情况下,变量仍然是按值传递,而对象是按引用传递。

看下面这个函数,它接受一个字符串并尝试将该字符串设置为一个值。

  
    function setString($string) {
      $string = 'something';
    }

    $string = '';
    setString($string);
    echo $string; // 输出 ''。
  

这个函数实际上什么都没做,因为函数内部的 $string 变量从技术上讲是一个新变量,所以如果我们为该变量设置一个值,它只会在函数运行期间存在。

如果我们更新函数,使 $string 变量按引用传递,那么我们在函数内部所做的任何操作都会改变外部的变量。

  
    function setString(&$string) {
      $string = 'something';
    }

    $string = '';
    setString($string);
    echo $string; // 输出 'something'。
  

如果你想直接对一个变量执行操作,这很有用,但如果可能的话,应该避免使用这种技术。函数式编程中的一个有用概念是不可变函数。函数应该接受参数,然后返回一个结果,该结果应该用于更新原始变量。这意味着我们对变量所做的任何更改都在一个地方完成,而不是分散在程序的各个部分。

为了将前面的示例更新为不可变的,我们只需返回更改后的字符串,并用更改后的版本更新原始变量。

  
    function setString($string) {
      $string = 'something';
      return $string;
    }

    $string = '';
    $string = setString($string);
    echo $string; // 输出 'something'。
  

显然,这是一个简单的示例,在 setString() 函数中应该有某种逻辑,但这展示了我们如何通过避免按引用传递来避免混淆。

按引用传递变量有一个很好的用途。成都长风云Drupal开发团队最近在研究如何创建递归闭包。要在PHP中创建递归闭包,你需要将闭包变量按引用传递给闭包的 "use" 全局状态。

下面是一个计算一个数的阶乘的示例。

  
    $factorial = function($n) use (&$factorial) {
        if ($n == 1) {
            return 1;
        }
        return $factorial($n - 1) * $n;
    };

    print $factorial(5); // 输出 120
  

如果你不按引用传递闭包,那么实际上你会传递一个空值,因为闭包还没有被定义。按引用传递意味着你可以在闭包创建后在其中使用该变量。