第1回 PHP 8 上級 模擬試験 第1回 PHP 8 上級 模擬試験 へようこそ。 《PHP 8 上級 模擬試験 の注意点》 特に明記がない場合、PHPのバージョンは 8.0、php.ini はデフォルトの値となります。 問題文のコードは、PHP 8.0.0 で動作確認をしています。 マニュアルの引用は、2021/01 時点のPHP 公式ページの日本語ドキュメントからになります。 PHP8技術者認定試験上級/準上級試験の本試験と、PHP 8 上級 模擬試験では解答形式 (複数解答方式や選択肢の個数など) が異なる場合がございます。 以下の各項目を入力後、「次へ」をクリックして模擬試験を開始してください。 ※あらかじめ利用規約をご確認ください(プライム・ストラテジーから採用やイベントの情報など届くことがあります)。 ※結果の詳細は入力いただいたメールアドレス宛てにのみお送りします。必ず確認可能なメールアドレスを入力してください。 お名前 メールアドレス(結果の詳細をお送りします) 利用規約に同意する。 1. 「PHPをインストールする」にあたっての一般的な注意事項のうち、誤っているものを1つ選びなさい。 PHPの最新のコードは、公式サイト https://www.php.net/ から、Downloads https://www.php.net/downloads に遷移すると、Changelogの確認を含めて取得できる。 PHPの「最新以外の(古い)コード」は、公式サイトでの提供は全くしていない。そのため、古いバージョンのコードが必要な場合、別途「非公式の外部サイト」からソースコードを入手する必要がある。 ダウンロードできるソースコードの拡張子は「.tar.bz2」「.tar.gz」「.tar.xz」等があるが、解凍すれば中身のコードは同じものである。 Downloads https://www.php.net/downloads にはファイル指紋(sha256)がついているので、ダウンロードしたら、改竄の確認をするとよい。 2. PHPの変数の型についての記述で、誤っているものを1つ選びなさい。なお、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 usort ( array &$array , callable $callback ) : bool 整数型 (integer) は整数 ({..., -2, -1, 0, 1, 2, ...} という集合) を扱う。整数のサイズはプラットフォームに依存するが、-2,147,483,648 ~ 2,147,483,647 (32 bit符号付) または -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 (64 bit符号付) である事が多い。integer 型の範囲外の数を指定した場合、float として解釈されるため、用途と環境によっては注意が必要である。 コールバック (callback) は PHP 5.4 以降では callable タイプヒントと呼ばれていた。これには、あらゆるビルドイン関数、ユーザ定義関数、メソッド、静的なクラスメソッドが指定できるが、言語構造 (例:echo や empty、isset 等) は指定が出来ない。コールバックに「オブジェクトのメソッド」を指定する場合、 配列の 0 番目の要素にオブジェクトを、 そして 1 番目の要素にメソッド名を指定する必要がある。静的なクラスメソッドを指定する場合は、配列の 0 番目の要素にクラス名を、1 番目の要素にメソッド名を指定する必要がある。或いは 'ClassName::methodName' という形式で指定してもよい。コールバック (callback) が使われる関数には usort() 関数がある。そのため、以下のコード class Hoge { public static function sort($a, $b) { return $a <=> $b; }}class Foo { public function sort($a, $b) { return $a <=> $b; }}$awk = [3, 1, 2];usort($awk, [Hoge::class, 'sort']);var_dump($awk);$awk = [3, 1, 2];usort($awk, 'Hoge::sort');var_dump($awk);$awk = [3, 1, 2];usort($awk, [new Foo(), 'sort']);var_dump($awk); は正しく実行でき、結果は次のとおりとなる。 array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3)}array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3)}array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3)} 浮動小数点数 (float / double) は、小数を扱う。浮動小数点数には精度があり、PHP では通常 IEEE 754 倍精度フォーマットが使われる。浮動小数点数はどうしても誤差を考慮する必要があるため、例えば「小数を直接比較して等しいかどうかを調べてはいけない」という事を理解する必要がある。そのため、以下のコード if (0.3 === (0.1 + 0.2)) { echo "=>ここにはならない";} else { echo "=>こちらになる";} を実行すると、=>こちらになる という結果が返る。 論理型 (boolean) は「真偽値」とも呼ばれ、値は、true か false か null のいずれかになる。なお、true、false、null の文字は、大文字で書いても小文字で書いてもよい。 3. クラスに関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); 三つの特別なキーワード self と parent そして static がクラス定義の内部からプロパティまたはメソッドにアクセスする際に使用される。そのため、以下のコード class Hoge { public function test_1() { echo __METHOD__, PHP_EOL; } public static function testStatic() { echo __METHOD__, PHP_EOL; } public static function staticCall() { self::testStatic(); static::testStatic(); }}class Foo extends Hoge { public function test_1() { parent::test_1(); echo __METHOD__, PHP_EOL; } public static function testStatic() { echo __METHOD__, PHP_EOL; }}$obj = new Foo();$obj->test_1();Foo::staticCall(); を実行すると、結果は次のとおりとなる。 Hoge::test_1Foo::test_1Hoge::testStaticFoo::testStatic static メソッドはオブジェクトのインスタンスを生成せずにコールするため、疑似変数 $this は、 static として宣言されたメソッドの内部から利用することはできない。そのため、以下のコード class Hoge { public static function test() { var_dump($this); }}Hoge::test(); を実行すると、結果は次のとおりとなる。 Fatal error: Uncaught Error: Using $this when not in object context in ... static プロパティは、アロー演算子 -> によりオブジェクトからアクセス することはできない。そのため、以下のコード class Hoge { static $i = 100; public function test() { echo $this->i; }}$obj = new Hoge();$obj->test(); を実行すると、結果は次のとおりとなる。 Notice: Accessing static property Hoge::$i as non static in ...Warning: Undefined property: Hoge::$i in ... 抽象クラスから継承する際、親クラスの宣言で abstract としてマークされた全てのメソッドは、子クラスで定義されなければならない。 また、これらのメソッドは同等 (あるいはより緩い制約) の可視性で定義される必要がある。 一方でメソッドのシグネチャ (引数の型と順番) については、必須引数の数は同じである必要があるが、型宣言は異なってもかまわない。 そのため、以下のコード abstract class Hoge { abstract public function __construct(string $s, array $a); protected $s; protected $a;}class Foo extends Hoge { public function __construct(string $s, \arrayObject $a) { $this->s = $s; $this->a = $a; }}$obj = new Foo('', new \arrayObject());var_dump($obj); は正しく実行でき、結果は次のとおりとなる。 object(Foo)#1 (2) { ["s":protected]=> string(0) "" ["a":protected]=> object(ArrayObject)#2 (1) { ["storage":"ArrayObject":private]=> array(0) { } }} 4. メソッドに関する説明の中で、誤っているものを1つ選びなさい。なお、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); PHP において、コンストラクタは、__construct() メソッドで実装される。そのため、以下のコード class Hoge { public function __construct() { echo __METHOD__, PHP_EOL; }}$obj = new Hoge(); は正しく実行でき、結果は Hoge::__construct となる。 なお、コンストラクタを有している場合、親クラスのコンストラクタが暗黙の内にコールされることはない。そのため、以下のコード class Hoge { public function __construct() { echo __METHOD__, PHP_EOL; }}class Foo extends Hoge{ public function __construct() { echo __METHOD__, PHP_EOL; }}$obj = new Foo(); を実行すると、結果は Foo::__construct となる。 親クラスのコンストラクタを実装する場合には、parent::をコールする事が必要となる。そのため、以下のコード class Hoge { public function __construct() { echo __METHOD__, PHP_EOL; }}class Foo extends Hoge{ public function __construct() { parent::__construct(); echo __METHOD__, PHP_EOL; }}$obj = new Foo(); を実行すると、結果は次のとおりとなる。 Hoge::__constructFoo::__construct PHP において、デストラクタは、__destruct() メソッドで実装される。そのため、以下のコード class Hoge { public function __destruct() { echo __METHOD__, PHP_EOL; }}$obj = new Hoge(); は正しく実行でき、結果は Hoge::__destruct となる。 なお、デストラクタを有している場合、親クラスのデストラクタは、暗黙の内にコールされる。コールされる順番は「子クラスのデストラクタ → 親クラスのデストラクタ」の順番である。そのため、以下のコード class Hoge { public function __destruct() { echo __METHOD__, PHP_EOL; }}class Foo extends Hoge{ public function __destruct() { echo __METHOD__, PHP_EOL; }}$obj = new Foo(); を実行すると、結果は次のとおりとなる。 Foo::__destructHoge::__destruct __call() マジックメソッドを使うと「アクセス不能なメソッドがオブジェクトのコンテキストで呼び出された時」に、処理を入れる事ができる。そのため、以下のコード class Hoge { public function __call(string $name, array $arguments) { echo "call: {$name}", PHP_EOL; var_dump($arguments); echo PHP_EOL; }}$obj = new Hoge();$obj->test();$obj->test2(1, '2', [3]); は正しく実行でき、結果は次のとおりとなる。 call: testarray(0) {}call: test2array(3) { [0]=> int(1) [1]=> string(1) "2" [2]=> array(1) { [0]=> int(3) }} __callStatic() マジックメソッドを使うと「アクセス不能なメソッドが静的コンテキストで呼び出された時」に、処理を入れる事ができる。 そのため、以下のコード class Hoge { public static function __callStatic(string $name, array $arguments) { echo "call: {$name}", PHP_EOL; var_dump($arguments); }}Hoge::test(1, '2', [3]); は正しく実行でき、結果は次のとおりとなる。 call: testarray(3) { [0]=> int(1) [1]=> string(1) "2" [2]=> array(1) { [0]=> int(3) }} なお、__callStatic() マジックメソッドが動くのは「静的コンテキストで呼び出された時」だけのため、「オブジェクトのコンテキストで呼び出された時」には動かない。そのため、以下のコード class Hoge { public static function __callStatic(string $name, array $arguments) { echo "call: {$name}", PHP_EOL; var_dump($arguments); }}$obj = new Hoge();$obj->test(); を実行すると、結果は次のとおりとなる。 Fatal error: Uncaught Error: Call to undefined method Hoge::test() in ... 5. メソッドに関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); __toString() メソッドにより、クラスが文字列に変換される際の動作を決めることができる。そのため、以下のコード class Hoge { public function __toString() { return $this->s; } private $s = 'string';}$obj = new Hoge();echo 'object: ' . $obj , PHP_EOL; は正しく実行でき、結果は object: string となる。 また、__toString() メソッドで例外を投げる事は、PHP 7.3 までは出来なかったが、PHP 7.4 からは出来るようになった。そのため、以下のコード class Hoge { public function __toString() { throw new \Exception('*** test string ***'); } private $s = 'string';}try { $obj = new Hoge(); echo 'object: ' . $obj, PHP_EOL;} catch(\Throwable $e) { echo $e->getMessage(), PHP_EOL;} を実行すると *** test string *** となる。 __invoke() メソッドは、 スクリプトがオブジェクトを関数としてコールしようとした際にコールされる。そのため、以下のコード class Hoge { public function __invoke() { var_dump($this); return 'string'; }}$obj = new Hoge();$r = $obj();var_dump($r); は正しく実行でき、結果は次のとおりとなる。 object(Hoge)#1 (0) {}string(6) "string" __get() は、アクセス不能 (protected または private) または存在しないプロパティからデータを読み込む際に使用する。__getStatic() は存在せず、オブジェクトのコンテキスト、静的コンテキストのどちらでも動く。そのため、以下のコード class Hoge { public function __get(string $name) { return "not exist {$name}"; }}$obj = new Hoge();echo $obj->test, PHP_EOL;echo Hoge::$test2, PHP_EOL; は正しく実行でき、結果は次のとおりとなる。 not exist testnot exist test2 __debugInfo() メソッドが実装されていると、var_dump() でオブジェクトをダンプするときに出力するプロパティの情報を制御できる。そのため、以下のコード class Hoge { public function __debugInfo() { return [ 's' => $this->s, 'i' => $this->i, ]; } private $pass = 'password'; private $s = 'string'; private $i = 999;}$obj = new Hoge();var_dump($obj); は正しく実行でき、結果は次のとおりとなる。 object(Hoge)#1 (2) { ["s"]=> string(6) "string" ["i"]=> int(999)} 6. リファレンスに関する説明の中で、誤っているものを1つ選びなさい。なお、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); PHP のリファレンスを使うと、ふたつの変数が同じ内容を指すようにできる。そのため、以下のコード $s = 'string';$s2 = &$s;$s2 = $s2 . ' add';echo $s; は正しく実行でき、結果は string add となる。 PHP のリファレンス渡しを使うと、関数内でその引数を修正可能になる。リファレンス渡しは、呼び出す側で変数に & を付ける必要がある。& を付けずに呼び出すと、リファレンス渡しにならない。そのため、以下のコード function hoge($s) { $s = $s . ' add hoge';}$s_hoge = 'string';hoge($s_hoge);hoge(&$s_hoge);var_dump($s_hoge); は正しく実行でき、結果は string(15) "string add hoge" となる。 PHPのリファレンス返しを使うと、関数の戻り値にリファレンスを指定する事が出来る。リファレンス返しは、「関数の定義」と「代入」の双方に & を付ける必要がある。そのため、以下のコード class hoge { public function &test() { return $this->s; } private $s = 'string';}$obj = new Hoge();$s = &$obj->test();$s = $s . ' add';var_dump($obj); は正しく実行でき、結果は次のとおりとなる。 object(hoge)#1 (1) { ["s":"hoge":private]=> &string(10) "string add"} なお、マニュアルには「パフォーマンスを向上させるためだけの目的でこの機能を用いることはやめてください。そのようなことをしなくても、PHPエンジンが自動的に最適化を行います」と記されている。 リファレンスは、unset を使うことで解除する事が出来る。そのため、以下のコード $s = 'string';$s2 = &$s;$s2 = $s2 . ' add';var_dump($s, $s2);unset($s2);$s2 = $s2 . ' add2';var_dump($s, $s2); を実行すると、次のとおりとなる。 string(10) "string add"string(10) "string add"Warning: Undefined variable $s2 in ...string(10) "string add"string(5) " add2" 7. 名前空間に関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 名前空間は、namespace キーワードを使って宣言する。名前空間の宣言は、通常他のコードより前にファイルの先頭で宣言をする必要がある。名前空間の宣言前に書かれたクラスなどは、宣言された名前空間には含まれない。そのため、以下のコード class Hoge {}namespace Name;class Foo {}$obj = new Hoge();var_dump($obj); を実行すると、結果は次のとおりとなる。 Fatal error: Uncaught Error: Class 'Name\Hoge' not found in ... これはHogeがグローバル空間で宣言をされているためなので、以下のコード class Hoge {}namespace Name;class Foo {}$obj = new \Hoge();var_dump($obj); は正しく実行でき、結果は次のとおりとなる。 object(Hoge)#1 (0) {} 名前空間は、namespace キーワードを使って宣言する。また、名前空間はディレクトリやファイルと同様に、名前空間の階層構造を指定することができる。そのため、以下のコード namespace Name\SubName;class Hoge {}$obj = new Hoge();var_dump($obj); を実行すると、結果は次のとおりとなる。 object(Name\SubName\Hoge)#1 (0) {} 名前空間の定義がない場合、すべてのクラスや関数の定義はグローバル空間に配置される。PHP で定義済みのクラスも、グローバル空間に配置される。先頭に \ (バックスラッシュ) を付けると、名前空間の内部からであってもグローバル空間の名前を指定することができる。そのため、以下のコード namespace Name;try { throw new Exception('error');} catch (Exception $e) { echo 'in to catch';} を実行すると、結果は次のとおりとなる。 Fatal error: Uncaught Error: Class 'Name\Exception' not found in ... 一方で以下のコード namespace Name;try { throw new \Exception('error');} catch (\Exception $e) { echo 'in to catch';} を実行すると in to catch となる。 PHP でのエイリアス作成には use 演算子を使用する。use で指定する名前空間付きの名前の、先頭の \ (バックスラッシュ) は不要で、推奨されない。そのため、以下のコード namespace Name;class Hoge {}class Foo {}namespace Name\Sub;// よく使われているuseuse Name\Hoge;$obj = new Hoge();var_dump(__NAMESPACE__, $obj);namespace Name\Sub2;// エイリアスを切ったuseuse \name\Hoge as Bar; // 先頭の \ は推奨されていないecho PHP_EOL;$obj = new Bar();var_dump(__NAMESPACE__, $obj);namespace Name\Sub3;// useのグループ化use Name\{Hoge, Foo};echo PHP_EOL;$hoge_obj = new Hoge();$foo_obj = new Foo();var_dump(__NAMESPACE__, $hoge_obj, $foo_obj); は正しく実行でき、結果は次のとおりとなる。 string(8) "Name\Sub"object(Name\Hoge)#1 (0) {}string(9) "Name\Sub2"object(Name\Hoge)#2 (0) {}string(9) "Name\Sub3"object(Name\Hoge)#1 (0) {}object(Name\Foo)#2 (0) {} 8. エラーに関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 Exception implements Throwable { }Error implements Throwable { } PHP で「何をエラーとして出力し、何をエラーとして出力しないか」の制御は、php.ini の error_reporting で行う。プログラム実行時に ini_set() 関数を使っても設定できるが、error_reporting() という専用の関数も存在する。ここには定数 (E_ERROR や E_NOTICE など) を、複数指定する場合はビット和演算子等をつかって指定する事が多い。値は int のため直接数値を入れてもよいが、定数を使う事が推奨されている。定数には色々あるが、数値で 0 を指定すると「全てのエラー出力をオフにする」事ができる。error_reporting() 関数で -1 を渡す事があるが、これは「将来のバージョンの PHP で新しいレベルと定数が追加されたとしてもすべてのエラーを表示するようになる (2の補数で、-1は「全てのbitが立つ」値になる)」という意味で使われる。そのため、以下のコード error_reporting(0);$i++;var_dump($i);error_reporting(-1);$j++;var_dump($j); を実行すると、結果は次のとおりとなる。 int(1)Warning: Undefined variable $j in ...int(1) set_error_handler() を使うと、ユーザー定義のエラーハンドラ関数を設定する事ができる。これを使うと「致命的なエラーの際になんらかの後処理が必要な場合」などにその処理を記述できる。また、書き方によっては「PHP の標準関数のエラー時に、例外を投げる事」や「E_NOTICE であっても例外を投げる」事も可能である。そのため、以下のコード set_error_handler( function ($errno, $errstr, $errfile, $errline) { if (0 !== $errno & error_reporting()) { throw new ErrorException( $errstr, 0, $errno, $errfile, $errline); } });try { $i++;} catch(\Throwable $e) { echo 'catch Exception', PHP_EOL; echo $e->getMessage(), PHP_EOL;} を実行すると、結果は次のとおりとなる。 catch ExceptionUndefined variable $i PHP のエラーのうち、E_ERRORは「重大な実行時エラー」、E_PARSE は「コンパイル時のパースエラー」となり、いずれもスクリプトの実行は中断される。一方で、E_WARNING「実行時の警告 (致命的なエラーではない)」とE_NOTICE「実行時の警告」は、スクリプトの実行は中断されない。そのため、以下のコード $i++;var_dump($i);echo 'fin.'; を実行すると、結果は次のとおりとなる。 Warning: Undefined variable $i in ...int(1)fin. PHP 5 において、Exception クラスは全ての例外の基底クラスであった。PHP 7 において、Exception クラスは「Throwable インタフェース」を基底クラスとしている。また、PHP 7 以降において Error クラスという「PHP のすべての内部エラーの基底クラス」ができた。そのため、以下のコード try { throw new \Exception('Exception');} catch(\Throwable $e) { echo 'catch ' , $e->getMessage();} を実行すると、結果は catch Exception となる。同様に、以下のコード try { throw new \Error('Error');} catch(\Exception $e) { echo 'catch ' , $e->getMessage();} を実行すると、結果は catch Error となる。 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 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 privatei => 1dummy => dummy value 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) となる。 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); は正しく実行でき、結果は次のとおりとなる。 999object(Hoge)#1 (1) { ["data":"Hoge":private]=> array(4) { ["s"]=> NULL ["s2"]=> NULL ["i"]=> NULL ["j"]=> int(999) }} 10. SPL に関する説明の中で、誤っているものを1つ選びなさい。なお、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); RecursiveDirectoryIterator クラスは、ファイルシステムのディレクトリを再帰的に反復処理するためのクラスである。RecursiveDirectoryIterator クラスのオブジェクトをRecursiveIteratorIterator クラスで処理する事によって、RecursiveIteratorIterator クラスが「再帰的なイテレータの反復処理をする」ために、ファイルシステムのディレクトリを再帰的に反復処理する事が出来るようになる。そのため、以下のコード $dir = new RecursiveIteratorIterator( new RecursiveDirectoryIterator(__DIR__));while($dir->valid()) { echo $dir->getPathname(), PHP_EOL; $dir->next();} は正しく実行でき、結果は次のような結果を得ることができる。 /home/phpexam/phpexam/./home/phpexam/phpexam/../home/phpexam/phpexam/dir/./home/phpexam/phpexam/dir/../home/phpexam/phpexam/dir/test/home/phpexam/phpexam/sample.php SplFileInfo クラスは、ファイルの情報取得のためのクラスである。単純にファイル名や拡張子の他、inode 番号や inode 変更時刻、ファイルの所有者など、様々な情報を得る事ができるほか、SplFileObject クラスと同様のファイル操作も行う事ができる。そのため、以下のコード $file_info = new SplFileInfo(__FILE__);echo $file_info->getFilename(), PHP_EOL;echo $file_info->getInode(), PHP_EOL;echo $file_info->fread(9999), PHP_EOL; を実行すると、結果は次のとおりとなる。 sample.php40318019declare(strict_types=1);error_reporting(-1);$file_info = new SplFileInfo(__FILE__);echo $file_info->getFilename(), PHP_EOL;echo $file_info->getInode(), PHP_EOL;echo $file_info->fread(9999), PHP_EOL; SplFileObject クラスは、ファイルのためのオブジェクト指向のインターフェイスを提供する。ファイルへの書き込みや読み込み、seek() メソッドでのファイルポインタの移動なども行うことができる。そのため、以下のコード $file = new SplFileObject(__FILE__);while($line = $file->fgets()) { echo $line;}echo 'fin'; を実行すると、結果は次のとおりとなる。 $file = new SplFileObject(__FILE__);while($line = $file->fgets()) { echo $line;}echo 'fin';fin SplTempFileObject クラスは、一時ファイルのためのオブジェクト指向のインターフェイスを提供する。一時ファイルは「メモリまたはテンポラリファイル」上に作成される。そのため、以下のコード $file = new SplTempFileObject();echo $file->getPathname(); を実行すると php://temp となる。 11. 定義済の変数 に関する説明の中で、誤っているものを1つ選びなさい。なお、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); $_SERVER は、ヘッダ、パス、スクリプトの位置のような 情報を有する配列である。この配列のエントリは Web サーバーにより生成されるため、PHP をコマンドラインで実行している場合には、使用できないものもある。そのため、以下のコード var_dump( $_SERVER['REMOTE_ADDR'] ); を Web サーバ経由で実行した場合は現在ページをみているユーザーの IP アドレスが出力されるが、コマンドラインから実行した場合は次のような結果となる。 Warning: Undefined array key "REMOTE_ADDR" in ...NULL $GLOBALS はグローバルスコープで使用可能なすべての変数への参照である。変数名が配列のキーとなる。そのため、以下のコード function hoge() { var_dump( $GLOBALS['i'] );}$i = 999;hoge(); は正しく実行でき、結果は次のとおりとなる。 int(999) $argv はスクリプトに渡された引数の配列である。$argv[0] はスクリプトの実行に使う名前となり、$argv[1] 以降に引数の配列が入る。そのため、 var_dump( $argv[1], $argv[2] ); 上記のコードを php sample.php aaa "bb cc dd" 999 のように呼び出すと、結果は次のとおりとなる。 string(3) "aaa"string(8) "bb cc dd" $_COOKIE は現在のスクリプトに HTTP クッキーから渡された変数の連想配列である。Cookie から渡された値は $_COOKIE に入り、また $_COOKIE に設定した値は Cookie として設定される。そのため、以下のコード $_COOKIE['i'] = ($_COOKIE['i'] ?? 0) + 1;var_dump($_COOKIE['i']); をブラウザから呼び出すと、1回目の結果は次のとおりとなる。 int(1) 2回目の結果は次のとおりとなる。 int(2) 3回目の結果は次のとおりとなる。 int(3) 12. PHP 7.0.x から PHP 7.1.x への移行 に関する説明の中で、誤っているものを1つ選びなさい。なお、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); 配列の短縮構文 ([]) を使って、 代入用に配列の値を取り出せるようになった。これは、list() の代替として使う事ができる(list() が無くなったわけではない)。そのため、以下のコード $data = [ [1, 2], [777, 999],];list($i, $j) = $data[0];var_dump($i, $j);[$i, $j] = $data[1];var_dump($i, $j); は正しく実行でき、結果は次のとおりとなる。 int(1)int(2)int(777)int(999) 新しい擬似型 (callable と同じような型) である iterable が導入された。パラメータおよび返り値の型指定で使うことができ、「配列か、あるいは Traversable インターフェイスを実装したオブジェクトを受け付ける」ようになる。そのため、以下のコード function hoge(iterable $itr) { var_dump($itr);}hoge([1, 2]);hoge(new ArrayObject()); は正しく実行でき、結果は次のとおりとなる。 array(2) { [0]=> int(1) [1]=> int(2)}object(ArrayObject)#1 (1) { ["storage":"ArrayObject":private]=> array(0) { }} 一方で以下のコード function hoge(iterable $itr) { var_dump($itr);}$obj = new stdClass();$obj->s = 'string';$obj->i = 999;hoge($obj); を実行すると、結果は次のとおりとなる。 Fatal error: Uncaught TypeError: Argument 1 passed to hoge() must be iterable, object given, called in ... 数値形式ではない文字列を使って、数値を期待する演算 (+, -, *, /, **, %, <<, >>, |, &, ^ や、これらを用いた代入演算) を行おうとしたときに、 E_WARNING あるいは E_NOTICE レベルのエラーが発生するようになった。そのため、以下のコード $i = '11' + '22';var_dump($i); は正しく実行でき、結果は次のとおりとなる。 int(33) 一方で以下のコード $i = '11' + 'a';var_dump($i); を実行すると、結果は次のとおりとなる。 Warning: A non-numeric value encountered in ...int(11) 文字列操作関数 のうちオフセット指定のできるものすべてについて、負のオフセットを指定できるようになった。[] や {} による 文字列への文字単位のアクセス についても同様となり、負のオフセットは、文字列の末尾からのオフセットと解釈される。負の値を渡した場合、-0 が「文字列の末尾」となる。そのため、以下のコード $s = 'abcdefg';var_dump( $s[-1] ); は正しく実行でき、結果は次のとおりとなる。 string(1) "f" 13. 可変変数に関する説明の中で、誤っているものを1つ選びなさい。 PHP では、変数名を可変にする事ができる。そのため、以下のコード declare(strict_types=1);error_reporting(-1);$test = 'hello';$s = 'test';echo $$s; を実行すると、結果は次のとおりとなる。 hello $を重ねて、2段以上の可変変数も、作ることは出来る。そのため、以下のコード declare(strict_types=1);error_reporting(-1);$test = 'hello';$s = 'test';$ss = 's';echo $$ss, PHP_EOL;echo $$$ss; を実行すると、結果は次のとおりとなる。 testhello 可変変数は、PHP 5 と PHP 8 で評価の順番が異なる。例えば「$$foo['bar']['baz']」という可変変数がある場合、PHP 5 では「${$foo['bar']['baz']}」、PHP 7 以降では「($$foo)['bar']['baz'] (PHP 5 でもパース可能な記法だと ${$foo}['bar']['baz']」と解釈される。そのため、以下のコード // declare(strict_types=1);error_reporting(-1);$awk['bar']['baz'] = 'aaa';$foo = 'awk';var_dump($$foo['bar']['baz']);$foo2['bar']['baz'] = 'bbb';$bbb = 'awk';var_dump($$foo2['bar']['baz']); を PHP 8 で実行すると、結果は次のとおりとなる。 string(3) "aaa"Warning: Array to string conversion in ...Warning: Undefined variable $Array in ...Warning: Trying to access array offset on value of type null in ...Warning: Trying to access array offset on value of type null in ...NULL 一方で PHP 5 で実行すると、結果は次のとおりとなる。 Warning: Illegal string offset 'bar' in ...Warning: Illegal string offset 'baz' in ...Notice: Undefined variable: a in ...NULLstring(3) "awk" そのため、波括弧 {} を使って「評価順番を明示的に書く」と、バージョンによらず互換性を保つ事ができる。 可変変数は、スーパーグローバル変数に対しても使う事ができる。そのため、以下のコード declare(strict_types=1);error_reporting(-1);function hoge() { $s = '_ENV'; echo ${$s}['SSH_TTY'], PHP_EOL;}echo $_ENV['SSH_TTY'], PHP_EOL;hoge(); を実行すると、結果は次のとおりとなる。 /dev/pts/1/dev/pts/1 14. PHP 7.3.x から PHP 7.4.x への移行に関する説明の中で、誤っているものを1つ選びなさい。明記していない限り、実行は PHP 7.4 で行っている。なお、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); クラスのプロパティは、新たに型宣言をサポートするようになった。そのため、以下のコード class Hoge { public int $i;}$obj = new Hoge();$obj->i = 'string'; を実行すると、結果は次のとおりとなる。 Fatal error: Uncaught TypeError: Typed property Hoge::$i must be int, string used in ... アロー関数は、暗黙的な値スコープを持った関数を定義する簡便な文法を提供する。また、アロー関数は親のスコープで使える変数が常に自動で使える。そのため、以下のコード $x = 11;$fn = fn($i) => $i * $x;var_dump($fn);var_dump( $fn(9) ); は正しく実行でき、結果は次のとおりとなる。 object(Closure)#1 (1) { ["parameter"]=> array(1) { ["$i"]=> string(10) "<required>" }}int(90) Null の場合の代入演算子が追加された。そのため、以下のコード $array = ['key' => 1];$array['key'] ??= 'default';$array['key2'] ??= 'default';var_dump($array); は正しく実行でき、結果は次のとおりとなる。 array(2) { ["key"]=> int(1) ["key2"]=> string(7) "default"} 波括弧を使って配列や文字列のオフセットにアクセスする文法は推奨されなくなった。そのため、以下のコード $s = 'abc';var_dump( $s{1} ); を実行すると、結果は次のとおりとなる。 Parse error: syntax error, unexpected '}', expecting ']' in ... 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::f1Hoge::f2NULL PHP 8 では、(厳密でないやり方で) 数値と非数値文字列を比較する場合、数値を文字列にキャストし、文字列と比較するようになった。そのため、以下のコードを var_dump( 2 == '2' );var_dump( 0 == '0' );var_dump( 2 == '2a' );var_dump( 0 == '' );var_dump( 0 == 'foo' ); PHP 7.4 で動かすと、結果は次のとおりとなる。bool(true)bool(true)bool(true)bool(true)bool(true) PHP 8.0 で動かすと、結果は次のとおりとなる。 bool(true)bool(true)bool(false)bool(false)bool(false) 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::pFunc1Foo::pFunc1 PHP 8 では、インタンスに対する ::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" 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より大きい<?php else : ?> $i は9以下<?php endif; ?> を実行すると、結果は次のとおりとなる。 $i は9より大きい break は、現在実行中の for, foreach, while, do-while, switch 構造の実行を終了する。そのため、以下のコード for($i = 0; $i < 10; ++$i) { echo "{$i}¥n"; break;} を実行すると、結果は次のとおりとなる。 0 break は、現在実行中の for, foreach, while, do-while, 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 0j is 0 continue は、ループ構造 (for, foreach, while, do-while) において現在の繰り返しループ の残りの処理をスキップし、条件式を評価した後に 次の繰り返しの最初から実行を続けるために使用される。PHP 7.3 以降、switch 内部で break 文のように振る舞おうとする continue については、E_WARNING をトリガーするようになった。そのため、以下のコード $i = 2;switch($i) { case 0: case 1: echo "i is {$i}", PHP_EOL; continue; default: echo "i is {$i}: final", PHP_EOL; 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 .... 17. XSS (クロスサイトスクリプティング) に関する説明の中で、誤っているものを1つ選びなさい。なお、すべてのコードの先頭には下記のコードが書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 htmlspecialchars ( string $string , int $flags = ENT_COMPAT , string|null $encoding = null , bool $double_encode = true ) : stringhtmlentities ( string $string , int $flags = ENT_COMPAT , string|null $encoding = null , bool $double_encode = true ) : string第二引数 (flags) の定数ENT_COMPAT ダブルクオートは変換しますがシングルクオートは変換しません。ENT_QUOTES シングルクオートとダブルクオートを共に変換します。ENT_NOQUOTES シングルクオートとダブルクオートは共に変換されません。 HTML の動的な生成において、実装に問題があるとXSS 脆弱性が発生する。そのためには、適切な引数で htmlspecialchars() 関数、または htmlentities() 関数を使う必要がある。そのため、以下のコード $input = "alert('test');";$e_input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');echo $e_input; を実行すると <script>alert('test');</script> となり、必要なエスケープが全てなされているので、XSSを防ぐ事ができる。 以下のコード $e_test = htmlspecialchars(filter_input(INPUT_GET, 'test'), ENT_QUOTES, 'UTF-8');echo "{$e_test}"; によって、適切に入力がエスケープされるため XSS を防ぐ事ができる。ただし、もしパラメタ test に配列が入ってきている場合、またはパラメタ test が存在しない場合は、filter_input() 関数の戻り値の仕様が成功した場合は要求された変数の値、フィルタリングに失敗した場合に false、 あるいは変数 var_name が設定されていない場合に null を返します。であるため、結果は次のとおりとなる。 Fatal error: Uncaught TypeError: htmlspecialchars(): Argument #1 ($string) must be of type string, null given in ... 以下のコード $val = $_GET['test'] ?? '';if ( is_string($val) ) { $ret = htmlentities($val, ENT_QUOTES, 'UTF-8'); echo "{$ret}";} else { foreach($val as $v) { echo "{$v}"; }} をブラウザ経由で実行すると、test に文字列が入ってきても配列が入ってきても、適切に XSS 対策を行う事ができる。 $_GET['test'] に文字列で任意のユーザ入力が入っている (配列ではない) とした時に、以下のコード $ret = htmlspecialchars($_GET['test'], ENT_COMPAT);echo "{$ret}"; をブラウザ経由で実行すると、シングルクオートが変換されないため、XSS が発生する可能性がある。この実装は適切ではない。 18. ファイルアップロード に関する説明の中で、誤っているものを1つ選びなさい。 PHP でファイルアップロードをする場合、HTML の form には必ず「enctype="multipart/form-data"」「method="POST"」を指定する必要がある。 これらを忘れると、ファイルを取得する事ができない。 そのため、以下の HTML <form action="./sample.php" method="POST"> <input name="f" type="file" /> <button>upload</button></form> をブラウザで閲覧して以下のコード var_dump($_FILES); を実行すると、結果は次のとおりとなる。 array(0) {} PHPでファイルアップロードされた時に、アップロードされたファイルがサーバー上で保存されているテンポラリファイルの名前は $_FILES['{form の name の値}']['tmp_name'] に入っている。 そのため、アップロードされたファイルを新しい位置に移動する move_uploaded_file() 関数の第一引数として適切に使う事ができる。 そのため、以下のHTML <form action="./sample.php" method="POST"> <input name="f" type="file" /> <button>upload</button></form> をブラウザで閲覧して以下のコード var_dump( $_FILES['f']['tmp_name'] );var_dump( is_readable($_FILES['f']['tmp_name']) ); を実行すると、結果は次のとおりとなる。(ファイル名は実行毎に変わる) string(14) "/tmp/php0stFZs"bool(true) PHPでファイルアップロードされた時に、クライアントマシンの元のファイル名は $_FILES['{form の name の値}']['name'] に入っている。 そのため、アップロードされたファイルを新しい位置に移動する move_uploaded_file() 関数の第二引数として適切に使う事ができる。 そのため、以下の HTML <form action="./sample.php" method="POST"> <input name="f" type="file" /> <button>upload</button></form> をブラウザで閲覧して以下のコード var_dump($_FILES['f']['name']);var_dump(is_readable($_FILES['f']['name'])); を実行すると、結果は次のとおりとなる。 string(8) "exam.txt"bool(true) PHP で複数のファイルをアップロードする時には、form に複数の「type="file"」が、異なる name アトリビュート値 (または配列) であれば受け取る事ができる。 そのため、以下の HTML <form action="sample.php" enctype="multipart/form-data" method="POST"> <input name="userfile[]" type="file" /> <input name="userfile[]" type="file" /> <input name="file_1" type="file" /> <input name="file_2" type="file" /> <button>Submit</button></form> をブラウザで閲覧して以下のコード var_dump($_FILES['userfile']['tmp_name']);var_dump($_FILES['file_1']['tmp_name']);var_dump($_FILES['file_2']['tmp_name']); を実行すると、結果は次のとおりとなる。(ファイル名は実行毎に変わる) array(2) { [0]=> string(14) "/tmp/phpdmFh86" [1]=> string(14) "/tmp/phpBOFw0X"}string(14) "/tmp/phpZP2LSO"string(14) "/tmp/php3GG1KF" 19. 推測困難なトークン に関する説明の中で、誤っているものを1つ選びなさい。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 uniqid ( string $prefix = "" , bool $more_entropy = false ) : string警告:この関数が生成する値は、暗号学的に安全ではありません。そのため、これを暗号として使ってはいけません。暗号学的に安全な値が必要な場合は、random_int() か random_bytes() あるいは openssl_random_pseudo_bytes() を使いましょう。mt_rand ( ) : intmt_rand ( int $min , int $max ) : int警告:この関数が生成する値は、暗号学的に安全ではありません。そのため、これを暗号として使ってはいけません。暗号学的に安全な値が必要な場合は、random_int() か random_bytes() あるいは openssl_random_pseudo_bytes() を使いましょう。mt_rand ( ) : intmt_rand ( int $min , int $max ) : int警告:この関数が生成する値は、暗号学的に安全ではありません。そのため、これを暗号として使ってはいけません。暗号学的に安全な値が必要な場合は、random_int() か random_bytes() あるいは openssl_random_pseudo_bytes() を使いましょう。 CSRF 等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において、random_bytes() は「暗号論的に安全な、疑似ランダムなバイト列を生成する」事が出来る。 そのため、以下のコード var_dump( bin2hex( random_bytes(24) ) ); は正しく実行でき、結果は string(48) "e5c471cbf4502eb90dbfbd2480f06a2d6af69ce4b0722b31" となる (乱数の値は実行毎に変わる)。この値は「暗号論的にランダムなバイト列 (の 16 進表現)」なので、長さが十分であれば「推測 (予測) 困難なトークン」となり得る。 CSRF 等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において、openssl_random_pseudo_bytes() は「疑似ランダムなバイト文字列を生成する」事が出来る。 古いシステムでない限り暗号学的に強いアルゴリズムを使って疑似乱数が生成される事が多いが、古いシステムなどでは「暗号学的に強くない」文字列である可能性もあり、それは第二引数によって知る事が出来る。 そのため、以下のコード var_dump( base64_encode( openssl_random_pseudo_bytes(24, $flg) ) );var_dump($flg); は正しく実行でき、結果は string(32) "OFUw6O7WR785Kg5YZbGZOKDwJzXRn60J"bool(true) となる (乱数の値は実行毎に変わる)。この値は、第二引数で与えた $flg の値が true であるため「暗号論的にランダムなバイト列 (の 16 進表現)」なので、長さが十分であれば「推測 (予測) 困難なトークン」となり得る。 CSRF等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において。 そのため、以下のコード var_dump( uniqid((string)mt_rand(), true) ); は正しく実行でき、結果は string(33) "17821489405e874a50df8b89.86544478" となる (乱数の値は実行毎に変わる)。uniqid() 関数と mt_rand() 関数を組み合わせているため、この値は「暗号論的にランダムな文字列」なので「推測 (予測) 困難なトークン」となり得る。 CSRF 等でも使われる事がある、秘密情報としての「推測(予測)困難なトークン」を作る場合において、mt_rand() は乱数を生成するが、 とあるため、これを使用しても推測(予測)困難なトークンを作成する事は出来ない。 そのため、以下のコード var_dump( mt_getrandmax() );var_dump( mt_rand() ); は正しく実行でき、結果は int(2147483647)int(248780506) となるが (乱数の値は実行毎に変わる)、この値は「暗号論的にランダムな文字列」ではないので「推測 (予測) 困難なトークン」となり得ない。 20. セッション に関する説明の中で、誤っているものを1つ選びなさい。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 session_save_path ( string|null $path = null ) : string|falsesession_save_path() は、 現在のセッションデータ保存パスを返します。session_set_cookie_params ( int $lifetime_or_options , string|null $path = null , string|null $domain = null , bool|null $secure = null , bool|null $httponly = null ) : boolsession_set_cookie_params ( array $lifetime_or_options ) : boolsession_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc , callable $create_sid = ? , callable $validate_sid = ? , callable $update_timestamp = ? ) : boolsession_set_save_handler ( object $sessionhandler , bool $register_shutdown = true ) : bool PHP は「複数回のアクセスを通じて特定のデータを保持する手段」としてのセッションサポート機能を持っている。 セッションサポート機能により、スーパーグローバル配列 $_SESSION を使ってリクエスト間でデータを格納できるようになる。 そのため、以下のコード session_start();$_SESSION['key'] = 'value'; をブラウザ経由で実行した後に以下のコード session_start();var_dump($_SESSION['key']); をブラウザ経由で実行すると、結果は次のとおりとなる。 string(5) "value" PHP のセッションサポート機能において、セッションデータはデフォルトではファイルに保存される。 また、保存先のファイルは session_save_path() 関数によって取得または設定する事ができる。 そのため、以下のコード ob_start();var_dump( session_save_path() );session_save_path('/tmp');var_dump( session_save_path() ); を実行すると、結果は次のとおりとなる。 string(0) ""string(4) "/tmp" PHP のセッションサポート機能において、セッション ID というセッション ID と呼ばれるユニークな ID が割り当てられ、それは基本的にユーザー側にクッキーとして保存される。 そのためクッキーを使うので、セッション ID を保存するクッキーに対するパラメータを設定する事ができる関数が存在する。 そのため、以下のコード ob_start();var_dump( session_get_cookie_params() );session_set_cookie_params(['lifetime' => 86400, 'samesite' => 'Strict', 'secure' => true, 'httponly' => true]);var_dump( session_get_cookie_params() ); を実行すると、結果は次のとおりとなる。 array(6) { ["lifetime"]=> int(0) ["path"]=> string(1) "/" ["domain"]=> string(0) "" ["secure"]=> bool(false) ["httponly"]=> bool(false) ["samesite"]=> string(0) ""}Warning: session_set_cookie_params(): Unrecognized key 'samesite' found in the options array in ...array(6) { ["lifetime"]=> int(86400) ["path"]=> string(1) "/" ["domain"]=> string(0) "" ["secure"]=> bool(true) ["httponly"]=> bool(true) ["samesite"]=> string(0) ""} PHP のセッションサポート機能において、セッションデータはデフォルトではファイルに保存される。 しかし「ファイル以外 (DB 等)」に保存をする事も出来る。そのためにsession_set_save_handler() という関数がある。 最近は後者の方法で実装される事が多いが、その場合、SessionHandlerInterface、 SessionIdInterface (オプション) または SessionUpdateTimestampHandlerInterface を実装したクラス を継承したクラスのオブジェクトを引数として指定する必要がある。 そのため、以下のコード class Hoge {}ob_start();session_set_save_handler(new Hoge); を実行すると、結果は次のとおりとなる。 Fatal error: Uncaught TypeError: session_set_save_handler(): Argument #1 ($open) must be of type SessionHandlerInterface, Hoge given in ... 21. PHP のメモリ消費 に関する説明の中で、誤っているものを1つ選びなさい。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); PHP では、「プログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に解放する」いわゆるガベージコレクションが機能として存在する。 PHP のガベージコレクションは「参照カウント法」という方式で管理されている。 参照された数は、Xdebug がインストール済みであれば xdebug_debug_zval() 関数によって得る事ができる。 そのため、以下のコード $obj = new stdClass();xdebug_debug_zval('obj');$obj2 = $obj;xdebug_debug_zval('obj');xdebug_debug_zval('obj2');unset($obj);xdebug_debug_zval('obj2'); を実行すると、結果は次のとおりとなる。 obj: (refcount=1, is_ref=0)=class stdClass { }obj: (refcount=2, is_ref=0)=class stdClass { }obj2: (refcount=2, is_ref=0)=class stdClass { }obj2: (refcount=1, is_ref=0)=class stdClass { } PHP の変数で、参照された数は、Xdebug がインストール済みであれば xdebug_debug_zval() 関数によって得る事ができる。 しかし int や string などの型の変数は、代入演算子 = によって「元の変数を新しい変数にコピーする (値による代入)」ために、通常の代入では refcount は増えない。 一方で参照による代入 & をすると refcount が増える。 そのため、以下のコード $i = 1;xdebug_debug_zval('i');$i2 = $i;xdebug_debug_zval('i');$i3 = &$i;xdebug_debug_zval('i'); を実行すると、結果は次のとおりとなる。 i: (refcount=0, is_ref=0)=1i: (refcount=0, is_ref=0)=1i: (refcount=2, is_ref=1)=1 PHP の変数で、参照された数は、Xdebug がインストール済みであれば xdebug_debug_zval() 関数によって得る事ができる。 オブジェクトを clone した場合は「オブジェクトのプロパティを 全てシャローコピーする」が、コピーオンライトによって内部的には参照が用いられているために、値を変更するまでの間は一時的に refcount が増える。 そのため、以下のコード $obj = new stdClass();xdebug_debug_zval('obj');$obj2 = clone $obj;xdebug_debug_zval('obj');$obj2->tset = 'value';xdebug_debug_zval('obj'); を実行すると、結果は次のとおりとなる。 obj: (refcount=1, is_ref=0)=class stdClass { }obj: (refcount=2, is_ref=0)=class stdClass { }obj: (refcount=1, is_ref=0)=class stdClass { } PHP の変数はコピーオンライトが使われているため、参照ではないコピーであっても、コピーのタイミングではメモリはほとんど消費される事がない。 ただしコピー先の値に変更が加わると、そのタイミングで「実態がコピーされる」ために、一気にメモリが消費される。 そのため、以下のコード $awk = range(0, 1000000);var_dump(memory_get_usage(true));$awk2 = $awk;var_dump(memory_get_usage(true));$awk2[] = 'v';var_dump(memory_get_usage(true)); を実行すると、結果は次のとおりとなる。(値は環境によって変わる) int(35655680)int(35655680)int(69214208) 22. 関数 に関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 Directory クラスのインスタンスを作るには dir() 関数を使います。new 演算子は使いません。chdir ( string $directory ) : boolgetcwd ( ) : stringDirectoryIterator extends SplFileInfo implements SeekableIterator {public DirectoryIterator::__construct ( string $path )RecursiveDirectoryIterator extends FilesystemIterator implements SeekableIterator , RecursiveIterator {RecursiveIteratorIterator implements OuterIterator {public RecursiveIteratorIterator::__construct ( Traversable $iterator , int $mode = RecursiveIteratorIterator::LEAVES_ONLY , int $flags = 0 )SeekableIterator extends Iterator {RecursiveIterator extends Iterator {Iterator extends Traversable { Directory クラスは、ディレクトリ内を走査するためのクラスである。 そのため、以下のコード $dir_obj = dir(__DIR__);var_dump($dir_obj);while (false !== ($entry = $dir_obj->read())) { echo $entry, PHP_EOL;}$dir_obj->close(); を実行すると、結果は次のとおりとなる。 object(Directory)#1 (2) { ["path"]=> string(10) "/home/php_exam" ["handle"]=> resource(5) of type (stream)}....cache.config(以下略) chdir() 関数はカレントディレクトリを変更する。また getcwd() 関数はカレントのワーキングディレクトリを取得する。 そのため、以下のコード var_dump(getcwd());chdir('../');var_dump(getcwd()); を実行すると、結果は次のとおりとなる。 string(14) "/home/php_exam"string(5) "/home" DirectoryIterator クラスは、ファイルシステムのディレクトリを閲覧するためのシンプルなインターフェイスを提供する。 SeekableIteratorを実装しているため foreach などが使える。 また DirectoryIterator が SplFileInfo を継承しているため、ファイルの情報に纏わるメソッドなどを一通り使う事ができる。 また SplFileInfo クラスには __toString() が実装されているため、ファイルへのパスを文字列で返すことが出来る。 そのため、以下のコード // 現在のディレクトリを走査foreach (new DirectoryIterator(__DIR__) as $file) { if($file->isDot()) { continue; } echo $file, PHP_EOL; if ($file->isDir()) { // DirectoryIteratorのインスタンスを使って1階層深いディレクトリを走査 foreach (new DirectoryIterator($file) as $file2) { if($file2->isDot()) continue; echo "\t{$file2}", PHP_EOL;; } }} を実行すると .cache.config.bashrcBeginner sample srcAdvanced sample src(以下略) のような表記となり、ディレクトリ階層を 1 段深くまで処理する事ができる。 RecursiveDirectoryIterator は、ファイルシステムのディレクトリを再帰的に反復処理するためのインターフェイスである。 ただしこのクラス単体では再帰的な反復処理はできず、RecursiveIteratorIterator クラスを合わせて使う必要がある。 そのため、以下のコード $directorys = new \RecursiveDirectoryIterator(__DIR__);$iterator = new \RecursiveIteratorIterator($directorys);foreach($iterator as $file) { echo $file, PHP_EOL;} を実行すると /home/php_exam/.cache/home/php_exam/.config/home/php_exam/.bashrc/home/php_exam/Beginner/home/php_exam/Beginner/sample/home/php_exam/Beginner/src/home/php_exam/Beginner/src/1.php/home/php_exam/Beginner/src/2.php(略)/home/php_exam/Advanced/home/php_exam/Advanced/sample/home/php_exam/Advanced/src(以下略) のような表記となり、再帰的にファイルを処理する事ができる。 23. 関数 に関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 mail ( string $to , string $subject , string $message , array|string $additional_headers = [] , string $additional_params = "" ) : boolrandom_bytes ( int $length ) : stringrandom_int ( int $min , int $max ) : int mail() 関数は、メールを送る事ができる。 subject と message (本文) に日本語などを用いる場合は、例えば subject であれば RFC 2047 の仕様を満たす必要があるため、適切な処理が必要になる。 その場合は mb_send_mail() 関数を使う事で、ヘッダと本文が mb_language() の設定に基づき変換、エンコードされる。 そのため、以下のコード $r = mb_send_mail('php-exam@example.com', '日本語タイトル', 'mail本文');var_dump($r); を実行すると bool(true) となり、同時にメールが送られる。 送られたメールの本文とそれに関連するヘッダは次のとおりとなる。 Subject: 日本語タイトルContent-Type: text/plain; charset=UTF-8Content-Transfer-Encoding: BASE64bWFpbOacrOaWhw== mail() 関数は、メールを送る事ができる。 第四引数の additional_headers には「追加のヘッダ」の情報が入る。 そのため、以下のコード $external_value = "test \r\ninjection_header: hoge"; // 外部からのデータを仮定// additional_headersの組み立て$headers = [];$headers[] = "X-test: test";$headers[] = "X-test2: {$external_value}";$r = mail('php-exam@example.com', 'subject', 'mail body', implode("\r\n", $headers));var_dump($r); を実行すると、結果は次のとおりとなる。 bool(true) となり、同時にメールが送られる。 送られたメールの本文とそれに関連するヘッダは次のとおりとなる。 X-test: testX-test2: test injection_header: hoge random_bytes() 関数は、暗号論的に安全な、疑似ランダムなバイト列を生成する事ができる。 この関数が返す値はバイナリであるため、使う場合には bin2hex() 関数や base64_encode() 関数を合わせて使う事が多い。 そのため、以下のコード $s = random_bytes(16);var_dump( bin2hex($s) );var_dump( base64_encode($s) ); を実行すると、結果は次のような表記となる。(乱数の値は実行毎に変わる) string(32) "9338779e0f8a377bcac56f0b7e299a03"string(24) "kzh3ng+KN3vKxW8LfimaAw==" random_int() 関数は、暗号論的に安全な、疑似ランダムな整数を生成する事ができる。 min は PHP_INT_MIN 以上、max は PHP_INT_MAX 以下である必要がある。 そのため、以下のコード random_int(PHP_INT_MIN, PHP_INT_MAX + 1); を実行すると、結果は次のとおりとなる。 Fatal error: Uncaught TypeError: random_int(): Argument #2 ($max) must be of type int, float given in ... 24. 関数 に関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 exec ( string $command , array &$output = null , int &$result_code = null ) : string|falseshell_exec ( string $cmd ) : stringsystem ( string $command , int &$result_code = null ) : string|falseescapeshellarg ( string $arg ) : stringescapeshellarg() は、文字列をシングルクオート で括り、既存のシングルクオートを全てクオート/エスケープします。これにより、文字列を直接シェル関数に渡し、単一の安全な引数として処理することを可能にしますescapeshellcmd ( string $command ) : stringescapeshellcmd() は、文字列中においてシェルコマンドを だまして勝手なコマンドを実行する可能性がある文字をエスケープします。(中略)&#;`|*?~^()[]{}$\、\x0A および \xFF については、その文字の前に \ (バックスラッシュ) が追加されます。' および " は、対になっていない場合にのみエスケープされます。proc_nice ( int $increment ) : boolposix_kill ( int $pid , int $sig ) : bool PHP で外部プログラムを動かすためには、 exec() 関数、passthru() 関数、shell_exec() 関数、system() 関数、バックティック演算子 ` などを使う。これらのうち、shell_exec() 関数とバックティック演算子は「同じもの」である。また、exec() 関数と system() 関数は返り値として「コマンド結果の最後の行を返す」、passthru() 関数は「未整形の出力を表示(stdout に出力)する」ところに差異がある。そのため、以下のコード $cmd = 'vmstat 1 1';echo "exec()\n";$s = exec($cmd, $output, $return_var);echo $s, PHP_EOL;echo PHP_EOL;echo "shell_exec()\n";$s = shell_exec($cmd);echo $s, PHP_EOL;echo PHP_EOL;echo "system()\n";ob_start();$s = system($cmd, $return_var);ob_end_clean();echo $s, PHP_EOL; を実行すると、結果は次のような表記となる。 exec()1 0 67236 87052 0 606796 0 0 0 1 1 1 0 0 100 0 0shell_exec()procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----r b swpd free buff cache si so bi bo in cs us sy id wa st1 0 67236 86728 0 606796 0 0 0 1 1 1 0 0 100 0 0system()1 0 67236 86564 0 606796 0 0 0 1 1 1 0 0 100 0 0 シェル引数として使われる文字列をエスケープするための関数として、escapeshellarg() 関数と escapeshellcmd() 関数がある。 どちらも「エスケープをする」処理を行うため、どちらを使っても意味合いは同じなので、どちらを使ってもよい。 しかし、外部入力をシェル引数に使う場合は、必ずどちらかの関数でエスケープをする必要がある。 そのため、以下のコード $arguments = 'ab&c "d ef" > \'gh\'';$e = escapeshellarg($arguments);echo $e, PHP_EOL;$e = escapeshellcmd($arguments);echo $e, PHP_EOL; を実行すると、結果は次のとおりとなる。 'ab&c "d ef" > '\''gh'\''''ab&c "d ef" > '\''gh'\''' 「システムコールとライブラリ関数を規定した POSIX.1 (IEEE Std 1003.1)」標準ドキュメントで 定義された関数へのインターフェイスが提供されている、POSIX 関数がある。 デフォルトで有効になっているが、Windows 環境では利用できないので注意が必要である。 現在のプロセスのグループ ID を返す posix_getpgrp()、 現在のプロセス ID を返す posix_getpid()、 親プロセスの ID を返す posix_getppid()、 プロセス時間を得る posix_times() などがある。 そのため、以下のコード var_dump( posix_getpgrp() );var_dump( posix_getpid() );var_dump( posix_getppid() );var_dump( posix_times() ); を実行すると、結果は次のとおりとなる。(プロセスID、時間のため、値は実行毎に変わる) int(694)int(694)int(523)array(5) { ["ticks"]=> int(1358853909) ["utime"]=> int(0) ["stime"]=> int(0) ["cutime"]=> int(0) ["cstime"]=> int(0)} プロセスに働きかける関数として、proc_nice() 関数や posix_kill() 関数がある。 proc_nice() は自プロセスの優先度を変更し、posix_kill() は指定したプロセスにシグナルを送信する。 シグナル SIGKILL は「プロセスの強制終了」なので、SIGKILL を送信されたプログラムはそこで実行を停止する。 そのため、以下のコード var_dump( trim(`nice`) );proc_nice(10);var_dump( trim(`nice`) );posix_kill(posix_getpid(), SIGKILL);echo 'test', PHP_EOL; を実行すると、結果は次のとおりとなる。 string(1) "0"string(2) "10"Killed 25. 関数 に関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 stream_wrapper_register ( string $protocol , string $class , int $flags = 0 ) : bool成功した場合に true を、失敗した場合に false を返します。stream_wrapper_register() は、 protocol というハンドラが既にある場合、 false を返します。stream_filter_prepend ( resource $stream , string $filtername , int $read_write = ? , mixed $params = ? ) : resource PHP では「ファイル、ネットワーク、データ圧縮などに関する、 共通した一連の関数群と利用法を持つ操作の一般化の手法」として、ストリームがある。また「ストリームにおいてどのように特定の プロトコル/エンコーディングを扱うかを扱うかを指示する付加的なコード」をラッパーと呼称する。PHP において、組み込みのラッパーは様々にあるが、例えば php:// はさまざまな入出力ストリームへのアクセスを提供している。例えば php://input は読み込み専用のストリームで、リクエストの body 部から生のデータを読み込むことができる。そのため、以下のコード var_dump( file_get_contents('php://input') ); を curl https://www.example.com/stream.php -d '{"exam_num":100,"exam_string":"value"}' の形で呼び出すと、結果は次のとおりとなる。 string(38) "{"exam_num":100,"exam_string":"value"}" php://memory および php://temp は読み書き可能なストリームで、一時データをファイルのように保存できるラッパーである。そのため、以下のコード $csv_string = "1,2,3\n4,5,6\n";$fp = fopen('php://memory', 'r+');fwrite($fp, $csv_string);fseek($fp, 0, SEEK_SET);while($row = fgetcsv($fp)) { var_dump($row);}を実行すると、結果は次のとおりとなる。 array(3) { [0]=> string(1) "1" [1]=> string(1) "2" [2]=> string(1) "3"}array(3) { [0]=> string(1) "4" [1]=> string(1) "5" [2]=> string(1) "6"} stream_wrapper_register() 関数を使うと、新しいラッパーとその挙動を登録する事ができる。そのため、以下のコード $r = stream_wrapper_register('dummy', 'PhpStreamDummy');var_dump($r); を実行すると、結果は次のとおりとなる。 Warning: stream_wrapper_register(): class 'PhpStreamDummy' is undefined in ...bool(false) 一方で以下のコード class PhpStream {}$r = stream_wrapper_register('php', 'PhpStream');var_dump($r); を実行すると、結果は次のとおりとなる。 bool(true) stream_filter_prepend() 関数を使うと、フィルタをストリームに付加する事ができる。そのため、exam.txt ファイルに「文字コード sjis で書かれた文字」を入れた上で、以下のコード $file_name = './exam.txt';$fp = fopen($file_name, 'r');var_dump( fgets($fp) );fclose($fp);$fp = fopen($file_name, 'r');$fp2 = stream_filter_prepend($fp, 'convert.iconv.SJIS-win/UTF-8');var_dump( fgets($fp) );fclose($fp); を実行すると、結果は次のとおりとなる。 string(17) "sjis?????????"string(23) "sjisでこんにちは" 26. 関数 に関する説明の中で、誤っているものを1つ選びなさい。なお「\」はバックスラッシュに読み替えること。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 htmlentities ( string $string , int $flags = ENT_COMPAT , string|null $encoding = null , bool $double_encode = true ) : stringhtmlspecialchars ( string $string , int $flags = ENT_COMPAT , string|null $encoding = null , bool $double_encode = true ) : stringENT_COMPAT ダブルクオートは変換しますがシングルクオートは変換しません。ENT_QUOTES シングルクオートとダブルクオートを共に変換します。ENT_NOQUOTES シングルクオートとダブルクオートは共に変換されません。trim ( string $string , string $characters = " \n\r\t\v\0" ) : stringltrim ( string $string , string $characters = " \n\r\t\v\0" ) : stringrtrim ( string $string , string $characters = " \n\r\t\v\0" ) : stringstrpos ( string $haystack , string $needle , int $offset = 0 ) : int|false 文字列 haystack の中で、 needle が最初に現れる位置を探します。返り値needle が見つかった位置を、 haystack 文字列の先頭 (offset の値とは無関係) からの相対位置で返します。 文字列の開始位置は 0 であり、1 ではないことに注意しましょう。needle が見つからない場合は false を返します。 htmlentities() 関数と htmlspecialchars() 関数は、いずれも「文字を HTML エンティティに変換する」。htmlspecialchars() 関数が「特殊文字」だけであるのに対し、htmlentities() 関数は「適用可能な全ての文字」を変換する。この関数は、XSS 対策のためのエスケープ処理としてよく使われている。XSS 対策で使う場合、どちらを使ってもよい。第二引数をデフォルトのままにすると「ダブルクオートは変換するがシングルクオートは変換しない」ので、XSS 対策用には、第二引数を ENT_QUOTES にするとよい。そのため、以下のコード $string = '<>&"\'∞';echo $string, PHP_EOL;echo htmlspecialchars($string), PHP_EOL;echo htmlspecialchars($string, ENT_QUOTES), PHP_EOL;echo htmlentities($string, ENT_QUOTES), PHP_EOL; を実行すると、結果は次のとおりとなる。 <>&"'∞<>&"'∞<>&"'∞<>&"'∞ trim() 関数は文字列の先頭および末尾にあるホワイトスペースを取り除く。また、 文字列の最初から空白 (もしくはその他の文字) を取り除く ltrim() 関数、文字列の最後から空白 (もしくはその他の文字) を取り除く rtrim() 関数もある。第二引数を指定しない場合は 0x20の空白、0x09のタブ、0x0Aのリターン などが削除されるが、引数を指定すると削除したい文字を指定する事も出来る。そのため、以下のコード $string = "\t a b\tc\n";var_dump( trim($string) );var_dump( trim($string, "\t\n ac") ); を実行すると、結果は次のとおりとなる。 string(5) "a b c"string(1) "b" ord() 関数は、文字列の先頭バイトを、0 から 255 までの値に変換する。また chr() 関数は、数値から、1 バイトの文字列を生成する。そのため、以下のコード $s = 'abc';$ascii = ord($s);var_dump($ascii);var_dump( chr($ascii) );$ascii += 5;var_dump( chr($ascii) ); を実行すると、結果は次のとおりとなる。 int(97)string(1) "a"string(1) "f" strpos() 関数は文字列内の部分文字列が最初に現れる場所を見つける。そのため、以下のコード $fn = function($needle) { $string = 'abc'; if ( strpos($string, $needle) != false ) { echo "{$needle} が見つかりました", PHP_EOL; } else { echo "{$needle} は見つかりませんでした", PHP_EOL; }};$fn('a');$fn('z'); を実行すると、結果は次のとおりとなる。 a が見つかりましたz は見つかりませんでした 27. 関数 に関する説明の中で、誤っているものを1つ選びなさい。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 public Phar::__construct ( string $fname , int $flags = ? , string $alias = ? ) phar 拡張モジュールは、PHP アプリケーション全体をひとつの "phar" (PHP Archive) ファイルにまとめて配布やインストールを容易にするためのものである。例えば、PHP で最近よく使われている Composer が Phar で配布されている。そのため、composer.phar の中は #!/usr/bin/env php という書き出しになっている。また、中身が PHP ファイルなので、ファイルに実行権限がなくても php composer.phar のコマンドラインで動かす事ができる。 phar のスタブには __HALT_COMPILER() という関数が使われている。__HALT_COMPILER() は「コンパイラの実行を中止する」関数である。この関数以降はコンパイルされる事がなく、インストール用ファイルのようなデータを PHP スクリプトに埋め込んでいる場合等に使われる。そのため、以下のコード echo 'test', PHP_EOL;__HALT_COMPILER();;dummy datahoge を実行すると、結果は次のとおりとなる。 Parse error: syntax error, unexpected identifier "data" in ... しかし以下のコード echo 'test', PHP_EOL;__HALT_COMPILER();;dummy data;hoge; を実行すると、結果は次のとおりとなる。 test phar のファイルを作る場合は、Phar クラスを用いる。そのため、hoge.php というファイル名で class Hoge { private $i = 999;} がある前提で、以下のコード $obj = new Phar('./exam.phar');$obj->addFile('hoge.php'); を実行すると exam.phar ができあがる。 ただし php.ini の phar.readonly が 0 でない時は、結果は次のとおりとなる。 Fatal error: Uncaught UnexpectedValueException: creating archive "./exam.phar" disabled by the php.ini setting phar.readonly in ... phar のファイルを使う場合には、Phar ストリームラッパーを使う。Phar ストリームラッパーは phar:// から始まる。そのため、先ほど作成した exam.phar がある前提で、以下のコード require_once('phar://exam.phar/hoge.php');$obj = new Hoge();var_dump($obj); を実行すると、結果は次のとおりとなる。 object(Hoge)#1 (1) { ["i":"Hoge":private]=> int(999)} 28. 関数 に関する説明の中で、誤っているものを1つ選びなさい。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 call_user_func ( callable $callback , mixed ...$args ) : mixedcall_user_func_array ( callable $callback , array $args ) : mixedforward_static_call ( callable $callback , mixed ...$args ) : mixedcallback パラメータで指定したユーザー定義の関数あるいはメソッドを、 それに続く引数を指定してコールします。この関数はメソッドのコンテキストでコールしなければなりません。 クラスの外部で使用することはできません。 この関数は 遅延静的束縛 を使います。forward_static_call_array ( callable $callback , array $args ) : mixedcallback パラメータで指定したユーザー定義の関数あるいはメソッドをコールします。 この関数はメソッドのコンテキストでコールしなければなりません。 クラスの外部で使用することはできません。 この関数は 遅延静的束縛 を使います。 転送先のメソッドへのすべての引数は値渡しで、 call_user_func_array() と同様に配列で指定します。function_exists ( string $function_name ) : bool組み込みの内部関数およびユーザー定義関数の中から、 function_name で指定した名前の関数を探します。register_shutdown_function ( callable $callback , mixed ...$args ) : voidスクリプト処理が完了したとき、あるいは exit() がコールされたときに実行するコールバック関数を登録します。register_shutdown_function() は複数回コールする ことが可能で、登録された順に関数がコールされます。 登録した関数内で exit() をコールした場合、 処理はそこで終了してその他のシャットダウン関数はコールされません。 call_user_func() 関数 と call_user_func_array() 関数は、最初の引数で指定したコールバック関数をコールする。 call_user_func() 関数が「渡す引数を可変長引数で指定」するのに対して、call_user_func_array() 関数は「渡す引数を配列で指定」する。 第一引数に[オブジェクト, メソッド名]または[クラス名, メソッド名]の配列を渡すと、オブジェクトメソッドや静的メソッドを呼ぶ事も出来る。 また、引数を参照渡しで受け取る関数を call_user_func() から呼ぶと、エラー(Warning)が発生する。 そのため、以下のコード function func($a, $b) { echo __FUNCTION__, PHP_EOL; var_dump($a, $b); echo PHP_EOL;}function func2(&$v) { $v[] = 999;}class Hoge { public function func1($a) { echo __METHOD__, PHP_EOL; var_dump($a); echo PHP_EOL; } public static function func2($a) { echo __METHOD__, PHP_EOL; var_dump(get_called_class(), $a); echo PHP_EOL; }}call_user_func('func', 1, 2);call_user_func_array('func', ['var', 'val']);$obj = new Hoge();call_user_func([$obj, 'func1'], 999);call_user_func([Hoge::class, 'func2'], 'val');$v = [];call_user_func('func2', $v);var_dump($v); を実行すると、結果は次のとおりとなる。 funcint(1)int(2)funcstring(3) "var"string(3) "val"Hoge::func1int(999)Hoge::func2string(4) "Hoge"string(3) "val"Warning: func2(): Argument #1 ($v) must be passed by reference, value given in ...array(0) {} forward_static_call() 関数 と forward_static_call_array() 関数は、静的メソッドをコールする。 そのため、以下のコード class Hoge { public static function func() { echo __METHOD__, PHP_EOL; var_dump(get_called_class()); echo PHP_EOL; }}class Foo extends Hoge { public static function callMethod() { forward_static_call(['Hoge', 'func']); echo PHP_EOL; call_user_func(['Hoge', 'func']); }}Foo::callMethod();forward_static_call(['Hoge', 'func']); を実行すると、結果は次のとおりとなる。 Hoge::funcstring(3) "Foo"Hoge::funcstring(4) "Hoge"Fatal error: Uncaught Error: Cannot call forward_static_call() when no class scope is active in ... function_exists() 関数 は指定した関数が定義されている場合に true を返す。 そのため、以下のコード class Hoge { public function func() { }}function foo() {}$fn = function($v) { var_dump( $v, function_exists($v) ); echo PHP_EOL;};$fn( [Hoge:class, 'func'] );$fn( 'foo' );$fn( 'function_exists' );$fn( 'echo' );$fn( 'dummy' ); を実行すると、結果は次のとおりとなる。 array(2) { [0]=> string(4) "Hoge" [1]=> string(4) "func"}bool(true)string(3) "foo"bool(true)string(15) "function_exists"bool(true)string(4) "echo"bool(true)string(5) "dummy"bool(false) register_shutdown_function() 関数はシャットダウン時に実行する関数を登録する。 そのため、以下のコード register_shutdown_function(function() { echo "register_shutdown_function 1", PHP_EOL;});register_shutdown_function(function($v) { echo "register_shutdown_function 2 {$v}", PHP_EOL;}, 'val');register_shutdown_function(function() { echo "register_shutdown_function 3", PHP_EOL; exit;});register_shutdown_function(function() { echo "register_shutdown_function 4", PHP_EOL;});echo "start", PHP_EOL;exit; を実行すると、結果は次のとおりとなる。 startregister_shutdown_function 1register_shutdown_function 2 valregister_shutdown_function 3 29. 関数 に関する説明の中で、誤っているものを1つ選びなさい。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 openssl_encrypt ( string $data , string $method , string $key , int $options = 0 , string $iv = "" , string &$tag = NULL , string $aad = "" , int $tag_length = 16 ) : string|falseopenssl_decrypt ( string $data , string $method , string $key , int $options = 0 , string $iv = "" , string $tag = "" , string $aad = "" ) : string|falseopenssl_x509_parse ( mixed $x509cert , bool $shortnames = true ) : array openssl_get_cipher_methods() 関数は利用可能な暗号メソッドを取得する。 そのため、以下のコード var_dump( openssl_get_cipher_methods() ); を実行すると、結果は次のとおりとなる。 array(201) { [0]=> string(11) "AES-128-CBC" [1]=> string(21) "AES-128-CBC-HMAC-SHA1" [2]=> string(23) "AES-128-CBC-HMAC-SHA256"(中略) [199]=> string(8) "seed-ecb" [200]=> string(8) "seed-ofb"} openssl_encrypt() 関数はデータを暗号化する。 そのため、以下のコード $method = 'AES-128-CBC';$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));$crypt = openssl_encrypt('exam', $method, 'key', 0, $iv);var_dump( $crypt ); を実行すると string(24) "GoaKntvUhPOEl5VS92Uiyg==" となる ( initialization vector が random なので、値は実行毎に変わる)。 なお、例えば CBC のような「initialization vector が必要な暗号利用モード」で initialization vector を指定しないと Warning が出る。 そのため以下のコード $method = 'AES-128-CBC';$crypt = openssl_encrypt('exam', $method, 'key', 0);var_dump( $crypt ); を実行すると、結果は次のとおりとなる。 Warning: openssl_encrypt(): Using an empty Initialization Vector (iv) is potentially insecure and not recommended in ...string(24) "5kBbn5hweqDYYT6VQjj2ag==" openssl_decrypt() 関数はデータを復号する。 そのため、以下のコード $method = 'AES-128-CBC';$key = 'key';$crypt = openssl_encrypt('exam', $method, $key, 0, openssl_random_pseudo_bytes(openssl_cipher_iv_length($method)));var_dump( $crypt );$decrypt_string = openssl_decrypt($crypt, $method, $key);var_dump( $decrypt_string ); を実行すると string(24) "MWFlUhetizqXeV321jVUvA=="string(4) "exam" となる。 initialization vector が random なので $crypt は実行毎に変わるが、復号された「exam」は常に同じになる。 また initialization vector は $crypt の中に含まれているため、引数として与えなくても正しく復号される。 openssl_x509_parse() 関数は X509 証明書をパースし、配列として情報を返す。 そのため、以下のコード $resource = @stream_socket_client( 'ssl://www.phpexam.jp:443', $errno, $errstr, 60, STREAM_CLIENT_CONNECT, stream_context_create(['ssl' => ['capture_peer_cert' => true]]),);$cont = stream_context_get_params($resource);$x509 = openssl_x509_parse($cont['options']['ssl']['peer_certificate']);var_dump($x509); を実行すると、結果は次のとおりとなる。 array(16) { ["name"]=> string(18) "/CN=www.phpexam.jp" ["subject"]=> array(1) { ["CN"]=> string(14) "www.phpexam.jp" } ["hash"]=> string(8) "491a204d" ["issuer"]=> array(3) { ["C"]=> string(2) "US" ["O"]=> string(13) "Let's Encrypt" ["CN"]=> string(2) "R3" } ["version"]=> int(2)(後略) 30. 関数 に関する説明の中で、誤っているものを1つ選びなさい。また、すべてのコードには下記のコードが適切な箇所に書かれているものとする。 declare(strict_types=1);error_reporting(-1); 下記はマニュアルから一部引用した内容である。 date_create()この関数は次の関数のエイリアスです。 DateTime::__construct()strtotime ( string $datetime , int|null $baseTimestamp = null ) : int|falseこの関数は英語の書式での日付を含む文字列が指定されることを期待しており、 baseTimestamp で与えられたその形式から Unix タイムスタンプ (1970 年 1 月 1 日 00:00:00 UTC からの経過秒数) への変換を試みます。 baseTimestamp が指定されていない場合は現在日時に変換します。成功時はタイムスタンプ、そうでなければ false を返します。time ( ) : intdate_default_timezone_set ( string $timezoneId ) : bool date_default_timezone_get ( ) : stringこの関数は、デフォルトのタイムゾーンを以下の優先順位で取得して返します。・date_default_timezone_set() 関数を使用して 設定したタイムゾーン (もし何か設定されていれば) を読み込む・date.timezone ini オプション (設定されていれば) を読み込む・上のすべてが失敗した場合は、date_default_timezone_get() はデフォルトのタイムゾーンである UTC を返します。 date_create() 関数を使うと DateTime クラスのインスタンスが作成できる。 そのため、以下のコード $date_obj = date_create('2021-01-01 11:22:33');var_dump($date_obj); を実行すると、結果は次のとおりとなる。 object(DateTime)#1 (3) { ["date"]=> string(26) "2021-01-01 11:22:33.000000" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC"} strtotime() 関数を使うと英文形式の日付を Unix タイムスタンプに変換することが出来る。 strtotime() は様々な書式を扱う事が出来るため、例えば "2008/6/30"、"30-6-2008"、"30-June 2008"、"July 1st, 2008" などの様々な書式に対応している。 そのため、以下のコード $t = strtotime('July 1st, 2008 4:08:37 pm');var_dump($t);echo date('Y-m-d H:i:s', $t) , PHP_EOL;echo PHP_EOL; $t = strtotime('July 1st, 1000 4:08:37 pm');var_dump($t); を実行すると、結果は次のとおりとなる。 int(1214928517)2008-07-01 16:08:37bool(false) time() 関数を使うと現在の Unix タイムスタンプを得る事ができる。 そのため、以下のコード $t = time();var_dump($t); を実行すると、結果は次のとおりとなる。 int(1610912345) date_default_timezone_set() 関数を使うとスクリプト中の日付/時刻関数で使用されるデフォルトタイムゾーンを設定する事ができる。 また、date_default_timezone_get() 関数を使うとスクリプト中の日付/時刻関数で使用されるデフォルトタイムゾーンを取得する事ができる。 そのため、以下のコード $t = strtotime('2038-1-19 3:14:8');var_dump( date_default_timezone_get() );echo date(DATE_ATOM, $t), PHP_EOL;echo PHP_EOL;date_default_timezone_set('Asia/Tokyo');var_dump( date_default_timezone_get() );echo date(DATE_ATOM, $t), PHP_EOL; を実行すると、結果は次のとおりとなる。 string(3) "UTC"2038-01-01T03:14:08+00:00string(10) "Asia/Tokyo"2038-01-01T12:14:08+09:00 Time's up試験は終了です。「回答を送信」ボタンを押下してください。