[PHP]stripos関数を完全解説!大文字小文字を区別しない文字列検索

PHP

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

stripos関数とは?

stripos()関数は、文字列内で指定した部分文字列が最初に現れる位置を、大文字小文字を区別せずに検索する関数です。

strpos()の大文字小文字を区別しないバージョンで、ユーザー入力の検索やキーワードマッチングなど、柔軟な文字列検索が必要な場面で活躍します!

基本的な構文

stripos(string $haystack, string $needle, int $offset = 0): int|false
  • $haystack: 検索対象の文字列
  • $needle: 検索する部分文字列
  • $offset: 検索開始位置(オプション)
  • 戻り値:
    • 見つかった場合: 位置(0から始まる整数)
    • 見つからない場合: false

基本的な使用例

シンプルな検索

$text = "Hello World";

// 大文字小文字を区別しない検索
$pos = stripos($text, "world");
echo $pos . "\n";  // 6

$pos = stripos($text, "WORLD");
echo $pos . "\n";  // 6

$pos = stripos($text, "WoRlD");
echo $pos . "\n";  // 6

// 見つからない場合
$pos = stripos($text, "PHP");
var_dump($pos);  // bool(false)

strpos()との違い

$text = "Hello World";

// strpos(): 大文字小文字を区別
$pos1 = strpos($text, "world");
var_dump($pos1);  // bool(false)

$pos2 = strpos($text, "World");
echo $pos2 . "\n";  // 6

// stripos(): 大文字小文字を区別しない
$pos3 = stripos($text, "world");
echo $pos3 . "\n";  // 6

$pos4 = stripos($text, "WORLD");
echo $pos4 . "\n";  // 6

オフセットの使用

$text = "apple banana apple orange";

// 最初のappleを検索
$pos = stripos($text, "apple");
echo "最初: {$pos}\n";  // 0

// 2番目のappleを検索
$pos = stripos($text, "apple", $pos + 1);
echo "2番目: {$pos}\n";  // 13

falseと0の区別

$text = "Hello World";

// 先頭にある場合(位置0)
$pos = stripos($text, "hello");
echo $pos . "\n";  // 0

// 正しい判定方法
if ($pos !== false) {
    echo "見つかりました\n";
} else {
    echo "見つかりません\n";
}

// 間違った判定方法(0はfalseと評価される)
if ($pos) {  // 危険!
    echo "見つかりました\n";
} else {
    echo "見つかりません\n";  // 誤って実行される
}

実践的な使用例

例1: キーワード検索システム

class KeywordSearch {
    /**
     * 文字列にキーワードが含まれているかチェック
     */
    public static function contains($text, $keyword) {
        return stripos($text, $keyword) !== false;
    }
    
