• PHP
【php8上級/準上級試験】模擬問題解説 問題9. interface implements

「第1回 PHP 8 上級 模擬試験」を解説していきます

本記事ではひたすら下記記事の模擬試験の解説をしていきたいと思います!

第1回 PHP 8 上級 模擬試験
https://study.prime-strategy.co.jp/study/ph8ex1/

解説記事一覧

模擬問題 9

定義済みのインターフェイスとクラスおよび SPL インターフェイスに関する説明の中で、誤っているものを1つ選びなさい。
なお、すべてのコードの先頭には下記のコードが書かれているものとする。

declare(strict_types=1);
error_reporting(-1);
下記はマニュアルから一部引用した内容である。

Iterator extends Traversable {
    abstract public current ( ) : mixed
    abstract public key ( ) : scalar
    abstract public next ( ) : void
    abstract public rewind ( ) : void
    abstract public valid ( ) : bool
}

Countable {
    abstract public count ( ) : int
}

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
}

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

「インターフェイス」とは?

インターフェイス(interface) とは、「このクラスには必ずこのメソッドを持たせなさい!」 というルールを決めるものです。

インターフェース (Animal) makeSound() を定義 クラス (Dog) makeSound() を実装

問題文の抽象インターフェイスは普通のインターフェイスと考えてよさそうです、、たぶん

ややこしいですよね「抽象クラス」と紛らわしいとも思います🤷‍♂️

「抽象クラス」と「インターフェイス」の違い

比較項目抽象クラス(abstract class)インターフェース(interface)
目的基本機能を一部実装しつつ、派生クラスに追加の実装を強制するすべてのメソッドを実装させるルールを決める
メソッド普通のメソッドと抽象メソッド(中身なし)を持てるすべてのメソッドは抽象メソッド(中身なし)
プロパティ(変数)プロパティ(メンバ変数)を持てるプロパティを持てない
継承の仕方extends を使う(1つのクラスしか継承できない)implements を使う(複数のインターフェースを実装できる)
用途「一部の機能を共通化したい」場合に使う「このクラスには必ず○○を作れ!」というルールを強制したい

implements とは?

implements は、クラスがインターフェースを使う(実装する)ときのキーワード です。

interface Animal {
    public function makeSound(); // 必ず作るべきメソッドを定義
}

class Dog implements Animal {
    public function makeSound() {
        echo "ワンワン!";
    }
}

Traversable インターフェースとは?

Traversable は 「このクラスが foreach でループできるか?」を判定するためのインターフェース

まず

foreachの基本的な動作

// 普通の配列
$fruits = ['りんご', 'みかん', 'バナナ'];

// foreachで順番に処理
foreach ($fruits as $fruit) {
    echo $fruit . "\n";
}
りんご みかん バナナ 順番に出力: りんご みかん バナナ

上記のように配列の各要素に対して、処理を繰り返すことはイメージしやすいかと思います!

本題は考え方として、クラス内部の配列に対してforeachをしようとしているんです。

class BookCollection // クラスのプロパティ(変数) private $books = []; // Book型オブジェクトの配列 private $totalPages = 0; // 総ページ数 $books の中身: Book オブジェクト[0] title: “PHP入門” pages: 200 author: “山田太郎” price: 2800 Book オブジェクト[1] title: “PHPの基礎” pages: 150 author: “鈴木次郎” price: 3200 Book オブジェクト[2] title: “PHP実践” pages: 300 author: “佐藤三郎” price: 3800 public function getIterator() { return new ArrayIterator($this->books); }

しかし問題になります

private配列の場合は直接アクセスできません

そこでTraversableが必要になります

クラス内部でのforeach class BookCollection { private $books = []; public function printAll() { foreach($this->books as $b) { echo $b->getTitle(); } } } 制限: ・処理が固定的 ・新しい処理ごとに  メソッドを追加必要 ・使う側の自由度が低い ・コードが膨らむ クラス自体をforeachで class BookCollection implements IteratorAggregate { public function getIterator() { return new ArrayIterator( $this->books); } } メリット: ・柔軟な処理が可能 ・使う側が自由に処理を  カスタマイズ可能 ・コードがシンプル ・再利用性が高い 改善

Traversable 自体は 直接 implements できません(エラーになります)

必ず以下の配列に対して「どのような処理をするかを定義している関数群」どちらかと一緒に使う必要があります:

  • IteratorAggregateインターフェイス
  • Iteratorインターフェイス

これは Traversable が ルール(メソッドの定義)を持たない特別なインターフェース だからです。

Traversable (interface) Iterator (interface) IteratorAggregate (interface) MyClass1 (implements Iterator) MyClass2 (implements IteratorAggregate) Traversableは直接実装できません。 IteratorまたはIteratorAggregateを通じて間接的に実装する必要があります。

選択肢通りエラー発生するので正解⭕です」

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

Iterator インターフェイスについて

Iterator インターフェイスを実装すると、そのオブジェクトを foreach で扱えるようになります。

必要なメソッド

  • current(): 現在の要素を返す
  • key(): 現在のキー(インデックス)を返す
  • next(): 次の要素に進む
  • rewind(): 先頭要素に戻す (ループ開始時に呼ばれる)
  • valid(): まだ要素があるかどうか
