プログラミング

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

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

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

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

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

1.独習PHP第4版

2.はじめてのPHP

 

3.PHP公式マニュアル

 

PHP8上級試験模擬問題

試験問題13

 

可変変数に関する説明の中で、誤っているものを1つ選びなさい。

 

選択肢①

 

PHP では、変数名を可変にする事ができる。そのため、以下のコード

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

$test = 'hello';
$s = 'test';
echo $$s;

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

hello

 

 

可変変数について深掘っていきます。

PHPの可変変数は、変数の値を取得する際に別の変数を使用して、動的に変数名を構築する方法です。

具体的には、$マークの後に変数名を格納する変数を作成し、その変数を{}で囲んで可変変数として使用します。

$foo = 'bar';
$$foo = 'baz';
echo $bar; // 出力:baz

class foo {
    var $bar = 'I am bar.';
    var $arr = array('I am A.', 'I am B.', 'I am C.');
    var $r   = 'I am r.';
}

$foo = new foo();
$bar = 'bar';
$baz = array('foo', 'bar', 'baz', 'quux');
echo $foo->$bar; // 出力:I am bar
echo $foo->{$baz[1]}; // 出力:I am bar

$start = 'b';
$end   = 'ar';
echo $foo->{$start . $end}; // I am barを出力

$arr = 'arr';
echo $foo->{$arr[1]}; // 出力:I am r

よって選択肢のコードは合っています。

 

選択肢②

 

$を重ねて、2段以上の可変変数も、作ることは出来る。そのため、以下のコード

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

$test = 'hello';
$s = 'test';
$ss = 's';
echo $$ss, PHP_EOL;
echo $$$ss;

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

test
hello

 

 

$を重ねて可変変数を作成することで、任意の深さの変数にアクセスすることができます。

ただし、可変変数はコードの可読性を低下させるため、必要な場合に限り使用することが望ましいといえるでしょう。

よって、選択肢のコードは合っています。

選択肢③

 

可変変数は、PHP 5 と PHP 8 で評価の順番が異なる。
例えば「$$foo['bar']['baz']」という可変変数がある場合、PHP 5 では「${$foo['bar']['baz']}」、PHP 7 以降では「($$foo)['bar']['baz'](PHP 5 でもパース可能な記法だと${$foo}['bar']['baz']」と解釈される。そのため、以下のコード

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

$awk['bar']['baz'] = 'aaa';
$foo = 'awk';
var_dump($$foo['bar']['baz']);
$foo2['bar']['baz'] = 'bbb';
$bbb = 'awk';
var_dump($$foo2['bar']['baz']);

を PHP 8 で実行すると、結果は次のとおりとなる。

string(3) "aaa"
Warning: Array to string conversion in ...
Warning: Undefined variable $Array in ...
Warning: Trying to access array offset on value of type null in ...
Warning: Trying to access array offset on value of type null in ...
NULL

一方で PHP 5 で実行すると、結果は次のとおりとなる。

Warning: Illegal string offset 'bar' in ...
Warning: Illegal string offset 'baz' in ...
Notice: Undefined variable: a in ...
NULL
string(3) "awk"

そのため、波括弧 {} を使って「評価順番を明示的に書く」と、バージョンによらず互換性を保つ事ができる。

 

この選択肢では、

PHP8で実行した場合は以下のような挙動になる。

var_dump($$foo['bar']['baz']); // $foo = 'awk';
→
var_dump(($awk)['bar']['baz']); // 出力:"aaa"

var_dump($$foo2['bar']['baz']);
→
var_dump(($$foo2)['bar']['baz']); // foo2 → ???

 

一方で、PHP5で実行した場合は以下のような挙動になる。

var_dump($$foo['bar']['baz']);
→
var_dump(${($foo)['bar']['baz']});
== var_dump(${($awk)['bar']['baz']}); // $awk['bar']['baz'] = 'aaa';
→
var_dump($aaa); // 未定義エラー


var_dump($$foo2['bar']['baz']);
→
var_dump(${($foo2)['bar']['baz']}); // $foo2['bar']['baz'] = 'bbb';
→
var_dump($bbb); // 出力:"awk"

また、波括弧を使うことで可変変数を含む式を明示的に区切ることができるのであって、波括弧を使っても可変変数の評価順序が変わるわけではないことに気をつけておきましょう。

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

可変変数の評価順

PHP5:${($foo)[‘bar’][‘baz’]}

PHP7以降:($$foo)[‘bar’][‘baz’]

 

選択肢④

 

可変変数は、スーパーグローバル変数に対しても使う事ができる。そのため、以下のコード

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

function hoge() {
    $s = '_ENV';
    echo ${$s}['SSH_TTY'], PHP_EOL;
}

echo $_ENV['SSH_TTY'], PHP_EOL;
hoge();

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

/dev/pts/1
/dev/pts/1

 

スーパーグローバルは、関数やクラスメソッドの中の可変変数として使用することはできません。

よって、この選択肢は誤りです。

 

試験問題14

 

PHP 7.3.x から PHP 7.4.x への移行に関する説明の中で、誤っているものを1つ選びなさい。
明記していない限り、実行は PHP 7.4 で行っている。
なお、すべてのコードの先頭には下記のコードが書かれているものとする。

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

 

 

選択肢①

 

クラスのプロパティは、新たに型宣言をサポートするようになった。

class Hoge {
    public int $i;
}

$obj = new Hoge();
$obj->i = 'string';

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

Fatal error: Uncaught TypeError: Typed property Hoge::$i must be int, string used in  ...

 

型宣言について深掘っていきます。

PHP 7.4.0 以降から関数のパラメータや戻り値、 クラスのプロパティに対して型を宣言することができるようになりました。

// ①関数における型宣言の例(引数・戻り値の型宣言)
function fuga(int $i, float $j): float{
    return $i * $j;
}
var_dump(fuga(2,2.5)); // float(5)

// ②戻り値が変換可能な異なる型の場合
function fuga2(int $i, float $j): String{
    return $i * $j;
}
var_dump(fuga2(2,2.5)); // String(1)"5"

// ③引数の型が異なる場合
function fuga2(int $i, float $j): float{
    return $i * $j;
}
var_dump(fuga2("2",2.5)); // float(5)

// ④TypeErrorとなる例
function hoge(int $i, float $j): int{
    return 'これはエラーです'.$i * $j;
}
var_dump(hoge(2,2.5)); // Uncaught TypeError

上記例の通り、PHPにおいては意図しない型が渡されても、最大限宣言された方に変換しようと試みるため、数値型の文字列はint型やfloat型との相互関係が維持されます。

しかし、コードの先頭に「declare(strict_types=1);」を追加した場合、declare命令が厳密な型チェックを有効にするため、②③④はいずれもTypeErrorが出力されます。

この選択肢ではdeclare命令の有無に関わらず、int型の変数に文字列は代入できないため、TypeErrorが出力されてしまいます。

よって、このコードは合っています。

 

選択肢②

 

アロー関数は、暗黙的な値スコープを持った関数を定義する簡便な文法を提供する。
また、アロー関数は親のスコープで使える変数が常に自動で使える。そのため、以下のコード

$x = 11;
$fn = fn($i) => $i * $x;
var_dump($fn);
var_dump( $fn(9) );

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

object(Closure)#1 (1) {
  ["parameter"]=>
  array(1) {
    ["$i"]=>
    string(10) ""
  }
}
int(90)

 

