[PHP]strrpos関数を完全解説!最後の出現位置を検索する方法

PHP

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

strrpos関数とは?

strrpos()関数は、文字列内で指定した部分文字列が最後に現れる位置(インデックス)を返す関数です。

“string reverse position”の略で、strpos()の逆方向バージョンです。ファイル拡張子の取得、パスの処理、最後の区切り文字の検索など、様々な用途で使用されます!

基本的な構文

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

基本的な使用例

シンプルな検索

$text = "apple banana apple orange";

// 最後の"apple"の位置
$pos = strrpos($text, "apple");
echo $pos . "\n";  // 13

// 最初の"apple"と比較
$first = strpos($text, "apple");
echo $first . "\n";  // 0

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

strpos()との違い

$text = "Hello World Hello PHP";

// strpos(): 最初の出現位置
$first = strpos($text, "Hello");
echo "最初: {$first}\n";  // 0

// strrpos(): 最後の出現位置
$last = strrpos($text, "Hello");
echo "最後: {$last}\n";  // 12

falseと0の区別(重要!)

$text = "Hello World";

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

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

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

オフセットの使用

$text = "apple banana apple orange apple";

// 最後のappleを検索
$pos = strrpos($text, "apple");
echo "最後: {$pos}\n";  // 26

// 位置20以降で検索
$pos = strrpos($text, "apple", 20);
echo "20以降: {$pos}\n";  // 26

// 負のオフセット(末尾から数えた位置より前を検索)
$pos = strrpos($text, "apple", -10);
echo "末尾10文字を除外: {$pos}\n";  // 13

1文字の検索

$text = "Hello World";

// 最後の'o'の位置
$pos = strrpos($text, 'o');
echo $pos . "\n";  // 7

// 最後の'l'の位置
$pos = strrpos($text, 'l');
echo $pos . "\n";  // 9

実践的な使用例

例1: ファイル拡張子の取得

class FileExtensionHelper {
    /**
     * ファイル拡張子を取得
     */
    public static function getExtension($filename) {
        $pos = strrpos($filename, '.');
        
        if ($pos === false) {
            return '';
        }
        
        return substr($filename, $pos + 1);
    }
    
    /**
     * 拡張子を除いたファイル名を取得
     */
    public static function getBasename($filename) {
        $pos = strrpos($filename, '.');
        
        if ($pos === false) {
            return $filename;
        }
        
        return substr($filename, 0, $pos);
    }
    
    /**
     * 拡張子を変更
     */
    public static function changeExtension($filename, $newExtension) {
        $basename = self::getBasename($filename);
        
        if (!empty($newExtension) && $newExtension[0] !== '.') {
            $newExtension = '.' . $newExtension;
        }
        
        return $basename . $newExtension;
    }
    
    /**
     * ダブル拡張子を取得
     */
    public static function getDoubleExtension($filename) {
        $lastDot = strrpos($filename, '.');
        
        if ($lastDot === false) {
            return '';
        }
        
        $beforeLastDot = substr($filename, 0, $lastDot);
        $secondLastDot = strrpos($beforeLastDot, '.');
        
        if ($secondLastDot === false) {
            return self::getExtension($filename);
        }
        
        return substr($filename, $secondLastDot + 1);
    }
    
    /**
     * 拡張子の数をカウント
     */
    public static function countExtensions($filename) {
        $count = 0;
        $pos = 0;
        
        while (($pos = strpos($filename, '.', $pos)) !== false) {
            $count++;
            $pos++;
        }
        
        return $count;
    }
}

// 使用例
$files = [
    'document.pdf',
    'image.jpg',
    'archive.tar.gz',
    'README',
    'backup.2024.zip'
];

echo "=== ファイル拡張子処理 ===\n";
foreach ($files as $file) {
    echo "\n{$file}:\n";
    echo "  拡張子: " . FileExtensionHelper::getExtension($file) . "\n";
    echo "  ベース名: " . FileExtensionHelper::getBasename($file) . "\n";
    echo "  ダブル拡張子: " . FileExtensionHelper::getDoubleExtension($file) . "\n";
    echo "  拡張子数: " . FileExtensionHelper::countExtensions($file) . "\n";
}

