PHPのリファレンスの仕組みや使い方について

採用はこちら

PHPのリファレンスとは、同じ変数内容に対して複数の名前からアクセスできるようにする仕組みです。

PHPでは主に&(アンパサンド)を使用してリファレンスを作成します。

通常の代入では、代入後の変数はそれぞれ独立して扱われます。

一方、リファレンスを使うと、複数の変数名が同じ変数内容に結び付くため、どちらか一方を変更すると、もう一方から見える値にも反映されます。

PHPのリファレンスは、C言語などにあるポインタとは異なります。

メモリアドレスを直接操作する仕組みではなく、同じ変数内容に対して別名を付ける仕組みとして理解すると分かりやすいでしょう。

目次

PHPの通常代入とリファレンス代入の違い

通常の代入では変数は独立して扱われる

PHPで通常の代入を行う場合は、=を使用します。

$a = 10;
$b = $a;

$b = 20;

echo $a; // 10
echo $b; // 20

この場合、$bを変更しても$aの値は変わりません。

代入後の$a$bは、それぞれ独立した変数として扱われます。

なお、PHP内部ではコピーに関する最適化が行われるため、「代入した瞬間に必ずデータ全体が物理的にコピーされる」と考える必要はありません。

リファレンスとの違いを理解するときは、内部処理ではなく、変数を変更したときの動作の違いを見るのが分かりやすいでしょう。

リファレンス代入では同じ変数内容を共有する

リファレンスによる代入では、=&を使用します。

$a = 10;
$b =& $a;

$b = 20;

echo $a; // 20
echo $b; // 20

$b =& $a;とすることで、$b$aと同じ変数内容に対する別名になります。

そのため、$bを変更すると、$aから見える値も変更されます。

逆に、$aを変更した場合も同様です。

$a = 10;
$b =& $a;

$a = 30;

echo $b; // 30

どちらの変数名から操作しても、同じ変数内容が変更されます。

PHPでリファレンスを作成する方法

基本的な書き方

PHPで変数同士をリファレンスとして結び付ける基本構文は次のとおりです。

$変数1 =& $変数2;

たとえば、次のように記述できます。

$value = 10;
$reference =& $value;

$reference++;

echo $value; // 11

$reference$valueと同じ変数内容を参照しているため、$referenceを変更すると$valueにも反映されます。

リファレンスは双方向に作用する

リファレンスでは、一方の変数だけがもう一方を一方的に参照していると考えるより、2つの変数名が同じ変数内容に結び付いていると考えるほうが正確です。

$a = 100;
$b =& $a;

$b = 200;
echo $a; // 200

$a = 300;
echo $b; // 300

$a$bのどちらを変更しても、もう一方から見える値も変わります。

関数の引数をリファレンス渡しする方法

通常の引数は呼び出し元を直接変更しない

PHPの関数では、通常は引数を値渡しとして扱います。

たとえば、次のコードでは関数内部で$numberを変更しても、呼び出し元の$valueには反映されません。

function addOne($number)
{
    $number++;
}

$value = 10;

addOne($value);

echo $value; // 10

関数内部の$numberを変更しても、呼び出し元の変数そのものを直接変更するわけではないためです。

引数に&を付けると呼び出し元を変更できる

呼び出し元の変数を関数内部から直接変更したい場合は、関数定義側の引数に&を付けます。

function addOne(&$number)
{
    $number++;
}

$value = 10;

addOne($value);

echo $value; // 11

この場合、関数内部の$numberが呼び出し元の$valueと結び付くため、変更結果がそのまま反映されます。

呼び出し側には&を書かない

参照渡しで重要なのは、&を付ける場所です。

次のように、関数を定義する側に記述します。

function update(&$value)
{
    $value++;
}

呼び出し側では通常どおり記述します。

$value = 10;
update($value);

現代のPHPでは、次のように呼び出し側へ&を付ける書き方は使用しません。

update(&$value);

参照渡しを利用するときは、関数定義側のパラメータに&を付けると覚えておくとよいでしょう。

リファレンスを使って変数の値を交換する方法

2つの変数を直接変更できる

参照渡しを使うと、2つの変数の値を交換する関数も作成できます。

function swap(&$a, &$b)
{
    $temp = $a;
    $a = $b;
    $b = $temp;
}

$x = 10;
$y = 20;

swap($x, $y);

echo $x; // 20
echo $y; // 10

$a$bがそれぞれ呼び出し元の変数とリファレンス関係になるため、関数内部の変更が直接反映されます。

