[PHP]strcasecmp関数を完全解説!大文字小文字を区別しない文字列比較

PHP

こんにちは!今回は、PHPの標準関数であるstrcasecmp()について詳しく解説していきます。大文字小文字を区別せずに文字列を比較できる、非常に便利な関数です!

strcasecmp関数とは?

strcasecmp()関数は、2つの文字列を大文字小文字を区別せずに比較する関数です。

通常の比較演算子(=====)やstrcmp()関数は大文字小文字を区別しますが、strcasecmp()を使えば “Hello” と “hello” を同じものとして扱えます!

基本的な構文

strcasecmp(string $string1, string $string2): int
  • $string1: 比較する1つ目の文字列
  • $string2: 比較する2つ目の文字列
  • 戻り値:
    • 0: 両者が等しい(大文字小文字を無視)
    • < 0: $string1が$string2より小さい
    • > 0: $string1が$string2より大きい

基本的な使用例

シンプルな比較

// 大文字小文字を無視した比較
$result = strcasecmp("Hello", "hello");
echo $result . "\n";  // 0(等しい)

$result = strcasecmp("HELLO", "hello");
echo $result . "\n";  // 0(等しい)

$result = strcasecmp("apple", "APPLE");
echo $result . "\n";  // 0(等しい)

// 異なる文字列
$result = strcasecmp("apple", "banana");
echo $result . "\n";  // 負の数(appleの方が小さい)

$result = strcasecmp("zebra", "apple");
echo $result . "\n";  // 正の数(zebraの方が大きい)

等しいかどうかのチェック

$username = "TaNaKa";
$input = "tanaka";

if (strcasecmp($username, $input) === 0) {
    echo "ユーザー名が一致しました\n";
} else {
    echo "ユーザー名が一致しません\n";
}
// 出力: ユーザー名が一致しました

strcmp()との違い

$str1 = "Hello";
$str2 = "hello";

// strcmp(): 大文字小文字を区別
echo strcmp($str1, $str2) . "\n";      // 0以外(異なる)

// strcasecmp(): 大文字小文字を区別しない
echo strcasecmp($str1, $str2) . "\n";  // 0(等しい)

実践的な使用例

例1: ユーザー認証システム

class UserAuth {
    private $users = [
        'admin' => 'password123',
        'user1' => 'mypass456',
        'JohnDoe' => 'secret789'
    ];
    
    public function authenticate($username, $password) {
        foreach ($this->users as $storedUser => $storedPass) {
            // ユーザー名は大文字小文字を区別しない
            if (strcasecmp($username, $storedUser) === 0) {
                // パスワードは大文字小文字を区別
                if ($password === $storedPass) {
                    return [
                        'success' => true,
                        'username' => $storedUser,
                        'message' => 'ログイン成功'
                    ];
                } else {
                    return [
                        'success' => false,
                        'message' => 'パスワードが正しくありません'
                    ];
                }
            }
        }
        
        return [
            'success' => false,
            'message' => 'ユーザーが見つかりません'
        ];
    }
    
    public function userExists($username) {
        foreach (array_keys($this->users) as $user) {
            if (strcasecmp($username, $user) === 0) {
                return true;
            }
        }
        return false;
    }
}

// 使用例
$auth = new UserAuth();

// 大文字小文字を変えてもログインできる
$result = $auth->authenticate('ADMIN', 'password123');
print_r($result);
// success => true

$result = $auth->authenticate('johndoe', 'secret789');
print_r($result);
// success => true

// ユーザー存在チェック
var_dump($auth->userExists('JOHNDOE'));  // true
var_dump($auth->userExists('admin'));     // true

例2: ファイル拡張子のチェック

