プログラミング

プログラミング初心者のPHP修羅の道#14〜PHP8上級問題を徹底追求する〜

この記事のキーワード

list()関数TraversableIterablecountablestdClassオフセット

PHP8上級資格取得へ向けたアウトプット記事になります。

PHP試験運営団体より公開されている模擬試験を1つ1つみていく形で事細かくみていきますが、勉強途中ゆえ間違っている部分に関してはご了承ください。

PHP上級資格のメイン教材はPHPマニュアルと指定されていますので、このマニュアルを主に踏襲した内容となっています。

主な勉強教材は以下になります。

1.独習PHP第4版

2.はじめてのPHP

 

3.PHP公式マニュアル

 

PHP8上級試験模擬問題

試験問題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)

 

 

まずはlist()関数について深掘っていきます。

以下はlist()関数を使用して、変数から値を取得する例です。

// ---------- 例① ----------
$info = array('コーヒー', '茶色', 'カフェイン');

/**すべての変数の取得
 * コーヒー の色は 茶色 で、カフェイン が含まれています。
 **/
list($drink, $color, $power) = $info;
echo "$drink の色は $color で、$power が含まれています。\n";

/**一部の変数の取得(三番目のみの取得)
 * カフェイン が欲しい!
 **/
list( , , $power) = $info;
echo "$power が欲しい!\n";

//★★★ list() は文字列では動作しません ★★★
list($bar) = "abcde";
var_dump($bar); // NULL
// --------------------

//---------- 例② ----------
//ネストした list() の使用法
list($a, list($b, $c)) = array(1, array(2, 3));
var_dump($a, $b, $c); // int(1), int(2), int(3)
// --------------------

//---------- 例③ ----------
// list() と添字の定義順
$foo = array(2 => 'a', 'foo' => 'b', 0 => 'c');
$foo[1] = 'd';
list($x, $y, $z) = $foo;
var_dump($foo, $x, $y, $z); 

//出力結果
array(4) {
  [2]=>
  string(1) "a"
  ["foo"]=>
  string(1) "b"
  [0]=>
  string(1) "c"
  [1]=>
  string(1) "d"
}
string(1) "c"
string(1) "d"
string(1) "a"
// --------------------

//---------- 例④ ----------
// list() を明示的にキーを指定して使う
list(1 => $second, 3 => $fourth) = [1, 2, 3, 4];
echo "$second, $fourth\n"; // 2, 4

list()関数は、文字列を扱うことは出来ません

また、選択肢のコードではlist()関数と同様の扱いができる短縮構文が使用されています。基本的にはlist()関数と同じような使い方ができます。

//ブラケット構文による分割代入の例
$data = [1, 2, 3];
[$a, $b, $c] = $data;
var_dump($a, $b, $c) // int(1), int(2), int(3)

//存在しないインデックスを指定した場合(警告)
$data = [1, 2, 3];
[$a, $b, $c, $d] = $data;
var_dump($a, $b, $c) // Undefined array key 3

//データ数よりも指定インデックス数が少ない場合(無視される)
$data = [1, 2, 3, 4];
[$a, $b, $c] = $data;
var_dump($a, $b, $c) // int(1), int(2), int(3)

// foreachによる分割代入の例
$data = array(
            array("Book", "電子書籍"),
            array("TV", "NHK"), 
            array("Web", "YouTube"),
        );

foreach($data as [$media, $value]){
    echo $media.":".$value."\n";
}

/**出力結果
*Book:電子書籍
*TV:NHK
*Web:YouTube
**/

PHP7.1よりブラケット([])による分割代入が導入され、list()関数と同じような操作がより簡潔に記述できるようになりました。

よって、この選択肢は合っています。

 

選択肢②

 