    /**
     * 複数のキーワードで検索(OR検索)
     */
    public static function containsAny($text, $keywords) {
        foreach ($keywords as $keyword) {
            if (self::contains($text, $keyword)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 複数のキーワードで検索(AND検索)
     */
    public static function containsAll($text, $keywords) {
        foreach ($keywords as $keyword) {
            if (!self::contains($text, $keyword)) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * キーワードの出現回数をカウント
     */
    public static function countOccurrences($text, $keyword) {
        $count = 0;
        $offset = 0;
        
        while (($pos = stripos($text, $keyword, $offset)) !== false) {
            $count++;
            $offset = $pos + 1;
        }
        
        return $count;
    }
    
    /**
     * すべての出現位置を取得
     */
    public static function findAllPositions($text, $keyword) {
        $positions = [];
        $offset = 0;
        
        while (($pos = stripos($text, $keyword, $offset)) !== false) {
            $positions[] = $pos;
            $offset = $pos + 1;
        }
        
        return $positions;
    }
    
    /**
     * キーワードをハイライト
     */
    public static function highlight($text, $keyword, $before = '<mark>', $after = '</mark>') {
        $positions = self::findAllPositions($text, $keyword);
        
        if (empty($positions)) {
            return $text;
        }
        
        // 後ろから置換(位置がずれないように)
        $keywordLength = strlen($keyword);
        
        foreach (array_reverse($positions) as $pos) {
            $original = substr($text, $pos, $keywordLength);
            $text = substr_replace($text, $before . $original . $after, $pos, $keywordLength);
        }
        
        return $text;
    }
}

// 使用例
$text = "PHP is a popular programming language. Many developers love PHP.";

echo "含まれているか:\n";
var_dump(KeywordSearch::contains($text, "php"));  // true
var_dump(KeywordSearch::contains($text, "PYTHON"));  // false

echo "\nOR検索:\n";
var_dump(KeywordSearch::containsAny($text, ["python", "php"]));  // true

echo "\nAND検索:\n";
var_dump(KeywordSearch::containsAll($text, ["php", "programming"]));  // true
var_dump(KeywordSearch::containsAll($text, ["php", "python"]));  // false

echo "\n出現回数:\n";
echo KeywordSearch::countOccurrences($text, "php") . "\n";  // 2

echo "\n出現位置:\n";
print_r(KeywordSearch::findAllPositions($text, "php"));
// Array ( [0] => 0 [1] => 59 )

echo "\nハイライト:\n";
echo KeywordSearch::highlight($text, "php") . "\n";
// <mark>PHP</mark> is a popular programming language. Many developers love <mark>PHP</mark>.

例2: フィルタリングシステム

class ContentFilter {
    private $blockedWords = [];
    
    public function __construct($blockedWords = []) {
        $this->blockedWords = array_map('strtolower', $blockedWords);
    }
    
    /**
     * 禁止ワードが含まれているかチェック
     */
    public function containsBlockedWord($text) {
        foreach ($this->blockedWords as $word) {
            if (stripos($text, $word) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 含まれている禁止ワードを取得
     */
    public function findBlockedWords($text) {
        $found = [];
        
        foreach ($this->blockedWords as $word) {
            if (stripos($text, $word) !== false) {
                $found[] = $word;
            }
        }
        
        return $found;
    }
    
    /**
     * 禁止ワードをマスク
     */
    public function maskBlockedWords($text, $maskChar = '*') {
        foreach ($this->blockedWords as $word) {
            $offset = 0;
            
            while (($pos = stripos($text, $word, $offset)) !== false) {
                $length = strlen($word);
                $mask = str_repeat($maskChar, $length);
                $text = substr_replace($text, $mask, $pos, $length);
                $offset = $pos + $length;
            }
        }
        
        return $text;
    }
    
    /**
     * テキストをフィルタリング
     */
    public function filter($text) {
        if ($this->containsBlockedWord($text)) {
            return [
                'approved' => false,
                'original' => $text,
                'masked' => $this->maskBlockedWords($text),
                'blocked_words' => $this->findBlockedWords($text)
            ];
        }
        
        return [
            'approved' => true,
            'original' => $text,
            'masked' => $text,
            'blocked_words' => []
        ];
    }
}

// 使用例
$filter = new ContentFilter(['spam', 'viagra', 'casino']);

$text1 = "Check out this great product!";
$result = $filter->filter($text1);
print_r($result);
// approved => true

$text2 = "Buy VIAGRA now! Visit our CASINO!";
$result = $filter->filter($text2);
print_r($result);
/*
Array (
    [approved] => false
    [original] => Buy VIAGRA now! Visit our CASINO!
    [masked] => Buy ****** now! Visit our ******!
    [blocked_words] => Array ( [0] => viagra [1] => casino )
)
*/

例3: ファイル検索システム

class FileSearcher {
    /**
     * ファイル名に検索語が含まれているかチェック
     */
    public static function matchesFilename($filename, $query) {
        return stripos($filename, $query) !== false;
    }
    
    /**
     * ディレクトリ内のファイルを検索
     */
    public static function searchFiles($directory, $query) {
        $results = [];
        
        if (!is_dir($directory)) {
            return $results;
        }
        
        $files = scandir($directory);
        
        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }
            
            if (self::matchesFilename($file, $query)) {
                $results[] = $file;
            }
        }
        
        return $results;
    }
    
    /**
     * 拡張子でフィルタ
     */
    public static function searchByExtension($directory, $extensions) {
        $results = [];
        
        if (!is_dir($directory)) {
            return $results;
        }
        
        $files = scandir($directory);
        
        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }
            
            $fileExt = pathinfo($file, PATHINFO_EXTENSION);
            
            foreach ($extensions as $ext) {
                if (strcasecmp($fileExt, $ext) === 0) {
                    $results[] = $file;
                    break;
                }
            }
        }
        
        return $results;
    }
    
    /**
     * ファイル名の部分一致検索
     */
    public static function searchPartial($directory, $parts) {
        $results = [];
        
        if (!is_dir($directory)) {
            return $results;
        }
        
        $files = scandir($directory);
        
        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }
            
            $matches = true;
            
            foreach ($parts as $part) {
                if (stripos($file, $part) === false) {
                    $matches = false;
                    break;
                }
            }
            
            if ($matches) {
                $results[] = $file;
            }
        }
        
        return $results;
    }
}

// 使用例
echo "「report」を含むファイル:\n";
$files = FileSearcher::searchFiles('.', 'report');
print_r($files);
// report.pdf, monthly_report.docx, etc.

echo "\n画像ファイルを検索:\n";
$images = FileSearcher::searchByExtension('.', ['jpg', 'png', 'gif']);
print_r($images);

echo "\n複数条件で検索:\n";
$files = FileSearcher::searchPartial('.', ['2024', 'report']);
print_r($files);
// 2024_annual_report.pdf, report_2024_Q1.xlsx, etc.

例4: URLルーティング

class SimpleRouter {
    private $routes = [];
    
    /**
     * ルートを追加
     */
    public function addRoute($pattern, $handler) {
        $this->routes[] = [
            'pattern' => $pattern,
            'handler' => $handler
        ];
    }
    
    /**
     * URLをルーティング
     */
    public function route($url) {
        foreach ($this->routes as $route) {
            $pattern = $route['pattern'];
            
            // 大文字小文字を区別しない部分一致
            if (stripos($url, $pattern) !== false) {
                return [
                    'matched' => true,
                    'pattern' => $pattern,
                    'handler' => $route['handler']
                ];
            }
        }
        
        return ['matched' => false];
    }
    
    /**
     * パスの開始部分をチェック
     */
    public function routePrefix($url) {
        foreach ($this->routes as $route) {
            $pattern = $route['pattern'];
            
            // 大文字小文字を区別しない前方一致
            if (stripos($url, $pattern) === 0) {
                return [
                    'matched' => true,
                    'pattern' => $pattern,
                    'handler' => $route['handler']
                ];
            }
        }
        
        return ['matched' => false];
    }
}

// 使用例
$router = new SimpleRouter();

$router->addRoute('/admin', 'AdminController');
$router->addRoute('/api', 'ApiController');
$router->addRoute('/user', 'UserController');

// 大文字小文字を区別しない検索
$result = $router->route('/ADMIN/dashboard');
print_r($result);
// matched => true, pattern => /admin

$result = $router->routePrefix('/API/v1/users');
print_r($result);
// matched => true, pattern => /api

例5: メールアドレスの検証

class EmailValidator {
    /**
     * メールアドレスにドメインが含まれているかチェック
     */
    public static function hasDomain($email, $domain) {
        return stripos($email, $domain) !== false;
    }
    
    /**
     * 許可されたドメインかチェック
     */
    public static function isAllowedDomain($email, $allowedDomains) {
        foreach ($allowedDomains as $domain) {
            if (stripos($email, '@' . $domain) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * ブロックされたドメインかチェック
     */
    public static function isBlockedDomain($email, $blockedDomains) {
        foreach ($blockedDomains as $domain) {
            if (stripos($email, '@' . $domain) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 使い捨てメールアドレスかチェック
     */
    public static function isDisposable($email) {
        $disposableDomains = [
            'tempmail.com', 'throwaway.email', '10minutemail.com',
            'guerrillamail.com', 'mailinator.com'
        ];
        
        return self::isBlockedDomain($email, $disposableDomains);
    }
    
    /**
     * 企業メールかチェック
     */
    public static function isCorporateEmail($email) {
        $personalDomains = [
            'gmail.com', 'yahoo.com', 'hotmail.com', 
            'outlook.com', 'aol.com'
        ];
        
        return !self::isBlockedDomain($email, $personalDomains);
    }
}

// 使用例
$email1 = "user@COMPANY.COM";
$email2 = "user@Gmail.com";
$email3 = "test@tempmail.com";

echo "企業ドメインチェック:\n";
var_dump(EmailValidator::hasDomain($email1, 'company.com'));  // true

echo "\n許可ドメイン:\n";
$allowed = ['company.com', 'corporation.net'];
var_dump(EmailValidator::isAllowedDomain($email1, $allowed));  // true
var_dump(EmailValidator::isAllowedDomain($email2, $allowed));  // false

echo "\n使い捨てメールチェック:\n";
var_dump(EmailValidator::isDisposable($email3));  // true

echo "\n企業メールチェック:\n";
var_dump(EmailValidator::isCorporateEmail($email1));  // true
var_dump(EmailValidator::isCorporateEmail($email2));  // false

例6: ログ解析

class LogAnalyzer {
    /**
     * ログにキーワードが含まれる行を検索
     */
    public static function searchLogs($logFile, $keyword) {
        if (!file_exists($logFile)) {
            return [];
        }
        
        $lines = file($logFile, FILE_IGNORE_NEW_LINES);
        $matches = [];
        
        foreach ($lines as $lineNum => $line) {
            if (stripos($line, $keyword) !== false) {
                $matches[] = [
                    'line_number' => $lineNum + 1,
                    'content' => $line
                ];
            }
        }
        
        return $matches;
    }
    
    /**
     * エラーレベルで検索
     */
    public static function searchByLevel($logFile, $level) {
        return self::searchLogs($logFile, "[{$level}]");
    }
    
    /**
     * 複数キーワードで検索
     */
    public static function searchMultiple($logFile, $keywords) {
        if (!file_exists($logFile)) {
            return [];
        }
        
        $lines = file($logFile, FILE_IGNORE_NEW_LINES);
        $matches = [];
        
        foreach ($lines as $lineNum => $line) {
            foreach ($keywords as $keyword) {
                if (stripos($line, $keyword) !== false) {
                    $matches[] = [
                        'line_number' => $lineNum + 1,
                        'keyword' => $keyword,
                        'content' => $line
                    ];
                    break;  // 1行につき1回だけ記録
                }
            }
        }
        
        return $matches;
    }
    
    /**
     * 統計情報を生成
     */
    public static function getStatistics($logFile, $keywords) {
        $stats = [];
        
        foreach ($keywords as $keyword) {
            $matches = self::searchLogs($logFile, $keyword);
            $stats[$keyword] = count($matches);
        }
        
        return $stats;
    }
}

// 使用例(ログファイルがあると仮定)
/*
echo "「error」を含む行:\n";
$errors = LogAnalyzer::searchLogs('app.log', 'error');
foreach ($errors as $error) {
    echo "Line {$error['line_number']}: {$error['content']}\n";
}

echo "\nエラーレベルで検索:\n";
$critical = LogAnalyzer::searchByLevel('app.log', 'CRITICAL');
print_r($critical);

echo "\n統計情報:\n";
$stats = LogAnalyzer::getStatistics('app.log', ['error', 'warning', 'info']);
print_r($stats);
*/

例7: 製品検索

class ProductSearch {
    private $products = [];
    
    public function __construct($products) {
        $this->products = $products;
    }
    
    /**
     * 名前で検索
     */
    public function searchByName($query) {
        $results = [];
        
        foreach ($this->products as $product) {
            if (stripos($product['name'], $query) !== false) {
                $results[] = $product;
            }
        }
        
        return $results;
    }
    
    /**
     * 説明で検索
     */
    public function searchByDescription($query) {
        $results = [];
        
        foreach ($this->products as $product) {
            if (stripos($product['description'], $query) !== false) {
                $results[] = $product;
            }
        }
        
        return $results;
    }
    
    /**
     * 全フィールドから検索
     */
    public function searchAll($query) {
        $results = [];
        
        foreach ($this->products as $product) {
            $searchText = implode(' ', [
                $product['name'],
                $product['description'],
                $product['category']
            ]);
            
            if (stripos($searchText, $query) !== false) {
                $results[] = $product;
            }
        }
        
        return $results;
    }
    
    /**
     * カテゴリでフィルタ
     */
    public function filterByCategory($category) {
        $results = [];
        
        foreach ($this->products as $product) {
            if (stripos($product['category'], $category) !== false) {
                $results[] = $product;
            }
        }
        
        return $results;
    }
    
    /**
     * スコアリング付き検索
     */
    public function searchWithScore($query) {
        $results = [];
        
        foreach ($this->products as $product) {
            $score = 0;
            
            // 名前に含まれる場合(高スコア)
            if (stripos($product['name'], $query) !== false) {
                $score += 10;
            }
            
            // カテゴリに含まれる場合(中スコア)
            if (stripos($product['category'], $query) !== false) {
                $score += 5;
            }
            
            // 説明に含まれる場合(低スコア)
            if (stripos($product['description'], $query) !== false) {
                $score += 2;
            }
            
            if ($score > 0) {
                $results[] = [
                    'product' => $product,
                    'score' => $score
                ];
            }
        }
        
        // スコア順にソート
        usort($results, function($a, $b) {
            return $b['score'] - $a['score'];
        });
        
        return $results;
    }
}

// 使用例
$products = [
    ['name' => 'iPhone 15 Pro', 'category' => 'Smartphones', 'description' => 'Latest iPhone with advanced features'],
    ['name' => 'Samsung Galaxy S24', 'category' => 'Smartphones', 'description' => 'Premium Android phone'],
    ['name' => 'iPad Pro', 'category' => 'Tablets', 'description' => 'Professional tablet by Apple'],
];

$search = new ProductSearch($products);

echo "名前で検索(iPhone):\n";
$results = $search->searchByName('iphone');
print_r($results);

echo "\nカテゴリで検索:\n";
$results = $search->filterByCategory('smartphones');
print_r($results);

echo "\nスコアリング付き検索:\n";
$results = $search->searchWithScore('phone');
foreach ($results as $result) {
    echo "Score {$result['score']}: {$result['product']['name']}\n";
}

パフォーマンスの考慮

// 大量のデータでの検索
$text = str_repeat("Lorem ipsum dolor sit amet. ", 10000);
$keyword = "amet";

// stripos()
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    stripos($text, $keyword);
}
$time1 = microtime(true) - $start;

// preg_match() (大文字小文字を区別しない)
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    preg_match('/' . preg_quote($keyword, '/') . '/i', $text);
}
$time2 = microtime(true) - $start;

echo "stripos(): {$time1}秒\n";
echo "preg_match(): {$time2}秒\n";

// stripos()の方が一般的に高速

まとめ

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

できること:

  • 大文字小文字を区別しない文字列検索
  • 検索開始位置の指定
  • 複数回の出現を検索

strpos()との違い:

  • strpos(): 大文字小文字を区別
  • stripos(): 大文字小文字を区別しない

推奨される使用場面:

  • キーワード検索
  • ユーザー入力の検証
  • ファイル名検索
  • URLルーティング
  • コンテンツフィルタリング
  • メールドメイン検証

注意点:

  • false0の区別に注意(!== falseを使用)
  • マルチバイト文字にはmb_stripos()を検討
  • パフォーマンスが重要な場合はstrpos()+strtolower()も選択肢

関連関数:

  • strpos(): 大文字小文字を区別する検索
  • strrpos(): 最後の出現位置を検索
  • strripos(): 最後の出現位置を検索(大文字小文字無視)
  • mb_stripos(): マルチバイト対応版
  • str_contains(): 含まれているかのみチェック(PHP 8.0+)

stripos()は柔軟な文字列検索に欠かせない関数です。ユーザーフレンドリーな検索機能を実装する際に積極的に活用しましょう!

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