class FileValidator {
    private $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'docx'];
    
    public function isAllowedExtension($filename) {
        $extension = pathinfo($filename, PATHINFO_EXTENSION);
        
        foreach ($this->allowedExtensions as $allowed) {
            if (strcasecmp($extension, $allowed) === 0) {
                return true;
            }
        }
        
        return false;
    }
    
    public function getFileType($filename) {
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        
        $imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
        $documentExtensions = ['pdf', 'doc', 'docx', 'txt', 'odt'];
        $videoExtensions = ['mp4', 'avi', 'mov', 'wmv', 'flv'];
        
        foreach ($imageExtensions as $ext) {
            if (strcasecmp($extension, $ext) === 0) {
                return 'image';
            }
        }
        
        foreach ($documentExtensions as $ext) {
            if (strcasecmp($extension, $ext) === 0) {
                return 'document';
            }
        }
        
        foreach ($videoExtensions as $ext) {
            if (strcasecmp($extension, $ext) === 0) {
                return 'video';
            }
        }
        
        return 'unknown';
    }
    
    public function validateUpload($filename, $allowedTypes = []) {
        if (!$this->isAllowedExtension($filename)) {
            return [
                'valid' => false,
                'message' => '許可されていないファイル形式です'
            ];
        }
        
        if (!empty($allowedTypes)) {
            $fileType = $this->getFileType($filename);
            
            $typeMatched = false;
            foreach ($allowedTypes as $type) {
                if (strcasecmp($fileType, $type) === 0) {
                    $typeMatched = true;
                    break;
                }
            }
            
            if (!$typeMatched) {
                return [
                    'valid' => false,
                    'message' => "ファイルタイプは " . implode(', ', $allowedTypes) . " のみ許可されています"
                ];
            }
        }
        
        return [
            'valid' => true,
            'message' => 'ファイルは有効です',
            'type' => $this->getFileType($filename)
        ];
    }
}

// 使用例
$validator = new FileValidator();

// 大文字小文字を変えても認識される
var_dump($validator->isAllowedExtension('photo.JPG'));   // true
var_dump($validator->isAllowedExtension('photo.jpg'));   // true
var_dump($validator->isAllowedExtension('photo.JpG'));   // true

echo $validator->getFileType('document.PDF') . "\n";     // document
echo $validator->getFileType('video.MP4') . "\n";        // video

// アップロード検証
$result = $validator->validateUpload('image.PNG', ['image']);
print_r($result);

例3: HTTPヘッダーの処理

class HttpHeaderParser {
    private $headers = [];
    
    public function addHeader($name, $value) {
        $this->headers[$name] = $value;
    }
    
    public function getHeader($name) {
        // HTTPヘッダー名は大文字小文字を区別しない
        foreach ($this->headers as $headerName => $headerValue) {
            if (strcasecmp($headerName, $name) === 0) {
                return $headerValue;
            }
        }
        
        return null;
    }
    
    public function hasHeader($name) {
        return $this->getHeader($name) !== null;
    }
    
    public function removeHeader($name) {
        foreach ($this->headers as $headerName => $headerValue) {
            if (strcasecmp($headerName, $name) === 0) {
                unset($this->headers[$headerName]);
                return true;
            }
        }
        
        return false;
    }
    
    public function getContentType() {
        return $this->getHeader('Content-Type');
    }
    
    public function isJson() {
        $contentType = $this->getContentType();
        
        if ($contentType === null) {
            return false;
        }
        
        // application/json かチェック(大文字小文字を区別しない)
        return strcasecmp($contentType, 'application/json') === 0 ||
               stripos($contentType, 'application/json') !== false;
    }
}

// 使用例
$parser = new HttpHeaderParser();

// 様々な大文字小文字でヘッダーを追加
$parser->addHeader('Content-Type', 'application/json');
$parser->addHeader('Authorization', 'Bearer token123');
$parser->addHeader('X-Custom-Header', 'value');

// 大文字小文字を変えても取得できる
echo $parser->getHeader('content-type') . "\n";      // application/json
echo $parser->getHeader('CONTENT-TYPE') . "\n";      // application/json
echo $parser->getHeader('Content-Type') . "\n";      // application/json

var_dump($parser->hasHeader('authorization'));       // true
var_dump($parser->hasHeader('AUTHORIZATION'));       // true

var_dump($parser->isJson());                         // true

例4: コマンドラインオプションの解析

class CommandLineParser {
    private $validCommands = [
        'start', 'stop', 'restart', 'status', 'help'
    ];
    
    private $validOptions = [
        'verbose', 'quiet', 'debug', 'force'
    ];
    
