こんにちは!今回は、PHPの標準関数であるstrncasecmp()について詳しく解説していきます。文字列の最初のn文字だけを、大文字小文字を区別せずに比較できる関数です!
strncasecmp関数とは?
strncasecmp()関数は、2つの文字列の最初のn文字を、大文字小文字を区別せずに比較する関数です。
プレフィックスのチェック、部分的な文字列マッチング、前方一致検索など、様々な場面で活用できます!
基本的な構文
strncasecmp(string $string1, string $string2, int $length): int
- $string1: 比較する1つ目の文字列
- $string2: 比較する2つ目の文字列
- $length: 比較する文字数
- 戻り値:
0: 指定した長さの範囲で等しい< 0: $string1が$string2より小さい> 0: $string1が$string2より大きい
基本的な使用例
シンプルな比較
// 最初の3文字を比較(大文字小文字無視)
echo strncasecmp("Hello", "HELLO", 3) . "\n"; // 0(Hel == HEL)
echo strncasecmp("Hello", "HELP", 3) . "\n"; // 0(Hel == HEL)
echo strncasecmp("Hello", "WORLD", 3) . "\n"; // 負の数(Hel < WOR)
// 文字列全体ではなく部分的な比較
$str1 = "HelloWorld";
$str2 = "HelloPHP";
echo strncasecmp($str1, $str2, 5) . "\n"; // 0(最初の5文字が同じ)
strcasecmp()との違い
$str1 = "Hello";
$str2 = "HelloWorld";
// strcasecmp(): 全体を比較
echo strcasecmp($str1, $str2) . "\n"; // 負の数(異なる)
// strncasecmp(): 最初の5文字のみ比較
echo strncasecmp($str1, $str2, 5) . "\n"; // 0(最初の5文字は同じ)
strncmp()との違い
$str1 = "hello";
$str2 = "HELLO";
// strncmp(): 大文字小文字を区別
echo strncmp($str1, $str2, 5) . "\n"; // 0以外(異なる)
// strncasecmp(): 大文字小文字を区別しない
echo strncasecmp($str1, $str2, 5) . "\n"; // 0(同じ)
長さが文字列より大きい場合
$str1 = "Hi";
$str2 = "HELLO";
// 文字列より長い長さを指定
echo strncasecmp($str1, $str2, 10) . "\n"; // 0以外(2文字しかないが比較)
echo strncasecmp($str1, $str2, 1) . "\n"; // 0(最初の1文字'H'が同じ)
実践的な使用例
例1: プレフィックスチェック
class PrefixChecker {
/**
* プレフィックスが一致するかチェック
*/
public static function hasPrefix($string, $prefix) {
return strncasecmp($string, $prefix, strlen($prefix)) === 0;
}
/**
* 複数のプレフィックスのいずれかに一致するかチェック
*/
public static function hasAnyPrefix($string, $prefixes) {
foreach ($prefixes as $prefix) {
if (self::hasPrefix($string, $prefix)) {
return true;
}
}
return false;
}
/**
* プレフィックスでフィルタリング
*/
public static function filterByPrefix($strings, $prefix) {
return array_filter($strings, function($str) use ($prefix) {
return self::hasPrefix($str, $prefix);
});
}
/**
* プレフィックスでグループ化
*/
public static function groupByPrefix($strings, $prefixLength = 3) {
$groups = [];
foreach ($strings as $string) {
$prefix = strtoupper(substr($string, 0, $prefixLength));
$groups[$prefix][] = $string;
}
ksort($groups);
return $groups;
}
/**
* プレフィックスを削除
*/
public static function removePrefix($string, $prefix) {
if (self::hasPrefix($string, $prefix)) {
return substr($string, strlen($prefix));
}
return $string;
}
}
// 使用例
echo "=== プレフィックスチェック ===\n";
var_dump(PrefixChecker::hasPrefix("HelloWorld", "hello")); // true
var_dump(PrefixChecker::hasPrefix("HelloWorld", "HELLO")); // true
var_dump(PrefixChecker::hasPrefix("HelloWorld", "world")); // false
$files = [
'IMG_001.jpg',
'img_002.jpg',
'DOC_report.pdf',
'doc_summary.pdf',
'VID_clip.mp4'
];
echo "\n=== IMG で始まるファイル ===\n";
$images = PrefixChecker::filterByPrefix($files, 'IMG');
print_r($images);
echo "\n=== プレフィックスでグループ化 ===\n";
$grouped = PrefixChecker::groupByPrefix($files);
print_r($grouped);
echo "\n=== プレフィックス削除 ===\n";
echo PrefixChecker::removePrefix("IMG_001.jpg", "IMG_") . "\n"; // 001.jpg
例2: URLルーティング
class SimpleRouter {
private $routes = [];
/**
* ルートを追加
*/
public function addRoute($prefix, $handler) {
$this->routes[] = [
'prefix' => $prefix,
'prefix_length' => strlen($prefix),
'handler' => $handler
];
}
/**
* URLをルーティング
*/
public function route($url) {
foreach ($this->routes as $route) {
if (strncasecmp($url, $route['prefix'], $route['prefix_length']) === 0) {
return [
'matched' => true,
'prefix' => $route['prefix'],
'handler' => $route['handler'],
'remaining' => substr($url, $route['prefix_length'])
];
}
}
return ['matched' => false];
}
/**
* 最長一致でルーティング
*/
public function routeLongestMatch($url) {
$bestMatch = null;
$longestLength = 0;
foreach ($this->routes as $route) {
if (strncasecmp($url, $route['prefix'], $route['prefix_length']) === 0) {
if ($route['prefix_length'] > $longestLength) {
$longestLength = $route['prefix_length'];
$bestMatch = [
'matched' => true,
'prefix' => $route['prefix'],
'handler' => $route['handler'],
'remaining' => substr($url, $route['prefix_length'])
];
}
}
}
return $bestMatch ?? ['matched' => false];
}
}
// 使用例
$router = new SimpleRouter();
$router->addRoute('/api/', 'ApiController');
$router->addRoute('/admin/', 'AdminController');
$router->addRoute('/user/', 'UserController');
$router->addRoute('/api/v1/', 'ApiV1Controller');
echo "=== ルーティング例 ===\n";
$result = $router->route('/API/users');
print_r($result);
// matched => true, prefix => /api/, handler => ApiController
$result = $router->route('/ADMIN/dashboard');
print_r($result);
// matched => true, prefix => /admin/, handler => AdminController
echo "\n=== 最長一致 ===\n";
$result = $router->routeLongestMatch('/api/v1/users');
print_r($result);
// prefix => /api/v1/, handler => ApiV1Controller
例3: ファイルタイプの判定
class FileTypeDetector {
private static $types = [
'IMG' => ['jpg', 'jpeg', 'png', 'gif', 'bmp'],
'DOC' => ['pdf', 'doc', 'docx', 'txt'],
'VID' => ['mp4', 'avi', 'mov', 'wmv'],
'AUD' => ['mp3', 'wav', 'flac', 'aac']
];
/**
* ファイル名からタイプを判定
*/
public static function detectType($filename) {
foreach (self::$types as $prefix => $extensions) {
if (strncasecmp($filename, $prefix, strlen($prefix)) === 0) {
return strtoupper($prefix);
}
}
return 'UNKNOWN';
}
/**
* プレフィックスが正しいかチェック
*/
public static function hasValidPrefix($filename) {
$type = self::detectType($filename);
if ($type === 'UNKNOWN') {
return false;
}
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
return in_array($ext, self::$types[$type]);
}
/**
* ファイル名を正規化
*/
public static function normalizeFilename($filename) {
$type = self::detectType($filename);
if ($type === 'UNKNOWN') {
return $filename;
}
// プレフィックスを大文字に統一
$prefixLength = strlen($type);
return strtoupper(substr($filename, 0, $prefixLength)) .
substr($filename, $prefixLength);
}
/**
* タイプ別にグループ化
*/
public static function groupByType($files) {
$grouped = [];
foreach ($files as $file) {
$type = self::detectType($file);
$grouped[$type][] = $file;
}
return $grouped;
}
}
// 使用例
$files = [
'img_001.jpg',
'IMG_002.png',
'doc_report.pdf',
'DOC_summary.docx',
'vid_clip.mp4',
'random_file.txt'
];
echo "=== ファイルタイプ判定 ===\n";
foreach ($files as $file) {
echo "{$file}: " . FileTypeDetector::detectType($file) . "\n";
}
echo "\n=== プレフィックス検証 ===\n";
var_dump(FileTypeDetector::hasValidPrefix('IMG_001.jpg')); // true
var_dump(FileTypeDetector::hasValidPrefix('IMG_001.mp4')); // false
echo "\n=== ファイル名正規化 ===\n";
foreach ($files as $file) {
echo FileTypeDetector::normalizeFilename($file) . "\n";
}
echo "\n=== タイプ別グループ化 ===\n";
$grouped = FileTypeDetector::groupByType($files);
print_r($grouped);
例4: コマンド解析
class CommandParser {
private $commands = [
'GET' => 'read',
'SET' => 'write',
'DEL' => 'delete',
'LIST' => 'list'
];
/**
* コマンドを解析
*/
public function parse($input) {
foreach ($this->commands as $cmd => $action) {
if (strncasecmp($input, $cmd, strlen($cmd)) === 0) {
$args = trim(substr($input, strlen($cmd)));
return [
'command' => strtoupper($cmd),
'action' => $action,
'arguments' => $args
];
}
}
return ['command' => null, 'error' => 'Unknown command'];
}
/**
* 補完候補を取得
*/
public function getCompletions($partial) {
$completions = [];
$partialLength = strlen($partial);
foreach (array_keys($this->commands) as $cmd) {
if (strncasecmp($cmd, $partial, $partialLength) === 0) {
$completions[] = $cmd;
}
}
return $completions;
}
/**
* コマンドのヘルプを表示
*/
public function getHelp($commandPrefix = null) {
$help = [];
foreach ($this->commands as $cmd => $action) {
if ($commandPrefix === null ||
strncasecmp($cmd, $commandPrefix, strlen($commandPrefix)) === 0) {
$help[] = sprintf("%-10s - %s", $cmd, $action);
}
}
return $help;
}
}
// 使用例
$parser = new CommandParser();
echo "=== コマンド解析 ===\n";
$result = $parser->parse('get user:123');
print_r($result);
// command => GET, action => read, arguments => user:123
$result = $parser->parse('SET key value');
print_r($result);
// command => SET, action => write, arguments => key value
echo "\n=== 補完候補 ===\n";
$completions = $parser->getCompletions('ge');
print_r($completions); // ['GET']
$completions = $parser->getCompletions('l');
print_r($completions); // ['LIST']
echo "\n=== ヘルプ ===\n";
$help = $parser->getHelp();
foreach ($help as $line) {
echo $line . "\n";
}
例5: プロトコル判定
class ProtocolDetector {
private static $protocols = [
'HTTP://' => 'http',
'HTTPS://' => 'https',
'FTP://' => 'ftp',
'FTPS://' => 'ftps',
'SSH://' => 'ssh',
'MAILTO:' => 'mailto',
'TEL:' => 'tel'
];
/**
* プロトコルを検出
*/
public static function detect($url) {
foreach (self::$protocols as $prefix => $protocol) {
if (strncasecmp($url, $prefix, strlen($prefix)) === 0) {
return [
'protocol' => $protocol,
'prefix' => $prefix,
'url_without_protocol' => substr($url, strlen($prefix))
];
}
}
return ['protocol' => null];
}
/**
* プロトコルが安全かチェック
*/
public static function isSecure($url) {
$info = self::detect($url);
if (!isset($info['protocol'])) {
return false;
}
return in_array($info['protocol'], ['https', 'ftps', 'ssh']);
}
/**
* HTTPまたはHTTPSかチェック
*/
public static function isHttp($url) {
$info = self::detect($url);
if (!isset($info['protocol'])) {
return false;
}
return in_array($info['protocol'], ['http', 'https']);
}
/**
* URLを正規化
*/
public static function normalizeUrl($url) {
$info = self::detect($url);
if (!isset($info['protocol'])) {
return $url;
}
$prefix = strtolower($info['prefix']);
return $prefix . $info['url_without_protocol'];
}
}
// 使用例
$urls = [
'HTTP://example.com',
'https://secure.example.com',
'FTP://ftp.example.com',
'mailto:user@example.com',
'tel:+1234567890'
];
echo "=== プロトコル検出 ===\n";
foreach ($urls as $url) {
$info = ProtocolDetector::detect($url);
echo "{$url}: " . ($info['protocol'] ?? 'unknown') . "\n";
}
echo "\n=== セキュリティチェック ===\n";
foreach ($urls as $url) {
$secure = ProtocolDetector::isSecure($url) ? '安全' : '非安全';
echo "{$url}: {$secure}\n";
}
echo "\n=== URL正規化 ===\n";
foreach ($urls as $url) {
echo ProtocolDetector::normalizeUrl($url) . "\n";
}
例6: データ検証
class DataValidator {
/**
* 電話番号の国コードをチェック
*/
public static function validateCountryCode($phone, $expectedCode) {
// +81, +1 などの国コード
$codeLength = strlen($expectedCode);
return strncasecmp($phone, $expectedCode, $codeLength) === 0;
}
/**
* 郵便番号のプレフィックスをチェック
*/
public static function validateZipPrefix($zip, $validPrefixes) {
foreach ($validPrefixes as $prefix) {
if (strncasecmp($zip, $prefix, strlen($prefix)) === 0) {
return true;
}
}
return false;
}
/**
* 製品コードのフォーマットをチェック
*/
public static function validateProductCode($code, $requiredPrefix) {
$prefixLength = strlen($requiredPrefix);
if (strncasecmp($code, $requiredPrefix, $prefixLength) !== 0) {
return [
'valid' => false,
'error' => "製品コードは {$requiredPrefix} で始まる必要があります"
];
}
return ['valid' => true];
}
/**
* バージョン番号のメジャーバージョンをチェック
*/
public static function validateMajorVersion($version, $requiredMajor) {
// v2.1.3 の場合、v2 かどうかチェック
$requiredLength = strlen($requiredMajor);
return strncasecmp($version, $requiredMajor, $requiredLength) === 0;
}
}
// 使用例
echo "=== 国コード検証 ===\n";
var_dump(DataValidator::validateCountryCode('+81-90-1234-5678', '+81')); // true
var_dump(DataValidator::validateCountryCode('+1-555-1234', '+81')); // false
echo "\n=== 郵便番号プレフィックス検証 ===\n";
$validPrefixes = ['100', '101', '102']; // 東京都千代田区
var_dump(DataValidator::validateZipPrefix('100-0001', $validPrefixes)); // true
var_dump(DataValidator::validateZipPrefix('200-0001', $validPrefixes)); // false
echo "\n=== 製品コード検証 ===\n";
$result = DataValidator::validateProductCode('PROD-12345', 'PROD');
print_r($result); // valid => true
$result = DataValidator::validateProductCode('ITEM-12345', 'PROD');
print_r($result); // valid => false
echo "\n=== メジャーバージョン検証 ===\n";
var_dump(DataValidator::validateMajorVersion('v2.1.3', 'v2')); // true
var_dump(DataValidator::validateMajorVersion('V2.5.0', 'v2')); // true
var_dump(DataValidator::validateMajorVersion('v3.0.0', 'v2')); // false
例7: テキスト検索
class TextSearcher {
/**
* 前方一致検索
*/
public static function searchByPrefix($texts, $prefix) {
$results = [];
$prefixLength = strlen($prefix);
foreach ($texts as $text) {
if (strncasecmp($text, $prefix, $prefixLength) === 0) {
$results[] = $text;
}
}
return $results;
}
/**
* オートコンプリート候補を生成
*/
public static function autocomplete($words, $partial, $maxResults = 10) {
$matches = self::searchByPrefix($words, $partial);
// 結果を制限
return array_slice($matches, 0, $maxResults);
}
/**
* 複数の候補からベストマッチを探す
*/
public static function findBestMatch($query, $candidates) {
$queryLength = strlen($query);
$matches = [];
foreach ($candidates as $candidate) {
if (strncasecmp($query, $candidate, $queryLength) === 0) {
$matches[] = [
'text' => $candidate,
'score' => $queryLength / strlen($candidate) // 類似度スコア
];
}
}
// スコアでソート
usort($matches, function($a, $b) {
return $b['score'] <=> $a['score'];
});
return $matches;
}
}
// 使用例
$words = [
'Apple', 'Application', 'Apply', 'Appreciate',
'Banana', 'Band', 'Bank', 'Banner'
];
echo "=== 前方一致検索 ===\n";
$results = TextSearcher::searchByPrefix($words, 'app');
print_r($results);
// Apple, Application, Apply, Appreciate
echo "\n=== オートコンプリート ===\n";
$completions = TextSearcher::autocomplete($words, 'ban', 3);
print_r($completions);
// Banana, Band, Bank
echo "\n=== ベストマッチ ===\n";
$bestMatches = TextSearcher::findBestMatch('app', $words);
foreach ($bestMatches as $match) {
echo "{$match['text']}: スコア {$match['score']}\n";
}
パフォーマンスの考慮
// 大量の前方一致チェック
$data = [];
for ($i = 0; $i < 10000; $i++) {
$data[] = 'PREFIX' . $i;
}
// strncasecmp()を使用
$start = microtime(true);
$count = 0;
foreach ($data as $item) {
if (strncasecmp($item, 'PREFIX', 6) === 0) {
$count++;
}
}
$time1 = microtime(true) - $start;
// substr() + strcasecmp()を使用
$start = microtime(true);
$count = 0;
foreach ($data as $item) {
if (strcasecmp(substr($item, 0, 6), 'PREFIX') === 0) {
$count++;
}
}
$time2 = microtime(true) - $start;
echo "strncasecmp(): {$time1}秒\n";
echo "substr() + strcasecmp(): {$time2}秒\n";
// strncasecmp()の方が効率的
まとめ
strncasecmp()関数の特徴をまとめると:
できること:
- 文字列の最初のn文字を比較
- 大文字小文字を区別しない比較
- プレフィックスのチェック
他の関数との違い:
strcmp(): 全体を比較、大文字小文字を区別strcasecmp(): 全体を比較、大文字小文字を区別しないstrncmp(): 最初のn文字を比較、大文字小文字を区別strncasecmp(): 最初のn文字を比較、大文字小文字を区別しない
推奨される使用場面:
- プレフィックスのチェック
- URLルーティング
- ファイル名の判定
- コマンド解析
- プロトコル判定
- オートコンプリート
- 前方一致検索
注意点:
- 指定した長さ分だけ比較(文字列全体ではない)
- マルチバイト文字には注意(
mb_strncasecmpは存在しない) - 長さが文字列より大きい場合でも動作する
関連関数:
strncmp(): 大文字小文字を区別する部分比較strcasecmp(): 大文字小文字を区別しない全体比較substr(): 部分文字列の取得strpos(): 文字列の位置検索
strncasecmp()は、プレフィックスチェックや前方一致検索など、文字列の先頭部分だけを柔軟に比較したい場面で非常に便利です。URLルーティングやコマンド解析など、実用的な場面で積極的に活用しましょう!
