PHP7.4で、$_SESSIONとACPuはどのようにキャッシュされるのか


|[../](https://qiita.com/taconana/items/3f5424f9abfc755772c2) |
|---|

PHP7.4での値渡しと参照渡しについて、もう少し調べてみた。`$_SESSION`や`ACPu`で保持した場合、値はコピーされるのか、参照として取得できるのか。

前回、「[PHPのforeachでは、多くの場合、参照渡しの方がいい？](https://qiita.com/taconana/items/e1a568de0b39b60c4dce)」でforeachにおいて、配列要素がユーザー定義のクラスをインスタンス化したオブジェクトの場合、値渡しにしても参照渡しになることを確認した。もちろん、配列要素が整数などプリミティブな型、文字列、配列の場合、値渡しにするとコピーされることも確認した。

## foreachでの値渡しと参照渡しの確認

再度、復習を兼ねて確認しておく。ユーザー定義のクラスをインスタンス化したオブジェクトとして、Userクラスを使う。`$name`という属性を持たせている。以下の4つが確認できる。

- 配列要素がUserオブジェクトで値渡しの場合 --> 値渡しで指定しても参照渡しになり、コピーされない
- 配列要素がUserオブジェクトで参照渡しの場合 --> 参照渡しなので、コピーされない
- 配列要素が配列で値渡しの場合 --> 値渡しなので、コピーされる
- 配列要素が配列で参照渡しの場合 --> 参照渡しなので、コピーされない

```php
<?php
namespace samples;

class User {
  
  private $id;
  private $name;

  public function __construct(string $id, string $name) {
    $this->setId($id);
    $this->setName($name);
  }
  
  public function getId(): string { return $this->id; }
  public function setId(string $id): void{ $this->id = $id; }
  public function getName(): string { return $this->name; }
  public function setName(string $name): void { $this->name = $name; }
}

echo '----- 配列要素がUserオブジェクトで値渡しの場合' . PHP_EOL;
$user1 = new User('U001', 'Aさん');
$user2 = new User('U002', 'Bさん');
$users = array(&$user1, &$user2);

unset($target);
foreach ($users as $u){           // 値渡し
  if ($u->getId() == 'U002'){
    $target = &$u;
    echo $target->getName() . PHP_EOL;
  }
}
$user2->setName('山本さん');
echo $target->getName() . PHP_EOL;

echo '----- 配列要素がUserオブジェクトで参照渡しの場合' . PHP_EOL;
$user1 = new User('U001', 'Aさん');
$user2 = new User('U002', 'Bさん');
$users = array(&$user1, &$user2);

unset($target);
foreach ($users as &$u){          // &を付けて参照渡し
  if ($u->getId() == 'U002'){
    $target = &$u;
    echo $target->getName() . PHP_EOL;
  }
}
unset($u); // foreachで参照渡しを使ったときは安全のため
$user2->setName('山本さん');
echo $target->getName() . PHP_EOL;

echo '----- 配列要素が配列で値渡しの場合' . PHP_EOL;
$person1 = array('id' => 'P001', 'name' => 'Cさん');
$person2 = array('id' => 'P002', 'name' => 'Dさん');
$persons = array(&$person1, &$person2);

unset($p); // foreachで参照渡しを使ったときは安全のため
unset($target);
foreach ($persons as $p){         // 値渡し
  if ($p['id'] == 'P002'){
    $target = &$p;
    echo $target['name'] . PHP_EOL;
  }
}
$person2['name'] = '池田さん';
echo $target['name']  . PHP_EOL;

echo '----- 配列要素が配列で参照渡しの場合' . PHP_EOL;
$person1 = array('id' => 'P001', 'name' => 'Cさん');
$person2 = array('id' => 'P002', 'name' => 'Dさん');
$persons = array(&$person1, &$person2);

unset($target);
foreach ($persons as &$p){        // &を付けて参照渡し
  if ($p['id'] == 'P002'){
    $target = &$p;
    echo $target['name'] . PHP_EOL;
  }
}
unset($p); // foreachで参照渡しを使ったときは安全のため
$person2['name'] = '池田さん';
echo $target['name']  . PHP_EOL;
```
4つのパターンで、foreach内で取得・退避したオブジェクト(`$target`)が、foreach後にオリジナルに対して属性変更した場合、オブジェクト(`$target`)に影響するかどうかを見ている。

実行結果は、以下のようになる。
```
----- 配列要素がUserオブジェクトで値渡しの場合
Bさん
山本さん
----- 配列要素がUserオブジェクトで参照渡しの場合
Bさん
山本さん
----- 配列要素が配列で値渡しの場合
Dさん
Dさん
----- 配列要素が配列で参照渡しの場合
Dさん
池田さん
```
foreachでは、以下のことが言える。

- 配列要素がUserオブジェクトで値渡しの場合 --> 値渡しで指定しても参照渡しになり、コピーされない
- 配列要素がUserオブジェクトで参照渡しの場合 --> 参照渡しなので、コピーされない
- 配列要素が配列で値渡しの場合 --> 値渡しなので、コピーされる
- 配列要素が配列で参照渡しの場合 --> 参照渡しなので、コピーされない


## $_SESSIONでの受け渡しを確認

foreachの確認結果を踏まえて、CLIにて、`$_SESSION`での受け渡しを確認してみる。

- 要素がUserオブジェクトの配列を`$_SESSION`に保持させた場合 --> 参照渡しとなり、コピーされない
- 要素が配列の配列を`$_SESSION`に保持させた場合 --> 参照渡しとなり、コピーされない

※この`$_SESSION`での受け渡しについては、コメントをいただきました。`$_SESSION`は単なるarrayであり、値の設定や取得のタイミングでは副作用はない実装のようです。フックされていて、共有メモリやファイルに保持するといったことはないそうです。なので、この確認作業はあまり意味がありませんでした。単にarrayへの値の設定と取得です。そして、`$_SESSION`にセットされた値は、phpのスレッドが終了する直前、つまり画面遷移するタイミングに、指定のファイルにセッション情報をflushするそうです。

```php
echo '----- 要素がUserオブジェクトの配列を$_SESSIONに保持させた場合' . PHP_EOL;
$user1 = new User('U001', 'Aさん');
$user2 = new User('U002', 'Bさん');
$users = array(&$user1, &$user2);

$_SESSION['users'] = $users;            // $_SESSIONに格納
sleep(2);
$storedUsers = $_SESSION['users'];      // $_SESSIONから再取得

unset($target);
foreach ($storedUsers as &$u){          // コピーされないように参照渡しで確認
  if ($u->getId() == 'U002'){
    $target = &$u;
    echo $target->getName() . PHP_EOL;
  }
}
unset($u); // foreachで参照渡しを使ったときは安全のため
$user2->setName('山本さん');
echo $target->getName() . PHP_EOL;

echo '----- 要素が配列の配列を$_SESSIONに保持させた場合' . PHP_EOL;
$person1 = array('id' => 'P001', 'name' => 'Cさん');
$person2 = array('id' => 'P002', 'name' => 'Dさん');
$persons = array(&$person1, &$person2);

$_SESSION['persons'] = $persons;	    // $_SESSIONに格納
sleep(2);
$storedPersons = $_SESSION['persons'];	// $_SESSIONから再取得

unset($target);
foreach ($storedPersons as &$p){         // コピーされないように参照渡しで確認
  if ($p['id'] == 'P002'){
    $target = &$p;
    echo $target['name'] . PHP_EOL;
  }
}
unset($p); // foreachで参照渡しを使ったときは安全のため
$person2['name'] = '池田さん';
echo $target['name']  . PHP_EOL;
```
実行結果は、以下のようになる。どちらの場合も、`$_SESSION`から再取得したオブジェクト(`$target`)に、オリジナルに対する属性変更が影響していることが分かる。`$_SESSION`に保持されている情報は、CLIで動作させるとメモリ上で保持されていることが分かる。
```
----- 要素がUserオブジェクトの配列を$_SESSIONに保持させた場合
Bさん
山本さん
----- 要素が配列の配列を$_SESSIONに保持させた場合
Dさん
池田さん
```
ファイルに書き出して読み込んでいたりすると、変更の影響は受けないはずなので、山本さんや池田さんではなく、BさんやDさんのままになる。ただし、これはCLIで動作させる場合である。サーバー上で動作させると、`$_SESSION`情報はランダムアクセスファイルに書き込まれて保持されるので、キャッシュされている状態と、ファイルから読み込んだ場合では異なると思われる。

※`$_SESSION`にセットされた値は、phpのスレッドが終了する直前、つまり画面遷移するタイミングに、ファイルにセッション情報を書き出すそうです。

## APCuでの受け渡しを確認

最後に、foreachの確認結果、`$_SESSION`の確認結果を踏まえて、APCuでの受け渡しを確認してみる。APCuは、メモリ上でキャッシュされていると言われている。APCuの使い方については、「[PHPでAPCuを使って共有メモリにキャッシュを実現する方法](https://qiita.com/taconana/items/0f78a831dab2c2e1cf58)」を参照のこと。

- 要素がUserオブジェクトの配列をAPCuに保持させた場合 --> 値渡しとなり、コピーされる
- 要素が配列の配列をAPCuに保持させた場合 --> 値渡しとなり、コピーされる

```php
echo '----- 要素がUserオブジェクトの配列をAPCuに保持させた場合' . PHP_EOL;
$user1 = new User('U001', 'Aさん');
$user2 = new User('U002', 'Bさん');
$users = array(&$user1, &$user2);

apcu_store('users', $users);            // APCuに格納
sleep(2);
$storedUsers = apcu_fetch('users');     // APCuから再取得

unset($target);
foreach ($storedUsers as &$u){          // コピーされないように参照渡しで確認
  if ($u->getId() == 'U002'){
    $target = &$u;
    echo $target->getName() . PHP_EOL;
  }
}
unset($u); // foreachで参照渡しを使ったときは安全のため
$user2->setName('山本さん');
echo $target->getName() . PHP_EOL;

echo '----- 要素が配列の配列をAPCuに保持させた場合' . PHP_EOL;
$person1 = array('id' => 'P001', 'name' => 'Cさん');
$person2 = array('id' => 'P002', 'name' => 'Dさん');
$persons = array(&$person1, &$person2);

apcu_store('persons', $persons);        // APCuに格納
sleep(2);
$storedPersons = apcu_fetch('persons'); // APCuから再取得

unset($target);
foreach ($storedPersons as &$p){        // コピーされないように参照渡しで確認
  if ($p['id'] == 'P002'){
    $target = &$p;
    echo $target['name'] . PHP_EOL;
  }
}
unset($p); // foreachで参照渡しを使ったときは安全のため
$person2['name'] = '池田さん';
echo $target['name']  . PHP_EOL;
```
実行結果は、以下のようになる。
```
----- 要素がUserオブジェクトの配列をAPCuに保持させた場合
Bさん
Bさん
----- 要素が配列の配列をAPCuに保持させた場合
Dさん
Dさん
```
どちらの場合も、APCuから再取得したオブジェクト(`$target`)には、オリジナルに対する属性変更が影響していないことが分かる。APCuに保持されている情報は、apcu_store()の瞬間のスナップショットがメモリ上にコピーされて保持されていることが分かる。

## まとめ

- ユーザー定義のクラスをインスタンス化したオブジェクトが要素である配列に対して、foreachで回すと、値渡しで指定しても参照渡しになる。オブジェクトはコピーされない。
- `$_SESSION`は単なるarrayである。値の設定や取得は単なる配列操作である。`$_SESSION`にセットされた値は、phpのスレッドが終了する直前、つまり画面遷移するタイミングに、ファイルに書き込まれて保持される。
- APCuの利用では、apcu_store()の瞬間のスナップショットがメモリ上にコピーされて保持される。


|[../](https://qiita.com/taconana/items/3f5424f9abfc755772c2) |
|---|