PHP 入門

PHP 関数における引数の渡し方:値渡しと参照渡

PHP において、関数の引数(Arguments/Parameters)とは、関数を呼び出す際に渡される変数(Variables)のことです。これらの引数によって、関数は入力を受け取り、処理を行い、結果を返すことが可能になります。

関数内部でこれらの引数をどのように扱うか——具体的には、関数内部での変更が関数外部の元の変数に影響を与えるかどうかは、その引数が「値渡し(Pass by Value)」なのか「参照渡し(Pass by Reference)」なのかによって決まります。

1. 値渡し (Pass by Value)

引数が値渡しで渡される場合、関数に送られるのはその引数の値の「コピー(複製)」です。関数内部でその引数に対して行われたいかなる変更も、関数外部で宣言された元の変数には一切影響を与えません。

これは PHP における引数渡しのデフォルトの挙動です。元の数値自体は変えずに、計算だけを行いたいといったシナリオを想像してください。

1.1 数値操作の基本

<?php
function incrementNumber($num) {
    // 関数内部では、$num は元の変数値のコピーに過ぎない
    $num++; // このローカルなコピーをインクリメント(増加)させる
    echo "関数内部の値:" . $num . "\n";
}

$originalNumber = 5;

echo "関数呼び出し前:" . $originalNumber . "\n";
incrementNumber($originalNumber); // デフォルトの値渡しで $originalNumber を渡す
echo "函数呼び出し後:" . $originalNumber . "\n";

// 出力:
// 関数呼び出し前:5
// 関数内部の値:6
// 関数呼び出し後:5 (元の変数は影響を受けない)
?>

この例では、incrementNumber() を呼び出した後でも $originalNumber の値は 5 のままです。関数が受け取ったのは 5 という値のコピーであり、元の変数そのものではないからです。

1.2 文字列の変更

<?php
function modifyString($str) {
    // $str は元の文字列のコピー
    $str .= " World!"; // ローカルなコピーに新しい内容を連結(コンカチネーション)
    echo "関数内部の値:" . $str . "\n";
}

$greeting = "Hello";

echo "関数呼び出し前:" . $greeting . "\n";
modifyString($greeting); // 値渡し
echo "関数呼び出し後:" . $greeting . "\n";

// 出力:
// 関数呼び出し前:Hello
// 関数内部の値:Hello World!
// 関数呼び出し後:Hello
?>

数値の例と同様に、modifyString() は独立したコピーに対して操作を行っているため、$greeting は最初に入力された "Hello" を保持し続けます。

1.3 配列操作(値渡し)

配列であっても、PHP のデフォルトの挙動は値渡しです。大きな配列の場合、コピーを作成するとパフォーマンスオーバーヘッドが発生する可能性があるため、PHP が配列のコピーをどのように処理するかを理解しておくことは非常に重要です。

<?php
function addToArray($arr) {
    // $arr は元の配列のコピー
    $arr[] = "ブドウ"; // ローカルなコピーに要素を追加
    echo "関数内部の配列:" . implode(", ", $arr) . "\n";
}

$fruits = ["リンゴ", "バナナ"];

echo "関数呼び出し前:" . implode(", ", $fruits) . "\n";
addToArray($fruits); // 値渡しで $fruits を渡す
echo "関数呼び出し後:" . implode(", ", $fruits) . "\n";

// 出力:
// 関数呼び出し前:リンゴ, バナナ
// 関数内部の配列:リンゴ, バナナ, ブドウ
// 関数呼び出し後:リンゴ, バナナ (元の配列は変更されない)
?>

2. 参照渡し (Pass by Reference)

引数が参照渡しで渡される場合、関数が受け取るのは元の変数への参照(リファレンス / メモリポインタ)であり、値のコピーではありません。つまり、関数内部で引数に対して行われた変更は、関数外部の元の変数に直接反映されます。

引数を参照渡しにするには、関数定義の引数名の前にアンパサンド(&)を付けます。

関数に渡された変数を直接変更させたい場合(新しい値を返すだけでなく、元の状態を更新したい場合)に、この手法は非常に有効です。

2.1 参照渡しによる数値の変更

<?php
function incrementNumberByReference(&$num) {
    // $num は元の変数への参照
    $num++; // 元の変数を直接インクリメントさせる
    echo "関数内部の値:" . $num . "\n";
}

$originalNumber = 5;

echo "関数呼び出し前:" . $originalNumber . "\n";
incrementNumberByReference($originalNumber); // 参照渡し
echo "関数呼び出し後:" . $originalNumber . "\n";