    public function parseCommand($command) {
        foreach ($this->validCommands as $valid) {
            if (strcasecmp($command, $valid) === 0) {
                return strtolower($valid);  // 正規化された形式を返す
            }
        }
        
        return null;
    }
    
    public function parseOption($option) {
        // --option または -o 形式を処理
        $option = ltrim($option, '-');
        
        foreach ($this->validOptions as $valid) {
            if (strcasecmp($option, $valid) === 0) {
                return strtolower($valid);
            }
        }
        
        return null;
    }
    
    public function execute($args) {
        if (empty($args)) {
            return ['error' => 'コマンドが指定されていません'];
        }
        
        $command = $this->parseCommand($args[0]);
        
        if ($command === null) {
            return ['error' => "不明なコマンド: {$args[0]}"];
        }
        
        $options = [];
        for ($i = 1; $i < count($args); $i++) {
            $option = $this->parseOption($args[$i]);
            if ($option !== null) {
                $options[] = $option;
            }
        }
        
        return [
            'command' => $command,
            'options' => $options
        ];
    }
}

// 使用例
$parser = new CommandLineParser();

// 大文字小文字を変えても認識される
$result = $parser->execute(['START', '--VERBOSE', '--DEBUG']);
print_r($result);
// command => start, options => [verbose, debug]

$result = $parser->execute(['Stop', '--Quiet']);
print_r($result);
// command => stop, options => [quiet]

$result = $parser->execute(['HELP']);
print_r($result);
// command => help, options => []

例5: 設定ファイルの読み込み

class ConfigReader {
    private $config = [];
    
    public function loadFromArray($data) {
        $this->config = $data;
    }
    
    public function get($key, $default = null) {
        // キー名は大文字小文字を区別しない
        foreach ($this->config as $configKey => $configValue) {
            if (strcasecmp($configKey, $key) === 0) {
                return $configValue;
            }
        }
        
        return $default;
    }
    
    public function set($key, $value) {
        // 既存のキーを探す(大文字小文字を区別しない)
        foreach ($this->config as $configKey => $configValue) {
            if (strcasecmp($configKey, $key) === 0) {
                $this->config[$configKey] = $value;
                return;
            }
        }
        
        // 新しいキーとして追加
        $this->config[$key] = $value;
    }
    
    public function has($key) {
        foreach (array_keys($this->config) as $configKey) {
            if (strcasecmp($configKey, $key) === 0) {
                return true;
            }
        }
        
        return false;
    }
    
    public function remove($key) {
        foreach ($this->config as $configKey => $configValue) {
            if (strcasecmp($configKey, $key) === 0) {
                unset($this->config[$configKey]);
                return true;
            }
        }
        
        return false;
    }
}

// 使用例
$config = new ConfigReader();

$config->loadFromArray([
    'DATABASE_HOST' => 'localhost',
    'Database_Port' => 3306,
    'database_name' => 'mydb'
]);

// 大文字小文字を変えても取得できる
echo $config->get('database_host') . "\n";        // localhost
echo $config->get('DATABASE_PORT') . "\n";        // 3306
echo $config->get('Database_Name') . "\n";        // mydb

var_dump($config->has('database_host'));          // true
var_dump($config->has('NONEXISTENT'));            // false

$config->set('database_port', 3307);              // 既存のキーを更新
echo $config->get('Database_Port') . "\n";        // 3307

例6: メール処理

class EmailValidator {
    private $blockedDomains = [
        'spam.com', 'fake.org', 'temporary.net'
    ];
    
    public function isDomainBlocked($email) {
        $domain = substr(strrchr($email, "@"), 1);
        
        foreach ($this->blockedDomains as $blocked) {
            if (strcasecmp($domain, $blocked) === 0) {
                return true;
            }
        }
        
        return false;
    }
    
    public function isSameDomain($email1, $email2) {
        $domain1 = strtolower(substr(strrchr($email1, "@"), 1));
        $domain2 = strtolower(substr(strrchr($email2, "@"), 1));
        
        return strcasecmp($domain1, $domain2) === 0;
    }
    
