对象和引用

PHP OOP 的一个关键点,经常被提及的是“对象默认按引用传递”。但这并不完全正确。本节将通过一些示例纠正这种普遍的看法。

PHP 引用是一个别名,它允许两个不同的变量写入同一个值。在 PHP 中,对象变量不包含对象本身作为值。它只包含一个对象标识符,允许对象访问器找到实际的对象。当一个对象作为参数传递、返回或分配给另一个变量时,不同的变量不是别名:它们持有标识符的副本,这些标识符指向同一个对象。

示例 #1 引用和对象

<?php
class A {
public
$foo = 1;
}

$a = new A;
$b = $a; // $a 和 $b 是同一个标识符的副本
// ($a) = ($b) = <id>
$b->foo = 2;
echo
$a->foo."\n";


$c = new A;
$d = &$c; // $c 和 $d 是引用
// ($c,$d) = <id>

$d->foo = 2;
echo
$c->foo."\n";


$e = new A;

function
foo($obj) {
// ($obj) = ($e) = <id>
$obj->foo = 2;
}

foo($e);
echo
$e->foo."\n";

?>

以上示例将输出

2
2
2
添加笔记

用户贡献笔记 18 条笔记

344
miklcct at gmail dot com
14 年前
关于引用的说明
引用不是指针。但是,对象句柄是指针。示例
<?php
class Foo {
private static
$used;
private
$id;
public function
__construct() {
$id = $used++;
}
public function
__clone() {
$id = $used++;
}
}

$a = new Foo; // $a 是指向 Foo 对象 0 的指针
$b = $a; // $b 是指向 Foo 对象 0 的指针,但是,$b 是 $a 的副本
$c = &$a; // $c 和 $a 现在是指向 Foo 对象 0 的指针的引用
$a = new Foo; // $a 和 $c 现在是指向 Foo 对象 1 的指针的引用,$b 仍然是指向 Foo 对象 0 的指针
unset($a); // 引用计数为 1 的引用会自动转换为值。现在 $c 是指向 Foo 对象 1 的指针
$a = &$b; // $a 和 $b 现在是指向 Foo 对象 0 的指针的引用
$a = NULL; // $a 和 $b 现在成为指向 NULL 的引用。现在可以回收 Foo 对象 0
unset($b); // $b 不再存在,$a 现在是 NULL
$a = clone $c; // $a 现在是指向 Foo 对象 2 的指针,$c 仍然是指向 Foo 对象 1 的指针
unset($c); // 现在可以回收 Foo 对象 1。
$c = $a; // $c 和 $a 是指向 Foo 对象 2 的指针
unset($a); // Foo 对象 2 仍然由 $c 指向
$a = &$c; // Foo 对象 2 只有一个指向它的指针,该指针有两个引用:$a 和 $c;
const ABC = TRUE;
if(
ABC) {
$a = NULL; // 现在可以回收 Foo 对象 2,因为 $a 和 $c 现在指向同一个 NULL 值的引用
} else {
unset(
$a); // Foo 对象 2 仍然由 $c 指向
}
254
匿名
13 年前
这里似乎有些混乱。指针和引用之间的区别并不是特别有用。
已经发布的一些“全面”示例中的行为可以用更简单的统一术语来解释。例如,Hayley 的代码完全按照你期望的方式执行。(使用 >= 5.3)

第一原则
指针存储一个内存地址来访问一个对象。任何时候分配一个对象,都会生成一个指针。(我还没有深入研究 Zend 引擎,但据我所知,这适用)

第二原则,也是造成最多混淆的地方
默认情况下,将变量传递给函数是通过值传递,也就是说,你正在使用副本。“但是对象是按引用传递的!”这是这里和 Java 世界中常见的一个误解。我从来没有说过是什么的副本。默认传递是通过值进行的。始终如此。但是,要复制和传递的是指针。当使用“->”时,你当然会访问与调用函数中的原始变量相同的内部结构。仅仅使用“=”只会处理副本。

第三原则
“&”自动且永久地将另一个变量名/指针设置为与其他变量相同的内存地址,直到你将它们分离。在这里使用“别名”这个词是正确的。将其视为将两个指针连接在一起,直到使用“unset()”强行分离。此功能在同一范围内以及将参数传递给函数时都存在。通常,传递的参数被称为“引用”,这是由于“按值传递”和“按引用传递”之间的一些区别,在 C 和 C++ 中更加清晰。