echo "\n=== 拡張子変更 ===\n";
echo FileExtensionHelper::changeExtension('document.pdf', 'docx') . "\n";
// document.docx

echo FileExtensionHelper::changeExtension('image.jpg', '.png') . "\n";
// image.png

例2: パス処理

class PathProcessor {
    /**
     * ファイル名を取得
     */
    public static function getFilename($path) {
        // 最後の / または \ を探す
        $unixPos = strrpos($path, '/');
        $winPos = strrpos($path, '\\');
        
        $pos = max($unixPos, $winPos);
        
        if ($pos === false) {
            return $path;
        }
        
        return substr($path, $pos + 1);
    }
    
    /**
     * ディレクトリ部分を取得
     */
    public static function getDirectory($path) {
        $unixPos = strrpos($path, '/');
        $winPos = strrpos($path, '\\');
        
        $pos = max($unixPos, $winPos);
        
        if ($pos === false) {
            return '';
        }
        
        return substr($path, 0, $pos);
    }
    
    /**
     * 親ディレクトリ名を取得
     */
    public static function getParentDirName($path) {
        $dir = self::getDirectory($path);
        
        if ($dir === '') {
            return '';
        }
        
        return self::getFilename($dir);
    }
    
    /**
     * パスの深さを取得
     */
    public static function getDepth($path) {
        $separators = substr_count($path, '/') + substr_count($path, '\\');
        
        // 先頭が/の場合は1引く
        if (!empty($path) && ($path[0] === '/' || $path[0] === '\\')) {
            $separators--;
        }
        
        return $separators;
    }
    
    /**
     * 最後のn個のセグメントを取得
     */
    public static function getLastSegments($path, $n) {
        $segments = [];
        $current = $path;
        
        for ($i = 0; $i < $n; $i++) {
            $unixPos = strrpos($current, '/');
            $winPos = strrpos($current, '\\');
            $pos = max($unixPos, $winPos);
            
            if ($pos === false) {
                $segments[] = $current;
                break;
            }
            
            $segments[] = substr($current, $pos + 1);
            $current = substr($current, 0, $pos);
        }
        
        return array_reverse($segments);
    }
}

// 使用例
$paths = [
    '/var/www/html/index.php',
    'C:\\Users\\Documents\\file.txt',
    'relative/path/to/file.pdf',
    'simple.txt'
];

echo "=== パス処理 ===\n";
foreach ($paths as $path) {
    echo "\nパス: {$path}\n";
    echo "  ファイル名: " . PathProcessor::getFilename($path) . "\n";
    echo "  ディレクトリ: " . PathProcessor::getDirectory($path) . "\n";
    echo "  親ディレクトリ名: " . PathProcessor::getParentDirName($path) . "\n";
    echo "  深さ: " . PathProcessor::getDepth($path) . "\n";
}

$path = '/var/www/html/images/2024/photo.jpg';
echo "\n=== 最後の3セグメント ===\n";
$segments = PathProcessor::getLastSegments($path, 3);
print_r($segments);
// [2024, photo.jpg]または[images, 2024, photo.jpg]

例3: 文字列の分割と抽出

class StringSplitter {
    /**
     * 最後の区切り文字で分割
     */
    public static function splitAtLast($text, $delimiter) {
        $pos = strrpos($text, $delimiter);
        
        if ($pos === false) {
            return [$text, ''];
        }
        
        return [
            substr($text, 0, $pos),
            substr($text, $pos + 1)
        ];
    }
    
    /**
     * 最後の出現以降を取得
     */
    public static function afterLast($text, $delimiter) {
        $pos = strrpos($text, $delimiter);
        
        if ($pos === false) {
            return '';
        }
        
        return substr($text, $pos + 1);
    }
    
    /**
     * 最後の出現まで取得
     */
    public static function beforeLast($text, $delimiter) {
        $pos = strrpos($text, $delimiter);
        
        if ($pos === false) {
            return $text;
        }
        
        return substr($text, 0, $pos);
    }
    