    public function normalizeEmail($email) {
        list($local, $domain) = explode('@', $email, 2);
        
        // ドメインは小文字に正規化(RFC 5321)
        return $local . '@' . strtolower($domain);
    }
}

class EmailMatcher {
    public function findByEmail($emails, $searchEmail) {
        $results = [];
        
        foreach ($emails as $index => $email) {
            // メールアドレスは大文字小文字を区別しない(ドメイン部分)
            if (strcasecmp($email, $searchEmail) === 0) {
                $results[] = [
                    'index' => $index,
                    'email' => $email
                ];
            }
        }
        
        return $results;
    }
    
    public function removeDuplicates($emails) {
        $unique = [];
        
        foreach ($emails as $email) {
            $isDuplicate = false;
            
            foreach ($unique as $uniqueEmail) {
                if (strcasecmp($email, $uniqueEmail) === 0) {
                    $isDuplicate = true;
                    break;
                }
            }
            
            if (!$isDuplicate) {
                $unique[] = $email;
            }
        }
        
        return $unique;
    }
}

// 使用例
$validator = new EmailValidator();

var_dump($validator->isDomainBlocked('user@SPAM.COM'));  // true
var_dump($validator->isDomainBlocked('user@spam.com'));  // true
var_dump($validator->isDomainBlocked('user@Spam.Com'));  // true

var_dump($validator->isSameDomain(
    'user1@EXAMPLE.COM',
    'user2@example.com'
));  // true

echo $validator->normalizeEmail('User@EXAMPLE.COM') . "\n";
// User@example.com

$matcher = new EmailMatcher();

$emails = [
    'user@EXAMPLE.COM',
    'admin@test.com',
    'user@example.com'  // 重複
];

$duplicates = $matcher->removeDuplicates($emails);
print_r($duplicates);
// user@EXAMPLE.COM と admin@test.com のみ

例7: データベースのカラム名マッチング

class DatabaseColumnMatcher {
    private $columns = [
        'user_id', 'username', 'email_address', 
        'created_at', 'updated_at'
    ];
    
    public function findColumn($searchName) {
        foreach ($this->columns as $column) {
            if (strcasecmp($column, $searchName) === 0) {
                return $column;
            }
        }
        
        return null;
    }
    
    public function hasColumn($columnName) {
        return $this->findColumn($columnName) !== null;
    }
    
    public function mapAliases($aliases) {
        $mapped = [];
        
        foreach ($aliases as $alias => $columnName) {
            $realColumn = $this->findColumn($columnName);
            
            if ($realColumn !== null) {
                $mapped[$alias] = $realColumn;
            }
        }
        
        return $mapped;
    }
}

class QueryBuilder {
    private $columnMatcher;
    
    public function __construct(DatabaseColumnMatcher $matcher) {
        $this->columnMatcher = $matcher;
    }
    
    public function select($columns) {
        $validColumns = [];
        
        foreach ($columns as $column) {
            $realColumn = $this->columnMatcher->findColumn($column);
            
            if ($realColumn !== null) {
                $validColumns[] = $realColumn;
            } else {
                throw new Exception("不明なカラム: {$column}");
            }
        }
        
        return "SELECT " . implode(', ', $validColumns) . " FROM users";
    }
}

// 使用例
$matcher = new DatabaseColumnMatcher();

// 大文字小文字を変えてもカラムを見つけられる
echo $matcher->findColumn('USER_ID') . "\n";        // user_id
echo $matcher->findColumn('Username') . "\n";       // username
echo $matcher->findColumn('EMAIL_ADDRESS') . "\n"; // email_address

var_dump($matcher->hasColumn('CREATED_AT'));        // true
var_dump($matcher->hasColumn('nonexistent'));       // false

// エイリアスマッピング
$aliases = [
    'id' => 'USER_ID',
    'name' => 'Username',
    'email' => 'email_address'
];

$mapped = $matcher->mapAliases($aliases);
print_r($mapped);
// ['id' => 'user_id', 'name' => 'username', 'email' => 'email_address']

// クエリビルダー
$builder = new QueryBuilder($matcher);