记住:对象指针,而不是对象本身,被传递给函数。这些指针是原始指针的副本,除非你在参数列表中使用“&”来实际传递原始指针。只有当你深入研究对象的内部结构时,原始才会发生改变。

示例

<?php

// 两个变量意图保持一致
$a = "Clark Kent"; // a==Clark Kent
$b = &$a; // 两个变量将共享同一个命运。

$b="Superman"; // $a 也等于 "Superman"。
echo $a;
echo
$a="Clark Kent"; // $b 也等于 "Clark Kent"。
unset($b); // $b 与 $a 分离
$b="Bizarro";
echo
$a; // $a 仍然等于 "Clark Kent",因为 $b 现在是一个独立的指针。

// 两个变量意图不保持一致。
$c="King";
$d="Pretender to the Throne";
echo
$c."\n"; // $c=="King"
echo $d."\n"; // $d=="Pretender to the Throne"
swapByValue($c, $d);
echo
$c."\n"; // $c=="King"
echo $d."\n"; // $d=="Pretender to the Throne"
swapByRef($c, $d);
echo
$c."\n"; // $c=="Pretender to the Throne"
echo $d."\n"; // $d=="King"

function swapByValue($x, $y){
$temp=$x;
$x=$y;
$y=$temp;
// 所有这些漂亮的代码都将消失
// 因为它们是在指针的副本上完成的。
// 原始指针仍然指向它们所指向的位置。
}

function
swapByRef(&$x, &$y){
$temp=$x;
$x=$y;
$y=$temp;
// 注意参数列表:现在我们真正交换了它们。
}

?>
55
Aaron Bond
15 年前
我遇到了一种行为,它帮助我澄清了对象和标识符之间的区别。

当我们传递一个对象变量时,我们得到一个指向该对象值的标识符。这意味着,如果我要从传递的变量中修改对象,那么源自该对象实例的所有变量都会改变。

但是,如果我将该对象变量设置为新实例,它会用一个新的标识符替换标识符本身,并将旧实例保留下来。

以下是一个示例

<?php
class A {
public
$foo = 1;
}

class
B {
public function
foo(A $bar)
{
$bar->foo = 42;
}

public function
bar(A $bar)
{
$bar = new A;
}
}

$f = new A;
$g = new B;
echo
$f->foo . "\n";

$g->foo($f);
echo
$f->foo . "\n";

$g->bar($f);
echo
$f->foo . "\n";

?>

如果对象变量始终是引用,我们期望得到以下输出
1
42
1

然而,我们得到
1
42
42

原因很简单。在 B 类的 bar 函数中,我们用一个全新的 A 类标识符替换了您传入的标识符,该标识符标识了与您的 $f 变量相同的 A 类实例。创建一个新的 A 实例不会修改 $f,因为 $f 不是作为引用传递的。

为了获得引用行为,需要在 B 类中输入以下代码

<?php
class B {
public function
foo(A $bar)
{
$bar->foo = 42;
}

public function
bar(A &$bar)
{
$bar = new A;
}
}
?>

foo 函数不需要引用,因为它正在修改 $bar 标识的对象实例。但是 bar 将替换对象实例。如果只传递一个标识符,变量标识符将被覆盖,但对象实例将保留在原位。
26
kristof at viewranger dot com
12 年前
我希望这能更清楚地解释引用

<?php
class A {
public
$foo = 1;
}

$a = new A;
$b = $a;
$a->foo = 2;
$a = NULL;
echo
$b->foo."\n"; // 2

