【PHP8.x】strcasecmp関数の使い方

作成日: 更新日:

strcasecmp関数は、大文字と小文字を区別せずに2つの文字列を比較する関数です。この関数は、例えばユーザーからの入力を既存のデータと照合する際や、ソートの基準として大文字と小文字の違いを無視したい場合に非常に役立ちます。具体的には、"Apple"と"apple"を同じものとして扱いたいケースで利用されます。比較対象となる2つの文字列を引数として受け取ります。これらの文字列は、内部的にバイト単位で比較されます。

比較結果は整数値で返されます。もし2つの文字列が大文字と小文字を無視して完全に一致する場合、関数は 0 を返します。最初の文字列が2番目の文字列よりも辞書順で小さい(前に来る)場合は負の値を返し、大きい(後に来る)場合は正の値を返します。

この関数の大きな特徴は、ロケール(言語や地域の設定)の影響を受けない「バイナリセーフ」である点です。これにより、異なる環境や言語設定でも一貫した比較結果が得られ、国際的なアプリケーション開発においても信頼性の高い文字列比較を実現します。PHPの内部関数として提供されており、効率的に動作します。

基本的な使い方

構文(syntax)

<?php
$string1 = "Hello PHP";
$string2 = "hello php";
$result = strcasecmp($string1, $string2);

引数(parameters)

string $string1, string $string2

  • string $string1: 比較対象となる1つ目の文字列
  • string $string2: 比較対象となる2つ目の文字列

戻り値(return)

int

二つの文字列を大文字小文字を区別せずに比較した結果を整数で返します。返り値は、文字列1が文字列2より小さい場合は負の値、等しい場合は0、大きい場合は正の値となります。

サンプルコード

PHP strcasecmpで大文字小文字を区別せずに比較する

<?php

/**
 * strcasecmp関数の使用例。
 *
 * strcasecmpは、大文字・小文字を区別せずに2つの文字列を比較します。
 *
 * 戻り値:
 * - 0: $string1と$string2が大文字・小文字を無視して等しい場合。
 * - 0より小さい値: $string1が大文字・小文字を無視して$string2より小さい場合(アルファベット順で前にある場合)。
 * - 0より大きい値: $string1が大文字・小文字を無視して$string2より大きい場合(アルファベット順で後ろにある場合)。
 *
 * @param string $string1 比較する最初の文字列
 * @param string $string2 比較する2番目の文字列
 */
function compareStringsCaseInsensitive(string $string1, string $string2): void
{
    echo "--- 大文字・小文字を区別しない比較 (strcasecmp) ---\n";
    echo "比較対象: '{$string1}' と '{$string2}'\n";

    $result = strcasecmp($string1, $string2);

    echo "結果: {$result}\n";

    if ($result === 0) {
        echo "  -> 2つの文字列は大文字・小文字を無視すると等しいです。\n\n";
    } elseif ($result < 0) {
        echo "  -> '{$string1}' は大文字・小文字を無視すると '{$string2}' より小さい(アルファベット順で前)です。\n\n";
    } else { // $result > 0
        echo "  -> '{$string1}' は大文字・小文字を無視すると '{$string2}' より大きい(アルファベット順で後)です。\n\n";
    }
}

// サンプルコード実行例
compareStringsCaseInsensitive("Hello World", "hello world"); // 等しい例 (0を返す)
compareStringsCaseInsensitive("apple", "Banana");          // 小さい例 (<0を返す)
compareStringsCaseInsensitive("Zebra", "ant");             // 大きい例 (>0を返す)
compareStringsCaseInsensitive("PHP", "php");               // 等しい例 (0を返す)

?>

このPHPサンプルコードは、strcasecmp関数の使い方を具体的に示しています。strcasecmp関数は、2つの文字列を比較する際に、大文字・小文字を区別しない点が大きな特徴です。

引数には、比較したい2つの文字列$string1$string2を指定します。戻り値は整数値で、比較の結果を表します。具体的には、2つの文字列が大文字・小文字を無視して完全に等しい場合は0が返されます。もし$string1$string2よりもアルファベット順で前にある場合(大文字・小文字を無視)、0より小さい値が返されます。逆に$string1$string2よりもアルファベット順で後ろにある場合(大文字・小文字を無視)、0より大きい値が返されます。