ここでは、アロー関数・無名関数について深掘っていきます。

まずはアロー関数を解説する前に無名関数について紹介します。

一般的に関数を使用する際は重複のない関数名をつける必要がありますが、大元の高階関数に具体的な処理を渡すことを目的としたユーザー定義関数(コールバック関数)は、いわば使い捨ての関数のためその場限りでの利用となるためわざわざ名前をつけるのも面倒です。

これを解決するのが無名関数(クロージャー)であり、関数名は不要」「関数定義を高階関数の引数に直接埋め込める」「変数に代入できるといったメリットがあります。

// ①コールバック関数を使用して高階関数に処理を渡す
function hoge($a, $b, $func){
    return $a + $b + $func;
}

function fuga($c, $d){
    return $c * $d;
}
var_dump(hoge(1, 2, fuga(2,2))); // int(7)


// ②無名関数を使用した方法
function hoge2($a, $b, $func){
    return $a + $b + $func(3,3);
}

var_dump(hoge2(2, 3, 
    function($c, $d){
        return $c * $d;
    }
));  // int(14)

// ③無名関数を使用した方法(変数に格納)
function hoge2($a, $b, $func){
    return $a + $b + $func(3,3);
}

$calc = function($c, $d){
        return $c * $d;
    }; // セミコロン必須
var_dump(hoge2(2, 3, $calc));  // int(14)

 

PHP7.4以降ではこの無名関数をさらにシンプルに表せる仕組みとしてアロー関数が追加されています。

アロー関数では無名関数に比べ簡潔に記述できる「functionがfnに短縮」「{}ではなく=>で接続」というメリットがありますが、単一の式しか書けない」「アロー関数を使用した参照渡しはできないという注意点もあります。

// アロー関数を使用した方法
function hoge2($a, $b, $func){
    return $a + $b + $func(3,3);
}

var_dump(hoge2(2, 3, fn($c, $d)=>$c * $d));  // int(14)

 

※この選択肢は本来であれば「int(99)」が出力されるので間違いですが、出題側のミスであるため模擬試験の解答上では合っている扱いとなっています。

 

選択肢③

 

Null の場合の代入演算子が追加された。そのため、以下のコード

$array = ['key' => 1];
$array['key'] ??= 'default';
$array['key2'] ??= 'default';
var_dump($array);

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

array(2) {
  ["key"]=>
  int(1)
  ["key2"]=>
  string(7) "default"
}

 

 

PHP 7.4において、null合体演算子(??)と代入演算子(=)を組み合わせた新しい演算子が追加されました。この新しい演算子は、左側の変数がnullである場合にのみ、右側の値を代入するものです。

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

 

選択肢④

 

波括弧を使って配列や文字列のオフセットにアクセスする文法は推奨されなくなった。そのため、以下のコード

$s = 'abc';
var_dump( $s{1} );

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

Parse error: syntax error, unexpected '}', expecting ']' in  ...

 

 

オフセットに関してはこちらの過去記事で解説しています。

PHP 8.0.0より前のバージョンでは、$str{5}のように波括弧を使用してアクセス可能だった(PHP 7.4.0 以降は非推奨、 PHP 8.0.0 以降はサポートなし(fatalエラー))。

という解説の通り、PHP7.4〜8.0までは非推奨ですが一応正常に出力はされます

Deprecated: Array and string offset access syntax with curly braces is deprecated in /in/gYKY6 on line 4
string(1) "b"

一方で、PHP8.0以降はサポートされていないためFatal Errorが出力されます。

PHP Fatal error:  Array and string offset access syntax with curly braces is no longer supported in /workspace/Main.php on line 5

よって、PHP7.4環境で実行しているこちらの選択肢は誤りです。

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