$c = new A;
$d = &$c;
$c->foo = 2;
$c = NULL;
echo
$d->foo."\n"; // Notice: Trying to get property of non-object...
?>
3
rnealxp at yahoo dot com
4 年前
对象赋值行为的简洁提醒,使用和不使用 "&" 运算符...
<?php
clsA{
public
$propA = 2;
}
clsB{
public
$propB = 3;
}
//------------
$a = new clsA();
$c = $a; // 两个变量现在都指向同一个对象实例(按引用赋值)。
$c->propA = 22; // 使用其中一个变量来修改实例的属性。
echo $c->propA . "\n"; // 输出:22
echo $a->propA . "\n"; // 输出:22
//------------
$b = new clsB();
$a = $b; // 在此赋值之前,$c 和 $a 都指向同一个对象实例;在 $a 切换为指向 clsB 的实例后,情况不再如此。
echo $c->propA . "\n"; // 输出:22(这是可行的,因为 $c 仍然是指向 clsA 类型对象实例的引用)
echo $c->propB . "\n"; // 输出:"Undefined property: clsA::$propB"(无法正常工作,因为 $c 不是指向 clsB 类型对象实例的引用)
//------------
// 重新开始并使用“&”运算符…
$a = new clsA();
$b = new clsB();
$c = &$a; //<--$c 将会指向 $a 当前和“未来”所指向的内容(也是一种按引用赋值);在 C 语言中,您可以将此视为复制指针。
echo $c->propA . "\n"; // 输出:2
$a = $b; // 此赋值会导致 $c 指向一个新/不同的对象。
echo $c->propA . "\n"; // 输出:"Undefined property: clsB::$propA"(无法正常工作,因为 $c 不再指向 clsA 类型对象实例)
echo $c->propB . "\n"; // 输出:3(可行,因为 $c 现在指向 clsB 类型对象实例)
//------------
?>
14
mjung at poczta dot onet dot pl
14 年前
对对象引用的终极解释
注意:词语“指向”可以轻松地替换为“引用”,并且使用较松散。
<?php
$a1
= new A(1); // $a1 == A(1) 的句柄 1-1
$a2 = $a1; // $a2 == A(1) 的句柄 1-2 - 按值分配(复制)
$a3 = &$a1; // $a3 指向 $a1(句柄 1-1)
$a3 = null; // 使 $a1 == null,$a3(仍然)指向 $a1,$a2 == 句柄 1-2(同一个对象实例 A(1))
$a2 = null; // 使 $a2 == null
$a1 = new A(2); // 使 $a1 == 新对象的句柄 2-1 且 $a3(仍然)指向 $a1 => 句柄 2-1(新对象),因此 $a1 和 $a3 的值是新对象,而 $a2 == null
// 按引用:
$a4 = &new A(4); //$a4 指向 A(4) 的句柄 4-1
$a5 = $a4; // $a5 == A(4) 的句柄 4-2(复制)
$a6 = &$a4; //$a6 指向(句柄 4-1),而不是指向 $a4(对引用的引用引用的是被引用的对象句柄 4-1,而不是引用本身)

$a4 = &new A(40); // $a4 指向句柄 40-1,$a5 == 句柄 4-2 且 $a6 仍然指向 A(4) 的句柄 4-1
$a6 = null; // 将句柄 4-1 设置为 null;$a5 == 句柄 4-2 = A(4);$a4 指向句柄 40-1;$a6 指向 null
$a6 =&$a4; // $a6 指向句柄 40-1
$a7 = &$a6; //$a7 指向句柄 40-1
$a8 = &$a7; //$a8 指向句柄 40-1
$a5 = $a7; //$a5 == 句柄 40-2(复制)
$a6 = null; // 使句柄 40-1 为 null,所有指向(hanlde40-1 == null)的变量都为 null,除了 ($a5 == 句柄 40-2 = A(40))
?>
希望这能有所帮助。
7
wassimamal121 at hotmail dot com
9 年前
PHP 手册中给出的示例非常巧妙且简单。
该示例首先解释了两个指向同一个对象的别名发生更改时的情况,只需重新思考示例的第一部分即可
<?php

A { public $foo = 1;}
函数
go($obj) { $obj->foo = 2;}
函数
bo($obj) {$obj=new A;}

函数
chan($p){$p=44;}
函数
chanref(&$p){$p=44;}

/**************操作简单变量******************/
$h=2;$k=$h;$h=4; echo '$k='.$k."<br/>";
//$k 指向包含值 2 的内存单元
//$k 被创建并指向 RAM 中的另一个单元
//$k=$h 表示取 $h 所指向单元的内容
//并将其放入 $k 所指向的单元中
//$h=4 表示更改 $h 所指向单元的内容
//不表示更改 $k 所指向单元的内容

$h=2;$k=&$h;$h=4; echo '$k='.$k."<br/>";
// 这里 $k 指向与 $h 相同的内存单元

$v=2;chan($v); echo '$v='.$v."<br/>";
// $v 的值不会改变,因为该函数将
// 指向值 2 的别名作为参数,在函数中,我们
// 仅更改此别名所指向的值

$u=2;chanref($u); echo '$u='.$u."<br/>";
// 这里值会发生改变,因为我们传递了
// $u 所指向的内存单元的地址,该函数正在操作
// 此内存单元的内容

