Help us understand the problem. What is going on with this article?

【PHP】全ボクが実務で泣いた「値渡し」と「参照渡し」を完!全!理!解!

はじめに

先日、仕事(PHPでのWebアプリ開発)で連想配列の中身を加工していたのですが30分程全然うまく行かなかったので、先輩に聞いたら3分くらいで解決しました。

結論、解決方法としては「値渡しではなく"参照渡し"を使う」でした。

これまで何となく聞いたことがあったけど「絶対ムズイからイヤッ!!!:persevere:」と目を背けていた"参照渡し"について、これを機にちゃんと理解しようと思ったのでまとめます。
(てかそろそろ理解しておかないとヤバイという危機感w)

あと...
「これ、参照渡ししてあげるとオッケーですよ」
ってスマートに言えたらカッコよくないですか?

↑カッコ良いですけど、参照渡し自体はむやみに使わない方が良いので一応取り消し(笑)
(正しくはコメントをいただいたので削除しました)

というわけで書きます。

この記事でわかること

  • 値渡し
  • 参照渡し

特に僕と同じように「参照渡しから目を背けてきた方」や、「そもそも参照渡しってなーに?」って方に読んで欲しいですね。

読めば絶対に理解できます!!!

直面した問題

仕事のコードをそのまま使うことはできないので、簡単に再現してみます。

以下のコードをご覧ください。

<?php

// 配列を2つ定義
$members = [
    'Aさんの好きな果物' => [
        'りんご' => 'apple',
        'ぶどう' => 'grape',
        'バナナ' => 'banana',
    ],
    'Bさんの好きな果物' => [
        'マンゴー' => 'mango',
        'パイナップル' => 'pineapple',
        'ドラゴンフルーツ' => 'dragon fruit',
    ],
];
$fruits = [
    'ざくろ' => 'pomegranate',
    'オレンジ' => 'orange',
    'もも' => 'peach',
];

// $membersに$fruitsの要素を追加
foreach ($members as $member) {
    $member['ざくろ'] = $fruits['ざくろ'];
    $member['もも'] = $fruits['もも'];
}

// 配列を出力
var_dump($members);

AさんとBさんについてそれぞれ好きな果物を$membersに定義しています。
Bさんは多分、多分なんですけど沖縄の方な気がしてます。

そして、$fruitsという果物を3つ入れた連想配列も定義。

次に、foreachの中でAさん、Bさんの好きな果物に$fruitsから2つ追加しています。

それでは、var_dumpした時の出力結果がどうなっているのかというと

array(2) {
  ["Aさんの好きな果物"]=>
  array(3) {
    ["りんご"]=>
    string(5) "apple"
    ["ぶどう"]=>
    string(5) "grape"
    ["バナナ"]=>
    string(6) "banana"
  }
  ["Bさんの好きな果物"]=>
  array(3) {
    ["マンゴー"]=>
    string(5) "mango"
    ["パイナップル"]=>
    string(9) "pineapple"
    ["ドラゴンフルーツ"]=>
    string(12) "dragon fruit"
  }
}

あれ?
...定義した時の$membersのままなんですよね。

Bさんは相変わらず沖縄の人。(多分ですけどね)

本当はこうなって欲しい。

array(2) {
  ["Aさんの好きな果物"]=>
  array(5) {
    ["りんご"]=>
    string(5) "apple"
    ["ぶどう"]=>
    string(5) "grape"
    ["バナナ"]=>
    string(6) "banana"
    ["ざくろ"]=>
    string(11) "pomegranate"
    ["もも"]=>
    string(5) "peach"
  }
  ["Bさんの好きな果物"]=>
  array(5) {
    ["マンゴー"]=>
    string(5) "mango"
    ["パイナップル"]=>
    string(9) "pineapple"
    ["ドラゴンフルーツ"]=>
    string(12) "dragon fruit"
    ["ざくろ"]=>
    string(11) "pomegranate"
    ["もも"]=>
    string(5) "peach"
  }
}

仕事では連想配列に要素が追加できず、必須にタイポがないかめっちゃ探してました。
が、原因は全然違うところにあったのです...

どうすればいいのかというとコチラ!ドン!

<?php

