[模擬問題] PHP8上級 – インターフェイス(CTO古庄道明からの出題)

こんにちは。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

この記事が気に入ったら
いいね または フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次