プログラミング

プログラミング初心者のPHP修羅の道#8〜PHP8上級問題を徹底追求する〜

この記事のキーワード

リファレンスglobalunset()

PHP8上級資格取得へ向けたアウトプット記事になります。

PHP試験運営団体より公開されている模擬試験を1つ1つみていく形で事細かくみていきますが、勉強途中ゆえ間違っている部分に関してはご了承ください。

PHP上級資格のメイン教材はPHPマニュアルと指定されていますので、このマニュアルを主に踏襲した内容となっています。

主な勉強教材は以下になります。

1.独習PHP第4版

2.はじめてのPHP

 

3.PHP公式マニュアル

 

PHP8上級試験模擬問題

試験問題6

 

リファレンスに関する説明の中で、誤っているものを1つ選びなさい。
なお、すべてのコードの先頭には下記のコードが書かれているものとする。

declare(strict_types=1);
error_reporting(-1);

 

 

選択肢①

 

PHP のリファレンスを使うと、ふたつの変数が同じ内容を指すようにできる。
そのため、以下のコード

$s = 'string';
$s2 = &$s;
$s2 = $s2 . ' add';
echo $s;

は正しく実行でき、結果は string add となる。

 

 

ここではリファレンスについて深掘っていきます。

リファレンスとは

同じ変数の内容を異なった名前でコールすること

値「string」は厳密にはコンピュータ上に用意されたメモリに格納されます。そしてそのメモリにはそれぞれの場所を示すアドレスが割り振られています。

つまり、変数はそのアドレスを指し示す名札のような役割を担っています。

値による代入

メモリ上の値を別のアドレスにコピーすること

参照(リファレンス)による代入(&を付加)

元の変数と代入された側の変数が同じアドレスを参照すること

値による代入を行う場合は値のみがコピーされるため以下のような結果になります。

$s = "string";
$s2 = "string2";
$s = $s2."add" ;

echo $s."\n";
echo $s2;

//出力結果
//string2add
//string2

 

一方で、参照(リファレンス)による代入では、$sは$s2と同じアドレスを参照するため違う結果が出力されます。

$s = "string";
$s2 = "string2";
$s = &$s2."add" ;

echo $s."\n";
echo $s2;

//出力結果
//string2
//string2

 

違いがわかるように図解で示してみました。

また、関数の内部(スコープの内側)からグローバル変数(スコープの外側)にアクセス・操作したいときは「global $変数名」もしくは「$GLOBALS[‘変数名’]」の構文を用います。

しかし、関数の内部でglobal宣言された変数にリファレンスを代入すると、そのリファレンスは関数の内部でのみ参照可能となり、それを防ぐには$GLOBALS配列を用いる必要があります。

$var1 = "Example variable";
$var2 = "";

function global_references($use_globals)
{
    global $var1, $var2; //global宣言あり
    $var3; $var4; //global宣言なし
    

    if (!$use_globals) {
        $var2 =& $var1; // 関数の内部でのみ参照可能
        $var3 =& $var1;
        echo "var3 の値は '$var3'\n"; //var3 の値は 'Example variable'
    } else {
        $GLOBALS["var2"] =& $var1; // 関数の外部でも参照可能
        $GLOBALS["var4"] =& $var1; // 関数の外部でも参照可能
    }
}

global_references(false);
echo "var2 の値は '$var2'\n"; // var2 の値は ''
/**
関数内部へはアクセスできない
Undefined variable $var3(Warning)・・・
echo "var3 の値は '$var3'\n";
**/

global_references(true);
echo "var2 の値は '$var2'\n"; // var2 の値は 'Example variable'
echo "var4 の値は '$var4'\n"; // var4 の値は 'Example variable'

 

よって、この選択肢は合っています。

 

選択肢②

 

PHP のリファレンス渡しを使うと、関数内でその引数を修正可能になる。
リファレンス渡しは、呼び出す側で変数に & を付ける必要がある。
& を付けずに呼び出すと、リファレンス渡しにならない。
そのため、以下のコード

function hoge($s) {
    $s = $s . ' add hoge';
}

$s_hoge = 'string';
hoge($s_hoge);
hoge(&$s_hoge);
var_dump($s_hoge);

は正しく実行でき、結果は string(15) "string add hoge" となる。

 

 

リファレンス渡しによって、同じ変数の内容「string」「$s_hoge」とは異なった変数名「$s」を使って関数内で修正することが可能となります。

/**正式な構文*/
/*関数定義のみ「&」を付加する*/

function foo(&$var){
    $var++;
}

function &bar(){
    $b = 1;
    return $b;
}

foo(bar());
$a=5;
foo($a);
// $a はここでは 6 となる
// $b はここでは 2 となる

 

この選択肢ではコール側でリファレンス記号を使用しているため、誤りです。

 

選択肢③

 

PHPのリファレンス返しを使うと、関数の戻り値にリファレンスを指定する事が出来る。
リファレンス返しは、「関数の定義」と「代入」の双方に & を付ける必要がある。
そのため、以下のコード

class hoge {
    public function &test() {
        return $this->s;
    }

    private $s = 'string';
}

$obj = new Hoge();
$s = &$obj->test();
$s = $s . ' add';
var_dump($obj);

は正しく実行でき、結果は次のとおりとなる。

object(hoge)#1 (1) {
  ["s":"hoge":private]=>
  &string(10) "string add"
}

なお、マニュアルには「パフォーマンスを向上させるためだけの目的でこの機能を用いることはやめてください。そのようなことをしなくても、PHPエンジンが自動的に最適化を行います」と記されている。

 

 

選択肢文にもある通り「リファレンス返し」の場合、関数定義と代入でリファレンス記号をつける必要があります。

class foo {
    public $value = 42;

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

$obj = new foo;
$myValue = &$obj->getValue(); // $myValue は $obj->value へのリファレンス、つまり 42 となります
$a=$obj->getValue();          //42という値が変数$aにコピーされただけ
$obj->value = 2;

echo $myValue;                // $obj->value の新しい値である 2 を表示します
echo $a;  

 

$myValueはfoo->$valueの見ている値のアドレスを参照し、getValue()によってコピーではなくリファレンスで指定された戻り値が代入されています。

一方で$aは、foo->$valueの値を返すgetValue()によって42という値がコピーされています。

リファレンスを使用するには、

リファレンスを代入しなければならない

よって、この選択肢は合っています。

 

選択肢④

 

リファレンスは、unset を使うことで解除する事が出来る。
そのため、以下のコード

$s = 'string';
$s2 = &$s;
$s2 = $s2 . ' add';
var_dump($s, $s2);
unset($s2);
$s2 = $s2 . ' add2';
var_dump($s, $s2);

を実行すると、次のとおりとなる。

string(10) "string add"
string(10) "string add"
Warning: Undefined variable $s2 in ...
string(10) "string add"
string(5) " add2"

 

リファレンスの解除はあくまで変数と値の結合が解除されるのであって、破棄ではありません。

そのため選択肢のコードでは以下のような動きとなっています。

 

追記

画像の②のコードが間違っていました🙇
正しくは下記コードになります。

$s2 = $s . 'add' ;

よって、この選択肢は合っています。

ABOUT ME
ヒロ
社会人4年目/25歳/食品商社で2年間営業した後、IT業界にシステムエンジニアとして転職/Java,PHP言語を扱う開発エンジニア