/***************操作对象************************/
$a = new A;
// 通过在内存中分配一些单元来创建对象,$a 指向
// 这些单元

$b = $a;
//$b 指向相同的单元,它与简单变量不同
// 简单变量是在创建后复制内容的

$b->foo = 2;echo $a->foo."<br/>";
// 您可以使用 $a 或 $b 来访问同一个对象

$c = new A;$d = &$c;$d->foo = 2;echo $c->foo."<br/>";
//$d 和 $c 不仅仅是指向相同的内存空间,
// 而是相同的

$e = new A;
go($e);
// 我们传递指针的副本
echo $e->foo."<br/>";
bo($e);
echo
$e->foo."<br/>";
// 如果您认为它是 1,很抱歉,我无法解释清楚
// 请记住,您只是传递了一个指针,当调用 new 时
// 指针将被断开连接
?>
6
gevorgmelkoumyan at gmail dot com
5 年前
我认为此示例应该可以阐明 PHP 引用(别名)和指针之间的区别

<?php

class A {
public
$var = 42;
}

$a = new A; // $a 指向 ID 为 1 的对象
echo 'A: ' . $a->var . PHP_EOL; // A: 42

$b = $a; // $b 指向 ID 为 1 的对象
echo 'B: ' . $b->var . PHP_EOL; // B: 42

$b->var = 5;
echo
'B: ' . $b->var . PHP_EOL; // B: 5
echo 'A: ' . $a->var . PHP_EOL; // A: 5

$b = new A; // 现在 $b 指向 ID 为 2 的对象,但 $a 仍然指向第一个对象
echo 'B: ' . $b->var . PHP_EOL; // B: 42
echo 'A: ' . $a->var . PHP_EOL; // A: 5

?>
4
Jon Whitener
11 年前
使用 `clone` 可以让你在将对象传递给函数时获得你预期的行为,以下使用 DateTime 对象作为示例。

<?php
date_default_timezone_set
( "America/Detroit" );

$a = new DateTime;
echo
"a = " . $a->format('Y-m-j') . "\n";

// 这可能不会给你你想要的结果...
$b = upDate( $a ); // a 和 b 都被更新了
echo "a = " . $a->format('Y-m-j') . ", b = " . $b->format('Y-m-j') . "\n";
$a->modify( "+ 1 day" ); // a 和 b 都被修改了
echo "a = " . $a->format('Y-m-j') . ", b = " . $b->format('Y-m-j') . "\n";

// 这可能就是你想要的结果...
$c = upDateClone( $a ); // 只有 c 被更新了,a 保持不变
echo "a = " . $a->format('Y-m-j') . ", c = " . $c->format('Y-m-j') . "\n";

function
upDate( $datetime ) {
$datetime->modify( "+ 1 day" );
return
$datetime;
}

function
upDateClone( $datetime ) {
$dt = clone $datetime;
$dt->modify( "+ 1 day" );
return
$dt;
}
?>

以上代码将输出类似以下内容

a = 2012-08-15
a = 2012-08-16, b = 2012-08-16
a = 2012-08-17, b = 2012-08-17
a = 2012-08-17, c = 2012-08-18
2
wbcarts at juno dot com
15 年前
有点过于简单了... 但还可以!

在上面的 PHP 示例中,函数 `foo($obj)` 实际上会为传递给它的任何对象创建一个名为 `$foo` 的属性,这让我感到困惑。
`$obj = new stdClass();`
`foo($obj);` // 在对象上标记了一个 `$foo` 属性
// 为什么这里有这个方法?
此外,在 OOP 中,全局函数操作对象的属性并不是一个好主意... 你的类对象也不应该允许它们这么做。为了说明这一点,示例应该改为

<?php

class A {
protected
$foo = 1;

public function
getFoo() {
return
$this->foo;
}

public function
setFoo($val) {
if(
$val > 0 && $val < 10) {
$this->foo = $val;
}
}

public function
__toString() {
return
"A [foo=$this->foo]";
}
}

$a = new A();
$b = $a; // $a 和 $b 是同一个标识符的副本
// ($a) = ($b) = <id>
$b->setFoo(2);
echo
$a->getFoo() . '<br>';

$c = new A();
$d = &$c; // $c 和 $d 是引用
// ($c,$d) = <id>
$d->setFoo(2);
echo
$c . '<br>';

$e = new A();
$e->setFoo(16); // 将被忽略
echo $e;

