[PHP]strrchr関数を完全解説!最後の出現位置から文字列を取得

PHP

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

strrchr関数とは?

strrchr()関数は、文字列内で指定した文字が最後に現れる位置から、文字列の最後までを返す関数です。

“string reverse character”の略で、strchr()strstr()のエイリアス)の逆方向バージョンです。ファイル拡張子の取得やパスのファイル名抽出などに便利です!

基本的な構文

strrchr(string $haystack, string $needle): string|false
  • $haystack: 検索対象の文字列
  • $needle: 検索する文字(文字列を指定しても最初の1文字のみ使用)
  • 戻り値:
    • 見つかった場合: その文字から文字列の最後までの部分文字列
    • 見つからない場合: false

基本的な使用例

シンプルな使用例

$path = "/var/www/html/index.php";

// 最後の / 以降を取得(ファイル名)
$filename = strrchr($path, '/');
echo $filename . "\n";
// 出力: /index.php

// / を除いたファイル名
echo substr($filename, 1) . "\n";
// 出力: index.php

// 最後の . 以降を取得(拡張子)
$ext = strrchr($path, '.');
echo $ext . "\n";
// 出力: .php

strchr()との違い

$text = "apple.banana.orange.txt";

// strchr(): 最初の . から
$first = strchr($text, '.');
echo $first . "\n";
// 出力: .banana.orange.txt

// strrchr(): 最後の . から
$last = strrchr($text, '.');
echo $last . "\n";
// 出力: .txt

複数文字を指定した場合

$text = "Hello World";

// 複数文字を指定しても最初の1文字のみ使用される
$result = strrchr($text, "or");
echo $result . "\n";
// 出力: orld('o'の最後の出現位置から)

見つからない場合

$text = "Hello World";

// 含まれていない文字を検索
$result = strrchr($text, 'x');
var_dump($result);
// 出力: bool(false)

実践的な使用例

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

class FileExtension {
    /**
     * 拡張子を取得(.付き)
     */
    public static function getExtension($filename) {
        $ext = strrchr($filename, '.');
        
        if ($ext === false) {
            return '';
        }
        
        return $ext;
    }
    
    /**
     * 拡張子を取得(.なし)
     */
    public static function getExtensionWithoutDot($filename) {
        $ext = self::getExtension($filename);
        
        if ($ext === '') {
            return '';
        }
        
        return substr($ext, 1);
    }
    
    /**
     * 拡張子を除いたファイル名を取得
     */
    public static function getBasename($filename) {
        $ext = strrchr($filename, '.');
        
        if ($ext === false) {
            return $filename;
        }
        
        return substr($filename, 0, -strlen($ext));
    }
    
    /**
     * 拡張子を変更
     */
    public static function changeExtension($filename, $newExtension) {
        $basename = self::getBasename($filename);
        
        // 新しい拡張子に . がなければ追加
        if (!empty($newExtension) && $newExtension[0] !== '.') {
            $newExtension = '.' . $newExtension;
        }
        
        return $basename . $newExtension;
    }
    
    /**
     * 特定の拡張子かチェック
     */
    public static function hasExtension($filename, $extension) {
        $ext = self::getExtensionWithoutDot($filename);
        return strcasecmp($ext, $extension) === 0;
    }
    
    /**
     * 許可された拡張子かチェック
     */
    public static function isAllowedExtension($filename, $allowedExtensions) {
        $ext = strtolower(self::getExtensionWithoutDot($filename));
        
        foreach ($allowedExtensions as $allowed) {
            if (strcasecmp($ext, $allowed) === 0) {
                return true;
            }
        }
        
        return false;
    }
}

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

echo "=== 拡張子取得 ===\n";
foreach ($files as $file) {
    echo "{$file}:\n";
    echo "  拡張子(.付き): " . FileExtension::getExtension($file) . "\n";
    echo "  拡張子(.なし): " . FileExtension::getExtensionWithoutDot($file) . "\n";
    echo "  ベース名: " . FileExtension::getBasename($file) . "\n";
}

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

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

echo "\n=== 拡張子チェック ===\n";
var_dump(FileExtension::hasExtension('document.pdf', 'pdf'));  // true
var_dump(FileExtension::hasExtension('document.pdf', 'PDF'));  // true

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

例2: ファイルパスの処理