サンプルコード内のcompareStringsCaseInsensitive関数では、このstrcasecmp関数を使って様々な文字列の組み合わせを比較しています。「Hello World」と「hello world」のように大文字・小文字が異なるだけの文字列が等しいと判断される例や、アルファベット順の前後関係に基づいた比較結果が確認できます。この関数は、例えばユーザー名やファイル名などを比較する際に、大文字・小文字の違いを許容したい場面で非常に便利に活用できます。

strcasecmp関数は、大文字・小文字を区別せずに2つの文字列を比較し、結果を整数値で返します。戻り値が0の場合は等しいことを意味するため、サンプルコードのようにif ($result === 0)と厳密な比較演算子===を使用することが重要です。これにより、PHPの型変換による予期せぬ誤判定を防ぎ、安全にコードを利用できます。しかし、この関数はASCII互換の文字列に最適化されており、日本語などのマルチバイト文字の比較には適していません。もしマルチバイト文字を含む文字列を比較する場合には、mb_strcasecmp関数の使用を検討してください。これにより、文字コードを意識した適切な比較が可能になります。

PHP strcasecmp UTF-8比較の注意点

<?php

/**
 * strcasecmp関数を使用して、大文字・小文字を区別しない文字列比較の動作を示します。
 * この関数はASCII文字の比較には適していますが、UTF-8などのマルチバイト文字の比較には注意が必要です。
 *
 * strcasecmpは文字列をバイト列として比較するため、UTF-8文字の大文字・小文字の違いを正しく認識できない場合があります。
 * UTF-8文字を正しく大文字・小文字を区別せずに比較するには、mb_strcasecmp関数を使用する必要があります。
 */
function demonstrateStrCaseCmpUsage(): void
{
    echo "--- strcasecmp関数による文字列比較 ---" . PHP_EOL;

    // 1. ASCII文字列の比較(大文字・小文字を無視)
    // 結果が0の場合、文字列は同じと見なされます。
    // 大文字・小文字の違いのみであれば、0が返されます。
    $stringAscii1 = "Hello PHP";
    $stringAscii2 = "hello php";
    $resultAscii = strcasecmp($stringAscii1, $stringAscii2);
    echo "比較: '{$stringAscii1}' と '{$stringAscii2}'" . PHP_EOL;
    echo "結果: {$resultAscii} (0は同じ、負の数は1つ目が小さい、正の数は1つ目が大きい)" . PHP_EOL; // 出力例: 0

    echo PHP_EOL;

    // 2. 異なるASCII文字列の比較
    // 辞書順で$stringAscii3が$stringAscii4より小さいと判断されるため、負の数が返されます。
    $stringAscii3 = "Apple";
    $stringAscii4 = "Banana";
    $resultAsciiDiff = strcasecmp($stringAscii3, $stringAscii4);
    echo "比較: '{$stringAscii3}' と '{$stringAscii4}'" . PHP_EOL;
    echo "結果: {$resultAsciiDiff}" . PHP_EOL; // 出力例: -1

    echo PHP_EOL;

    // 3. UTF-8マルチバイト文字を含む文字列の比較(strcasecmpの限界を示す)
    // 'Ä' (C3 84) と 'ä' (C3 A4) はUTF-8では大文字・小文字の違いですが、バイト列が異なります。
    // strcasecmpはバイト列を比較するため、これらの文字を「異なる」と判断し、0以外の値を返します。
    // これは、strcasecmpがUTF-8の大文字・小文字のルールを認識しないことを示しています。
    $stringUtf81 = "Äpfel"; // ドイツ語で「リンゴ」 (大文字のÄ)
    $stringUtf82 = "äpfel"; // ドイツ語で「リンゴ」 (小文字のä)
    $resultUtf8 = strcasecmp($stringUtf81, $stringUtf82);
    echo "比較: '{$stringUtf81}' (UTF-8) と '{$stringUtf82}' (UTF-8)" . PHP_EOL;
    echo "結果: {$resultUtf8} (strcasecmpはUTF-8の大文字・小文字を正しく扱えません)" . PHP_EOL; // 出力例: 負の数 (0以外)

    // 補足: UTF-8マルチバイト文字を大文字・小文字を区別せずに正しく比較するには、
    // mb_strcasecmp関数を使用してください。mbstring拡張が必要です。
    if (extension_loaded('mbstring')) {
        $resultMbUtf8 = mb_strcasecmp($stringUtf81, $stringUtf82);
        echo "mb_strcasecmpでの比較: '{$stringUtf81}' と '{$stringUtf82}'" . PHP_EOL;
        echo "結果 (mb_strcasecmp): {$resultMbUtf8} (UTF-8で正しく比較されました)" . PHP_EOL; // 出力例: 0
    } else {
        echo "注意: mbstring拡張がロードされていないため、mb_strcasecmpの比較例はスキップされました。" . PHP_EOL;
    }

    echo "--------------------------------------" . PHP_EOL;
}