?>
- - -
2
A [foo=2]
A [foo=1]
- - -
由于全局函数 `foo()` 被删除,类 A 定义更加清晰,更健壮,并将处理所有 `foo` 操作... 并且只针对类型为 A 的对象。我现在可以理所当然地认为你指的是 "A" 对象及其引用。但它仍然让我联想到克隆和对象比较,在我看来,这更像是机器式的编程,而不是面向对象编程,而面向对象编程是一种完全不同的思维方式。
2
Anonymous
12 年前
这个例子可以帮助你

<?php
A {
public
$testA = 1;
}

B {
public
$testB = "类 B";
}

$a = new A;
$b = $a;
$b->testA = 2;

$c = new B;
$a = $c;

$a->testB = "更改了类 B";

echo
"<br/> 对象 a: "; var_dump($a);
echo
"<br/> 对象 b: "; var_dump($b);
echo
"<br/> 对象 c: "; var_dump($c);

// 通过引用

$aa = new A;
$bb = &$aa;
$bb->testA = 2;

$cc = new B;
$aa = $cc;

$aa->testB = "更改了类 B";

echo
"<br/> 对象 aa: "; var_dump($aa);
echo
"<br/> 对象 bb: "; var_dump($bb);
echo
"<br/> 对象 cc: "; var_dump($cc);

?>
3
Ivan Bertona
15 年前
在我看来,手册页中没有足够强调的一点是,在 PHP5 中,将对象作为函数调用的参数传递而不使用 & 运算符意味着按值传递该对象的唯一标识符(意指类的实例),该标识符将存储在具有函数作用域的另一个变量中。

此行为与 Java 中使用的行为相同,在 Java 中确实没有按引用传递参数的概念。另一方面,在 PHP 中,您可以按引用传递值(在 PHP 中,我们将引用称为“别名”),如果您不知道自己在做什么,这会构成威胁。请考虑以下两个类:

<?php
A
{
function
__toString() {
return
"类 A";
}
}

B
{
function
__toString() {
return
"类 B";
}
}
?>

在第一个测试用例中,我们使用类 A 和 B 创建了两个对象,然后使用临时变量和普通的赋值运算符 (=) 交换变量。

<?php
$a
= new A();
$b = new B();

$temp = $a;
$a = $b;
$b = $temp;

print(
'$a: ' . $a . "\n");
print(
'$b: ' . $b . "\n");
?>

正如预期的那样,脚本将输出

$a: 类 B
$b: 类 A

现在考虑以下代码片段。它与前一个类似,但赋值 $a = &$b 使 $a 成为 $b 的别名。

<?php
$a
= new A();
$b = new B();

$temp = $a;
$a = &$b;
$b = $temp;

print(
'$a: ' . $a . "\n");
print(
'$b: ' . $b . "\n");
?>

此脚本将输出

$a: 类 A
$b: 类 A

也就是说,修改 $b 会反映 $a 上的相同赋值……这两个变量最终指向同一个对象,而另一个对象将丢失。总而言之,在处理 PHP5 对象时,最好不要使用别名,除非您非常确定自己在做什么。
-1
Hayley Watson
14 年前
使用 &$this 会导致一些奇怪且违反直觉的行为——它开始欺骗你。

<?php

Bar
{
public
$prop = 42;
}

Foo
{
public
$prop = 17;
function
boom()
{
$bar = &$this;
echo
"\$bar 是 \$this 的别名,一个 Foo 类的对象。\n";
echo
'$this 是一个 ', get_class($this), '; $bar 是一个 ', get_class($bar), "\n";

echo
"它们是同一个对象吗? ", ($bar === $this ? "是的\n" : "不是\n");
echo
"它们相等吗? ", ($bar === $this ? "是的\n" : "不是\n");
echo
'$this 说它的 prop 属性的值是 ';
echo
$this->prop;
echo
',而 $bar 说它是 ';
echo
$bar->prop;
echo
"\n";

echo
"\n";

$bar = new Bar;
echo
"\$bar 已经变成了一个新的 Bar 类的对象。\n";
echo
'$this 是一个 ', get_class($this), '; $bar 是一个 ', get_class($bar), "\n";

echo
"它们是同一个对象吗? ", ($bar === $this ? "是的\n" : "不是\n");
echo
"它们相等吗? ", ($bar === $this ? "是的\n" : "不是\n");
echo
'$this 说它的 prop 属性的值是 ';
echo
$this->prop;
echo
',而 $bar 说它是 ';
echo
$bar->prop;
echo
"\n";

}
}

