php语言中对象是通过引用传递的吗?不是的,因为一个对象只会存在一次

2023-06-01 00:00:00 中对 只会 象是

PHP中的引用

PHP 文档告诉我们引用是“别名”,这意味着我们可以告诉 PHP 我们希望两个变量指向相同的数据

(文档还告诉我们,尽管看起来它们不是指针,这并不是特别有用,除非你'已经精通C)。

创建参考的符号是&。

所以我们可以这样做:

$a = 'foo'; // $a contains 'foo'
$b = &$a;   // $b is now a reference, or an alias of $a
$b = 'bar' // We're actually changing the content of $a
echo $a    // displays 'bar' (because we just changed the value)
echo $b    // displays 'bar' (because it's still a reference to $a)

这是一个不错的小怪事,但在您的日常代码中确实没那么有用。

现在更有用的是,您可以通过引用函数来传递变量,这样函数就可以在自己的范围之外实际修改变量的内容。

这是通过&在函数声明中的参数前面附加的来完成的。

/**
 * Decrements a number if it's > 0.
 * Returns true if it was decremented, false if it was already 0 or negative;
 */
function decrement(int &$a) {
    if ($a > 0) {
        $a = $a - 1;
        return true;
    }
    return false;
}
$timeLeft = 10;
$wasModified = decrement($timeLeft);
echo $timeLeft; // will output 9

如您所见,这允许我们返回一些内容,并直接修改传递给函数的内容。

这在preg_match()函数中特别使用,如果匹配,它将返回 true,并接受$m将被修改以包含匹配内容的参数。这是一个有用的功能,但它也可能导致难以跟踪错误,因此您应该谨慎使用它。

引用传递

为什么对象看起来像是通过引用传递的?

您可能已经在某处读到过在 PHP 中对象总是通过引用传递的。

它确实看起来像。举个简单的例子:

function doStuff($object) {
    $object->data = 'newValue';
}

$object = (object)['data' => 'value'];
doStuff($object);
echo $object->data; // displays 'newValue'

如您所见,&我们的函数声明中没有,但$object->data现在包含“newValue”而不是“value”。

所以这个函数确实修改了对象,它看起来确实很像它是通过引用传递的。


修改对象的问题

这是一个更具体的示例,它是实际生产代码库中错误的来源。

假设我们想向用户发送一封包含下个月收据的电子邮件,并且我们希望标题以用户友好的方式显示期间的开始和结束,例如“从 7 月 1 日星期四到 7 月星期六31 ",以及电子邮件页脚中的当前日期。

我们将创建一些函数来获取与当前日期对应的月份的开始和结束,并对其进行格式化,然后在字符串(或更实际的模板)中使用结果。

它看起来像这样:

function getFirstDay($date) {
    return $date->modify('first day of this month')->format('l F j');
}

function getLastDay($date) {
    return $date->modify('last day of this month')->format('l F j');
}

$today = new DateTime('2022-07-15');
$firstday = getFirstDay($today);
$lastDay = getLastDay($today);

$emailTitle = "Your receipt for the period from $firstday to $lastDay";
$emailFooter = "Sent on " .  $today->format("l F j");

标题会说

“ Your receive for the period from Thursday July 1 to Saturday July 31 ”

这是我们想要的,但页脚说

“Sent on Saturday July 31 ”

这绝对不是我们想要的。

问题是当我们在日期调用 ->modify() 时 $today 实际上被函数修改了,

所以一旦我们在日期使用它,我们就会永远失去日期的原始值......

有两种解决方法:

1.在处理之前克隆对象:

function getFirstDay($today) {
    $date = clone $today;
    return $date->modify('first day of this month')
        ->format('l F j');
}

2.使用像 Carbon 2 这样提供不可变对象的库,它们本质上是对象,其中每个方法总是返回一个克隆而不是修改当前对象。


为什么对象不是真正通过引用传递的?

所以我们已经看到它看起来很像对象是通过引用传递的,以及如何避免潜在的问题,

但这篇文章的重点是表明它们不是,所以这里有几个例子可以很明显.


示例 1:数组中的对象

让我们假设一个 User 类,它在构造函数中获取用户的名称并将其分配给 name 属性。

现在让我们看看如果我们将其中几个用户放在一个数组中并将其传递给一个函数,该函数将每个元素的名称设为大写,会发生什么情况:

Class User
{
    public function __construct(public string $name){}
}

function capitalizeNames(array $users) {
    foreach ($users as $user) {
        $user->name = strtoupper($user->name);
    }
}

$users = [
    new User('John'),
    new User('Jack'),
];

capitalizeNames($users);
echo json_encode($users); // [{"name":"JOHN"},{"name":"JACK"}]

如您所见,数组中的每个用户都由 capitalizeNames() 函数修改。

我们没有使用引用,我们从未将对象传递给函数,因为我们传递了一个数组(按值),所以我们可能认为我们是安全的。然而,数组中的每个对象都被修改了。


示例 2:将一个对象分配给另一个变量

现在让我们看看如果我们将一个对象的实例分配给一个新变量会发生什么:

$user1 = new User('name');
$user2 = $user1;
$user1->name = 'Jack';
echo $user1->name; // Jack
echo $user2->name; // Jack

所以基本上只要我们这样做$user2 = $user1,我们就可以互换使用,改变一个会改变另一个。


所以发生了什么事?

问题是变量永远不会“包含”对象。每当我们调用new时,PHP都会返回一个句柄,它是一个参考... 一个指针...是的,让我们坚持使用手柄。该句柄基本上是一个标识对象的数字。

使用时实际上可以看到句柄var_dump(),它是转储对象时前面带有#的数字,var_dump($user1, $user2)从我们之前的示例中,我们可以看到它们都是同一个对象,因为它们具有相同的句柄:

object(App\Console\Commends\User)#538 (1) {
   ["name"]=>
   string(4) "Jack"
}

object(App\Console\Commends\User)#538 (1) {
   ["name"]=>
   string(4) "Jack"
}

每当我们复制此变量、将其传递给函数或将其放入数组时,我们都不会传递或复制对象的实例,而只是传递或复制它的句柄。

所以一个对象只会存在一次,除非我们使用clone. 有点像现实生活中的物体。


结论

虽然认为对象通过引用传递可能很有用,但我认为最好记住,

当您创建一个对象时,只有一个实例存在,您使用的不是对象,而只是一个句柄。

我希望这对某人有用。

此外,我没有任何 C 语言背景,对PHP核心中的实际实现方式只有模糊的理解。

因此,如果上面有明显的错误,请在评论中告诉我。

相关文章