1) foreach開始 => rewind() 2) valid() => key() => current() => 値を取得 3) next() => 次の要素に進む 4) valid() … falseなら終了

問題文は上記の結果正しい内容ですので⭕

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) となる。

count() は基本的に「要素を数える」目的の関数。

Countable を実装すれば、オブジェクトでも好きなロジックで「件数」を返せる。

count() を使う場合、配列 か Countable を実装しているオブジェクトのみ受け付ける

そのため問題文の通り下記のエラー、正しく処理されます

class Hoge // Countable 未実装 count($obj) → Fatal error Uncaught TypeError: count(): … class Hoge implements Countable public function count() { return 4; } count($obj) → int(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)
  }
}

問題番号正解の選択肢
1PHPの「最新以外の(古い)コード」は、公式サイトでの提供は全くしていない。そのため、古いバージョンのコードが必要な場合、別途「非公式の外部サイト」からソースコードを入手する必要がある。
2論理型 (boolean) は「真偽値」とも呼ばれ、値は true か false か null のいずれかになる。なお、true、false、null の文字は、大文字でも小文字でもよい。
3抽象クラスから継承する際、親クラスで abstract としてマークされた全てのメソッドは子クラスで定義する必要があり、可視性は同等(またはより緩い制約)で、必須引数の数は同じであれば型宣言が異なってもかまわない。
4PHP において、デストラクタは __destruct() メソッドで実装される。親クラスのデストラクタは暗黙的に呼ばれ、呼び出し順序は「子クラスのデストラクタ → 親クラスのデストラクタ」となる。
5__get() はアクセス不能(protected または private)または存在しないプロパティからデータを読み込む際に使用される。なお、__getStatic() は存在せず、オブジェクトや静的コンテキストで動作しない。
6PHP のリファレンス渡しを使用すると、関数内で引数を修正できる。(※※正解テキストでは「呼び出す側で変数に & を付ける必要がある」と記載されています。)
7名前空間は namespace キーワードで宣言する。通常はファイルの先頭に記述する必要があり、名前空間宣言前に書かれたクラスはその名前空間に含まれない。
8PHP 5 では Exception クラスが全例外の基底クラスだったが、PHP 7 以降は Exception クラスは Throwable インタフェースを基底とし、Error クラスが内部エラーの基底クラスとして導入された。
9ArrayAccess インターフェイスは、オブジェクトを配列としてアクセスするための機能を提供する。
10SplFileInfo クラスは、ファイルの情報取得や操作を行うためのクラスである。
11$_COOKIE は、HTTP クッキーから渡された値が連想配列として格納され、また設定も可能である。
12PHP 7.1.x では、文字列操作関数で負のオフセット指定が可能となり、[] や {} による文字単位のアクセスも文字列の末尾からのオフセットとして解釈される。
13可変変数は、スーパーグローバル変数にも使用できる。
14PHP 7.4.x では、波括弧を使った配列や文字列のオフセットアクセスの文法は非推奨となった。
15PHP 8 では、private メソッドの継承に関するルールが変更され、親クラスの同名メソッドの可視性に関係なく子クラスでオーバーライド可能となった。
16break は、現在実行中の for, foreach, while, do-while, switch 構造の実行を終了し、オプションでネストしたループの何段分を抜けるか指定できる。
17XSS 対策として、htmlentities() を適切に使用することで、文字列や配列の入力に対して安全な出力が可能となる。
18アップロードされたファイルの元のファイル名は、$_FILES[‘{formのnameの値}’][‘name’] に格納され、move_uploaded_file() の第二引数として利用できる。
19uniqid() と mt_rand() を組み合わせることで、暗号論的にランダムな(推測困難な)トークンを生成できる。
20PHP のセッションでは、セッション ID がクッキーに保存され、session_set_cookie_params() によりそのクッキーのパラメータを設定できる。
21PHP の変数の参照カウントは、xdebug_debug_zval() で確認でき、オブジェクトを clone した場合は内部的に参照が使われ、一時的に参照カウントが増加する。
22DirectoryIterator クラスは、ディレクトリ内のファイルやサブディレクトリの情報を取得するシンプルなインターフェイスを提供する。
23mail() 関数は、メールを送信するための関数で、第四引数で追加のヘッダー情報を指定できる。
24escapeshellarg() と escapeshellcmd() は、外部入力をシェルコマンドの引数として使用する際に必要なエスケープ処理を行い、どちらを使用しても意味合いは同じである。
25stream_wrapper_register() 関数を使用すると、新しいストリームラッパー(プロトコルハンドラ)を登録できるが、既に存在する場合は失敗する。
26strpos() 関数は、文字列内で指定した部分文字列の最初の出現位置を返し、見つからなければ false を返す。ただし、先頭位置の場合は 0 が返るため注意が必要。
27Phar のスタブには __HALT_COMPILER() が使用され、これ以降のコードはコンパイルされない。
28function_exists() 関数は、指定された関数が定義されているかどうかをチェックし、存在すれば true を返す。
29openssl_decrypt() 関数は、openssl_encrypt() で暗号化されたデータを正しく復号し、元のデータを取り戻す。
30strtotime() 関数は、英語形式の日付文字列を Unix タイムスタンプに変換し、無効な日付の場合は false を返す。