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