// 関数を実行して動作を確認します。
demonstrateStrCaseCmpUsage();

strcasecmp関数は、2つの文字列を大文字・小文字を区別せずに比較し、その結果を整数で返します。引数として比較対象のstring $string1string $string2をとり、戻り値はint型です。2つの文字列が大文字・小文字の違いを除いて等しい場合は0を返します。$string1$string2より辞書順で小さい場合は負の数を、大きい場合は正の数を返します。

この関数は、主にASCII文字の比較に適しています。たとえば、「Hello PHP」と「hello php」のように、大文字・小文字のみが異なるASCII文字列を比較した場合、strcasecmp0を返し、両者が同じであると判断します。

しかし、strcasecmpは文字列をバイト列として比較するため、UTF-8などのマルチバイト文字の大文字・小文字の区別を正しく認識できないという注意点があります。例えば、ドイツ語の「Äpfel」と「äpfel」はUTF-8では大文字・小文字の違いですが、strcasecmpで比較すると異なる文字列と判断され、0以外の値が返されます。

UTF-8文字を含む文字列を大文字・小文字を区別せずに正しく比較したい場合は、mb_strcasecmp関数を使用する必要があります。mb_strcasecmpは、マルチバイト文字列の文字エンコーディングを考慮して比較を行うため、より正確な結果が得られます。strcasecmpを使用する際は、比較する文字列がASCII文字のみであるか、あるいはマルチバイト文字の扱いについて十分な理解が必要です。

PHPのstrcasecmp関数は、二つの文字列を大文字・小文字を区別せずに比較し、辞書順に基づいて整数を返します。ASCII文字のみで構成される文字列の比較には適切ですが、UTF-8などのマルチバイト文字を含む文字列の比較には注意が必要です。この関数は文字列をバイト列として比較するため、UTF-8文字における大文字・小文字の違いを正しく認識できない場合があります。例えば、ドイツ語の「Ä」と「ä」のような文字は、strcasecmpでは異なるものとして扱われてしまいます。UTF-8マルチバイト文字を大文字・小文字を区別せずに正しく比較したい場合は、mb_strcasecmp関数を利用してください。mb_strcasecmpを利用するには、PHPのmbstring拡張が有効になっている必要があります。コード作成時には、扱う文字列の種類に応じて適切な関数を選ぶことが重要です。

PHP strcasecmpの脆弱性を示す

<?php

/**
 * Demonstrates a potential "vulnerability" or pitfall related to `strcasecmp`
 * in PHP 8.4.12, specifically when used with non-string inputs in security-sensitive
 * comparisons. This highlights the importance of explicit type validation.
 *
 * `strcasecmp` is designed for case-insensitive string comparison. However, PHP's
 * implicit type juggling (when `declare(strict_types=1)` is not used or strict
 * types are not enforced for internal functions) can lead to unexpected results.
 *
 * The common pitfall shown here is when `strcasecmp` compares an empty string
 * with `null` or `false`. Both `null` and `false` are coerced to an empty string (`""`)
 * before comparison, causing `strcasecmp` to return `0` (indicating a match)
 * even though the original input was not a string. This could lead to a bypass
 * if an empty string token grants access in a vulnerable system.
 *
 * @param string $expectedToken The expected token for comparison. For this demonstration,
 *                              we use an empty string to illustrate the specific type juggling behavior.
 */
