PHP初心者が失敗しがち/間違えがちなこと7選

PHP初心者が失敗しがち/間違えがちなこと

Web開発を中心に多くのエンジニアに使用されるプログラミング言語、PHP。

手軽にWebページに組み込めるのが特徴であり、Web開発の最初の一歩としてPHPを選んだ人も多いでしょう。

そんなPHPですが、初心者にとっては「罠」とも思えるような、間違えやすい点が多くあります。今回はPHP初心者が失敗しがち / 間違えがちなことを7個ピックアップしました。

1. 単純な文法ミスを見逃しがち

PHPでは単純な文法ミスで出たエラーでも、そのエラーの意味が分かりにくく、一見どこに問題があるのか理解できないことがあります。

PHP_単純ミスなのにエラーが分かりづらい

上記画像のコードでは、foreach文に「syntax error」が表示されていますね。「予期しないforeachがあります」とエラーには説明されていますが、なにをどう修正すればいいかイマイチ分かりません。

「foreachにエラーが出てるのだから……」と、foreachの文ばかりに集中してしまうかもしれませんが、それは誤りです。ここで見るべきはforeachではなく、foreachのひとつ上の文です。

お気づきになられましたか? そうです、「$result = “”」の式の末尾にセミコロンが抜けていますね。PHPでは式の終わりにはセミコロンが必要なので、正しくは「$result = “”; 」となります。

このようにPHPの単純な文法ミスは、エラーの位置が分かりづらい傾向にあります。セミコロン以外にも、ドル記号のつけ忘れなど、エラー出力が慣れないうちは分かりづらいので気をつけましょう。

2. 文字列を結合しようとしてプラス記号を使ってしまう

ふたつの文字列を繋げ合わせたいとき、次のようなコードを書いてしまったことはありませんか?

/** ふたつの引数で与えられた文字列を結合したい */
function joinstr($str1, $str2) {
    return $str1 + $str2;
}
echo joinstr("foo", "hoo");
// ▲ エラー:A non-numeric value encountered in ...

一見、正しそうに見えるこのコードですが、引数に”foo”と”hoo”を指定して実行したら、エラーが出てしまいました。エラーには「数値以外の値が検出されました」とあります。

PHPにおいて、プラス「 + 」は数値の加算を行う演算子であり、文字列の結合には使えません。文字列の結合にはドット「 . 」を使います。

/** ふたつの引数で与えられた文字列を結合する */
function joinstr($str1, $str2) {
    return $str1 . $str2;
}
echo joinstr("foo", "hoo");  //=> foohoo

このように、ドット「 . 」を使えば文字列を結合できます。

なお、プラス「 + 」を代入と同時に行うプラスイコール「 += 」演算子があるのと同様に、文字列の結合演算子のドット「 . 」にも、結合と代入を同時に行うドットイコール演算子「 .= 」というものがあります。

/** 引数の文字列を倍にして返す */
function todouble($str) {
    return $str .= $str;
}
echo todouble("foo");  //=> foofoo

3. 日本語を扱うとき、文字コード・バイト数の考慮漏れでエラーを出してしまう

PHPの文字列操作では、操作対象の文字列の文字コードやバイト数に注意しないと、エラーが起きる場合があります。

$str = "Sig";
print_r(str_split($str));
// ▼ str_splitで1文字ごとに分割
// Array
// (
//     [0] => S
//     [1] => i
//     [2] => g
// )

$str = "しぐ";
print_r(str_split($str));
// ▼ str_splitで1文字ごとに...あれ?
// Array
// (
//     [0] => �
//     [1] => �
//     [2] => �
//     [3] => �
//     [4] => �
//     [5] => �
// )

上記コードでは、「str_split()」で文字列を配列に変換しています。

最初の”Sig”という文字列は目的通り1文字ずつに配列に分割できていますが、次の”しぐ”という文字列は謎の文字として分割されてしまっていますね。配列の要素数も”しぐ”だから2文字になるのではなく、なぜか6文字になっています。

これは、「str_split()」はデフォルトで1バイトずつ文字列を分割する関数だからです。あくまで「1バイトずつ」であって、「1文字ずつ」ではないのです。