ただし、呼び出し元の変数が関数によって変更されるため、コードを読む人にとって処理の流れが分かりにくくなる場合があります。

必要性が低い場合は、戻り値を使う方法も検討するとよいでしょう。

配列要素をリファレンスで操作する方法

配列の特定要素をリファレンスにできる

PHPでは、配列の要素に対してリファレンスを作ることもできます。

$numbers = [10, 20, 30];

$ref =& $numbers[0];

$ref = 100;

print_r($numbers);

実行結果は次のようになります。

Array
(
    [0] => 100
    [1] => 20
    [2] => 30
)

$ref$numbers[0]と同じ変数内容に結び付いているため、$refを変更すると配列の要素も変更されます。

foreachでリファレンスを使う方法

配列の要素を直接変更できる

foreachで配列の各要素を直接変更したい場合にも、リファレンスを使用できます。

たとえば、次のコードでは配列の各要素を2倍にできます。

$numbers = [1, 2, 3];

foreach ($numbers as &$number) {
    $number *= 2;
}

unset($number);

print_r($numbers);

結果は次のとおりです。

Array
(
    [0] => 2
    [1] => 4
    [2] => 6
)

foreach ($numbers as &$number)とすることで、$numberが各配列要素とリファレンス関係になります。

そのため、$numberの変更内容が元の配列へ直接反映されます。

foreach後はunsetする

参照付きのforeachでは、ループ終了後にもループ変数と配列の最後の要素とのリファレンス関係が残ります。

そのため、次のようにunset()を記述することが重要です。

foreach ($numbers as &$number) {
    $number *= 2;
}

unset($number);

unset($number)を行わないまま同じ変数名を再利用すると、意図しない配列要素の変更につながる可能性があります。

参照付きforeachを使用した場合は、ループ終了後にunset()する習慣を付けておくと安全です。

クロージャーでリファレンスを使う方法

useに&を付けると外側の変数を変更できる

PHPの無名関数やクロージャーでは、useを使って外側の変数を取り込めます。

外側の変数そのものを変更したい場合は、&を付けます。

$count = 0;

$increment = function () use (&$count) {
    $count++;
};

$increment();
$increment();

echo $count; // 2

use (&$count)とすることで、クロージャー内部から外側の$countを変更できます。

一方、次のように&を付けない場合は動作が異なります。

$count = 0;

$increment = function () use ($count) {
    $count++;
};

外側の$countを直接変更したい場合は、use (&$count)のようにリファレンスとして取り込む必要があります。

関数からリファレンスを返す方法

PHPでは参照返しも利用できる

PHPでは、関数からリファレンスを返すことも可能です。

たとえば、次のように記述できます。

class Example
{
    public int $value = 10;

    public function &getValue()
    {
        return $this->value;
    }
}

呼び出す場合は、戻り値を受け取る側にも&を付けます。

$example = new Example();

$ref =& $example->getValue();

$ref = 100;

echo $example->value; // 100

$ref$example->valueに結び付くため、$refを変更するとプロパティの値も変更されます。

ただし、参照返しは一般的なPHP開発では頻繁に必要になる機能ではありません。

仕組みを複雑にしやすいため、技術的に必要なケースに限定して使用するのが適切です。

unsetでリファレンスを解除する方法

unsetは変数名との結び付きを解除する

リファレンスとして結び付いた変数は、unset()を使用して解除できます。

$a = 10;
$b =& $a;

unset($b);

echo $a; // 10

ここで重要なのは、unset($b)によって$aの値まで削除されるわけではない点です。

unset()は、対象となる変数名と変数内容との結び付きを解除します。

たとえば、次のコードでも$bは残ります。

$a = 10;
$b =& $a;

unset($a);

echo $b; // 10

そのため、「リファレンスそのものを削除する」というより、対象の変数名と変数内容との結び付きを解除すると理解するほうが正確です。

PHPのオブジェクトとリファレンスの違い

オブジェクトの代入は&によるリファレンスとは異なる

PHPでは、オブジェクトを別の変数へ代入した場合、複数の変数から同じオブジェクトへアクセスできます。

class User
{
    public string $name;
}

$user1 = new User();
$user1->name = 'Taro';

$user2 = $user1;

$user2->name = 'Hanako';

echo $user1->name; // Hanako

この動作を見ると、

$user2 = $user1;

がリファレンス代入のように見えるかもしれません。