// 出力:
// 関数呼び出し前:5
// 関数内部の値:6
// 関数呼び出し後:6 (元の変数が変更された!)
?>

ここでは、incrementNumberByReference() が参照先の変数を直接アクセスして修正したため、$originalNumber6 に変化しています。

2.2 参照渡しによる配列の変更

配列を参照渡しで扱うのは非常に一般的です。例えば、関数が外部の配列に対して要素の追加、削除、変更を行う必要があり、かつ修正後の配列全体を戻り値として返したくない場合などに使用されます。

<?php
function addToArrayByReference(&$arr) {
    // $arr は元の配列への参照
    $arr[] = "ブドウ"; // 元の配列に要素を追加
    echo "関数内部の配列:" . implode(", ", $arr) . "\n";
}

$fruits = ["リンゴ", "バナナ"];

echo "関数呼び出し前:" . implode(", ", $fruits) . "\n";
addToArrayByReference($fruits); // 参照渡し
echo "関数呼び出し後:" . implode(", ", $fruits) . "\n";

// 出力:
// 関数呼び出し前:リンゴ, バナナ
// 関数内部の配列:リンゴ, バナナ, ブドウ
// 関数呼び出し後:リンゴ, バナナ, ブドウ (元の配列が修正された!)
?>

3. 値渡しと参照渡しのどちらを選択すべきか?

どちらの渡し方を選択するかは、期待する挙動とパフォーマンスの考慮によって決まります。

3.1 値渡し (デフォルトの挙動)

  • 使用シーン: 元のデータが関数によって意図せず変更されるのを防ぎたい(データの保護)場合。関数が独立したコピー上で動作することを保証することで、データの整合性が高まり、関数の挙動が予測しやすく、独立性が保たれます。計算や変換を行い、結果を返すだけでサイドエフェクト(副作用)を持たない一般的な関数に適しています。
  • パフォーマンスに関する注意点: PHP は内部で、多くのスカラ型や配列に対して「コピーオンライト(Copy-on-Write)」と呼ばれるメモリ最適化メカニズムを使用しています。これは、関数内部で実際に値が変更されたときに初めて、PHP がメモリ上に実際のコピーを作成することを意味します。したがって、巨大な配列であっても、変更を加えない限り、値渡しが大きなメモリ負担になることはありません。

3.2 参照渡し (&)

  • 使用シーン: 関数が引数を直接修正しなければならない場合。例えば、関数内で空の配列を埋める、スワップ関数(2つの変数の値を入れ替える)の実装、あるいは関数が複数の「結果」を返す必要がある場合(ただし、通常はアレイを返した方がクリーンな設計になります)などです。
  • 予測可能性に関する警告: 使用には注意が必要です。参照渡しは「サイドエフェクト」を導入します。関数がそのスコープ外の変数を変更してしまうため、無節制に使用するとコードのデバッグや理解が極めて困難になります。

3.3 オブジェクト (Objects) に関する特殊な説明

PHP において、オブジェクト(Objects)は常に値渡しされますが、渡されるこの「値」は実際にはオブジェクトの識別子ハンドル(Handle)です。

これは、関数内部でオブジェクトのプロパティを修正すると、その変更が関数外部の元のオブジェクトにも影響を与えることを意味します(一見すると参照渡しのように見えます)。

しかし、関数内部でそのオブジェクト変数に対して「再代入」(例:$obj = new OtherClass();)を行っても、それはローカルな変数の参照先を変えるだけであり、関数外部の元のオブジェクトは維持されます。

<?php
class MyObject {
    public $value = '初期値';
}

// オブジェクトのプロパティを修正
function modifyObjectProperties($obj) {
    $obj->value = 'プロパティが修正されました'; // これは元のオブジェクトを変更します
}

// オブジェクト変数への再代入
function reassignObjectVariable($obj) {
    $obj = new MyObject(); // ローカルで全く新しいオブジェクトを作成
    $obj->value = 'ローカルな再代入'; // ローカルな新しいオブジェクトにのみ影響
}

$myInstance = new MyObject();

echo "初期オブジェクト値:" . $myInstance->value . "\n"; // 初期値

modifyObjectProperties($myInstance);
echo "modify 実行後:" . $myInstance->value . "\n"; // プロパティが修正されました

reassignObjectVariable($myInstance);
echo "reassign 実行後:" . $myInstance->value . "\n"; // 依然として:プロパティが修正されました (元のオブジェクトは置換されない)
?>