    /**
     * 最後のn個の単語を取得
     */
    public static function getLastWords($text, $n) {
        $words = [];
        $remaining = $text;
        
        for ($i = 0; $i < $n; $i++) {
            $pos = strrpos($remaining, ' ');
            
            if ($pos === false) {
                $words[] = $remaining;
                break;
            }
            
            $words[] = substr($remaining, $pos + 1);
            $remaining = substr($remaining, 0, $pos);
        }
        
        return array_reverse($words);
    }
    
    /**
     * 最後の文を取得
     */
    public static function getLastSentence($text) {
        $markers = ['.', '!', '?'];
        $lastPos = -1;
        
        foreach ($markers as $marker) {
            $pos = strrpos($text, $marker);
            if ($pos !== false && $pos > $lastPos) {
                $lastPos = $pos;
            }
        }
        
        if ($lastPos === -1) {
            return $text;
        }
        
        return trim(substr($text, $lastPos + 1));
    }
}

// 使用例
$text = "apple.banana.orange.grape";

echo "=== 最後の.で分割 ===\n";
list($before, $after) = StringSplitter::splitAtLast($text, '.');
echo "前: {$before}\n";  // apple.banana.orange
echo "後: {$after}\n";   // grape

$email = "user@example.com";
echo "\n=== メールアドレス分割 ===\n";
echo "ローカル部: " . StringSplitter::beforeLast($email, '@') . "\n";
echo "ドメイン部: " . StringSplitter::afterLast($email, '@') . "\n";

$sentence = "The quick brown fox jumps over the lazy dog";
echo "\n=== 最後の3単語 ===\n";
$lastWords = StringSplitter::getLastWords($sentence, 3);
print_r($lastWords);
// [the, lazy, dog]

$paragraph = "First sentence. Second sentence. Third sentence.";
echo "\n=== 最後の文 ===\n";
echo StringSplitter::getLastSentence($paragraph) . "\n";
// Third sentence

例4: URL処理

class UrlProcessor {
    /**
     * クエリ文字列を除いたURLを取得
     */
    public static function removeQueryString($url) {
        $pos = strrpos($url, '?');
        
        if ($pos === false) {
            return $url;
        }
        
        return substr($url, 0, $pos);
    }
    
    /**
     * フラグメントを除去
     */
    public static function removeFragment($url) {
        $pos = strrpos($url, '#');
        
        if ($pos === false) {
            return $url;
        }
        
        return substr($url, 0, $pos);
    }
    
    /**
     * 最後のパスセグメントを取得
     */
    public static function getLastPathSegment($url) {
        // クエリとフラグメントを除去
        $url = self::removeQueryString($url);
        $url = self::removeFragment($url);
        
        $pos = strrpos($url, '/');
        
        if ($pos === false) {
            return $url;
        }
        
        return substr($url, $pos + 1);
    }
    
    /**
     * スキームを除去
     */
    public static function removeScheme($url) {
        $pos = strrpos($url, '://');
        
        if ($pos === false) {
            return $url;
        }
        
        return substr($url, $pos + 3);
    }
    
    /**
     * URLパラメータの最後の値を取得
     */
    public static function getLastParamValue($url, $paramName) {
        $pattern = $paramName . '=';
        $pos = strrpos($url, $pattern);
        
        if ($pos === false) {
            return null;
        }
        
        $start = $pos + strlen($pattern);
        $ampPos = strpos($url, '&', $start);
        $hashPos = strpos($url, '#', $start);
        
        $end = false;
        if ($ampPos !== false) $end = $ampPos;
        if ($hashPos !== false && ($end === false || $hashPos < $end)) {
            $end = $hashPos;
        }
        
        if ($end === false) {
            return substr($url, $start);
        }
        
        return substr($url, $start, $end - $start);
    }
}

// 使用例
$urls = [
    'https://example.com/path/to/page.html?key=value#section',
    'https://example.com/api/v1/users',
    'https://example.com/page?id=1&id=2&id=3'
];