class PathHelper {
    /**
     * ファイル名を取得
     */
    public static function getFilename($path) {
        // 最後の / または \ を探す
        $unixSep = strrchr($path, '/');
        $winSep = strrchr($path, '\\');
        
        // どちらが後ろにあるか判定
        if ($unixSep === false && $winSep === false) {
            return $path;
        }
        
        if ($unixSep === false) {
            return substr($winSep, 1);
        }
        
        if ($winSep === false) {
            return substr($unixSep, 1);
        }
        
        // 両方ある場合は後ろの方を使用
        return strlen($unixSep) < strlen($winSep) 
            ? substr($unixSep, 1) 
            : substr($winSep, 1);
    }
    
    /**
     * ディレクトリ部分を取得
     */
    public static function getDirectory($path) {
        $filename = self::getFilename($path);
        
        if ($filename === $path) {
            return '';
        }
        
        return substr($path, 0, -strlen($filename) - 1);
    }
    
    /**
     * 親ディレクトリ名を取得
     */
    public static function getParentDirectory($path) {
        $dir = self::getDirectory($path);
        
        if ($dir === '') {
            return '';
        }
        
        return self::getFilename($dir);
    }
    
    /**
     * パスを正規化(末尾のスラッシュを削除)
     */
    public static function normalize($path) {
        $lastChar = substr($path, -1);
        
        if ($lastChar === '/' || $lastChar === '\\') {
            return substr($path, 0, -1);
        }
        
        return $path;
    }
}

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

echo "=== パス解析 ===\n";
foreach ($paths as $path) {
    echo "\nパス: {$path}\n";
    echo "  ファイル名: " . PathHelper::getFilename($path) . "\n";
    echo "  ディレクトリ: " . PathHelper::getDirectory($path) . "\n";
    echo "  親ディレクトリ: " . PathHelper::getParentDirectory($path) . "\n";
}

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

class EmailHelper {
    /**
     * ドメイン部分を取得
     */
    public static function getDomain($email) {
        $atSign = strrchr($email, '@');
        
        if ($atSign === false) {
            return null;
        }
        
        return substr($atSign, 1);
    }
    
    /**
     * トップレベルドメインを取得
     */
    public static function getTLD($email) {
        $domain = self::getDomain($email);
        
        if ($domain === null) {
            return null;
        }
        
        $dot = strrchr($domain, '.');
        
        if ($dot === false) {
            return null;
        }
        
        return substr($dot, 1);
    }
    
    /**
     * サブドメインを含まないドメイン名を取得
     */
    public static function getMainDomain($email) {
        $domain = self::getDomain($email);
        
        if ($domain === null) {
            return null;
        }
        
        // 最後の2つの部分を取得(example.com)
        $lastDot = strrchr($domain, '.');
        
        if ($lastDot === false) {
            return $domain;
        }
        
        $beforeLastDot = substr($domain, 0, -strlen($lastDot));
        $secondLastDot = strrchr($beforeLastDot, '.');
        
        if ($secondLastDot === false) {
            return $domain;
        }
        
        return substr($secondLastDot, 1) . $lastDot;
    }
    
    /**
     * ローカル部分を取得
     */
    public static function getLocal($email) {
        $atSign = strrchr($email, '@');
        
        if ($atSign === false) {
            return null;
        }
        
        return substr($email, 0, -strlen($atSign));
    }
    
    /**
     * メールアドレスをマスク
     */
    public static function mask($email) {
        $local = self::getLocal($email);
        $domain = strrchr($email, '@');
        
        if ($local === null || $domain === false) {
            return $email;
        }
        
        $localLength = strlen($local);
        $visibleChars = min(2, $localLength);
        
        $masked = substr($local, 0, $visibleChars) . 
                  str_repeat('*', max(0, $localLength - $visibleChars));
        
        return $masked . $domain;
    }
}

// 使用例
$emails = [
    'user@example.com',
    'john.doe@mail.example.co.uk',
    'admin@subdomain.example.com',
    'test@localhost'
];

echo "=== メールアドレス解析 ===\n";
foreach ($emails as $email) {
    echo "\n{$email}:\n";
    echo "  ローカル: " . EmailHelper::getLocal($email) . "\n";
    echo "  ドメイン: " . EmailHelper::getDomain($email) . "\n";
    echo "  TLD: " . EmailHelper::getTLD($email) . "\n";
    echo "  メインドメイン: " . EmailHelper::getMainDomain($email) . "\n";
    echo "  マスク: " . EmailHelper::mask($email) . "\n";
}

