こんにちは。PHP技術者認定機構 CTOの古庄道明です。
今日もPHP8上級試験の模擬問題と解説を紹介します。
以下を解いて出題のイメージと知識を補っていただけると幸いです。
問題
定義済みのインターフェイスとクラスおよび SPL インターフェイスに関する説明の中で、誤っているものを1つ選びなさい。
なお、すべてのコードの先頭には下記のコードが書かれているものとする。
declare(strict_types=1);
error_reporting(-1);
下記はマニュアルから一部引用した内容である。
interface Iterator extends Traversable {
/* メソッド */
abstract public current ( ) : mixed
abstract public key ( ) : scalar
abstract public next ( ) : void
abstract public rewind ( ) : void
abstract public valid ( ) : bool
}
interface Countable {
/* メソッド */
abstract public Countable::count(): int
}
interface ArrayAccess {
/* メソッド */
abstract public offsetExists ( mixed $offset ) : bool
abstract public offsetGet ( mixed $offset ) : mixed
abstract public offsetSet ( mixed $offset , mixed $value ) : void
abstract public offsetUnset ( mixed $offset ) : void
}
選択肢
選択肢1
Traversable インターフェイスは「そのクラスの中身が foreach を使用してたどれるかどうかを検出するインターフェイス」である。
これは抽象インターフェイスであり、単体で実装することはできず、 IteratorAggregate あるいは Iterator を実装しなければならない。
そのため、以下のコード
class Hoge implements Traversable {
}
を実行すると、結果は次のとおりとなる。
Fatal error: Class Hoge must implement interface Traversable as part of either Iterator or IteratorAggregate in Unknown on line 0
選択肢2
Iterator インターフェイスは「外部のイテレータあるいはオブジェクト自身から反復処理を行うためのインターフェイス」である。
また、Traversable を継承しているため foreach でも使うことができる。
そのため、以下のコード
class Hoge implements Iterator {
public function current() {
$key = $this->key();
if ('dummy' === $key) {
return 'dummy value';
}
return $this->$key;
}
public function key() {
return $this->keys[$this->position];
}
public function next() {
$this->position ++;
}
public function rewind() {
$this->position = 0;
}
public function valid() {
return isset($this->keys[$this->position]);
}
public $s = 'string';
public $i = 1;
private $ps = 'string private';
private $pi = 2;
private $position = 0;
private $keys = [ 'ps', 'i', 'dummy' ];
}
$obj = new Hoge();
foreach($obj as $k => $v) {
echo "{$k} => {$v}", PHP_EOL;
}
は正しく実行でき、結果は次のとおりとなる。
ps => string private
i => 1
dummy => dummy value
選択肢3
Countable インターフェイスを実装したクラスは、count() 関数で使用することができる。
そのため、以下のコード
class Hoge {
public $s = 'string';
public $i = 1;
private $ps = 'private string';
private $pi = 2;
}
$obj = new Hoge();
var_dump( count($obj) );
を実行すると、結果は次のとおりとなる。
Fatal error: Uncaught TypeError: count(): Argument #1 ($var) must be of type Countable|array, Hoge given in ...
一方で以下のコード
class Hoge implements Countable {
public function count() {
return 4;
}
public $s = 'string';
public $i = 1;
private $ps = 'private string';
private $pi = 2;
}
$obj = new Hoge();
var_dump( count($obj) );
は正しく実行でき、結果は int(4) となる。
選択肢4
ArrayAccess インターフェイスは「配列としてオブジェクトにアクセスするための機能のインターフェイス」である。
そのため、以下のコード
class Hoge extends ArrayAccess {
private $data = [
's' => null,
's2' => null,
'i' => null,
'j' => null,
];
}
$obj = new Hoge();
$obj['j'] = 999;
echo $obj['j'], PHP_EOL;
var_dump($obj);
は正しく実行でき、結果は次のとおりとなる。
999
object(Hoge)#1 (1) {
["data":"Hoge":private]=>
array(4) {
["s"]=>
NULL
["s2"]=>
NULL
["i"]=>
NULL
["j"]=>
int(999)
}
}
回答と解説
問題は「インターフェイス」についてです。
公式マニュアルの「インターフェイス」のページを読んでおきましょう。
https://www.php.net/manual/ja/language.oop5.interfaces.php
また、定義済みのインターフェイスやSPLインターフェイスについても読んでおくとよいでしょう。
https://www.php.net/manual/ja/reserved.interfaces.php
https://www.php.net/manual/ja/spl.interfaces.php
選択肢1を見ていきましょう。
まず最初のコードは、以下です。
class Hoge implements Traversable {
}
こちらは、選択肢にもあるとおりですが、マニュアルに以下のように書かれています。
「これは抽象インターフェイスであり、単体で実装することはできません。」
https://www.php.net/manual/ja/class.traversable.php
そのため、このコードを実行すると以下のエラーになります。
Fatal error: Class Hoge must implement interface Traversable as part of either Iterator or IteratorAggregate in Unknown on line 0
したがってこの選択肢には正しいことが書かれています。
選択肢2を見ていきましょう。
選択肢2は、Iteratorインターフェイスを実装しているコードになります。
少し分解しながら見ていきましょう。
はじめに動くのはこちらになります。
public function rewind() {
$this->position = 0;
}
まず rewind() が呼ばれ、反復位置を先頭に戻します。
これによってpositionが0になります。
foreachで動くときに使われるのは以下になります。
public function valid() {
return isset($this->keys[$this->position]);
}
public function key() {
return $this->keys[$this->position];
}
public function current() {
$key = $this->key();
if ('dummy' === $key) {
return 'dummy value';
}
return $this->$key;
}
valid() で、現在位置が有効かどうかを確認しています。
今は現在位置(position)が0、keysは[ 'ps', 'i', 'dummy' ]なので、存在しているので「有効」となります。
有効な場合、current() で現在の値を、key() で現在のキーを取得します。
そのため、keyは 'ps'が返ります。valueは、コードを見ると「keyを取得、その値が 'dummy' 以外なら $this->$key という可変プロパティを使い、今回は ps プロパティの値を返す」という実装になっています。
また、プロパティ $ps は
private $ps = 'string private';
と定義されています。
結果として、はじめの出力は
ps => string private
となります。
foreachで次の要素に進むので、このタイミングで
public function next() {
$this->position ++;
}
が呼び出されます。
positionがインクリメントされて、0から1になります。
valid() で、現在位置が有効かどうかを確認しています。
今は現在位置(position)が1、keysは[ 'ps', 'i', 'dummy' ] なので、存在しているので「有効」となります。
有効なため、keyとvalueを返します。
keyは key() で、valueは current() で返します。
そのため、keyは 'i' が返ります。valueは、コードを見ると「keyを取得、その値が'dummy' 以外なら $this->$key という可変プロパティを使い、今回は i プロパティ の値を返す」という実装になっています。
また、プロパティ $i は
public $i = 1;
と定義されています。
結果として、出力は
i => 1
となります。
foreachで次に進みます。
positionがインクリメントされて、1から2になります。
valid() で、現在位置が有効かどうかを確認しています。
今は現在位置(position)が2、keysは [ 'ps', 'i', 'dummy' ] なので、存在しているので「有効」となります。
有効なため、keyとvalueを返します。
keyは key() で、valueは current() で返します。
そのため、keyは 'dummy' が返ります。valueは、コードを見ると「keyを取得、その値が 'dummy' なら 'dummy value' を返す」という実装になっています。
結果として、出力は
dummy => dummy value
となります。
foreachで次に進みます。
positionがインクリメントされて、2から3になります。valid() で、現在位置が有効かどうかを確認しています。
今は現在位置(position)が3、keysは [ 'ps', 'i', 'dummy' ] なので、存在していないので「無効」となり、ここでforeachが終了します。
上述をまとめると、出力は以下の通りになります。
ps => string private
i => 1
dummy => dummy value
したがってこの選択肢には正しいことが書かれています。
なお、この問題はPHP 8.0を対象としているため、上記コードでは戻り値の型を記述していません。
PHP 8.1以降では、Iterator::current() などPHP内部のインターフェイスのメソッドについて、実装側にも互換性のある戻り値型が求められるようになり、指定されていない場合はDeprecatedとなります。
Deprecated: Return type of Hoge::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in ...
マニュアルだと、以下を見るとよいでしょう。
内部クラスと戻り値の型の互換性
https://www.php.net/manual/ja/migration81.incompatible.php#migration81.incompatible.core.type-compatibility-internal
また、マニュアル上のインターフェイスの表記も現在とは少し異なります。以前のマニュアルでは、
interface Countable {
/* メソッド */
abstract public Countable::count(): int
}
といった書き方でしたが、現在のマニュアルだと
interface Countable {
/* メソッド */
public function count(): int
}
となっています。abstract の記述が消えて、function が書かれるようになっています。
あと、Iteratorインターフェイスのkey() の戻り値は、この問題が作成された当時のマニュアルでは scalar と表記されていましたが、現在は mixed になっています。
https://github.com/php/doc-ja/blob/d4e6a6eedf7826cc73a53f830cb3a70b50f5f626/language/predefined/iterator/key.xml
https://www.php.net/manual/ja/class.iterator.php
選択肢3を見ていきましょう。
配列の要素を数える count() 関数があります。
function count(Countable|array $value, int $mode = COUNT_NORMAL): int
このように、第1パラメータには array または Countable を指定する必要があります。
そのため、オブジェクトをcount()関数に渡すのであれば、そのクラスはCountableを実装している必要があります。
選択肢の最初のコードを見てみます。
class Hoge {
public $s = 'string';
public $i = 1;
private $ps = 'private string';
private $pi = 2;
}
$obj = new Hoge();
var_dump( count($obj) );
count()に渡している引数の型が正しくないため、エラーになります。
Fatal error: Uncaught TypeError: count(): Argument #1 ($var) must be of type Countable|array, Hoge given in ...
選択肢の次のコードを見てみます。
class Hoge implements Countable {
public function count() {
return 4;
}
public $s = 'string';
public $i = 1;
private $ps = 'private string';
private $pi = 2;
}
$obj = new Hoge();
var_dump( count($obj) );
こちらは「Countableを実装して」「count()で、値を返す」ようになっています。
そのため、こちらは問題なく動きます。
したがってこの選択肢には正しいことが書かれています。
なお戻り値の型指定は選択肢2と同様になるので、現在コードを書くのであれば、count(): int で戻り値の型も指定するようにするとよいでしょう。
選択肢4を見ていきましょう。
まず、コードを改めて確認します。
class Hoge extends ArrayAccess {
private $data = [
's' => null,
's2' => null,
'i' => null,
'j' => null,
];
}
$obj = new Hoge();
$obj['j'] = 999;
echo $obj['j'], PHP_EOL;
var_dump($obj);
一方で、ArrayAccessを改めて確認します。
interface ArrayAccess {
/* メソッド */
public function offsetExists(mixed $offset): bool
public function offsetGet(mixed $offset): mixed
public function offsetSet(mixed $offset, mixed $value): void
public function offsetUnset(mixed $offset): void
}
まず、ArrayAccessはインターフェイスなので、継承はextendsではなくてimplementsになります。
そのためこのコードだと以下のエラーになります。
Fatal error: Class Hoge cannot extend from interface ArrayAccess in ...
また、この extends を implements に置き換えたとしても、次は以下のエラーになります。
Fatal error: Class Hoge contains 4 abstract methods and must therefore be declared abstract or implement the remaining methods (ArrayAccess::offsetExists, ArrayAccess::offsetGet, ArrayAccess::offsetSet, ...) in ...
したがってこの選択肢には誤りがあります。
選択肢4に誤りがあるため、解答は4になります。
PHP8上級試験と公式問題集に興味がある方は、以下のページをご覧のうえ、この機会に是非受験ください。
PHP8上級試験
https://www.phpexam.jp/summary/expert8
公式問題集:PHP8技術者認定上級試験公式問題集上巻
https://www.amazon.co.jp/dp/B0FQ1LTTQ4/
公式問題集:PHP8技術者認定上級試験公式問題集下巻ベータ
https://www.amazon.co.jp/dp/B0F7F4H8FP/
また、試験対策セミナーは毎月開催しています。興味がある方は以下のページをご覧
いただき是非セミナーにもご参加ください。(フォローいただけるとセミナーのご案
内が届くようになります)
PHP技術者認定機構の公式Peatixグループ
https://peatix.com/group/43424
www.phpexam.jp

