プログラミング

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

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

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

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

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

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, dowhile, switch 構造の実行を終了する。そのため、以下のコード

for($i = 0; $i < 10; ++$i) {
    echo "{$i}¥n";
    break;
}

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

0

 

 

break命令はループを強制的に抜けるために使用したり、switch文においてcase句から脱出するために必要な命令となります。

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

 

選択肢③

 

break は、現在実行中の for, foreach, while, dowhile, 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, dowhile) において現在の繰り返しループ の残りの処理をスキップし、条件式を評価した後に 次の繰り返しの最初から実行を続けるために使用される。
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

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

ABOUT ME
ヒロ
社会人3年目/25歳/食品商社で2年営業した後、IT業界にシステムエンジニアとして転職/Java,PHP言語を扱う開発エンジニア/副業としてWEBライティングも行っています。 ヒロの人生放浪記1(はてブ版)→https://t-nero.hatenablog.com/