PHPのオブジェクトは参照渡しではないし、変数リファレンスは全然別物

まだ参照渡しで消耗してたの？ 僕はPHPerなのでPHPのことしかわからないけど、だいたいPHPで参照渡しのことを気にしても時間の無駄だし積極的に利用するべき場面が極めて限定されることは[PHPのリファレンス(参照&)の傾向と対策、あるいはさよなら](https://qiita.com/tadsan/items/74f992dcc48216b571bd)に書いたので読んでほしい。

それとPHPマニュアルでは「参照渡し」ではなく「[リファレンス渡し](http://php.net/manual/ja/language.references.pass.php)」と呼ぶが、どちらにせよ、わざわざ「渡し (<q>call by-</q>)」を付けて呼ぶ意義は乏しいので、これからは「**変数リファレンス**」として覚えてほしい。もうちょっと具体的なことは[PHP: リファレンスとは? - Manual](http://php.net/manual/ja/language.references.whatare.php)を読んで。

:::note warn
端的に言うと、PHPでは「オブジェクト引数はデフォルトで引数渡しされる」「オブジェクトのプロパティ(メンバー変数)は参照渡し」のような理解は**誤解**であり、**完全に嘘**です。
:::

## ふしぎなふしぎなオブジェクト

PHPの配列とオブジェクトは、似たような書きかたをしてもまったく異なる挙動をします。

以下のコードはarray(連想配列)とオブジェクトで異なる挙動をします。

```php
function setPrice1(array $a, float $tax): array
{
    if (!isset($a['prime_cost'])) {
        $a['prime_cost'] = $a['price'];
        $a['tax'] = $tax;
        $a['price'] = $a['prime_cost'] + $a['prime_cost'] * $tax;
    }

    return $a;
}

function setPrice2(stdClass $a, float $tax): stdClass
{
    if (!isset($a->prime_cost)) {
        $a->prime_cost = $a->price;
        $a->tax = $tax;
        $a->price = $a->prime_cost + $a->prime_cost * $tax;
    }

    return $a;
}
```

動かしてみましょう **(練習問題)**

```php
<?php

$ary = ['name' => 'りんご', 'price' => 100];

echo "before:";
var_dump($ary);

$bry = setPrice1($ary, 0.03);

echo "after:";
var_dump($bry);

var_dump($ary === $bry);

echo PHP_EOL;

////////////////////

$obj = (object)['name' => 'りんご', 'price' => 100];

echo "before:";
var_dump($obj);

$obj2 = setPrice2($obj, 0.03);

echo "after:";
var_dump($obj2);

var_dump($obj === $obj2);
```

だいじなのは `$ary === $bry` と `$obj === $obj2` のところです。おなじように書いても別の結果になりましたでしょうか。

なぜこうなるかと言ったら、PHPの配列は代入(および関数呼び出しの引数)によってコピーされると定義されているからです。 ([PHP: 配列 - Manual](https://www.php.net/manual/ja/language.types.array.php))

> 配列への代入においては、常に値がコピーされることに注意してください。

どうしてそうしたのかというと**作者がこうなるようにPHPを設計したから**と表現するしかないのですが、実際この挙動の差は便利なことの方が多いです。この話は[オブジェクトをいい感じに複製(クローン)する [myclabs/deep-copy] - 超PHPerになろう](http://cho-phper.hateblo.jp/entry/2016/08/12/011135)にも書きました。

この例では`stdClass`を使ってますが、これを実コードで積極的に活用することはおすすめしません。**クラスを定義してください。**

問題は、この両者の差異を「PHPは参照渡しだから」「オブジェクトは参照渡しされるから」のような意味不明な**謎解説をして初心者を騙すひとが居る**ことです。繰り返しますが、このオブジェクトの挙動は「変数リファレンス」とは無関係です。

<details><summary>PHP4のオブジェクトについて</summary><p>これは昔話なので忘れてよいのですが、PHP4のオブジェクトのプロパティは現在の配列と同様にコピーされていました。</p><a href="https://3v4l.org/61sTY"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4121/e4a8ca6f-94e0-4e8a-6669-899a6e6b4dba.png"></a><p>PHP4時代のオブジェクトを現在に近い使い勝手にするために常用されていたのがオブジェクトの引数に<code>&amp;</code>を付けて変数リファレンスを受けるというテクニックです。古いコードには稀にこの痕跡が残っていますが、これは過去にPHP4系との相互運用性が考慮されていた名残といえます。単にPHP5の変化を知らずに慣習として引き摺っただけかもしれないですね。</p></details>

## それでも疑り深いひとのための補足

ここでは「日時オブジェクトを日本時間の正午にセットする」3種類の函数を用意してみましょう。ここではPHP7を用いています。

```php
function setdate1_オブジェクト(DateTime $dt): void
{
    $dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
    $dt->setTime(12, 0);
}

function setdate2_変数リファレンス(DateTimeInterface &$dt): void
{
    $dt = $dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
    $dt = $dt->setTime(12, 0);
}

function setdate3_イミュータブル(DateTimeImmutable $dt): DateTimeImmutable
{
    $dt = $dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
    $dt = $dt->setTime(12, 0);

    return $dt;
}
```

### 課題
この三種類の実装を比較し、それぞれの利点と欠点を挙げて考察せよ。

~~レポートは来週の講義開始前までに提出のこと~~

### 参考にするとよい資料

* [PHP: DateTimeInterface - Manual](http://php.net/manual/ja/class.datetimeinterface.php)
* [PHP: DateTime - Manual](http://php.net/manual/ja/class.datetime.php)
* [PHP: DateTimeImmutable - Manual](http://php.net/manual/ja/class.datetimeimmutable.php)
* [イミュータブル - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB)
* [Psr7を使ってみた（というか不変オブジェクトを初めて使った感想） - Qiita](https://qiita.com/asaokamei/items/b86e384867bcf0379cea)

### ヒント

このような`.php`ファイルを用意し、呼び出し側と実装コードをそれぞれ変更し出力を観察するとよい。

```datetime-jikken.php
<?php

echo "---------------------------------", PHP_EOL;
echo "[setdate1_オブジェクト] (DateTime)", PHP_EOL;
echo "---------------------------------", PHP_EOL, PHP_EOL;
$dt1 = new DateTime;

echo "before:";
var_dump($dt1);

setdate1_オブジェクト($dt1);

echo "after:";
var_dump($dt1);
echo PHP_EOL;

////////////////////

echo "-------------------------------------", PHP_EOL;
echo "[setdate2_変数リファレンス] (DateTime)", PHP_EOL;
echo "-------------------------------------", PHP_EOL, PHP_EOL;
$dt2_1 = new DateTime;

echo "before:";
var_dump($dt2_1);

setdate2_変数リファレンス($dt2_1);

echo "after:";
var_dump($dt2_1);
echo PHP_EOL;

////////////////////

echo "----------------------------------------------", PHP_EOL;
echo "[setdate2_変数リファレンス] (DateTimeImmutable)", PHP_EOL;
echo "----------------------------------------------", PHP_EOL, PHP_EOL;
$dt2_2 = new DateTimeImmutable;

echo "before:";
var_dump($dt2_2);

setdate2_変数リファレンス($dt2_2);

echo "after:";
var_dump($dt2_2);
echo PHP_EOL;

////////////////////

echo "----------------------------------------------", PHP_EOL;
echo "[setdate3_イミュータブル] (DateTimeImmutable)", PHP_EOL;
echo "----------------------------------------------", PHP_EOL, PHP_EOL;
$dt3 = new DateTimeImmutable;

echo "before:";
var_dump($dt3);

$dt3 = setdate3_イミュータブル($dt3);

echo "after:";
var_dump($dt3);
echo PHP_EOL;


function setdate1_オブジェクト(DateTime $dt): void
{
    $dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
    $dt->setTime(12, 0);
}

function setdate2_変数リファレンス(DateTimeInterface &$dt): void
{
    $dt = $dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
    $dt = $dt->setTime(12, 0);
}

function setdate3_イミュータブル(DateTimeImmutable $dt): DateTimeImmutable
{
    $dt = $dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
    $dt = $dt->setTime(12, 0);

    return $dt;
}
```

## 追記1: 関数呼び出しは特に関係ないしプロパティも参照渡しでも暗黙的に変数リファレンスの代入になるようなことはない

上記の例では関数呼び出しをまたいでいますが、同じ関数内のローカル変数でも同じことです。

改めて強調します。オブジェクトと配列の以下のような振舞からPHPのプロパティもリファレンス渡しであるかのように誤解されることがありますが、そうではありません。

```php
$ary = ['author' => '太宰治', 'title' => '人間失格'];
$obj = (object)$ary; // stdClass にキャスト

// それぞれ別オブジェクトにコピーする
$ary2 = $ary;
$obj2 = $obj;

// この時点では === で等値になることを確認
var_dump([
    'array' => ['===' => $ary === $ary2, $ary, $ary2],
    'object' => ['===' => $obj === $obj2, $obj, $obj2],
]);

$ary['author'] = 'Osamu Dazai';
$obj->author = 'Osamu Dazai';

// arrayは結果が分かれ、objectは一致する
var_dump([
    'array' => ['===' => $ary === $ary2, $ary, $ary2],
    'object' => ['===' => $obj === $obj2, $obj, $obj2],
]);
```

この挙動は変数代入と似てなくもないので「なるほどオブジェクトの代入は参照渡しなんだな」と誤解するのも理解はできます。

```php
$title = '人間失格';
$title2 = &$title; // 変数のリファレンスを代入

$title = 'Ningen Shikkaku';

var_dump([
    'variable' => ['===' => $title === $title2, $title, $title2],
]);
```

似てるように見えるかもしれませんが、プロパティの代入が暗黙的に変数リファレンス(参照渡し)になることはありません。

以下のコードでプロパティに値を代入したときと明示的に変数リファレンスを代入したときで振舞が異なることを確認できます。

```php
// オリジナルのタイトル
$orig_title = '人間失格';

// 今度はコピーではなく別オブジェクトとして作成する
$obj1 = (object)['author' => '太宰治', 'title' => $orig_title];
$obj2 = (object)['author' => '太宰治', 'title' => $orig_title];

// 新しいタイトル
$new_title = '人間合格';

$obj1->title = $new_title;
$obj2->title = &$new_title;

// $new_title に "人間失格" を代入
$new_title = $orig_title;

var_dump([
    'title' => ['===' => $obj1->title === $obj2->title, $obj1->title, $obj2->title],
]);
```

そもそも `$obj->author = 'Osamu Dazai'` という構文が許可されている時点で `=` の右辺が暗黙的に変数リファレンスになるということはありえません。別物。

まれに「配列は値型でオブジェクトは参照型」という説明がされることがありますが、そうではありません。PHP(7.0以降)の実装として内部的には「`IS_REFERENCE`型」という型があります([PHP7における内部値の表現―パート1 : PHP5とPHP7のzvalの仕組み | POSTD](https://postd.cc/internal-value-representation-in-php-7-part-1/))が、この型は配列とオブジェクトの違いを反映するためのものではなく、変数リファレンス(`&`)を表現するものです。

あくまで配列は[PHP: 配列 - Manual](https://www.php.net/manual/ja/language.types.array.php)での説明通り、**配列は代入および関数呼び出しによって常にコピーされて(いることになって)います**。

> 配列への代入においては、常に値がコピーされることに注意してください。配列をリファレンスでコピーする場合には、 リファレンス演算子を使う必要があります。

ただし、配列を別の変数に代入したり関数呼び出しをする度に新しい配列を作成して内容を全てコピーして… ということを行っていては実行効率が大変悪いので、[PHP: 参照カウント法の原理 - Manual](https://www.php.net/manual/ja/features.gc.refcounting-basics.php)のメカニズムによって別の配列を作成する必要が生じたときだけコピーを行なっています。この最適化を**コピーオンライト**(**CoW**: Copy on Write)といいます。

このPHPの配列の振舞を指して「PHPの配列を別の変数に代入すると別の配列になるのはCoWだから」という説明がなされることもありますが、**これも大嘘です**。

**意味としては**PHPの全ての値は`&`を付けない代入や関数呼び出しの引数として渡されるごとにコピーされます。先に書いた「**なんでこうなるかと言ったらこうなるようにPHPが設計されたからと表現するしかない**」という表現は「PHPの配列は代入によってコピーされると定義されている」ということを指しています。

関数呼び出し時に引数の値がコピーされることを一般的に[**値渡し**(call by value)](https://www.weblio.jp/content/call+by+value)と呼ぶのです。ただし「毎回コピーされる」というのははコードの意味としてはということであり実際にはそれほどコピーが起こらないのは、PHP処理系の実装上の最適化(CoW)によってコピーされるタイミングを遅らせる(必要なければコピーせずに消す)からです。この最適化によってユーザーはメモリ使用量の最適化など多くの恩恵を受けています。

## 追記2: 配列と他言語のデータ構造との比較

この配列のコピーという振舞を奇妙に感じるでしょうか。人間はダブルスタンダードを持っているので、別の箇所ではこの振舞を当然のものとして受け入れています。

```php
<?php

$f = 'ｶﾞｯ';
$g = $f;
$f = 'ぬるぽ';
echo $f, $g, PHP_EOL; // "ぬるぽｶﾞｯ"

$a = [];
$a['msg'] = 'ｶﾞｯ';
$b = $a;
$a['msg'] = 'ぬるぽ';
echo $a['msg'], $b['msg'], PHP_EOL; // "ぬるぽｶﾞｯ"

$o = new stdClass;
$o->msg = 'ｶﾞｯ';
$p = $o;
$o->msg = 'ぬるぽ';
echo $o->msg, $p->msg, PHP_EOL; // "ぬるぽぬるぽ"
```

代入先が変数への直接代入か、配列の固定キーへの代入か、オブジェクトの固定プロパティへの代入かによって挙動が別れています。

これを雑にRubyで書き直すとこうなります。

```ruby
f = 'ｶﾞｯ'
g = f
f = 'ぬるぽ'
puts "#{f}#{g}" # "ぬるぽｶﾞｯ"

a = {} # Hash: PHPの配列に相当
a['msg'] = 'ｶﾞｯ'
b = a
a['msg'] = 'ぬるぽ'
puts "#{a['msg']}#{b['msg']}" # "ぬるぽぬるぽ"

require 'ostruct'
o = OpenStruct.new # なんでも代入できるオブジェクト
o.msg = 'ｶﾞｯ'
p = o
o.msg = 'ぬるぽ'
puts "#{o.msg}#{p.msg}" # "ぬるぽぬるぽ"
```

Rubyの変数とPHPの変数の振舞、その後のRubyの`OpenStruct`とPHPの`stdClass`の振舞はそれぞれ、およそ同じと見て差し支えないでしょう。Rubyの`Hash`とPHPの配列の振舞が自然と感じられるかは個人の感性や経験によるでしょうが、PHPの配列は**変数の延長線上にある非オブジェクトのデータ構造として提供されている**と捉えることもできます。

PHPであってもRubyの`Hash`の挙動に近似させたければ`ArrayObject`を使うかクラス定義するかという選択肢が用意されています。

```php
<?php

$a = new ArrayObject;
$a['msg'] = 'ｶﾞｯ';
$b = $a;
$a['msg'] = 'ぬるぽ';
echo $a['msg'], $b['msg'], PHP_EOL; // "ぬるぽぬるぽ"
```

PHPの配列の特性をよく知って安全にコードを書きましょう。