echo "=== URL処理 ===\n";
foreach ($urls as $url) {
    echo "\n元のURL: {$url}\n";
    echo "  クエリ除去: " . UrlProcessor::removeQueryString($url) . "\n";
    echo "  フラグメント除去: " . UrlProcessor::removeFragment($url) . "\n";
    echo "  最後のセグメント: " . UrlProcessor::getLastPathSegment($url) . "\n";
    echo "  スキーム除去: " . UrlProcessor::removeScheme($url) . "\n";
}

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

例5: データ解析

class DataParser {
    /**
     * 最後のキー・値ペアを抽出
     */
    public static function extractLastKeyValue($text, $separator = '=') {
        $pos = strrpos($text, $separator);
        
        if ($pos === false) {
            return null;
        }
        
        $key = trim(substr($text, 0, $pos));
        $value = trim(substr($text, $pos + 1));
        
        // キーの最後の単語のみを取得
        $lastSpace = strrpos($key, ' ');
        if ($lastSpace !== false) {
            $key = substr($key, $lastSpace + 1);
        }
        
        return [
            'key' => $key,
            'value' => $value
        ];
    }
    
    /**
     * 階層構造の最後の要素を取得
     */
    public static function getLastElement($path, $separator = '.') {
        $pos = strrpos($path, $separator);
        
        if ($pos === false) {
            return $path;
        }
        
        return substr($path, $pos + 1);
    }
    
    /**
     * 最後のn個の要素を取得
     */
    public static function getLastElements($path, $separator, $n) {
        $elements = [];
        $current = $path;
        
        for ($i = 0; $i < $n; $i++) {
            $pos = strrpos($current, $separator);
            
            if ($pos === false) {
                $elements[] = $current;
                break;
            }
            
            $elements[] = substr($current, $pos + 1);
            $current = substr($current, 0, $pos);
        }
        
        return array_reverse($elements);
    }
    
    /**
     * CSVの最後のフィールドを取得
     */
    public static function getLastCsvField($line, $delimiter = ',') {
        $pos = strrpos($line, $delimiter);
        
        if ($pos === false) {
            return trim($line);
        }
        
        return trim(substr($line, $pos + 1));
    }
}

// 使用例
$config = "database.connection.pool.size=100";
echo "=== キー・値抽出 ===\n";
$kv = DataParser::extractLastKeyValue($config);
print_r($kv);
// key => size, value => 100

$objectPath = "user.profile.address.city";
echo "\n=== 階層構造 ===\n";
echo "最後の要素: " . DataParser::getLastElement($objectPath) . "\n";
// city

$elements = DataParser::getLastElements($objectPath, '.', 2);
print_r($elements);
// [address, city]

$csvLine = "John,Doe,30,New York";
echo "\n=== CSV最後のフィールド ===\n";
echo DataParser::getLastCsvField($csvLine) . "\n";
// New York

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

class TextReplacer {
    /**
     * 最後の出現を置換
     */
    public static function replaceLast($text, $search, $replace) {
        $pos = strrpos($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 = strrpos($result, $search);
            
            if ($pos === false) {
                break;
            }
            
            $result = substr_replace($result, $replace, $pos, strlen($search));
        }
        
        return $result;
    }
    
    /**
     * 最後の出現を削除
     */
    public static function removeLast($text, $search) {
        return self::replaceLast($text, $search, '');
    }
    
    /**
     * 最後の出現位置に挿入
     */
    public static function insertAfterLast($text, $marker, $insertion) {
        $pos = strrpos($text, $marker);
        
        if ($pos === false) {
            return $text;
        }
        
        return substr($text, 0, $pos + strlen($marker)) . 
               $insertion . 
               substr($text, $pos + strlen($marker));
    }
}

// 使用例
$text = "apple banana apple orange apple";

echo "=== 最後の出現を置換 ===\n";
echo TextReplacer::replaceLast($text, "apple", "grape") . "\n";
// apple banana apple orange grape

echo "\n=== 最後の2個を置換 ===\n";
echo TextReplacer::replaceLastN($text, "apple", "grape", 2) . "\n";
// apple banana grape orange grape