しかし、オブジェクト変数の仕組みと、&を使ったPHPのリファレンスは別のものです。

PHPのオブジェクト変数には、オブジェクトへアクセスするための識別子が保持されます。

別の変数へ代入すると、その識別子を通じて同じオブジェクトへアクセスできるようになります。

一方、

$b =& $a;

では、変数名そのものが同じ変数内容に対する別名として結び付きます。

したがって、「PHPのオブジェクトは自動的に参照渡しされる」と単純に説明するのは正確ではありません。

PHPのリファレンスを使うメリット

関数から呼び出し元の変数を直接変更できる

リファレンスを使う代表的なメリットは、関数内部から呼び出し元の変数を直接変更できることです。

function update(&$value)
{
    $value++;
}

複数の値を一度に変更したい処理などでは便利な場合があります。

配列の要素を直接変更できる

参照付きforeachを使用すれば、配列の各要素を直接変更できます。

foreach ($items as &$item) {
    // $itemを変更する
}

unset($item);

配列の値をまとめて変換する処理などで利用できます。

同じ変数内容を複数の名前から操作できる

次のようにすれば、同じ変数内容を複数の変数名から操作できます。

$a = 10;
$b =& $a;

特殊な処理を実装する場合には便利ですが、変更箇所を追跡しにくくなる点には注意が必要です。

PHPのリファレンスを使うときの注意点

値が変更される場所が分かりにくくなる

参照渡しを多用すると、どこで変数の値が変更されたのか分かりにくくなる場合があります。

たとえば、

function updateUser(&$user)
{
    // $userを変更する処理
}

という関数を、

updateUser($user);

と呼び出した場合、呼び出し部分だけでは$userが変更されることが分かりにくいことがあります。

場合によっては、

$user = updateUser($user);

のように戻り値を利用した設計のほうが、処理内容を明示しやすくなります。

foreachのリファレンスを解除し忘れない

参照付きforeachを使用した場合は、ループ終了後にunset()を行うことが重要です。

foreach ($array as &$value) {
    // 処理
}

unset($value);

参照関係を残したまま同じ変数を再利用すると、思わぬバグにつながる可能性があります。

高速化だけを目的に使わない

リファレンスを使用すれば常に処理が高速になるわけではありません。

PHPには内部的なメモリ管理やコピーに関する最適化があります。

そのため、「コピーを防げそうだから」という理由だけで、すべての処理にリファレンスを使うのは適切ではありません。

リファレンスは、同じ変数内容を共有したり、呼び出し元を直接変更したりする必要がある場合に使用する機能と考えるとよいでしょう。

C言語のポインタと混同しない

PHPのリファレンスは、C言語などのポインタとは異なります。

PHPでは、リファレンスを使ってメモリアドレスを取得したり、ポインタ演算を行ったりするわけではありません。

PHPのリファレンスは、

「同じ変数内容に対して複数の名前を付ける仕組み」

として理解するのが適切です。

PHPのリファレンスで使われる主な書き方

リファレンス代入

$b =& $a;

同じ変数内容に複数の変数名を結び付けます。

関数の参照渡し

function update(&$value)
{
    $value++;
}

関数内部から呼び出し元の変数を直接変更できます。

foreachの参照

foreach ($array as &$value) {
    // 配列要素を直接変更
}

unset($value);

配列の各要素を直接変更できます。

クロージャーの参照

function () use (&$value) {
    // 外側の$valueを変更
}

クロージャー内部から外側の変数を変更できます。

関数からの参照返し

function &getValue()
{
    // リファレンスを返す
}

変数などへのリファレンスを関数から返すことができます。

PHPのリファレンスは必要な場面で使うことが重要

PHPのリファレンスは、&を使用して同じ変数内容に複数の名前からアクセスできるようにする仕組みです。

代表的な使い方には、リファレンス代入、関数の参照渡し、参照付きforeach、クロージャーでの参照、関数からの参照返しなどがあります。

特に基本となるのは、次のようなリファレンス代入です。

$a = 10;
$b =& $a;

また、関数の参照渡しでは次のように記述します。

function update(&$value)
{
    $value++;
}

リファレンスは便利な機能ですが、多用すると副作用が増え、変数がどこで変更されたのか分かりにくくなる場合があります。

そのため、通常は値渡しや戻り値を利用し、呼び出し元の変数を直接変更する必要がある場合など、リファレンスを使う明確な理由があるときに限定して利用するのがおすすめです。

以上、PHPのリファレンスの仕組みや使い方についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次