// 配列を2つ定義
$members = [
    'Aさんの好きな果物' => [
        'りんご' => 'apple',
        'ぶどう' => 'grape',
        'バナナ' => 'banana',
    ],
    'Bさんの好きな果物' => [
        'マンゴー' => 'mango',
        'パイナップル' => 'pineapple',
        'ドラゴンフルーツ' => 'dragon fruit',
    ],
];
$fruits = [
    'ざくろ' => 'pomegranate',
    'オレンジ' => 'orange',
    'もも' => 'peach',
];

// $membersに$fruitsの要素を追加
// $memberの前に&をつける→参照渡しにする
foreach ($members as &$member) {
    $member['ざくろ'] = $fruits['ざくろ'];
    $member['もも'] = $fruits['もも'];
}

// 配列を出力
var_dump($members);

どこを変えたかというとコチラ!ドン!

// $membersに$fruitsの要素を追加
// $memberの前に&をつける→参照渡しにする
foreach ($members as &$member) {
    $member['ざくろ'] = $fruits['ざくろ'];
    $member['もも'] = $fruits['もも'];
}

$memberの前に&をつけて&$memberにしてあげると参照渡しになり、問題解決なのです...

ちなみに実際に出力結果はコチラ!ドン!

array(2) {
  ["Aさんの好きな果物"]=>
  array(5) {
    ["りんご"]=>
    string(5) "apple"
    ["ぶどう"]=>
    string(5) "grape"
    ["バナナ"]=>
    string(6) "banana"
    ["ざくろ"]=>
    string(11) "pomegranate"
    ["もも"]=>
    string(5) "peach"
  }
  ["Bさんの好きな果物"]=>
  &array(5) {
    ["マンゴー"]=>
    string(5) "mango"
    ["パイナップル"]=>
    string(9) "pineapple"
    ["ドラゴンフルーツ"]=>
    string(12) "dragon fruit"
    ["ざくろ"]=>
    string(11) "pomegranate"
    ["もも"]=>
    string(5) "peach"
  }
}

ちゃんとAとBさんの要素に2つの要素(ざくろ、もも)が追加されていますね。
これでBさんの沖縄感も少し薄まりましたね。

(てかざくろって英語でpomegranateなんだ。読み方わかんない)

ここまで来たけど

「というか!!何!!参照渡しって!?」って話。

ハイ、詳しく見ていきましょう。

値渡し

値渡しは関数の引数や変数を扱う上でデフォルトの考え方です。
何もしない(=参照渡しに変更しない)場合は、値渡しになります。

適当に書いてみましょう。

<?php

//変数を定義
$number = 1;

//関数を定義
function addNumber($num) {
    $num += 99;
    echo $num;
}

//関数を実行
addNumber($number);

//変数を出力
var_dump($number);

ハイ、こちらの結果はこうなります。

//addNumber()の中のechoの出力
100
//var_dumpの出力
int(1)

ですよね。
var_dumpで出力した場合はデータの型も表示されます。今回だと数字なので(int)型)

まあハイハイ、って感じですよね。

これが値渡しです。

これ、どういう流れで処理を行っているかというと

値渡し.jpg

addNumber($number)

で、addNumber関数の仮引数として設定した$num$numberを渡していますが、渡しているのは

$number = 1;

1という数字の値だけってこと。だけを渡すから値渡し。ナルホド。

(僕、今書きながら改めてナルホド!ってなってます)

なので

//関数を定義
function addNumber($num) {
    $num += 99;
    echo $num;
}

で99を足しているけど、渡しているのは1という数字だけだから、その後の

var_dump($number);

では定義した時の1がそのまま出力されるってことですね。完!全!理!解!

参照渡し

参照渡しについて公式ドキュメントを見てみましょう。

リファレンスにより関数に変数を渡すことが可能です。この場合、関数内で その引数を修正可能になります。構文は次のようになります。

引用元:リファレンス渡し

ハイ、意味不明。

じゃあさっき値渡しの説明で使ったコードで参照渡ししてみましょう。

<?php

//変数を定義
$number = 1;

//関数を定義
//仮引数$numの前に&をつける
function addNumber(&$num) {
    $num += 99;
    echo $num;
}