echo "\n=== 最後の出現を削除 ===\n";
echo TextReplacer::removeLast($text, " apple") . "\n";
// apple banana apple orange

$code = "function test() { return true; }";
echo "\n=== 最後の}の後に挿入 ===\n";
echo TextReplacer::insertAfterLast($code, "}", "// end of function") . "\n";
// function test() { return true; }// end of function

例7: バージョン番号とタイムスタンプ

class VersionTimestampHelper {
    /**
     * バージョン番号の最後の部分を取得
     */
    public static function getLastVersionPart($version) {
        $pos = strrpos($version, '.');
        
        if ($pos === false) {
            return $version;
        }
        
        return substr($version, $pos + 1);
    }
    
    /**
     * タイムスタンプの最後の部分を取得
     */
    public static function getLastTimestampPart($timestamp, $separator = ':') {
        $pos = strrpos($timestamp, $separator);
        
        if ($pos === false) {
            return $timestamp;
        }
        
        return substr($timestamp, $pos + 1);
    }
    
    /**
     * ビルド番号を除去
     */
    public static function removeBuildNumber($version) {
        $plusPos = strrpos($version, '+');
        $hyphenPos = strrpos($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);
    }
    
    /**
     * 日付の最後の部分を取得
     */
    public static function getLastDatePart($date, $separator = '-') {
        $pos = strrpos($date, $separator);
        
        if ($pos === false) {
            return $date;
        }
        
        return substr($date, $pos + 1);
    }
}

// 使用例
$versions = ['1.2.3', '2.10.5-alpha', '3.0.0+build.123'];

echo "=== バージョン処理 ===\n";
foreach ($versions as $version) {
    echo "{$version}:\n";
    echo "  最後の部分: " . VersionTimestampHelper::getLastVersionPart($version) . "\n";
    echo "  ビルド番号除去: " . VersionTimestampHelper::removeBuildNumber($version) . "\n";
}

$timestamp = "2024-02-23 14:35:22";
echo "\n=== タイムスタンプ ===\n";
echo "秒: " . VersionTimestampHelper::getLastTimestampPart($timestamp) . "\n";

$date = "2024-02-23";
echo "日: " . VersionTimestampHelper::getLastDatePart($date) . "\n";

パフォーマンスの考慮

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

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

// 手動で後方検索
$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
    $pos = false;
    for ($j = strlen($text) - strlen($keyword); $j >= 0; $j--) {
        if (substr($text, $j, strlen($keyword)) === $keyword) {
            $pos = $j;
            break;
        }
    }
}
$time2 = microtime(true) - $start;

echo "strrpos(): {$time1}秒\n";
echo "手動検索: {$time2}秒\n";

// strrpos()は高度に最適化されており非常に高速

まとめ

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

できること:

  • 最後の出現位置を検索
  • 後方からの効率的な検索
  • オフセットを使った柔軟な検索

他の関数との違い:

  • strpos(): 最初の出現位置を検索
  • strrpos(): 最後の出現位置を検索
  • stripos(): 最初の出現位置(大文字小文字無視)
  • strripos(): 最後の出現位置(大文字小文字無視)

推奨される使用場面:

  • ファイル拡張子の取得
  • パスからファイル名を抽出
  • 最後の区切り文字で分割
  • URLのクエリ文字列除去
  • 最後の出現箇所の置換
  • バージョン番号の処理

重要な注意点:

  • false0の区別に必ず!== falseを使用
  • 負のオフセットも使用可能
  • 大文字小文字を区別する

関連関数:

  • strpos(): 最初の出現位置を検索
  • strripos(): 最後の出現位置(大文字小文字無視)
  • strrchr(): 最後の出現位置から文字列の最後まで取得
  • substr(): 部分文字列の取得
  • mb_strrpos(): マルチバイト対応版

パフォーマンス:

  • 高度に最適化されており非常に高速
  • 大量のデータでも効率的

strrpos()は、文字列の最後の部分を扱う処理で欠かせない関数です。特にファイル拡張子の取得やパス処理では頻繁に使用されるので、false0の区別に注意しながら、しっかり使いこなしましょう!

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