例4: URLの処理

class UrlHelper {
    /**
     * ファイル名を取得
     */
    public static function getFilename($url) {
        // クエリ文字列とフラグメントを除去
        $url = preg_replace('/[?#].*$/', '', $url);
        
        $lastSlash = strrchr($url, '/');
        
        if ($lastSlash === false) {
            return '';
        }
        
        return substr($lastSlash, 1);
    }
    
    /**
     * パス部分を取得
     */
    public static function getPath($url) {
        // プロトコルを除去
        $url = preg_replace('#^https?://[^/]+#', '', $url);
        
        // クエリ文字列を除去
        $questionMark = strrchr($url, '?');
        
        if ($questionMark !== false) {
            $url = substr($url, 0, -strlen($questionMark));
        }
        
        return $url;
    }
    
    /**
     * 最後のパスセグメントを取得
     */
    public static function getLastSegment($url) {
        $path = self::getPath($url);
        $lastSlash = strrchr($path, '/');
        
        if ($lastSlash === false) {
            return $path;
        }
        
        return substr($lastSlash, 1);
    }
}

// 使用例
$urls = [
    'https://example.com/path/to/file.html',
    'https://example.com/products/item123',
    'https://example.com/api/v1/users?id=123',
    'https://example.com/page.php?key=value#section'
];

echo "=== URL解析 ===\n";
foreach ($urls as $url) {
    echo "\n{$url}:\n";
    echo "  ファイル名: " . UrlHelper::getFilename($url) . "\n";
    echo "  パス: " . UrlHelper::getPath($url) . "\n";
    echo "  最後のセグメント: " . UrlHelper::getLastSegment($url) . "\n";
}

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

class VersionHelper {
    /**
     * マイナーバージョン以降を取得
     */
    public static function getMinorAndPatch($version) {
        $lastDot = strrchr($version, '.');
        
        if ($lastDot === false) {
            return '';
        }
        
        return $lastDot;
    }
    
    /**
     * パッチバージョンを取得
     */
    public static function getPatchVersion($version) {
        $lastDot = strrchr($version, '.');
        
        if ($lastDot === false) {
            return null;
        }
        
        return substr($lastDot, 1);
    }
    
    /**
     * メジャー.マイナーバージョンを取得
     */
    public static function getMajorMinor($version) {
        $lastDot = strrchr($version, '.');
        
        if ($lastDot === false) {
            return $version;
        }
        
        return substr($version, 0, -strlen($lastDot));
    }
    
    /**
     * ビルド番号を除去
     */
    public static function removeBuildinformation($version) {
        $plus = strrchr($version, '+');
        
        if ($plus === false) {
            return $version;
        }
        
        return substr($version, 0, -strlen($plus));
    }
}

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

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

例6: ログファイルの処理

class LogHelper {
    /**
     * ログレベルを抽出(最後の [ ] 内)
     */
    public static function extractLastBracketContent($log) {
        $openBracket = strrchr($log, '[');
        
        if ($openBracket === false) {
            return null;
        }
        
        $closeBracket = strpos($openBracket, ']');
        
        if ($closeBracket === false) {
            return null;
        }
        
        return substr($openBracket, 1, $closeBracket - 1);
    }
    
    /**
     * 最後の単語を取得
     */
    public static function getLastWord($text) {
        $lastSpace = strrchr($text, ' ');
        
        if ($lastSpace === false) {
            return $text;
        }
        
        return trim(substr($lastSpace, 1));
    }
    
    /**
     * ファイル名とライン番号を抽出
     */
    public static function extractFileAndLine($message) {
        // "file.php:123" のような形式を想定
        $lastColon = strrchr($message, ':');
        
        if ($lastColon === false) {
            return null;
        }
        
        $lineNumber = substr($lastColon, 1);
        
        if (!is_numeric($lineNumber)) {
            return null;
        }
        
        $file = substr($message, 0, -strlen($lastColon));
        
        return [
            'file' => $file,
            'line' => (int)$lineNumber
        ];
    }
}

// 使用例
$logs = [
    '[2024-02-23 10:30:00] [ERROR] Database connection failed',
    'Error in file script.php:125',
    'User admin logged in successfully'
];

echo "=== ログ解析 ===\n";
foreach ($logs as $log) {
    echo "\n{$log}:\n";
    echo "  最後の[]内容: " . LogHelper::extractLastBracketContent($log) . "\n";
    echo "  最後の単語: " . LogHelper::getLastWord($log) . "\n";
}