try {
    $query = $builder->select(['USER_ID', 'Username', 'EMAIL_ADDRESS']);
    echo $query . "\n";
    // SELECT user_id, username, email_address FROM users
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}

ソート処理での使用

class CaseInsensitiveSorter {
    public static function sort($array) {
        usort($array, function($a, $b) {
            return strcasecmp($a, $b);
        });
        
        return $array;
    }
    
    public static function sortAssociative($array) {
        uksort($array, function($a, $b) {
            return strcasecmp($a, $b);
        });
        
        return $array;
    }
    
    public static function sortByField($array, $field) {
        usort($array, function($a, $b) use ($field) {
            return strcasecmp($a[$field], $b[$field]);
        });
        
        return $array;
    }
}

// 使用例
$names = ['john', 'Alice', 'BOB', 'charlie', 'DAVID'];
$sorted = CaseInsensitiveSorter::sort($names);
print_r($sorted);
// Alice, BOB, charlie, DAVID, john (アルファベット順、大文字小文字無視)

$data = [
    ['name' => 'john', 'age' => 30],
    ['name' => 'Alice', 'age' => 25],
    ['name' => 'bob', 'age' => 35]
];

$sorted = CaseInsensitiveSorter::sortByField($data, 'name');
print_r($sorted);
// Alice, bob, john の順

注意点と制限事項

ロケール依存

// strcasecmp()はロケールに依存しない
// 非ASCII文字の扱いには注意

$str1 = "café";
$str2 = "CAFÉ";

// 結果はロケールによって異なる可能性がある
echo strcasecmp($str1, $str2) . "\n";

// マルチバイト文字列には mb_strtolower を使用
if (mb_strtolower($str1) === mb_strtolower($str2)) {
    echo "等しい(マルチバイト対応)\n";
}

バイナリセーフではない

// NULL文字を含む文字列の扱い
$str1 = "hello\0world";
$str2 = "hello";

$result = strcasecmp($str1, $str2);
// NULL文字以降は比較されない可能性がある

代替手段との比較

$str1 = "Hello";
$str2 = "hello";

// 方法1: strcasecmp()
$result = strcasecmp($str1, $str2) === 0;
echo $result ? "等しい\n" : "異なる\n";

// 方法2: strtolower() + 比較
$result = strtolower($str1) === strtolower($str2);
echo $result ? "等しい\n" : "異なる\n";

// 方法3: stripos() (部分一致チェック)
$result = stripos($str1, $str2) === 0 && strlen($str1) === strlen($str2);
echo $result ? "等しい\n" : "異なる\n";

// パフォーマンス比較
$iterations = 100000;

$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    strcasecmp($str1, $str2);
}
$time1 = microtime(true) - $start;

$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    strtolower($str1) === strtolower($str2);
}
$time2 = microtime(true) - $start;

echo "strcasecmp(): {$time1}秒\n";
echo "strtolower(): {$time2}秒\n";

まとめ

strcasecmp()関数の特徴をまとめると:

できること:

  • 大文字小文字を区別しない文字列比較
  • アルファベット順の比較(ソート用)
  • 戻り値で大小関係を判定

戻り値:

  • 0: 等しい
  • < 0: 第1引数が小さい
  • > 0: 第1引数が大きい

推奨される使用場面:

  • ユーザー名やメールアドレスの比較
  • ファイル拡張子のチェック
  • HTTPヘッダーの処理
  • コマンドラインオプションの解析
  • 設定キーの検索
  • 大文字小文字を無視したソート

関連関数:

  • strcmp(): 大文字小文字を区別する比較
  • strncasecmp(): 最初のn文字のみ比較(大文字小文字無視)
  • strnatcasecmp(): 自然順ソート(大文字小文字無視)
  • stripos(): 大文字小文字を無視した位置検索

注意点:

  • 非ASCII文字の扱いはロケール依存
  • マルチバイト文字列には適さない場合がある
  • バイナリセーフではない

strcasecmp()は、ユーザー入力の処理やファイル拡張子のチェックなど、大文字小文字を区別したくない場面で非常に便利です。適切に使いこなすことで、ユーザーフレンドリーなアプリケーションを構築できます!

タイトルとURLをコピーしました