こんにちは!今回は、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ルーティング
- コンテンツフィルタリング
- メールドメイン検証
注意点:
falseと0の区別に注意(!== falseを使用)- マルチバイト文字には
mb_stripos()を検討 - パフォーマンスが重要な場合は
strpos()+strtolower()も選択肢
関連関数:
strpos(): 大文字小文字を区別する検索strrpos(): 最後の出現位置を検索strripos(): 最後の出現位置を検索(大文字小文字無視)mb_stripos(): マルチバイト対応版str_contains(): 含まれているかのみチェック(PHP 8.0+)
stripos()は柔軟な文字列検索に欠かせない関数です。ユーザーフレンドリーな検索機能を実装する際に積極的に活用しましょう!