$t = new Foo;
$t->boom();
?>
在上面的代码中,$this 声称自己是一个 Bar 类的对象(实际上,它声称自己与 $bar 是同一个对象),同时仍然具有 Foo 类的所有属性和方法。

幸运的是,它不会持续存在于你犯下错误的方法之外。
<?php
echo get_class($t), "\t", $t->prop;
?>
-2
cesoid at gmail dot com
11 年前
将别名与指针进行比较就像将 spoken word 与说话者的神经化学物质进行比较。你知道说话者可以使用两个不同的词来指代同一件事,但是他们的大脑中发生了什么才能使这成为可能,这是你每次说话时都不想考虑的事情。(如果你用汇编语言或 C++ 编程,那么你就没有这个幸运了。)

同样,PHP *语言* 和给定的 php 解释器并不是一回事,这篇文章和大多数评论都没有在解释中说明这一点。别名/引用是语言的一部分,指针是计算机使引用生效的方式的一部分。你通常无法保证解释器会以相同的方式在内部继续工作。

从功能的角度来看,解释器的内部 *确实* 对优化很重要,但 *并不* 影响程序的最终结果。像 PHP 这样的高级编程语言应该尽力向程序员隐藏这样的细节,以便他们能够编写更清晰、更易于管理的代码,并且快速完成。

不幸的是,几年前,大量使用按引用传递在优化方面确实非常有用。幸运的是,这种情况在几年前就结束了,所以现在我们不再需要进行引用赋值,并希望记住当另一个变量应该保持不变时不要更改一个变量。当你读到这段话时,将这些文字发送给你的 PHP 可能正在运行在一个使用某种新型奇异技术的服务器上,而“指针”这个词不再能准确地描述任何东西,因为服务器将程序状态和指令混合在一起,以非顺序原子形式绑定到分子中,这些分子通过高速随机反弹来工作,从而交换原子并交叉繁殖它们的指令和信息,从而在总体上成功运行 PHP 5 代码。但是代码本身仍然会有以相同方式工作的引用,因此你无需考虑我刚描述的机器是否有意义。
-5
Rob Marscher
13 年前
以下是我创建的一个示例,它帮助我理解了在 PHP 5 中按引用传递和按值传递对象之间的区别。

<?php
class A {
public
$foo = 'empty';
}
class
B {
public
$foo = 'empty';
public
$bar = 'hello';
}

function
normalAssignment($obj) {
$obj->foo = 'changed';
$obj = new B;
}

function
referenceAssignment(&$obj) {
$obj->foo = 'changed';
$obj = new B;
}

$a = new A;
normalAssignment($a);
echo
get_class($a), "\n";
echo
"foo = {$a->foo}\n";

referenceAssignment($a);
echo
get_class($a), "\n";
echo
"foo = {$a->foo}\n";
echo
"bar = {$a->bar}\n";

/*
prints:
A
foo = changed
B
foo = empty
bar = hello
*/
?>
-5
lazybones_senior
15 年前
哇... 保持简单!

关于 secure_admin 的说明:你已经使用 OOP 简化了 PHP 创建和使用对象引用的能力。现在使用 PHP 的静态关键字来简化你的 OOP。

<?php

class DataModelControl {
protected static
$data = 256; // 默认值;
protected $name;

public function
__construct($dmcName) {
$this->name = $dmcName;
}

public static function
setData($dmcData) {
if(
is_numeric($dmcData)) {
self::$data = $dmcData;
}
}

public function
__toString() {
return
"DataModelControl [name=$this->name, data=" . self::$data . "]";
}
}

# 创建多个 DataModelControl 实例...
$dmc1 = new DataModelControl('dmc1');
$dmc2 = new DataModelControl('dmc2');
$dmc3 = new DataModelControl('dmc3');
echo
$dmc1 . '<br>';
echo
$dmc2 . '<br>';
echo
$dmc3 . '<br><br>';

# 要更改数据,请使用任何 DataModelControl 对象...
$dmc2->setData(512);
# 或者,直接从类中调用 setData()...
DataModelControl::setData(1024);
echo
$dmc1 . '<br>';
echo
$dmc2 . '<br>';
echo
$dmc3 . '<br><br>';
?>

