この記事のキーワード
dir()・chdir()・getcwd()・DirectoryIterator・RecursiveDirectoryIterator・RecursiveIteratorIterator・mail()・mb_send_mail()・random_bytes()・bin2hex()・base64encode()・random_int()
PHP8上級資格取得へ向けたアウトプット記事になります。
PHP試験運営団体より公開されている模擬試験を1つ1つみていく形で事細かくみていきますが、勉強途中ゆえ間違っている部分に関してはご了承ください。
PHP上級資格のメイン教材はPHPマニュアルと指定されていますので、このマニュアルを主に踏襲した内容となっています。
試験問題1-2 | 模擬試験を解く#1 |
---|---|
試験問題3 | 模擬試験を解く#2 |
試験問題4 | 模擬試験を解く#3 |
試験問題5 | 模擬試験を解く#4 |
試験問題6 | 模擬試験を解く#5 |
試験問題7 | 模擬試験を解く#6 |
試験問題8 | 模擬試験を解く#7 |
試験問題9 | 模擬試験を解く#8 |
試験問題10 | 模擬試験を解く#9 |
試験問題11 | 模擬試験を解く#10 |
試験問題12 | 模擬試験を解く#11 |
試験問題13-14 | 模擬試験を解く#12 |
試験問題15-16 | 模擬試験を解く#13 |
試験問題17-18 | 模擬試験を解く#14 |
試験問題19-20 | 模擬試験を解く#15 |
試験問題21 | 模擬試験を解く#16 |
本記事 | 模擬試験を解く#17 |
主な勉強教材は以下になります。
1.独習PHP第4版
2.はじめてのPHP
3.PHP公式マニュアル
PHP8上級試験模擬問題
試験問題22
関数に関する説明の中で、誤っているものを1つ選びなさい。
なお「¥」はバックスラッシュに読み替えること。
選択肢①
Directory クラスは、ディレクトリ内を走査するためのクラスである。
Directory クラスのインスタンスを作るには dir() 関数を使います。new 演算子は使いません。
そのため、以下のコード
declare(strict_types=1);
error_reporting(-1);
echo $dir = dir();
while (false !== ($entry = $dir->read())) {
echo $entry;
}
$dir->close();
を実行すると
object(Directory)#1 (2) {
["path"]=>
string(10) "/home/php_exam"
["handle"]=>
resource(5) of type (stream)
}
.
..
.cache
.config
(以下略)
となる。
dir(string $directory, ?resource $context = null): Directory|false
dir()関数は、Directoryクラスの新しいインスタンスを作成します。このインスタンスは、ディレクトリ内のファイルとサブディレクトリを読み取るために使用されます。
whileループでは、ディレクトリ内のすべてのファイルおよびサブディレクトリを読み取っています。また、read()メソッドはディレクトリ内の次のエントリを返しています。
$dir->close();は、Directoryオブジェクトを閉じますが、これは使用が完了したら必ず行う必要があります。close()メソッドが呼び出されると、Directoryオブジェクトはクリーンアップされ、ディレクトリリソースが解放されます。
$dir->close();を実行すると、Directoryオブジェクトの情報が表示されます。これには、ディレクトリのパスとハンドルの情報が含まれます。その後、ディレクトリ内のすべてのエントリが表示されます。.
と ..
は、それぞれカレントディレクトリと親ディレクトリを表します。
よって、この選択肢は合っています。
選択肢②
chdir() 関数はカレントディレクトリを変更する。またgetcwd() 関数はカレントのワーキングディレクトリを取得する。
chdir( string $directory ) : bool
getcwd( ) : string
そのため、以下のコード
declare(strict_types=1);
error_reporting(-1);
var_dump(getcwd());
chdir('../');
var_dump(getcwd());
を実行すると
string(14) "/home/php_exam"
string(5) "/home"
となる。
getcwd()関数は、カレントのワーキングディレクトリを取得し、chair()関数は、カレントディレクトリを変更します。
引数として、新しいディレクトリを指定します。この場合、../
は、親ディレクトリに移動することを意味しています。
最初のvar_dump(getcwd())
で、現在のワーキングディレクトリが /home/php_exam
であることがわかりますが、chdir('../')
を実行すると、ワーキングディレクトリが /home
に変更されます。
よって、この選択肢は合っています。
選択肢③
DirectoryIterator クラスは、
DirectoryIterator extends SplFileInfo implements SeekableIterator {
public DirectoryIterator::__construct( string $path )
SeekableIteratorを実装しているためforea
またDirectoryIteratorがSplFileInf
またSplFileInfo クラスには __toString() が実装されているため、
そのため、以下のコード
declare(strict_types=1);
error_reporting(-1);
// 現在のディレクトリを走査
foreach (new DirectoryIterator(__DIR__) as $file) {
if($file->isDot()) {
continue;
}
echo $file , "¥n";
if ($file->isDir()) {
// DirectoryIteratorのインスタンスを使って1階層深いディレクトリを走査
foreach (new DirectoryIterator($file) as $file2) {
if($file2->isDot()) continue;
echo "¥t{$file2}¥n";
}
}
}
を実行すると
.cache
.config
.bashrc
Beginner
sample
src
Advanced
sample
src
(以下略)
となり、ディレクトリ階層を1段深くまで処理する事ができる。
DirectoryIteratorクラスで利用できるメソッド
DirectoryIterator::isDot()
メソッドは、DirectoryIterator
オブジェクトがカレントディレクトリまたは親ディレクトリを指しているかどうかを判定するために使用されます。
カレントディレクトリは「.」、親ディレクトリは「..」で表されます。isDot()
メソッドは、現在のDirectoryIterator
オブジェクトがこれらのディレクトリを表している場合に TRUE
を返し、それ以外の場合は FALSE
を返します。
このメソッドはディレクトリ内のファイルやサブディレクトリのみを処理するために使用されます。isDot()
メソッドを使用することで、カレントディレクトリと親ディレクトリを無視することができます。
選択肢文では「ディレクトリ階層を1段深くまで処理する事ができる」とありますが、実際にはディレクトリ内のファイルやサブディレクトリを列挙していることから、現在のディレクトリを走査してそれぞれのファイル名を表示し、ディレクトリが見つかった場合はそれを走査してファイル名を表示しているということがわかります。
よって、この選択肢は誤りです。
選択肢④
RecursiveDirectoryIterator は、ファイルシステムのディレクトリを再帰的に反復処理するためのインターフェイスである。
ただしこのクラス単体では再帰的な反復処理はできず、RecursiveIteratorIteratorクラスを合わせて使う必要がある。
RecursiveDirectoryIterator extends FilesystemIterator implements SeekableIterator , RecursiveIterator {
RecursiveIteratorIterator implements OuterIterator {
public RecursiveIteratorIterator::__construct( Traversable iterator,intmode = RecursiveIteratorIterator::LEAVES_ONLY , int $flags = 0 )
SeekableIterator extends Iterator {
RecursiveIterator extends Iterator {
Iterator extends Traversable {
そのため、以下のコード
declare(strict_types=1);
error_reporting(-1);
$iterator = new RecursiveIteratorIterator(directorys);
foreach($iterator as $file) {
echo $file , "¥n";
}
を実行すると
/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
(以下略)
となり、再帰的にファイルを処理する事ができる。
「RecursiveDirectoryIterator」と「RecursiveIteratorIterator」を組み合わせることで、指定したディレクトリ以下のファイルやサブディレクトリを再帰的に反復処理することができます。以下のようなコードを実行すると、再帰的にファイルを処理することができます。
$directory = new RecursiveDirectoryIterator('/path/to/directory');
$iterator = new RecursiveIteratorIterator($directory);
foreach ($iterator as $file) {
echo $file, "\n";
}
上記のコードでは、指定したディレクトリ以下のファイルやサブディレクトリを再帰的に処理して、ファイル名を表示しています。
よって、この選択肢は合っています。
ある関数の中でその関数自身を呼び出すことによって、同じ処理を繰り返し行うことを指します。
再帰的な処理は、同じ処理を繰り返し行う必要がある場合に有用で、たとえば、ディレクトリの中にディレクトリがある場合、再帰的な処理を用いることで階層構造をたどって処理を行うことができるためスマートに処理できます。
配列やオブジェクトのような反復可能な構造から順番に値を取り出すためのインターフェースのことを指します。イテレータを実装したクラスは、foreach文で反復処理することができます
Traversableは、foreach文で反復処理できるクラスに実装するインターフェースで、Iteratorは、基本的なイテレータの機能を定義するインターフェースで、IteratorAggregateは、イテレータを返すためのインターフェースです。
試験問題23
関数に関する説明の中で、誤っているものを1つ選びなさい。
なお「¥」はバックスラッシュに読み替えること。
選択肢①
mail() 関数は、mailを送る事ができる。
mail( string $to, string $subject, string $message, array|string $additional_headers = [] , string $additional_params=""):bool
とmessage(mail本文)に日本語などを用いる場合は、例えばsubjectであればRFC2047の仕様を満たす必要があるため、適切な処理が必要になる。
その場合はmb_send_mail()関数を使う事で、ヘッダと本文がmb_language()の設定に基づき変換、エンコードされる。そのため、以下のコード
declare(strict_types=1);
error_reporting(−1);
$r = mb_send_mail('php-exam@example.com', '日本語タイトル', 'mail本文');
var_dump($r);
を実行するとbool(true)となり、同時にmailが送られる。送られたmailの本文とそれに関連するヘッダは
Subject: 日本語タイトル
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: BASE64
bWFpbOacrOaWhw==
となる。
まずは規格について見ていきます。
電子メールの構文に関する規格で、この規格にはメールのヘッダや本文、アドレスの表記法などが定義されています。
例えば、メールアドレスは “user@example.com” のような形式で表記され、メールのヘッダには「From:」、「To:」、「Subject:」などが含まれます。
このように、メールの送信に必要な様々な要素が定義されています。
電子メールのエンコーディングに関する規格で、この規格にはメールの件名や本文などの文字列がマルチバイト文字を含む場合、適切なエンコーディング方式によってエンコードされる必要があることが定義されています。
RFC 2047では、Base64エンコーディングやQuoted-Printableエンコーディングなどのエンコーディング方式が定義されており、これらのエンコーディング方式を使用することで、日本語や他のマルチバイト文字を含むメールの送信が可能になります。
つまり、RFC 2822はメールの構文に関する規格であり、RFC 2047はマルチバイト文字を含むメールのエンコーディングに関する規格であるということです。
mb_send_mail()関数は、件名や本文にマルチバイト文字を含んだメールを送信するための関数ですが、mail()関数はマルチバイト文字に対応していないため、文字エンコーディングの変換を自前で行う必要があります。
以下2つはmb_send_mail()関数を利用してメールを送信する例です.
配列を使わない例
// 宛先
$to = 'user111@example.com';
// 件名
$subject = '転生してもPHPの勉強が必要だった件について';
// 本文
$body = 'こんにちは';
// ヘッダー情報
$headers = "From : user222@example.com\r\n";
$headers .= "Cc : user333@example.com\r\n";
$headers .= "X-Mailer : PHP/" . phpversion();
// メールを送信
if(mb_send_mail($to, $subject, $body, $headers)){
print 'メールを送信しました。';
}else{
print 'メール送信に失敗しました。';
}
配列を使用する例(PHP7.2以降可能)
// 宛先
$to = 'user111@example.com';
// 件名
$subject = '転生してもPHPの勉強が必要だった件について';
// 本文
$body = 'こんにちは';
// ヘッダー情報
$headers = [
'From' => 'user222@example.com',
'Cc' => 'user333@example.com',
'X-Mailer' => 'PHP/' . phpversion()
];
// メールを送信
if(mb_send_mail($to, $subject, $body, $headers)){
print 'メールを送信しました。';
}else{
print 'メール送信に失敗しました。';
}
よって、この選択肢は合っています。
選択肢②
mail() 関数は、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)となり、同時にmailが送られる。送られたmailの本文とそれに関連するヘッダは
X-test: test
X-test2: test injection_header: hoge
となる。
メールを送信する際には、必ずFromヘッダが含まれていなければなりません。 additional_headers
パラメータで指定するか、 あるいは php.ini にデフォルト値を指定します。
指定しなかった場合は、以下のようなエラーメッセージが返ります。
Warning: mail(): "sendmail_from" not set in php.ini or custom "From:" header missing
よってこの選択肢は誤りといえます。
選択肢③
random_bytes() 関数は、暗号論的に安全な、疑似ランダムなバイト列を生成する事ができる。
random_bytes(int length):string
この関数が返す値はバイナリであるため、使う場合にはbin2hex()関数やbase64encode()関数を合わせて使う事が多い。
そのため、以下のコード
declare(strict_types=1);
error_reporting(−1);
$s = random_bytes(16);
var_dump( bin2hex($s));
var_dump(base64encode($s));
を実行すると
string(32) "9338779e0f8a377bcac56f0b7e299a03"
string(24) "kzh3ng+KN3vKxW8LfimaAw=="
となる(乱数の値は実行毎に変わる)。
random_bytes()関数は、指定された長さの暗号論的に安全なランダムなバイト列を生成します。今回は、16バイトのランダムなバイト列が生成されていますが、var_dump()関数を使って表示するために、bin2hex()関数を使って16進数の文字列に変換しています。
一方で、base64encode()関数の出力は、生成されたバイト列をBase64エンコードしたものです。Base64エンコードはバイナリデータをテキスト形式に変換するためのエンコーディング方式であり、結果として24文字の文字列が表示されます。
なお、実行ごとに異なる結果が得られますが、これはrandom_bytes()関数が暗号論的に安全な乱数を生成するためであり、ランダムなデータが生成されるたびに異なる結果が得られるからです。
よって、この選択肢は合っています。
選択肢④
random_int() 関数は、暗号論的に安全な、疑似ランダムな整数を生成する事ができる。
random_int(int min,int max):int
minはPHP_INT_MIN 以上、maxはPHP_INT_MAX 以下である必要がある。
そのため、以下のコード
declare(strict_types=1);
error_reporting(-1);
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 ...
となる。
random_int()関数は、指定された範囲内で暗号論的に安全なランダムな整数を生成しますが、関数の引数には、最小値 (min) と最大値 (max) を指定する必要があります。
PHP_INT_MAXは PHP の整数型が表現できる最大の整数値であり、PHP_INT_MINは最小の整数値のため、範囲指定を行う際にはこれらの範囲内の数値である必要があります。
よって、この選択肢は合っています。