【上級資格取得に向けて】PHPを理解するために知っておくべきこと①

言語が違えばルールや常識も変わるものです。

この記事ではPHP8.0技術者認定試験上級資格取得を目指すにあたり、PHPを理解するために知っておくべきことについてさらっとまとめてみました。

ぜひご参考までに読んでいただければと思います。

<h1>目次</h1>

[1. シャローコピーとディープコピー](#シャローコピーとディープコピー)
　- [シャローコピーについて](#シャローコピーについて)
　- [ディープコピーについて](#ディープコピーについて)
　- [__cloneメソッドについて](#__cloneメソッドについて)
[2. コピーオンライト](#コピーオンライト)
[3. リファレンスカウントの原理](#リファレンスカウントの原理)
　- [zvalコンテナ](#zvalコンテナ)
[4. まとめ](#まとめ)

<h2>シャローコピーとディープコピー</h2>
PHPにおいて、変数の代入は値渡し（ディープコピー）が規定ですが、オブジェクト変数だけは例外的に参照渡し（リファレンス渡し/シャローコピー）が規定となっています。

```PHP
$p1 = new Person('太郎', 'PHP');
$p2 = $p1; // オブジェクトを代入
```
そのため上記のような場合、オブジェクトのコピー＄p2で名前を変更する処理を加えると、その変更処理はコピー元である$p1にも反映されてしまいます。

しかし、コピー先では別の名前を設定できるようにしたい（ディープコピーしたい）場合があります。この時登場するのが「__cloneメソッド」です。

ここからは順を追って説明していきますので、まずは、シャローコピーとディープコピーについて見ていきます。
<h3>シャローコピーについて</h3>

:::note
<b><font color="cc005f">見ている元データは同じですが、データ自体をコピーしているわけではありません。</font></b>
:::

そのため、同じデータを指す新しい変数を作成しているだけのコピーとなります。
<a href="https://hiro-hobbyblog.com/wp-content/uploads/2023/04/shallow-deep-copy-1.jpg"><img src="https://hiro-hobbyblog.com/wp-content/uploads/2023/04/shallow-deep-copy-1.jpg" alt="" width="960" height="540" class="aligncenter size-full wp-image-2238" /></a>
<span style="font-size: 8px;"><strong>引用元：<a href="https://tadtadya.com/copy-shallow-deep/">ただ屋ぁのブログ</a></strong></span>

<h3>ディープコピーについて</h3>
シャローコピーとは異なり、

:::note
<b><font color="cc005f">新しい変数を作り、元データの実体そのものもコピーします。</font></b>
:::

いわゆる、<b>一般的にイメージされるコピーは<font color="SteelBlue">ディープコピー</font></b>のことを指します。

<a href="https://hiro-hobbyblog.com/wp-content/uploads/2023/04/shallow-deep-copy-2-1.jpg"><img src="https://hiro-hobbyblog.com/wp-content/uploads/2023/04/shallow-deep-copy-2-1.jpg" alt="" width="960" height="540" class="aligncenter size-full wp-image-2239" /></a>

<span style="font-size: 10px;"><strong>引用元：<a href="https://tadtadya.com/copy-shallow-deep/">ただ屋ぁのブログ</a></strong></span>

<h3>__cloneメソッドについて</h3>
clone命令はオブジェクトのコピーを生成するための命令ですが、

:::note
<b><font color="cc005f">clone命令の規定の挙動はシャローコピーです。</font></b>
:::

上述しているように、コピー先では別の名前を設定できるようにしたい（ディープコピーしたい）場合は「<b>__cloneメソッド</b>」を使用します。

この「__cloneメソッド」は、<b><font color="#ed4f0c">clone命令による複製が完了したタイミングで呼び出されるメソッド</font></b>で、コピー先のプロパティ値を強制的に変更することができます。

「__cloneメソッド」についてPHP公式リファレンスの例を用いて、解説していきます。

```PHP
<?php
class SubObject
{
    static $instances = 0;
    public $instance;

    public function __construct() {
        $this->instance = ++self::$instances;
    }

    public function __clone() {
        $this->instance = ++self::$instances;
    }
}

class MyCloneable
{
    public $object1;
    public $object2;

    function __clone()
    {
        // this->object のコピーを作成します。こうしないと、
        // 同じオブジェクトを指すことになってしまいます。
        $this->object1 = clone $this->object1;
    }
}

$obj = new MyCloneable();

$obj->object1 = new SubObject();
$obj->object2 = new SubObject();

$obj2 = clone $obj;


print("元のオブジェクト\n");
print_r($obj);

print("クローンオブジェクト\n");
print_r($obj2);

?>
```
__constructメソッドは、オブジェクトが作成されるたびに＄instancesをインクリメントして、＄instanceにその値を設定します。

__cloneメソッドは、オブジェクトがクローンされるたびに＄instancesをインクリメントして、＄instanceにその値を設定します。

コードの処理としては以下のようになります。

>①<code>$obj = new MyCloneable();</code>によって、MyCloneableクラスのインスタンスを新規作成
>
>②<code>$obj->object1 = new SubObject();</code>によって、＄object1にSubObjectクラスのインスタンスが生成・代入され、SubObjectクラスの__construct()メソッドによって＄instancesがインクリメントされる（＄instances：1）
>
>③<code>$obj->object2 = new SubObject();</code>によって、＄object2にSubObjectクラスのインスタンスが生成・代入され、SubObjectクラスの__construct()メソッドによって＄instancesがインクリメントされる（＄instances：2）
>
>④<code>$obj2 = clone $obj;</code>によって、＄obj2に＄objの複製（シャローコピー）が完了した段階で、MyCloneableクラスの__clone()が呼び出される
>
>⑤④の__clone()メソッドによって、＄object1（SubObjectのインスタンス）が＄object1に複製（シャローコピー）された段階で、SubObjectクラスの__clone()メソッドが呼び出されて＄instancesがインクリメントされ（＄instances：3）、クローンされたオブジェクト(＄obj2)の＄object1に代入される。
>
>⑥<code>print_r($obj);</code>によって、元のオブジェクトが出力される
>⑦<code>print_r($obj2);</code>クローンオブジェクトが出力される

上記の説明の通り、__clone()メソッドを用いて強制的に必要な変更を行うことでディープコピーを実現している訳です。

<h2>コピーオンライト</h2>
PHPでは<b><font color="SteelBlue">コピーオンライト</font></b>という仕組みが用いられています。

コンピュータ内部で、ある程度大きなデータを複製する必要が生じたとき、愚直な設計では、直ちに新たな空き領域を探して割り当て、コピーを実行してしまいます。

ところが、もし複製したデータに対する書き換えがなければその複製は無駄だったことになるでしょう。

そこで、複製を要求されても、コピーをした振りをして、とりあえず原本をそのまま参照させる方が無駄はありません。ただし、そのままで本当に書き換えては意図しない変更を加えてしまう可能性があります。

これは先ほど説明した「<b>シャローコピー</b>」のような、<b><font color="#ed4f0c">コピー先に変更を加えるとコピー元にも変更が反映されてしまう</font></b>ような場合です。

:::note
<b><font color="cc005f">原本またはコピーのどちらかを書き換えようとしたときに、それを検出し、その時点ではじめて新たな空き領域を探して割り当て、コピーを実行する。</font></b>
:::


これが「書き換え時にコピーする」、すなわち<b>コピーオンライト (Copy-On-Write) </b>の基本的な形態となります。


<h2>リファレンスカウントの原理</h2>
PHPでは、「プログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に解放する」いわゆるガベージコレクションが機能として存在します。

&nbsp;
PHPのガベージコレクションでは「参照カウント（リファレンスカウント）法」という方式で管理されています。

<h3>zvalコンテナ</h3>
PHPの変数は「zval」と呼ばれる特別なコンテナに値と型と共に保管されており、変数の型と値の他に、情報の追加ビットを2つ含んでいます。

>＜zvalコンテナに含まれる情報＞
>①変数の型
>②値
>③is_ref
>④refcount

<b>【is_ref】</b>

:::note
<b>「is_ref」は、変数が参照集合の一部かどうかを示すブール値の情報で、これによりPHPエンジンは通常の変数と参照を区別することができます。</b>

<b>PHPでは参照を使うことができるため、zvalコンテナは内部的なリファレンスカウント機構を持っており、メモリ使用状況を最適化します。</b>

<b>デフォルトで<font color="#ed4f0c">false</font>にセットされます。</b>
:::

<b>【refcount】</b>

:::note
<b>「refcount」は、この1つのzvalコンテナをどれだけ多くの変数名が指すかを表します。</b>
:::

具体的に挙動を確認していきます。

```PHP
$a = "new string";
xdebug_debug_zval('a');

// 出力結果
// a: (refcount=1, is_ref=0)='new string'
```

・<b>$a = "new string";</b>というコードが実行されると、新しい変数名＄aが現在のスコープで作成され、この$a変数には値が<b> "new string"</b>であり、型が<b>string</b>である新しい変数コンテナが作成されます。

・この変数コンテナには、参照集合の一部かどうかを示す<b>is_ref</b>という情報のビットが含まれていますが、この時点ではユーザーランド参照（リファレンスによる代入：&$変数）が作成されていないため、<b>is_ref</b>は<b>デフォルトでfalseにセット</b>されます。

また、<b>この変数コンテナを利用するシンボル（変数名：＄a）が1つだけあるため、refcountは1に設定</b>されます。


refcountの減少でも同様に、以下のようにリンクが解除されることでrefcountの値は変化します。

```PHP
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
$b = 42;
xdebug_debug_zval( 'a' );
unset( $c );
xdebug_debug_zval( 'a' );

// 出力結果
// a: (refcount=3, is_ref=0)='new string'
// a: (refcount=2, is_ref=0)='new string'
// a: (refcount=1, is_ref=0)='new string'
```

&nbsp;
<b>配列型</b>の場合は少し複雑で、それらのプロパティをそれら自身のシンボルテーブルに保管します。

```PHP
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );

/**    出力結果
*    a: (refcount=1, is_ref=0)=array (
*       'meaning' => (refcount=1, is_ref=0)='life',
*       'number' => (refcount=1, is_ref=0)=42
*    )
**/
```

イメージは以下のようになります。

<a href="https://www.php.net/manual/ja/images/12f37b1c6963c1c5c18f30495416a197-simple-array.png"><img src="https://www.php.net/manual/ja/images/12f37b1c6963c1c5c18f30495416a197-simple-array.png" alt="" width="593" height="143" class="aligncenter size-full wp-image-2237" /></a>
<b>引用元：<a href="https://www.php.net/manual/ja/features.gc.refcounting-basics.php">PHP公式リファレンス</a></b>

<h2>まとめ</h2>

:::note
<b><font color="cc005f">PHPでは、変数の代入は値渡し（ディープコピー）が規定ですが、オブジェクト変数だけは例外的に参照渡し（リファレンス渡し/シャローコピー）が規定となっている。</font></b>
:::

<b>シャローコピー</b>

:::note
<b><font color="cc005f">見ている元データは同じですが、データ自体をコピーしているわけではありません。</font></b>
:::

<b>ディープコピー</b>

:::note
<b><font color="cc005f">新しい変数を作り、元データの実体そのものもコピーします。</font></b>
:::

<b>clone</b>

:::note
<b><font color="cc005f">clone命令の規定の挙動はシャローコピーです。</font></b>
:::

<b>コピーオンライト</b>

:::note
<b><font color="cc005f">原本またはコピーのどちらかを書き換えようとしたときに、それを検出し、その時点ではじめて新たな空き領域を探して割り当て、コピーを実行する。</font></b>
:::

<b>リファレンスカウントの原理</b>

:::note
<b><font color="cc005f">＜zvalコンテナに含まれる情報＞
①変数の型
②値
③is_ref
④refcount</font></b>
:::