この記事のキーワード
PHP8上級資格取得へ向けたアウトプット記事になります。
PHP試験運営団体より公開されている模擬試験を1つ1つみていく形で事細かくみていきますが、勉強途中ゆえ間違っている部分に関してはご了承ください。
PHP上級資格のメイン教材はPHPマニュアルと指定されていますので、このマニュアルを主に踏襲した内容となっています。
試験問題1-2 | 模擬試験を解く#1 |
---|---|
試験問題3 | 模擬試験を解く#2 |
試験問題4 | 模擬試験を解く#3 |
試験問題5 | 模擬試験を解く#4 |
試験問題6 | 模擬試験を解く#5 |
試験問題7 | 模擬試験を解く#6 |
試験問題8 | 模擬試験を解く#7 |
試験問題9 | 模擬試験を解く#8 |
試験問題10 | 模擬試験を解く#9 |
試験問題11 | 模擬試験を解く#10 |
試験問題12 | 模擬試験を解く#11 |
試験問題13-14 | 模擬試験を解く#12 |
本記事 | 模擬試験を解く#13 |
主な勉強教材は以下になります。
1.独習PHP第4版
2.はじめてのPHP
3.PHP公式マニュアル
PHP8上級試験模擬問題
試験問題15
PHP 7.4.x から PHP 8.0.x への移行 に関する説明の中で、誤っているものを1つ選びなさい。
なお、すべてのコードの先頭には下記のコードが書かれているものとする。
declare(strict_types=1);
error_reporting(-1);
選択肢①
PHP 8 では、nullsafe 演算子がサポートされた。そのため、以下のコード
class Hoge {
public function f1() {
echo __METHOD__, PHP_EOL;
return $this;
}
public function f2() {
echo __METHOD__, PHP_EOL;
return null;
}
public function f3() {
echo __METHOD__, PHP_EOL;
return $this;
}
}
$val = (new Hoge())?->f1()?->f2()?->f3();
var_dump($val);
を実行すると、結果は次のとおりとなる。
Hoge::f1
Hoge::f2
NULL
ここでは「nullsafe演算子」について深堀りしていきます。
nullsafe演算子はnull安全演算子とも呼ばれ、「オブジェクトがnullでない時だけそのメンバにアクセスし、nullの場合はそのままnullを返す」ことができます。
この演算子はPHP8.0から導入された新しい演算子で、nullの可能性があるオブジェクトのメソッドやプロパティを安全に参照するためのものなので、null合体演算子(??)よりも簡潔で使いやすいとされています。
・オブジェクトがnullと評価された場合、例外はスローされずnullが返される
・オブジェクトの評価がチェインの一部だった場合、 残りのチェインはスキップされる
下記のコードはnullsafe演算子を活用した一例です。
class hoge{
private $str = "String";
function fuga($a, $arr){
return $a + $arr(2,4);
}
function getString(){
return $this->str;
}
}
$hoge = new hoge();
$foo = null;
// nullsafe演算子を使用したコード(アロー関数を使用)
var_dump($hoge?->fuga(1,fn($b, $c)=>$b*$c)); // 出力:int(9)
var_dump($foo?->fuga(1,fn($b, $c)=>$b*$c)); // 出力:NULL
// PHP 8.0以前のコード
if (($hoge !== null) && ($hoge->getString() !== null)) {
echo $hoge->getString(); // 出力:String
よって、この選択肢は合っています。
選択肢②
PHP8では、(厳密でないやり方で) 数値と非数値文字列を比較する場合、数値を文字列にキャストし、文字列と比較するようになった。そのため、以下のコード
var_dump( 2 == '2' );
var_dump( 0 == '0' );
var_dump( 2 == '2a' );
var_dump( 0 == '' );
var_dump( 0 == 'foo' );
PHP7.4で動かすと、結果は次のとおりとなる。
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
PHP8.0で動かすと、結果は次のとおりとなる。
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
緩やかな比較では、文字列を数値に変換した上で比較しようとしますが、PHP7.4までの出力結果とPHP8.0以降の出力結果では異なっています。
PHP7.4までの場合
var_dump( 3.14 == '3.14000' ); // true
var_dump( 3.14 == 3.14000 ); // true
var_dump( '3.14E2' == '314' ); // true
var_dump( '3.14E2' == 314 ); // true
var_dump( '0X10' == 16 ); // false
var_dump( '010' == 8 ); // false
var_dump( '0b11' == 3 ); // false
var_dump( 0X10 == 16 ); // true(16進数)
var_dump( 010 == 8 ); // true(8進数)
var_dump( 0b11 == 3 ); // true(2進数)
var_dump( 2 == '2a' ); // ★true
var_dump( '2' == '2a' ); // false
var_dump( 0 == '' ); // ★true
var_dump( 0 == 'asasasa' ); // ★true
PHP8.0以降の場合
var_dump( 3.14 == '3.14000' ); // true
var_dump( 3.14 == 3.14000 ); // true
var_dump( '3.14E2' == '314' ); // true
var_dump( '3.14E2' == 314 ); // true
var_dump( '0X10' == 16 ); // false
var_dump( '010' == 8 ); // false
var_dump( '0b11' == 3 ); // false
var_dump( 0X10 == 16 ); // true(16進数)
var_dump( 010 == 8 ); // true(8進数)
var_dump( 0b11 == 3 ); // true(2進数)
var_dump( 2 == '2a' ); // ★false
var_dump( '2' == '2a' ); // false
var_dump( 0 == '' ); // ★false
var_dump( 0 == 'asasasa' ); // ★false
2,8,16進数リテラルは、PHP7.4以前の緩やかな比較であっても正しく認識されません。
また、PHP7.4以前では「空文字」「文字列のみ」の場合は「0」に変換されます。
公式マニュアルにはさらに詳しく比較について記述されているため、こちらも一緒にチェックしておきましょう。
よってこの選択肢は合っています。
選択肢③
private
なメソッドは「継承されない」はずだが、PHP 7 では親のメソッドと同じ名前のメソッドは、親のメソッドの可視性に関係なく、一部の継承ルールがチェックされていたが、これが PHP 8 では変更された。そのため、以下のコードを
class Hoge {
final private function pFunc1() {
echo __METHOD__, PHP_EOL;
}
}
class Foo extends Hoge {
private function pFunc1() {
parent::pFunc1();
echo __METHOD__, PHP_EOL;
}
public function callPri() {
$this->pFunc1();
}
}
(new Foo())->callPri();
PHP 7.4 で動かすと、結果は次のとおりとなる。
PHP Fatal error: Cannot override final method Hoge::pFunc1() in ...
PHP 8.0 で動かすと、結果は次のとおりとなる。
Hoge::pFunc1
Foo::pFunc1
PHP8.0において選択肢のコードを実行すると以下のようなエラーが出力されます。
Warning: Private methods cannot be final as they are never overridden by other classes in /in/a4LWD on line 3
Fatal error: Uncaught Error: Call to private method Hoge::pFunc1() from scope Foo in /in/a4LWD:10
Stack trace:
#0 /in/a4LWD(15): Foo->pFunc1()
#1 /in/a4LWD(19): Foo->callPri()
#2 {main}
thrown in /in/a4LWD on line 10
Warningに関しては、「final修飾詞のあるメソッドはオーバーライドできないよ」ということを示しています。
また、FatalErrorに関しては、「parent::pFunc1();」で親クラスのprivateメソッドにアクセスしようとしているためエラーが出力されています。
そのため、「parent::pFunc1();」をコメントアウトすると下記のように子クラスのメソッドが処理されて、出力されます。
Warning: Private methods cannot be final as they are never overridden by other classes in /in/OvGpY on line 3
Foo::pFunc1
混乱の元となる継承関係にあるクラスの同名メソッドに関して、PHP 8では、親クラスの同名のメソッドが private
である場合は、子クラスで定義した同名のメソッドはオーバーライドされなくなりました。
よって、この選択肢は誤りです。
選択肢④
PHP8では、インタンスに対する ::class
がサポートされた。そのため、以下のコードを
class Hoge {
}
var_dump( Hoge::class );
$obj = new Hoge();
var_dump( $obj::class );
PHP 7.4 で動かすと、結果は次のとおりとなる。
PHP Fatal error: Cannot use ::class with dynamic class name in ...
PHP 8.0 で動かすと、結果は次のとおりとなる。
string(4) "Hoge"
string(4) "Hoge"
PHP 7.4では、インスタンスに対して ::class
を使用することはできず、動的なクラス名を指定する場合にはエラーが発生しました。
しかし、PHP 8では、クラス名を文字列で表現するために ::class
が導入されたことにより、クラス名を記述する場所で文字列で指定する代わりに ::class
を使用することができるようになり、インスタンスのクラス名を取得するための便利な方法として利用できます。
よって、この選択肢は合っています。
試験問題16
制御構造に関する説明の中で、誤っているものを1つ選びなさい。
なお、すべてのコードの先頭には下記のコードが書かれているものとする。
declare(strict_types=1);
error_reporting(-1);
選択肢①
PHP の制御構造 (if
,while
,for
,foreach
,switch
) では、波括弧{}
の変わりに「開き波括弧をコロン(:
)、閉じ波括弧をそれぞれendif;
,endwhile;
,endfor;
,endforeach;
,endswitch;
に変更する」事が出来る。そのため、以下のコード
<?php
$i = 10;
if ($i > 9) : ?>
$i は9より大きい
$i は9以下
を実行すると、結果は次のとおりとなる。
$i は9より大きい
選択肢のコードでは「endif;」で閉じられていないため、下記エラーが発生します。
Parse error: syntax error, unexpected end of file, expecting "elseif" or "else" or "endif" in /workspace/Main.php on line 7
正しく記述するには下記のように「:」と「endif;」を記述する必要があります。
<?PHP
$i = 10;
if ($i > 9) :
echo "aaa"; // 出力:aaa
endif;
?>
よって、この選択肢は誤りです。
選択肢②
break
は、現在実行中の for
, foreach
, while
, do
–while
, switch
構造の実行を終了する。そのため、以下のコード
for($i = 0; $i < 10; ++$i) {
echo "{$i}¥n";
break;
}
を実行すると、結果は次のとおりとなる。
0
break命令はループを強制的に抜けるために使用したり、switch文においてcase句から脱出するために必要な命令となります。
そのため、選択肢のコードは合っています。
選択肢③
break
は、現在実行中の for
, foreach
, while
, do
–while
, switch
構造の実行を終了する。
また、break
ではオプションの引数でネストしたループ構造を抜ける数を指定することができる。
いくつかネストしているループの内側で「その全てのループを抜け出したい」場合、大きな値をとりあえず入れておくとよい。そのため、以下のコード
$i = $j = 0;
while($i < 10) {
echo "i is {$i}", PHP_EOL;
$i ++;
while($j < 10) {
echo "j is {$j}", PHP_EOL;
$j ++;
break 999;
}
}
を実行すると、結果は次のとおりとなる。
i is 0
j is 0
ネストした全てのループを一度に終了する機能はないことに注意する必要があります。
break命令では、オプションの引数でネストしたループ構造を抜ける数を指定することができます。 選択肢のコードの場合は「break 1;」でネストしたループを抜け、「break 2;」で全てのループを抜けます。
3以上の数を指定すると該当するループ処理がないため以下のようなエラーが発生します。
Fatal error: Cannot 'break' 3 levels
よってこの選択肢は誤りです。
選択肢④
continue
は、ループ構造 (for
, foreach
, while
, do
–while
) において現在の繰り返しループ の残りの処理をスキップし、条件式を評価した後に 次の繰り返しの最初から実行を続けるために使用される。
PHP 7.3 以降、switch
内部で break
文のように振る舞おうとする continue
については、E_WARNING
をトリガーするようになった。そのため、以下のコード
$i = 2;
switch($i) {
case 0:
case 1:
echo "i is {$i}¥n";
continue;
default:
echo "i is {$i}: final¥n";
continue;
}
PHP 7.2 までであれば、結果は次のとおりとなる。
i is 2: final
PHP 7.3 以降は、結果は次のとおりとなる。
Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in ....
continue文には整数の引数を指定することができ、現在のループ内で次の反復を開始する場所を指定できます。
$i = 0;
while ($i++ < 2) { // continue 3による反復開始場所
echo "3 ";
while (1) { // continue 2による反復開始場所
echo "2 ";
while (1) { // continueかcontinue 1による反復開始場所
echo "1 ";
continue 3;
}
echo "出力されない\n";
}
echo "出力されない\n";
}
// 出力:3 2 1 3 2 1
よって、この選択肢は合っています。