function demonstrateStrCaseCmpVulnerability(string $expectedToken = ''): void
{
    echo "--- Demonstrating strcasecmp Vulnerability/Pitfall ---\n\n";
    echo "Context: An authentication system expects a specific token for access.\n";
    echo "         We'll use an empty string ('{$expectedToken}') as the 'expected' token\n";
    echo "         to highlight a common type-juggling pitfall in security checks.\n\n";

    // Simulate various user inputs, which could come from $_GET, $_POST, or API payloads.
    $testInputs = [
        'correct_string'      => '',          // User provides the correct empty string
        'non_matching_string' => 'some_token', // User provides a different string
        'null_input'          => null,        // User provides null (e.g., parameter missing)
        'false_input'         => false,       // User provides boolean false
        'integer_zero'        => 0,           // User provides integer 0 (coerces to "0")
        'array_input'         => [],          // User provides an array (coerces to "Array")
    ];

    foreach ($testInputs as $label => $userInput) {
        echo "Testing input type '{$label}':\n";
        echo "  - User Input Value: " . (is_string($userInput) ? "'{$userInput}'" : var_export($userInput, true)) . "\n";
        echo "  - User Input Type: " . gettype($userInput) . "\n";

        // --- VULNERABLE APPROACH ---
        // Direct use of strcasecmp without explicit input type validation.
        // If $expectedToken is '', and $userInput is null or false, strcasecmp
        // will coerce them to "" and return 0, leading to an unintended match.
        $vulnerableMatch = strcasecmp($expectedToken, $userInput) === 0;

        echo "  VULNERABLE CHECK (strcasecmp(\"{$expectedToken}\", \$userInput) === 0):\n";
        if ($vulnerableMatch) {
            echo "    -> Result: MATCHED! Access granted (Potentially unintended bypass for {$label}).\n";
        } else {
            echo "    -> Result: MISMATCHED. Access denied.\n";
        }

        // --- SECURE APPROACH ---
        // Always validate input types for security-sensitive comparisons.
        // Ensure inputs are strings before using string comparison functions.
        $secureMatch = false;
        if (is_string($userInput)) {
            // For case-insensitive comparison of strings, strcasecmp is suitable.
            // For password hashes or truly constant-time comparisons, `hash_equals()`
            // (after converting both to lowercase if case-insensitivity is needed)
            // is generally preferred. Here, we focus on type safety with strcasecmp.
            $secureMatch = strcasecmp($expectedToken, $userInput) === 0;
        }

        echo "  SECURE CHECK (is_string(\$userInput) && strcasecmp(\"{$expectedToken}\", \$userInput) === 0):\n";
        if ($secureMatch) {
            echo "    -> Result: MATCHED! Access granted (Input was a string and matched).\n";
        } else {
            echo "    -> Result: MISMATCHED. Access denied (Input was not a string or did not match).\n";
        }
        echo "\n";
    }

    echo "--- Conclusion ---\n";
    echo "Always explicitly validate and, if necessary, cast user input to the expected type ";
    echo "before using comparison functions like `strcasecmp` in security-critical contexts.\n";
    echo "This prevents unintended type juggling from leading to security vulnerabilities.\n";
}

// Execute the demonstration with an empty string as the expected token.
demonstrateStrCaseCmpVulnerability('');

?>

PHPのstrcasecmp関数は、二つの文字列を大文字・小文字を区別せずに比較し、その結果を整数値で返します。引数$string1$string2には比較対象の文字列を指定します。両者が等しい場合は0を、$string1$string2より小さい場合は負の値を、大きい場合は正の値を戻り値として返します。

この関数は本来文字列の比較に用いますが、PHPの柔軟な型変換(型ジャグリング)により、意図しない挙動を示すことがあります。特に、セキュリティに関わる場面での利用には注意が必要です。

サンプルコードでは、strcasecmp関数に空の文字列を期待値として渡し、ユーザーからの入力としてnullfalseを与えた場合の挙動を検証しています。PHPではnullfalseが空の文字列("")に型変換されるため、strcasecmpはこれらを空文字列と比較すると「一致」(0)と判断してしまいます。これは、もし認証トークンとして空文字列を期待しているシステムがあった場合、nullfalseの入力によって認証が意図せず通過してしまう「脆弱性」や「落とし穴」につながる可能性があります。

このような問題を避けるためには、strcasecmpのような比較関数を使用する前に、is_string()関数などで入力が期待する型(この場合は文字列)であるかを明示的に検証することが非常に重要です。これにより、意図しない型変換によるセキュリティ上のリスクを防ぎ、より安全なコードを作成できます。

PHPのstrcasecmp関数は、文字列の比較を大文字小文字を区別せずに行いますが、PHPの暗黙的な型変換には注意が必要です。この関数は非文字列の入力を受け取ると、それを文字列に変換して比較します。特にnullfalseは空文字列("")として扱われるため、比較対象が空文字列の場合に予期せず「一致」と判断されてしまう可能性があります。これは、認証トークンの検証など、セキュリティが重要な場面で意図しないアクセス許可(バイパス)につながる危険性があります。そのため、strcasecmpをセキュリティ関連の比較に使用する際は、必ず事前にis_string()などで入力値が文字列であることを明示的に検証してください。型チェックを怠ると、意図せぬ脆弱性を生む原因となりますので、十分に注意して利用してください。

【PHP8.x】strcasecmp関数の使い方 | いっしー@Webエンジニア