//関数を実行
addNumber($number);

//変数を出力
var_dump($number);

では出力結果!ドン!

//addNumber()の中のechoの出力
100
//var_dumpの出力
int(100)

つまり

var_dumpした出力結果がint(100)、つまり100になっちゃってるってことですね。

ということでここで詳しく理解していきます。

まず、どういう流れで処理を行っているかというと

参照渡し.jpg

こんな感じです。
(正式にはちょっと違うのかもしれませんがわかりやすくまとめました)

値渡しは「変数の値(文字列、数字など)を仮引数に渡す」ことでしたが、参照渡しは「変数自体を渡す」ことです。

(ググったら「変数のメモリ番地を渡す渡し方」と書いてましたが、多くの方にとってはちょっと難しい言い回しかと)

一応リンクだと貼っておきます。
値渡しと参照渡しの違いを理解する

値渡しと同じ手順ですが、

addNumber($number)

で、addNumber関数の仮引数として設定した$num$numberを渡していますが、渡しているのは

$number = 1;

$number = 1自体、つまり「numberという変数が数字の1という値を持っていますよ〜」という情報を渡しているんですね。

なので

//関数を定義
function addNumber(&$num) {
    $num += 99;
    echo $num;
}

で99を足しているのは$numberに格納されている数字です。
つまり、$numberの値が100に上書きされているっていうことになります。

というわけで

var_dump($number);

では1に99を足した100が出力されるわけですね。完!全!理!解!

以上をふまえて

値渡しと参照渡しを理解した上で最初のコードに戻ります。

// 配列を2つ定義
$members = [
    'Aさんの好きな果物' => [
        'りんご' => 'apple',
        'ぶどう' => 'grape',
        'バナナ' => 'banana',
    ],
    'Bさんの好きな果物' => [
        'マンゴー' => 'mango',
        'パイナップル' => 'pineapple',
        'ドラゴンフルーツ' => 'dragon fruit',
    ],
];
$fruits = [
    'ざくろ' => 'pomegranate',
    'オレンジ' => 'orange',
    'もも' => 'peach',
];

// $membersに$fruitsの要素を追加
// $memberの前に&をつける→参照渡しにする
foreach ($members as &$member) {
    $member['ざくろ'] = $fruits['ざくろ'];
    $member['もも'] = $fruits['もも'];
}

// 配列を出力
var_dump($members);

つまりこんなことが起こってる。(画質悪くてスミマセン)
参照渡し-2.jpg

foreach ($members as &$member) {

&$memberには$membersの各要素が入るので、

1回目は

'Aさんの好きな果物' => [
    'りんご' => 'apple',
    'ぶどう' => 'grape',
    'バナナ' => 'banana',
]

2回目は

'Bさんの好きな果物' => [
    'マンゴー' => 'mango',
    'パイナップル' => 'pineapple',
    'ドラゴンフルーツ' => 'dragon fruit',
]

になりますよね。

その各要素(連想配列)自体の情報が格納されるので好きな果物にざくろとももが追加(=上書き)されてvar_dumpで追加された状態で出力することができるってわけですね。ふむふむ。

おわり

値渡しと参照渡しのそれぞれの考え方を説明しました。
僕なりに初学者にもわかりやすくまとめることができたんじゃないかと思います。

※明らかにおかしいところあればぜひコメントをいただけると幸いですm(__)m

「内容がよかった!」と思っていただけましたらLGTMをしてくださると嬉しいです:sob:

以上です。

P.S.
この記事を書いている途中に別の先輩から参照渡しをせずに連想配列の中身を書き換えるワザを教えてもらったので、別の記事にまとめます。
投稿したらここにリンク貼ります。

投稿しました↓
【PHP】リファレンスの使用(≒参照渡し)をせずに配列の中身をイジろう

(というか参照渡し使ってたらその後の処理が上手くいかず最終的に上記の方法で実装しました)

なお、コメント欄を見ていただけるとわかりますが、参照渡し自体はできれば使わない方がいいみたいです。理由についてもコメントに書いていただいているので参照くださいm(__)m

shimotaroo
関西でWebアプリの開発をしています。
https://shimotaroo.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away