[PHP]strripos関数を完全解説!大文字小文字を区別せず最後の出現位置を検索

PHP

こんにちは!今回は、PHPの標準関数であるstrripos()について詳しく解説していきます。文字列内で指定した部分文字列が最後に現れる位置を、大文字小文字を区別せずに検索できる関数です!

strripos関数とは?

strripos()関数は、文字列内で指定した部分文字列が最後に現れる位置を、大文字小文字を区別せずに検索し、その位置(インデックス)を返す関数です。

strrpos()の大文字小文字を区別しないバージョンで、柔軟な後方検索が必要な場面で活躍します!

基本的な構文

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

基本的な使用例

シンプルな検索

$text = "Hello World, Hello PHP";

// 最後の"Hello"を検索(大文字小文字無視)
$pos = strripos($text, "hello");
echo $pos . "\n";  // 13

$pos = strripos($text, "HELLO");
echo $pos . "\n";  // 13

$pos = strripos($text, "HeLLo");
echo $pos . "\n";  // 13

strrpos()との違い

$text = "Apple apple APPLE";

// strrpos(): 大文字小文字を区別
$pos1 = strrpos($text, "apple");
echo $pos1 . "\n";  // 6(小文字のapple)

$pos2 = strrpos($text, "APPLE");
echo $pos2 . "\n";  // 12(大文字のAPPLE)

// strripos(): 大文字小文字を区別しない
$pos3 = strripos($text, "apple");
echo $pos3 . "\n";  // 12(最後のAPPLE)

$pos4 = strripos($text, "APPLE");
echo $pos4 . "\n";  // 12(最後のAPPLE)

stripos()との違い

$text = "apple BANANA apple BANANA";

// stripos(): 最初の出現位置(大文字小文字無視)
$pos1 = stripos($text, "banana");
echo $pos1 . "\n";  // 6

// strripos(): 最後の出現位置(大文字小文字無視)
$pos2 = strripos($text, "banana");
echo $pos2 . "\n";  // 19

オフセットの使用

$text = "Test test TEST test";

// 最後のtestを検索
$pos1 = strripos($text, "test");
echo $pos1 . "\n";  // 15

// 位置10以降で検索
$pos2 = strripos($text, "test", 10);
echo $pos2 . "\n";  // 15

// 負のオフセット(文字列の末尾から)
$pos3 = strripos($text, "test", -10);
echo $pos3 . "\n";  // 10

falseと0の区別

$text = "Hello World";

// 見つかった場合
$pos = strripos($text, "hello");
if ($pos !== false) {
    echo "見つかりました(位置: {$pos})\n";
} else {
    echo "見つかりません\n";
}

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

実践的な使用例

例1: ファイル拡張子の処理

class FileExtensionHandler {
    /**
     * 最後の拡張子を取得(大文字小文字無視)
     */
    public static function getLastExtension($filename) {
        $pos = strripos($filename, '.');
        
        if ($pos === false) {
            return '';
        }
        
        return substr($filename, $pos + 1);
    }
    
    /**
     * 特定の拡張子で終わるかチェック
     */
    public static function hasExtension($filename, $extension) {
        $lastDot = strripos($filename, '.');
        
        if ($lastDot === false) {
            return false;
        }
        
        $fileExt = substr($filename, $lastDot + 1);
        
        return strcasecmp($fileExt, $extension) === 0;
    }
    