新しい擬似型 (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 ...

 

 

この選択肢のポイントは「Traversableインターフェース」を実装しているかどうかです。

iterable型は、配列またはTraversableインターフェイスを実装したオブジェクトを受け付けることができます。

最初のコードでは、iterable型をパラメータとして取る関数「hoge」を定義し、配列およびArrayObjectインスタンスを引数として渡しています。

ArrayObjectは配列とオブジェクトの機能を組み合わせたデータ構造で、配列のようにインデックスを使って要素にアクセスすることができます。

また、CountableIteratorAggregateインターフェースを実装しているため、count()関数やforeachループを使用することができます。

// ①--- 配列と同じように使用する例 ---
$fruits = new ArrayObject(array('apple', 'orange', 'banana'));

// 配列のように要素にアクセスすることができる
echo $fruits[0]; // apple

// count()関数を使用することができる
echo count($fruits); // 3

// foreachループを使用することができる
foreach ($fruits as $fruit) {
    echo $fruit . "\n";
}
// apple
// orange
// banana


// ②--- ArrayObjectを参照として使用する例 ---
function processFruits(ArrayObject &$fruits) {
    // 配列を変更する処理
    $fruits[] = 'grape';
}

$fruits = new ArrayObject(array('apple', 'orange', 'banana'));

processFruits($fruits);

foreach ($fruits as $fruit) {
    echo $fruit . "\n";
}
// apple
// orange
// banana
// grape

 

次に、stdClassオブジェクトを使用したコードについてみていきます。

stdClassオブジェクトを引数として渡していますが、stdClassはTraversableインターフェイスを実装していないため、この関数の実行時に「Argument 1 passed to hoge() must be iterable, object given」というエラーメッセージが表示されます。

つまり、iterable型を引数に取る場合、引数に渡すオブジェクトがTraversableインターフェイスを実装している必要があるということです。

よって、選択肢は合っています。

stdClassは、PHPの標準クラスであり、空のオブジェクトを作成するための便利な方法です。

stdClassオブジェクトは属性を持つことができ、オブジェクトのプロパティとしてアクセスできます。

$obj = new stdClass();

// 属性を設定してアクセスする
$obj->name = 'John';
$obj->age = 30;
echo $obj->name;
echo $obj->age;
// 出力結果:John30

// JSONデータをstdClassオブジェクトに変換する
$json = '{"name":"John","age":30}';
$obj = json_decode($json);
echo $obj->name;
echo $obj->age;
// 出力結果:John30

このように、stdClassはPHPで簡単なオブジェクトを作成するための便利な機能となっています。

 

選択肢③

 

数値形式ではない文字列を使って、数値を期待する演算 (+, -, *, /, **, %<<, >>, |, &, ^ や、これらを用いた代入演算) を行おうとしたときに、 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)

 

 

数値形式の文字列に関して深掘っていきます。

PHP7.1.0〜8.0.0までは数値形式ではない文字列を使って数値演算を行おうとすると、PHPはE_WARNINGレベルの警告を発生させます。

数値演算の左右のオペランドのうち、少なくとも1つが数値形式ではない場合その文字列は0として解釈されます。

なお、PHP 8.0以降では、数値演算において数値形式ではない文字列を使用した場合には、E_WARNINGではなく、TypeErrorがスローされます。

以下は文字列の計算について例を挙げています。

①数値+先頭が数値の文字列

$foo = 1 + "10.2 String"; // float(11.2) 
// PHP7.1.0〜8.0.0 ※E_NOTICE
// PHP8.0.0以降 ※E_WARNING(A non-numeric value encountered)

②数値+数値でない文字列

$foo = 1 + "String 10.2"; // float(1) 
// PHP 8.0.0以前 float(1) ※E_WARNING
// PHP 8.0.0以降
//※TypeError(Unsupported operand types: string + string)

③数値+指数表現

$foo = 1 + "10.2E1"; // int(103) 
$foo = 1 + 10.2E1;   // int(103) 
$foo = 1 + "-1.3e3"; // float(-1299)

 

 

この問題では、PHP8.0.0からの事象を取り上げているわけではなく、PHP7.0からPHP7.1に移行する際の事象を取り上げています。

そのため、こちらの選択肢は合っていると言えます。

 

選択肢④

 

文字列操作関数のうちオフセット指定のできるものすべてについて、負のオフセットを指定できるようになった。
[]{} による 文字列への文字単位のアクセス についても同様となり、負のオフセットは、文字列の末尾からのオフセットと解釈される。
負の値を渡した場合、-0 が「文字列の末尾」となる。そのため、以下のコード

$s = 'abcdefg';
var_dump( $s[-1] );

は正しく実行でき、結果は次のとおりとなる。

string(1) "f"

 

ここではオフセットについて深掘っていきます。

$String = "abcde";
var_dump($String[0]);  // string(1) "a"
var_dump($String[-0]); // string(1) "a"
var_dump($String[-1]); // string(1) "e"

上記のように、角括弧[]を使用してゼロから始まるオフセットを指定すると、 文字列内の任意の文字にアクセスし、修正することが可能です。 つまり、文字列を文字の配列として考えることが可能になります。

文字列の先頭は[0]で表し、末尾は[-1]で表します。

そのため、この選択肢は誤りです。

PHP 7.1.0 以前

・負のオフセットで読み込もうとするとE_NOTICEが発生し(空文字列を返しす)

・負のオフセットで書き込もうとするとE_WARNINGが発生 (文字列変更不可)

PHP 7.1.0 以降

負の文字列オフセットが対応可能となり、文字列の末尾からのオフセットを表せるようになる。

・オフセットに空の文字列を代入するとfatal エラーが発生。

・PHP 8.0.0より前のバージョンでは、$str{5}のように波括弧を使用してアクセス可能だった(PHP 7.4.0 以降は非推奨、 PHP 8.0.0 以降はサポートなし(fatalエラー))。

PHP 8.0.0以降

・範囲外、整数型以外の型のオフセット形式を指定した場合はE_WARNINGを発行。

ABOUT ME
ヒロ
社会人4年目/25歳/食品商社で2年間営業した後、IT業界にシステムエンジニアとして転職/Java,PHP言語を扱う開発エンジニア