英数字などはコンピュータ上で1バイトとして扱われていますが、日本語をはじめとするその他の文字は一般にマルチバイトで扱われています。“しぐ”を「str_split()」で分割すると文字化けしたのは、マルチバイト文字である日本語を、1バイトずつ分解しようとしたため、正しい文字として認識されなかったのです。

「str_split()」は第二引数で区切るバイト数を変更できます。UTF-8の日本語なので、3バイトずつ区切るようにコードを変えてみましょう。

$str = "しぐ";
print_r(str_split($str, 3));
// ▼ 3バイトずつ分割
// Array
// (
//     [0] => し
//     [1] => ぐ
// )

これで日本語1文字ずつの配列に分割できるようになりました。ただし、この方法ではバイト数の異なる文字が混在する場合、正しく1文字ずつ分解できません。

$str = "sigしぐ";
print_r(str_split($str, 3));
// ▼ 異なるバイト数の文字列が混ざると…
// Array
// (
//     [0] => sig
//     [1] => し
//     [2] => ぐ
// )

英数字は1文字1バイトで扱われているので、配列の最初の要素には”sig”の3文字が格納されてしまいました。

バイト数の異なる文字が混在する文字列で、正しく1文字ずつ抜き出すには、正規表現で文字列を分割するpreg_split()を使用するのが簡単です。

$str = "sigしぐ";
print_r(preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY));
// ▼ //uでUTF-8の文字列を1文字ずつ分割
// Array
// (
//     [0] => s
//     [1] => i
//     [2] => g
//     [3] => し
//     [4] => ぐ
// )

これで英語と日本語を同時に1文字ずつ扱えるようになりました。上記コードは「preg_split()」の第一引数に指定した正規表現「//u」により「正規表現と対象文字列をUTF-8として扱い、1文字ずつ区切る」ことを実現しています。

ここでは例として、文字列を「1文字ずつ区切る」操作を挙げましたが、PHPにおけるその他の文字列操作においても、文字コードとバイト数の問題は考慮しなければなりません。日本語などのマルチバイト文字を扱う際には注意しましょう。

4. 正規表現のデリミタを忘れがち

PHPで正規表現を扱うとき、このようなエラーを出したことはありませんか?

/** 英語の文字列から単語を抜き出したい */
function get_words($target) {
    // 余分なドットやコンマなどを除いて抽出
    $pattern = "(\S+?)[ ,\.\z]";
    preg_match_all($pattern, $target, $data);
    return $data[1];
}
$target = "I need paper, a pencil and an eraser.";
print_r(get_words($target));
// ▲ エラー出力 : preg_match_all(): Unknown modifier '[' in ...

