PHP8上級資格取得へ向けたアウトプット記事になります。
PHP試験運営団体より公開されている模擬試験を1つ1つみていく形で事細かくみていきますが、勉強途中ゆえ間違っている部分に関してはご了承ください。
PHP上級資格のメイン教材はPHPマニュアルと指定されていますので、このマニュアルを主に踏襲した内容となっています。
試験問題1-2 | 模擬試験を解く#1 |
---|---|
試験問題3 | 模擬試験を解く#2 |
試験問題4 | 模擬試験を解く#3 |
試験問題5 | 模擬試験を解く#4 |
試験問題6 | 模擬試験を解く#5 |
試験問題7 | 模擬試験を解く#6 |
試験問題8 | 模擬試験を解く#7 |
本記事 | 模擬試験を解く#8 |
主な勉強教材は以下になります。
1.独習PHP第4版
2.はじめてのPHP
3.PHP公式マニュアル
PHP8上級試験模擬問題
試験問題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
Traversableインターフェイス
- このインターフェイスを実装したオブジェクトは
foreach
で反復できるようになる。 - 内部エンジンのインターフェイスであるため、PHPコードでこのインターフェイスを直接実装することはできない特殊な抽象インターフェイスである。
- IteratorAggregateあるいはIteratorを実装しなければなりません。
- PHP5で追加された。
Iteratorインターフェイス
- 外部のイテレータあるいはオブジェクト自身から反復処理を行うためのインターフェイス。
- Iteratorインターフェースを実装すると、foreach文でループできるクラスを作成することができる。
- 下記5つのメソッドを実装する必要がある。
/**
*1回目のループ
*rewaind()→valid()→current()→key()
*2回目〜最終ループ
*next()→valid()→current()→key()
*最終ループ後
*next()→valid()
*/
/**foreachループの開始時に最初にコールされるメソッド*/
public Iterator::rewind(): void
/**Iterator::rewind() および Iterator::next()の後にコールされ、現在の位置が有効かどうかを調べる*/
public Iterator::valid(): bool
/**現在の要素を返す*/
public Iterator::current(): mixed
/**現在の要素のキーを返す*/
public Iterator::key(): mixed
/**各foreachループの後にコールされ,現在位置を次の要素に移動する。*/
public Iterator::next(): void
IteratorAggregate インターフェイス
- カスタムの反復機能を提供するためのメソッドを取り決めている。
- カスタムの反復処理を実装するには、IteratorAggregateインターフェイスを実装した上で、getIterator()メソッドをオーバーライドする必要がある。
class myData implements IteratorAggregate {
public $property1 = "Public property one";
public $property2 = "Public property two";
public $property3 = "Public property three";
public function __construct() {
$this->property4 = "last property";
}
public function getIterator() {
return new ArrayIterator($this);
}
}
$obj = new myData;
foreach($obj as $key => $value) {
var_dump($key, $value);
echo "\n";
}
上記の出力結果は以下のようになります。
string(9) "property1"
string(19) "Public property one"
string(9) "property2"
string(19) "Public property two"
string(9) "property3"
string(21) "Public property three"
string(9) "property4"
string(13) "last property"
よって、Traversableインターフェイスは単体で実装できず、IteratorAggregateあるいはIteratorを実装しなければならないため、この選択肢は合っています。
選択肢②
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}¥n";
}
は正しく実行でき、結果は次のとおりとなる。
ps => string private
i => 1
dummy => dummy value
選択肢のコードを見ていく前に「オブジェクトの反復処理」について深掘りしていきます。
以下に示す例では、MyClassの各プロパティに指定されたアクセス権限によって出力される値の違いを確認しています。
class MyClass
{
public $var1 = 'value 1';
protected $protected = 'protected var';
private $private = 'private var';
function iterateVisible() {
echo "MyClass::iterateVisible:\n";
foreach ($this as $key => $value) {
print "$key => $value\n";
}
}
}
$class = new MyClass();
foreach($class as $key => $value) {
print "$key => $value\n";
}
echo "\n";
$class->iterateVisible();
/**
*<出力値>
*var1 => value 1
*
*MyClass::iterateVisible:
*var1 => value 1
*protected => protected var
*private => private var
*/
クラスの外からオブジェクトの反復処理をかけた場合は「publicなプロパティ値のみ」の出力となり、
MyClass内で定義されているiterateVisible()メソッドを介してオブジェクトの反復処理をかけた場合は「すべてのプロパティ値」が出力されます。
「protectedなプロパティ値まで」を出力させたい場合はMyClassを継承した子クラス内でオブジェクトの反復処理をかける必要があります。例としては次のソースで実現できます。
class MyClass {
public $var1 = 'value 1';
protected $protected = 'protected var';
private $private = 'private var';
}
class SubClass extends MyClass {
public function printParentProperties() {
foreach ($this as $key => $value) {
if (property_exists('MyClass', $key)) {
echo "MyClass->$key = $value\n";
}
}
}
}
$subObj = new SubClass();
$subObj->printParentProperties();
/**
*<出力結果>
*MyClass->var1 = value 1
*MyClass->protected = protected var
*/
ちなみに、こちらのコードで記述している「property_exists()メソッド」はオブジェクトもしくはクラスにプロパティが存在するかどうかを調べるメソッドです。
isset()とは異なり、プロパティの値がnullの場合でもproperty_exists()はtrueを返します。
よって、選択肢①で解説したイテレータ(反復処理を行うオブジェクト)でforeachを使ったときに呼ばれるメソッドの順番と先ほど解説したオブジェクト反復処理の動作から、この選択肢は合っているといえます。
選択肢③
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()関数について軽く触れておきます。
count()関数
count(Countable|array $value, int $mode = COUNT_NORMAL): int
・配列またはCountableオブジェクトに含まれるすべての要素の数を数える
・Countableインターフェイスを実装したオブジェクトの場合は、Countable::count()の戻り値を返す。
・オプションのmode引数がCOUNT_RECURSIVE(または 1)にセットされた場合、多次元配列の全ての要素をカウントする。
$food = array('fruits' => array('orange', 'banana', 'apple'),
'veggie' => array('carrot', 'collard', 'pea'));
var_dump(count($food, 1));
var_dump(count($food, COUNT_RECURSIVE));
/**
*出力結果
*int(8)
*int(8)
*/
・パラメータに不正な型を渡した場合、PHP8.0.0ではFatal error(TypeError)をスローし、PHP7.2.0以下では警告(Warning)を発生させます。
//不正な値
var_dump(count(null));
var_dump(count(false));
この選択肢のコードでは、Countableインターフェースを実装していない場合はTypeErrorが出力され、実装している場合はCountable::count()の戻り値である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)
}
}
ArrayAccessを実装したクラスでは以下のメソッドを実装する必要があります。
//isset() あるいはempty()を使用した場合にオフセットが存在するかどうかを返す
public offsetExists(mixed $offset): bool
//オフセットがempty()かどうかを調べる際に実行され、指定したオフセットの値を返す
public offsetGet(mixed $offset): mixed
//指定したオフセットに値を代入します。
public offsetSet(mixed $offset, mixed $value): void
//オフセットの設定を解除する
public offsetUnset(mixed $offset): void
まず、選択肢のコードではArrayAccessを継承しようとしているため誤りで、インターフェースを実装するためにはimplementsキーワードの記述が必要です。。
また、上記4つのメソッドを実装していないためFatal Errorとなってしまいます。
これは、「クラスHogeには4つの抽象メソッドが含まれているため、抽象メソッドとして宣言するか、残りのメソッドを実装する必要がある」からです。
/**抽象メソッドとして宣言する場合*/
abstract class Hoge implements ArrayAccess {
abstract public function offsetExists(mixed $offset):bool;
abstract public function offsetGet(mixed $offset):mixed;
abstract public function offsetSet(mixed $offset, mixed $value): void;
abstract public function offsetUnset(mixed $offset): void;
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インターフェースの実装及び抽象メソッドとしての宣言またはメソッドの実装がないため、誤りです。