$errorMsg = 'Error occurred in /var/www/html/index.php:42';
echo "\n=== ファイル・ライン抽出 ===\n";
$info = LogHelper::extractFileAndLine($errorMsg);
if ($info) {
    print_r($info);
}

例7: データ解析

class DataParser {
    /**
     * 最後のデリミタで分割
     */
    public static function splitAtLast($text, $delimiter) {
        $lastDelimiter = strrchr($text, $delimiter);
        
        if ($lastDelimiter === false) {
            return [$text, ''];
        }
        
        $before = substr($text, 0, -strlen($lastDelimiter));
        $after = substr($lastDelimiter, 1);
        
        return [$before, $after];
    }
    
    /**
     * 最後のキー・値ペアを抽出
     */
    public static function extractLastKeyValue($text, $separator = '=') {
        $lastSeparator = strrchr($text, $separator);
        
        if ($lastSeparator === false) {
            return null;
        }
        
        $key = substr($text, 0, -strlen($lastSeparator));
        $value = substr($lastSeparator, 1);
        
        // キーの最後の単語のみを取得
        $lastSpace = strrchr($key, ' ');
        if ($lastSpace !== false) {
            $key = substr($lastSpace, 1);
        }
        
        return [
            'key' => trim($key),
            'value' => trim($value)
        ];
    }
    
    /**
     * ネストした構造の最後の要素を取得
     */
    public static function getLastElement($path, $separator = '.') {
        $lastSep = strrchr($path, $separator);
        
        if ($lastSep === false) {
            return $path;
        }
        
        return substr($lastSep, 1);
    }
}

// 使用例
$text = "part1.part2.part3.part4";

echo "=== 最後のデリミタで分割 ===\n";
list($before, $after) = DataParser::splitAtLast($text, '.');
echo "前: {$before}\n";  // part1.part2.part3
echo "後: {$after}\n";   // part4

$config = "database.connection.host=localhost";
echo "\n=== キー・値抽出 ===\n";
$kv = DataParser::extractLastKeyValue($config);
print_r($kv);
// key => host, value => localhost

$objectPath = "user.address.city.name";
echo "\n=== 最後の要素 ===\n";
echo DataParser::getLastElement($objectPath) . "\n";  // name

strrpos()との違い

$text = "apple banana apple";

// strrpos(): 最後の出現位置(数値)
$pos = strrpos($text, "apple");
echo "位置: {$pos}\n";  // 13

// strrchr(): 最後の出現位置から最後まで(文字列)
$result = strrchr($text, 'a');
echo "文字列: {$result}\n";  // apple(最後の'a'から)

// 組み合わせて使用
$pos = strrpos($text, "apple");
$extracted = substr($text, $pos);
echo "抽出: {$extracted}\n";  // apple

パフォーマンスの考慮

// 大量の文字列処理
$text = str_repeat("path/to/file.txt.", 1000);

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

// strrpos() + substr()
$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
    $pos = strrpos($text, '.');
    if ($pos !== false) {
        substr($text, $pos);
    }
}
$time2 = microtime(true) - $start;

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

// strrchr()の方が若干高速

まとめ

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

できること:

  • 最後の出現位置から文字列の最後までを取得
  • ファイル拡張子の抽出
  • パスからファイル名を取得

他の関数との違い:

  • strchr() / strstr(): 最初の出現位置から
  • strrchr(): 最後の出現位置から
  • strrpos(): 最後の出現位置(数値)を返す

推奨される使用場面:

  • ファイル拡張子の取得
  • ファイル名の抽出
  • メールアドレスのドメイン取得
  • パスの最後の要素取得
  • バージョン番号の処理

注意点:

  • 文字列を指定しても最初の1文字のみ使用される
  • 見つからない場合はfalseを返す
  • 大文字小文字を区別する

関連関数:

  • strchr() / strstr(): 最初の出現位置から取得
  • strrpos(): 最後の出現位置を返す
  • pathinfo(): パス情報を取得
  • basename(): ファイル名を取得
  • dirname(): ディレクトリ名を取得

strrchr()は、ファイル拡張子の取得やパスの処理など、文字列の最後の部分を扱う場面で非常に便利です。特にファイル操作では頻繁に使用される関数なので、しっかり使いこなしましょう!

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