エラーには不明な修飾子「 [ 」があると書かれていますね。正規表現は正しく書けているはずなのに、なぜこのようなエラーが出てしまうのでしょう?

答えは「デリミタがないから」です。PHPで正規表現を扱うPCRE関数では、正規表現のはじめとおわりにデリミタ(delimiters, 区切り文字)をつける必要があります。上記コードの「$pattern = “(\S+?)[ ,\.\z]”;」はデリミタがないために、ただの文字列として認識されてしまっているのです。

「$pattern = “(\S+?)[ ,\.\z]”;」の前後に、デリミタとしてスラッシュ「 / 」をつけてみましょう。

/** 英語の文字列から単語を抜き出したい */
function get_words($target) {
    // 余分なドットやコンマなどを除いて抽出
    $pattern = "/(\S+?)[ ,\.\z]/";  // ◀︎ デリミタとしてをつけると正規表現としてみなされる
    preg_match_all($pattern, $target, $data);
    return $data[1];
}
$target = "I need paper, a pencil and an eraser.";
print_r(get_words($target));
// ▼ 出力
// Array
// (
//     [0] => I
//     [1] => need
//     [2] => paper
//     [3] => a
//     [4] => pencil
//     [5] => and
//     [6] => an
//     [7] => eraser
// )

これで無事、コードが正しく動くようになりましたね。

なお上記コードではスラッシュ「 / 」をデリミタとして使用しましたが、スラッシュ以外にも英数字、バックスラッシュ、空白文字以外の任意の文字であればデリミタとして使用できます。

またPCRE関数ではデリミタが必要ですが、PHPのPOSIX系正規表現エンジン(※現在は非推奨)の関数では、デリミタが不要です。

5. 関数内でグローバル変数を使うとき、global宣言を忘れがち

PHPでグローバル変数を扱う関数を実装するとき、以下のように書くとエラーが出てしまいます。

$num = 50;
function plusnum($n) {
    return $n + $num;
}
$result = plusnum(10);  // エラー出力:Undefined variable: num in ...

エラーには変数$numが未定義であると書かれていますね。グローバル変数として「$num = 50;」を定義しているのに、なぜ未定義とされてしまうのでしょう?

答えはglobal宣言がないからです。PHPでは関数の中からグローバル変数にアクセスする際には、その変数がグローバル変数であることを示すglobal宣言を、以下のように記述する必要があります。

$num = 50;
function plusnum($n) {
    global $num;  // ◀︎ global宣言で変数$numがグローバルに存在する変数であることを示す
    return $n + $num;
}
$result = plusnum(10);
echo $result . "\n";  // 60

このようにglobal宣言を行うことで、関数内からグローバル変数にアクセスできます。

6. 「==」の比較で思わぬ条件をtrueとみなしてしまう

動的型付け言語であるPHPでは、さまざまな場面で「型の自動変換」が行われます。型をあまり気にせず、短いコードでプログラムを実装できるのは便利ですが、if文などで同じ値かどうかを等価演算子で調べる際には、少し注意が必要です。

例えば、以下の比較は全てtrueとみなされます。

var_dump("1"  == 1);  // true
var_dump("2.0" == 2);  // true
var_dump("2" == "2.0");  // true
var_dump(null == 0);  // true
var_dump("Sig" == 0);  // true

「”1″ == 1」がtrueとみなされるのは分かりやすいですが、それ以外はどうでしょう。場面によってはこれらをtrueとみなしてしまうと、バグのもとになってしまうこともあるはずです。

型変換を行わず、厳密に値の比較をしたい場合には「 ==」ではなく「===」を使いましょう。

var_dump("1"  === 1);  // false
var_dump("2.0" === 2);  // false
var_dump("2" === "2.0");  // false
var_dump(null === 0);  // false
var_dump("Sig" === 0);  // false

このように「===」で比較をすれば、型の自動変換は行われません。型が違えば左辺と右辺は異なるものとみなされるので、型変換による想定外の挙動を防げます。

7. in_arrayの第三引数を考慮せずに使ってしまう

特定の値が配列内に存在するかを確認する「in_array()」には、第三引数が存在します。第三引数には値を設定せずとも関数を実行できますが、その場合「in_array()」は要素が配列内にあるかどうかの確認を「==」で行います。

しかし「==」は型変換を行うため、場合によっては想定外の値がtrueとみなされてしまうのです。

$nums = [0, 1, 2, 3, 4];
var_dump(in_array("3.0", $nums));  // true
var_dump(in_array("Sig", $nums));  // true

型変換を行わず、厳密な比較を行いたい場合には「in_array()」の第三引数にtrueを設定します。

$nums = [0, 1, 2, 3, 4];
var_dump(in_array("3.0", $nums, true));  // false
var_dump(in_array("Sig", $nums, true));  // false

第三引数に「true」を設定したことで、型の異なる値は「false」とみなされるようになりました。必要があって型変換を許容するのであれば問題ありませんが、そうではない多くの場面では、安全性のために型変換を許容しない厳密な比較にするのが良いでしょう。

なお、「in_array()」の第三引数に「true」ではなく「false」を設定した場合には、第三引数が存在しないときと同様に、型変換を許容する比較を行います。

まとめ

動的型付け言語であるPHPはその扱いやすさで多くの人気を集めています。一方でエラー出力の癖や、文字列操作や正規表現をはじめとした組み込み関数の仕様、さらには自動的な型変換の副作用などを知らずにいると、思わぬ箇所で壁にぶつかりがちです。

動的型付け言語にはありがちですが、「なんとなく」で動かせるからといって、言語の理解を「なんとなく」のままで放置しておくのは良くありません。言語仕様を正確に把握して、バグを産みにくい適切なコーディングを心がけましょう。

SHARE

  • 広告主募集
  • ライター・編集者募集
  • WorkshipSPACE
週2日20万円以上のお仕事多数
Workship