    /**
     * 複数の拡張子のいずれかで終わるかチェック
     */
    public static function hasAnyExtension($filename, $extensions) {
        foreach ($extensions as $ext) {
            if (self::hasExtension($filename, $ext)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 拡張子を変更(大文字小文字を保持)
     */
    public static function changeExtension($filename, $newExtension) {
        $lastDot = strripos($filename, '.');
        
        if ($lastDot === false) {
            return $filename . '.' . $newExtension;
        }
        
        return substr($filename, 0, $lastDot + 1) . $newExtension;
    }
    
    /**
     * ダブル拡張子をチェック
     */
    public static function hasDoubleExtension($filename) {
        $lastDot = strripos($filename, '.');
        
        if ($lastDot === false || $lastDot === 0) {
            return false;
        }
        
        $beforeLastDot = substr($filename, 0, $lastDot);
        $secondLastDot = strripos($beforeLastDot, '.');
        
        return $secondLastDot !== false;
    }
}

// 使用例
$files = [
    'document.PDF',
    'image.JPG',
    'archive.tar.GZ',
    'README',
    'file.backup.PHP'
];

echo "=== 拡張子取得 ===\n";
foreach ($files as $file) {
    echo "{$file}: " . FileExtensionHandler::getLastExtension($file) . "\n";
}

echo "\n=== 拡張子チェック ===\n";
foreach ($files as $file) {
    $isPdf = FileExtensionHandler::hasExtension($file, 'pdf');
    echo "{$file}: " . ($isPdf ? 'PDF' : '非PDF') . "\n";
}

$allowedExtensions = ['jpg', 'png', 'gif', 'pdf'];
echo "\n=== 許可された拡張子 ===\n";
foreach ($files as $file) {
    $allowed = FileExtensionHandler::hasAnyExtension($file, $allowedExtensions);
    echo "{$file}: " . ($allowed ? 'OK' : 'NG') . "\n";
}

echo "\n=== ダブル拡張子チェック ===\n";
foreach ($files as $file) {
    $double = FileExtensionHandler::hasDoubleExtension($file);
    echo "{$file}: " . ($double ? 'あり' : 'なし') . "\n";
}

例2: テキスト検索と置換

class TextSearcher {
    /**
     * 最後の出現位置を検索
     */
    public static function findLast($text, $needle) {
        $pos = strripos($text, $needle);
        
        if ($pos === false) {
            return null;
        }
        
        return [
            'position' => $pos,
            'found' => substr($text, $pos, strlen($needle)),
            'before' => substr($text, 0, $pos),
            'after' => substr($text, $pos + strlen($needle))
        ];
    }
    
    /**
     * 最後の出現を置換
     */
    public static function replaceLast($text, $search, $replace) {
        $pos = strripos($text, $search);
        
        if ($pos === false) {
            return $text;
        }
        
        return substr_replace($text, $replace, $pos, strlen($search));
    }
    
    /**
     * 最後のn個の出現を置換
     */
    public static function replaceLastN($text, $search, $replace, $n) {
        $result = $text;
        
        for ($i = 0; $i < $n; $i++) {
            $pos = strripos($result, $search);
            
            if ($pos === false) {
                break;
            }
            
            $result = substr_replace($result, $replace, $pos, strlen($search));
        }
        
        return $result;
    }
    
    /**
     * 最後の出現位置から切り取り
     */
    public static function truncateAfterLast($text, $marker) {
        $pos = strripos($text, $marker);
        
        if ($pos === false) {
            return $text;
        }
        
        return substr($text, 0, $pos + strlen($marker));
    }
    
    /**
     * 最後の出現位置より前を切り取り
     */
    public static function truncateBeforeLast($text, $marker) {
        $pos = strripos($text, $marker);
        
        if ($pos === false) {
            return '';
        }
        
        return substr($text, $pos);
    }
}

// 使用例
$text = "Hello world, hello PHP, HELLO everyone!";

echo "=== 最後の出現位置 ===\n";
$result = TextSearcher::findLast($text, "hello");
print_r($result);

echo "\n=== 最後の出現を置換 ===\n";
$replaced = TextSearcher::replaceLast($text, "hello", "hi");
echo $replaced . "\n";
// Hello world, hello PHP, hi everyone!

echo "\n=== 最後の2個を置換 ===\n";
$replaced = TextSearcher::replaceLastN($text, "hello", "hi", 2);
echo $replaced . "\n";
// Hello world, hi PHP, hi everyone!

$path = "/var/www/html/images/photo.jpg";
echo "\n=== 最後の/以降を取得 ===\n";
echo TextSearcher::truncateBeforeLast($path, "/") . "\n";
// /photo.jpg

例3: URLとパスの処理

class UrlPathProcessor {
    /**
     * 最後のパスセグメントを取得
     */
    public static function getLastSegment($path) {
        $pos = strripos($path, '/');
        
        if ($pos === false) {
            return $path;
        }
        
        return substr($path, $pos + 1);
    }
    
    /**
     * クエリパラメータの最後の値を取得
     */
    public static function getLastParamValue($url, $paramName) {
        // 大文字小文字を区別せずパラメータを検索
        $searchParam = $paramName . '=';
        $pos = strripos($url, $searchParam);
        
        if ($pos === false) {
            return null;
        }
        
        $valueStart = $pos + strlen($searchParam);
        $ampPos = strpos($url, '&', $valueStart);
        $hashPos = strpos($url, '#', $valueStart);
        
        $endPos = false;
        if ($ampPos !== false) $endPos = $ampPos;
        if ($hashPos !== false && ($endPos === false || $hashPos < $endPos)) {
            $endPos = $hashPos;
        }
        
        if ($endPos === false) {
            return substr($url, $valueStart);
        }
        
        return substr($url, $valueStart, $endPos - $valueStart);
    }
    
    /**
     * ファイル名(クエリ除く)を取得
     */
    public static function getFilename($url) {
        // クエリとフラグメントを除去
        $url = preg_replace('/[?#].*$/', '', $url);
        
        return self::getLastSegment($url);
    }
    
    /**
     * 親ディレクトリのパスを取得
     */
    public static function getParentPath($path) {
        $pos = strripos($path, '/');
        
        if ($pos === false || $pos === 0) {
            return '/';
        }
        
        return substr($path, 0, $pos);
    }
}

// 使用例
$urls = [
    'https://example.com/path/to/FILE.html',
    'https://example.com/API/v1/users',
    'https://example.com/page.PHP?key=value&KEY=value2',
    '/var/WWW/html/INDEX.php'
];

echo "=== 最後のセグメント ===\n";
foreach ($urls as $url) {
    echo UrlPathProcessor::getLastSegment($url) . "\n";
}

echo "\n=== ファイル名取得 ===\n";
foreach ($urls as $url) {
    echo UrlPathProcessor::getFilename($url) . "\n";
}

$url = 'https://example.com/page?KEY=first&key=second&Key=third';
echo "\n=== 最後のパラメータ値 ===\n";
echo UrlPathProcessor::getLastParamValue($url, 'key') . "\n";
// third

例4: ログ解析

class LogAnalyzer {
    /**
     * 最後のログレベルマーカーを検出
     */
    public static function findLastLogLevel($log) {
        $levels = ['ERROR', 'WARN', 'INFO', 'DEBUG'];
        $lastPos = -1;
        $lastLevel = null;
        
        foreach ($levels as $level) {
            $pos = strripos($log, $level);
            
            if ($pos !== false && $pos > $lastPos) {
                $lastPos = $pos;
                $lastLevel = $level;
            }
        }
        
        if ($lastLevel === null) {
            return null;
        }
        
        return [
            'level' => $lastLevel,
            'position' => $lastPos
        ];
    }
    
    /**
     * 最後のタイムスタンプを抽出
     */
    public static function extractLastTimestamp($log) {
        // [YYYY-MM-DD HH:MM:SS] 形式を想定
        $pattern = '/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]/';
        
        if (preg_match_all($pattern, $log, $matches)) {
            return end($matches[0]);
        }
        
        return null;
    }
    
    /**
     * 最後のエラーメッセージを取得
     */
    public static function getLastError($logs) {
        $errorPos = strripos($logs, 'ERROR');
        
        if ($errorPos === false) {
            return null;
        }
        
        // エラー行の終わりまで取得
        $newlinePos = strpos($logs, "\n", $errorPos);
        
        if ($newlinePos === false) {
            return substr($logs, $errorPos);
        }
        
        return substr($logs, $errorPos, $newlinePos - $errorPos);
    }
}

// 使用例
$log = "[2024-02-23 10:30:00] INFO User logged in\n" .
       "[2024-02-23 10:31:00] ERROR Database connection failed\n" .
       "[2024-02-23 10:32:00] WARN High memory usage\n" .
       "[2024-02-23 10:33:00] error File not found";

echo "=== 最後のログレベル ===\n";
$lastLevel = LogAnalyzer::findLastLogLevel($log);
print_r($lastLevel);

echo "\n=== 最後のタイムスタンプ ===\n";
echo LogAnalyzer::extractLastTimestamp($log) . "\n";

echo "\n=== 最後のエラー ===\n";
echo LogAnalyzer::getLastError($log) . "\n";

例5: メールアドレスの処理

class EmailProcessor {
    /**
     * 最後の@の位置を取得
     */
    public static function findLastAtSign($text) {
        $pos = strripos($text, '@');
        
        if ($pos === false) {
            return null;
        }
        
        return $pos;
    }
    
    /**
     * テキストから最後のメールアドレスを抽出
     */
    public static function extractLastEmail($text) {
        $atPos = strripos($text, '@');
        
        if ($atPos === false) {
            return null;
        }
        
        // @の前を遡る
        $start = $atPos;
        while ($start > 0 && preg_match('/[a-zA-Z0-9._-]/', $text[$start - 1])) {
            $start--;
        }
        
        // @の後を進む
        $end = $atPos;
        while ($end < strlen($text) - 1 && preg_match('/[a-zA-Z0-9._-]/', $text[$end + 1])) {
            $end++;
        }
        
        return substr($text, $start, $end - $start + 1);
    }
    
    /**
     * 複数のメールアドレスから最後のドメインを取得
     */
    public static function getLastDomain($emails) {
        $lastEmail = is_array($emails) ? end($emails) : $emails;
        $atPos = strripos($lastEmail, '@');
        
        if ($atPos === false) {
            return null;
        }
        
        return substr($lastEmail, $atPos + 1);
    }
}

// 使用例
$text = "Contact: info@example.com or support@TEST.org for help.";

echo "=== 最後のメールアドレス ===\n";
echo EmailProcessor::extractLastEmail($text) . "\n";
// support@TEST.org

$emails = ['user@example.com', 'admin@TEST.com', 'info@DEMO.org'];
echo "\n=== 最後のドメイン ===\n";
echo EmailProcessor::getLastDomain($emails) . "\n";
// DEMO.org

例6: データ検証

class DataValidator {
    /**
     * 最後の禁止ワードをチェック
     */
    public static function findLastBannedWord($text, $bannedWords) {
        $lastPos = -1;
        $lastWord = null;
        
        foreach ($bannedWords as $word) {
            $pos = strripos($text, $word);
            
            if ($pos !== false && $pos > $lastPos) {
                $lastPos = $pos;
                $lastWord = $word;
            }
        }
        
        if ($lastWord === null) {
            return null;
        }
        
        return [
            'word' => $lastWord,
            'position' => $lastPos,
            'context' => substr($text, max(0, $lastPos - 20), 40)
        ];
    }
    
    /**
     * 最後の無効な文字をチェック
     */
    public static function findLastInvalidChar($text, $invalidChars) {
        $lastPos = -1;
        $lastChar = null;
        
        foreach (str_split($invalidChars) as $char) {
            $pos = strripos($text, $char);
            
            if ($pos !== false && $pos > $lastPos) {
                $lastPos = $pos;
                $lastChar = $char;
            }
        }
        
        if ($lastChar === null) {
            return null;
        }
        
        return [
            'character' => $lastChar,
            'position' => $lastPos
        ];
    }
}

// 使用例
$text = "This is a SPAM message with more spam content";
$banned = ['spam', 'virus', 'phishing'];

echo "=== 最後の禁止ワード ===\n";
$result = DataValidator::findLastBannedWord($text, $banned);
print_r($result);

$filename = "file<name>test";
echo "\n=== 最後の無効文字 ===\n";
$invalid = DataValidator::findLastInvalidChar($filename, '<>:"|?*');
print_r($invalid);

例7: バージョン番号の処理

class VersionProcessor {
    /**
     * 最後のドットの位置を検索
     */
    public static function findLastDot($version) {
        return strripos($version, '.');
    }
    
    /**
     * パッチバージョンを取得
     */
    public static function getPatchVersion($version) {
        $pos = strripos($version, '.');
        
        if ($pos === false) {
            return null;
        }
        
        return substr($version, $pos + 1);
    }
    
    /**
     * メジャー.マイナーバージョンを取得
     */
    public static function getMajorMinor($version) {
        $pos = strripos($version, '.');
        
        if ($pos === false) {
            return $version;
        }
        
        return substr($version, 0, $pos);
    }
    
    /**
     * ビルド情報を削除
     */
    public static function removeBuildInfo($version) {
        $plusPos = strripos($version, '+');
        $hyphenPos = strripos($version, '-');
        
        $pos = false;
        if ($plusPos !== false) $pos = $plusPos;
        if ($hyphenPos !== false && ($pos === false || $hyphenPos < $pos)) {
            $pos = $hyphenPos;
        }
        
        if ($pos === false) {
            return $version;
        }
        
        return substr($version, 0, $pos);
    }
}

// 使用例
$versions = [
    'v1.2.3',
    'V2.10.5-ALPHA',
    'V3.0.0+BUILD.123'
];

echo "=== バージョン処理 ===\n";
foreach ($versions as $version) {
    echo "\n{$version}:\n";
    echo "  パッチ: " . VersionProcessor::getPatchVersion($version) . "\n";
    echo "  メジャー.マイナー: " . VersionProcessor::getMajorMinor($version) . "\n";
    echo "  ビルド情報除去: " . VersionProcessor::removeBuildInfo($version) . "\n";
}

パフォーマンスの考慮

// 大量のテキストで最後の出現位置を検索
$text = str_repeat("word1 WORD2 word3 ", 10000);
$keyword = "word2";

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

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

echo "strripos(): {$time1}秒\n";
echo "strrpos() + strtolower(): {$time2}秒\n";

// strripos()の方が効率的

まとめ

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

できること:

  • 最後の出現位置を検索
  • 大文字小文字を区別しない検索
  • 後方からの柔軟な検索

他の関数との違い:

  • strpos(): 最初の出現位置、大文字小文字を区別
  • stripos(): 最初の出現位置、大文字小文字を区別しない
  • strrpos(): 最後の出現位置、大文字小文字を区別
  • strripos(): 最後の出現位置、大文字小文字を区別しない

推奨される使用場面:

  • ファイル拡張子の検出
  • URLパスの処理
  • 最後の出現箇所の置換
  • ログの解析
  • メールアドレスの処理
  • バージョン番号の処理

注意点:

  • false0の区別に!== falseを使用
  • 負のオフセットも使用可能
  • マルチバイト文字にはmb_strripos()を使用

関連関数:

  • strrpos(): 大文字小文字を区別する最後の検索
  • stripos(): 大文字小文字を区別しない最初の検索
  • strpos(): 最初の出現位置を検索
  • mb_strripos(): マルチバイト対応版

パフォーマンス:

  • 効率的に最適化されている
  • strtolower()との組み合わせより高速

strripos()は、大文字小文字を気にせずに最後の出現位置を見つけたい場合に非常に便利です。ファイル拡張子の処理やログ解析など、実用的な場面で積極的に活用しましょう!

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