DataModelControl [name=dmc1, data=256]
DataModelControl [name=dmc2, data=256]
DataModelControl [name=dmc3, data=256]

DataModelControl [name=dmc1, data=1024]
DataModelControl [name=dmc2, data=1024]
DataModelControl [name=dmc3, data=1024]

... 更好!现在,PHP 创建 $data 的一个副本,该副本在所有 DataModelControl 对象之间共享。
-7
akam at akameng dot com
10 年前
即使在原始对象被删除之后,对象也被引用,因此在将对象复制到数组中时要小心。

<?php
$result
= json_decode(' {"1":1377809496,"2":1377813096}');
$copy1['object'] = $result;
$copy2['object'] = $result;

unset(
$result);

// 现在将 $copy1['object'][1] 更改为 'test';
$copy1['object']->{"1"} = 'test';

echo (
$copy1 === $copy2) ? "Yes" : "No";
print_r($copy2);
/*
Array
(
[API] => stdClass Object
(
[1] => test
[2] => 1377813096
)

)
*/
?>
-5
Joe F
5 年前
我计划将对象序列化和反序列化作为一种存储方式,并且我的应用程序可以方便地将大量对象分组到单个对象中进行序列化。但是,这提出了一些我需要回答的问题。

假设我计划序列化的父对象是“A”,我存储在其中的对象将是 A(a-z)。如果将 A(b) 传递给 A(c),则按引用传递。因此,如果 A(c) 采取了影响 A(b) 值的操作,这也将更新存储在 A 中的原始 A(b)。太棒了!

但是,当我序列化 A 时,其中 A(c) 引用了 A(b),然后反序列化会发生什么?A(c) 会有 A(b) 的一个新的唯一副本,还是会继续引用存储在 A 中的 A(b)?

答案是,PHP 5.5 和 PHP 7 都跟踪某些东西是否是它在反序列化过程中“重新创建”的已有的对象的引用,请看此示例。

<?php

class foo {
protected
$stored_object;
protected
$stored_object2;
protected
$stored_value;

public function
__construct($name, $stored_value) {
$this->store_value($stored_value);
echo
'已构造: '.$name.' => '.$stored_value.'<br/>';
}

public function
store_object(foo $object) {
$this->stored_object = $object;
}

public function
store_object2(foo $object) {
$this->stored_object2 = $object;
}

public function
store_value($value) {
$this->stored_value = $value;
}

public function
stored_method($method, array $parameters) {
echo
'调用存储方法: '.$method.'{ <br/>';
call_user_func_array(array($this->stored_object, $method), $parameters);
echo
'} <br/>';
}

public function
stored_method2($method, array $parameters) {
echo
'调用存储方法 2: '.$method.'{ <br/>';
call_user_func_array(array($this->stored_object2, $method), $parameters);
echo
'} <br/>';
}

public function
echo_value() {
echo
'值: '.$this->stored_value.'<br/>';
}
}

$foo = new foo('foo', 'Hello!'); // 已构造: foo => Hello!
$new_foo = new foo('new_foo', 'New Foo 2!'); // 已构造: new_foo => New Foo 2!
$third_foo = new foo('third_foo', 'Final Foo!'); // 已构造: third_foo => Final Foo!

$foo->store_object($new_foo);
$foo->store_object2($third_foo);
$foo->stored_method('store_object', array($third_foo)); //调用存储方法: store_object{ }

$serialized = serialize($foo);
unset(
$foo);
unset(
$new_foo);
unset(
$third_foo);
$unserialized_foo = unserialize($serialized);

//下面,我更新了表示为 A(c) 的对象,但通过 A 对象更新它
$unserialized_foo->stored_method2('store_value', array('Super Last Foo!')); // 调用存储方法 2: store_value{}
$unserialized_foo->echo_value(); // 值: Hello!
$unserialized_foo->stored_method('echo_value', array());
//调用存储方法: echo_value{
//值: New Foo 2!
//}

// 最后,我检查存储在 A(b) 中的 A(c) 的值,看看是否通过 A 更新 A(c) 也更新了 A(b) 的副本/引用:
$unserialized_foo->stored_method('stored_method', array('echo_value', array()));
//调用存储方法: stored_method{
//调用存储方法: echo_value{
//值: Super Last Foo!
//}
//}
?>

根据最后一行,A(b) 中 A(c) 的 "副本" 仍然是存储在 A 中的原始 A(b) 的引用,即使在反序列化之后。
To Top