[PHP]stristr関数を完全解説!大文字小文字を区別しない部分文字列取得

PHP

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

stristr関数とは?

stristr()関数は、文字列内で指定した部分文字列が最初に現れる位置を検索し、その位置から文字列の最後まで(またはその前まで)を大文字小文字を区別せずに返す関数です。

strstr()の大文字小文字を区別しないバージョンで、柔軟な文字列抽出が必要な場面で活躍します!

基本的な構文

stristr(
    string $haystack,
    string $needle,
    bool $before_needle = false
): string|false
  • $haystack: 検索対象の文字列
  • $needle: 検索する部分文字列
  • $before_needle: true の場合、見つかった文字列の前の部分を返す
  • 戻り値:
    • 見つかった場合: 部分文字列
    • 見つからない場合: false

基本的な使用例

シンプルな使用例

$email = "user@EXAMPLE.COM";

// @以降を取得(大文字小文字を区別しない)
$domain = stristr($email, '@');
echo $domain . "\n";
// 出力: @EXAMPLE.COM

// @より前を取得
$username = stristr($email, '@', true);
echo $username . "\n";
// 出力: user

strstr()との違い

$text = "Hello WORLD";

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

$result2 = strstr($text, "WORLD");
echo $result2 . "\n";  // WORLD

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

$result4 = stristr($text, "WoRlD");
echo $result4 . "\n";  // WORLD

before_needleの使用

$url = "https://EXAMPLE.COM/path/to/page";

// プロトコル部分を除外
$afterProtocol = stristr($url, "://");
echo $afterProtocol . "\n";
// 出力: ://EXAMPLE.COM/path/to/page

// プロトコル部分を取得
$protocol = stristr($url, "://", true);
echo $protocol . "\n";
// 出力: https

実践的な使用例

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

class EmailParser {
    /**
     * ドメイン部分を取得
     */
    public static function getDomain($email) {
        $domain = stristr($email, '@');
        
        if ($domain === false) {
            return null;
        }
        
        return substr($domain, 1);  // @ を除去
    }
    
    /**
     * ローカル部分を取得
     */
    public static function getLocalPart($email) {
        return stristr($email, '@', true);
    }
    
    /**
     * トップレベルドメインを取得
     */
    public static function getTLD($email) {
        $domain = self::getDomain($email);
        
        if ($domain === null) {
            return null;
        }
        
        $tld = stristr($domain, '.');
        
        if ($tld === false) {
            return null;
        }
        
        return substr($tld, 1);  // . を除去
    }
    
    /**
     * ドメイン名(TLD除く)を取得
     */
    public static function getDomainName($email) {
        $domain = self::getDomain($email);
        
        if ($domain === null) {
            return null;
        }
        
        return stristr($domain, '.', true);
    }
    
    /**
     * メールアドレスが特定のドメインか確認
     */
    public static function isDomain($email, $targetDomain) {
        $domain = self::getDomain($email);
        
        if ($domain === null) {
            return false;
        }
        
        return strcasecmp($domain, $targetDomain) === 0;
    }
    
    /**
     * メールアドレスをマスク
     */
    public static function mask($email) {
        $local = self::getLocalPart($email);
        $domain = stristr($email, '@');
        
        if ($local === false || $domain === false) {
            return $email;
        }
        
        $localLength = strlen($local);
        $visibleChars = min(3, $localLength);
        
        $masked = substr($local, 0, $visibleChars) . 
                  str_repeat('*', max(0, $localLength - $visibleChars));
        
        return $masked . $domain;
    }
}

// 使用例
$email = "John.Doe@EXAMPLE.COM";

echo "ドメイン: " . EmailParser::getDomain($email) . "\n";
// EXAMPLE.COM

echo "ローカル部分: " . EmailParser::getLocalPart($email) . "\n";
// John.Doe

echo "TLD: " . EmailParser::getTLD($email) . "\n";
// COM

echo "ドメイン名: " . EmailParser::getDomainName($email) . "\n";
// EXAMPLE

var_dump(EmailParser::isDomain($email, 'example.com'));
// bool(true)

echo "マスク: " . EmailParser::mask($email) . "\n";
// Joh*****@EXAMPLE.COM

例2: URLパーサー

class UrlParser {
    /**
     * プロトコルを取得
     */
    public static function getProtocol($url) {
        return stristr($url, '://', true);
    }
    
    /**
     * プロトコルを除いた部分を取得
     */
    public static function withoutProtocol($url) {
        $result = stristr($url, '://');
        
        if ($result === false) {
            return $url;
        }
        
        return substr($result, 3);  // :// を除去
    }
    
    /**
     * ホスト部分を取得
     */
    public static function getHost($url) {
        $withoutProtocol = self::withoutProtocol($url);
        
        // パス部分を除去
        $host = stristr($withoutProtocol, '/', true);
        
        if ($host === false) {
            // クエリ文字列を除去
            $host = stristr($withoutProtocol, '?', true);
            
            if ($host === false) {
                $host = $withoutProtocol;
            }
        }
        
        return $host;
    }
    
    /**
     * パス部分を取得
     */
    public static function getPath($url) {
        $withoutProtocol = self::withoutProtocol($url);
        $path = stristr($withoutProtocol, '/');
        
        if ($path === false) {
            return '/';
        }
        
        // クエリ文字列を除去
        $pathOnly = stristr($path, '?', true);
        
        return $pathOnly !== false ? $pathOnly : $path;
    }
    
    /**
     * クエリ文字列を取得
     */
    public static function getQueryString($url) {
        $query = stristr($url, '?');
        
        if ($query === false) {
            return '';
        }
        
        // フラグメントを除去
        $queryOnly = stristr($query, '#', true);
        
        return substr($queryOnly !== false ? $queryOnly : $query, 1);  // ? を除去
    }
    
    /**
     * フラグメントを取得
     */
    public static function getFragment($url) {
        $fragment = stristr($url, '#');
        
        if ($fragment === false) {
            return '';
        }
        
        return substr($fragment, 1);  // # を除去
    }
    
    /**
     * URL全体を解析
     */
    public static function parse($url) {
        return [
            'protocol' => self::getProtocol($url),
            'host' => self::getHost($url),
            'path' => self::getPath($url),
            'query' => self::getQueryString($url),
            'fragment' => self::getFragment($url)
        ];
    }
}

// 使用例
$url = "HTTPS://Example.COM:8080/Path/To/Page?key=value&foo=bar#SECTION";

echo "プロトコル: " . UrlParser::getProtocol($url) . "\n";
// HTTPS

echo "ホスト: " . UrlParser::getHost($url) . "\n";
// Example.COM:8080

echo "パス: " . UrlParser::getPath($url) . "\n";
// /Path/To/Page

echo "クエリ: " . UrlParser::getQueryString($url) . "\n";
// key=value&foo=bar

echo "フラグメント: " . UrlParser::getFragment($url) . "\n";
// SECTION

echo "\n完全な解析:\n";
print_r(UrlParser::parse($url));

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

class PathHelper {
    /**
     * ファイル名を取得
     */
    public static function getFilename($path) {
        // 最後の / または \ を探す
        $lastSlash = max(
            strrpos($path, '/'),
            strrpos($path, '\\')
        );
        
        if ($lastSlash === false) {
            return $path;
        }
        
        return substr($path, $lastSlash + 1);
    }
    
    /**
     * 拡張子を取得
     */
    public static function getExtension($path) {
        $filename = self::getFilename($path);
        $ext = stristr($filename, '.');
        
        if ($ext === false) {
            return '';
        }
        
        return substr($ext, 1);  // . を除去
    }
    
    /**
     * 拡張子を除いたファイル名を取得
     */
    public static function getBasename($path) {
        $filename = self::getFilename($path);
        return stristr($filename, '.', true) ?: $filename;
    }
    
    /**
     * ディレクトリ部分を取得
     */
    public static function getDirectory($path) {
        $lastSlash = max(
            strrpos($path, '/'),
            strrpos($path, '\\')
        );
        
        if ($lastSlash === false) {
            return '';
        }
        
        return substr($path, 0, $lastSlash);
    }
    
    /**
     * 特定の拡張子か確認
     */
    public static function hasExtension($path, $extension) {
        $ext = self::getExtension($path);
        return strcasecmp($ext, $extension) === 0;
    }
    
    /**
     * 拡張子を変更
     */
    public static function changeExtension($path, $newExtension) {
        $basename = self::getBasename($path);
        $directory = self::getDirectory($path);
        
        $newPath = $basename . '.' . $newExtension;
        
        if (!empty($directory)) {
            $newPath = $directory . '/' . $newPath;
        }
        
        return $newPath;
    }
}

// 使用例
$path = "C:\\Users\\Documents\\Report.PDF";

echo "ファイル名: " . PathHelper::getFilename($path) . "\n";
// Report.PDF

echo "拡張子: " . PathHelper::getExtension($path) . "\n";
// PDF

echo "ベース名: " . PathHelper::getBasename($path) . "\n";
// Report

echo "ディレクトリ: " . PathHelper::getDirectory($path) . "\n";
// C:\Users\Documents

var_dump(PathHelper::hasExtension($path, 'pdf'));
// bool(true)

echo "拡張子変更: " . PathHelper::changeExtension($path, 'docx') . "\n";
// C:\Users\Documents/Report.docx

例4: コンテンツタイプの判定

class ContentTypeDetector {
    /**
     * Content-Typeヘッダーからメディアタイプを取得
     */
    public static function getMediaType($contentType) {
        // ; より前を取得
        $mediaType = stristr($contentType, ';', true);
        
        return $mediaType !== false ? trim($mediaType) : trim($contentType);
    }
    
    /**
     * 文字セットを取得
     */
    public static function getCharset($contentType) {
        $charsetPart = stristr($contentType, 'charset=');
        
        if ($charsetPart === false) {
            return null;
        }
        
        $charset = substr($charsetPart, 8);  // "charset=" を除去
        
        // ; があればそこまで
        $end = stristr($charset, ';', true);
        
        return $end !== false ? trim($end) : trim($charset);
    }
    
    /**
     * boundaryを取得(multipart用)
     */
    public static function getBoundary($contentType) {
        $boundaryPart = stristr($contentType, 'boundary=');
        
        if ($boundaryPart === false) {
            return null;
        }
        
        $boundary = substr($boundaryPart, 9);  // "boundary=" を除去
        
        // 引用符を除去
        $boundary = trim($boundary, '"\'');
        
        // ; があればそこまで
        $end = stristr($boundary, ';', true);
        
        return $end !== false ? trim($end) : trim($boundary);
    }
    
    /**
     * Content-Typeを解析
     */
    public static function parse($contentType) {
        return [
            'media_type' => self::getMediaType($contentType),
            'charset' => self::getCharset($contentType),
            'boundary' => self::getBoundary($contentType)
        ];
    }
    
    /**
     * 特定のメディアタイプか確認
     */
    public static function isMediaType($contentType, $targetType) {
        $mediaType = self::getMediaType($contentType);
        return strcasecmp($mediaType, $targetType) === 0;
    }
}

// 使用例
$contentType1 = "text/html; CHARSET=UTF-8";
$contentType2 = "MULTIPART/FORM-DATA; boundary=----WebKitFormBoundary";
$contentType3 = "APPLICATION/JSON";

echo "=== 例1 ===\n";
print_r(ContentTypeDetector::parse($contentType1));
/*
Array (
    [media_type] => text/html
    [charset] => UTF-8
    [boundary] => 
)
*/

echo "\n=== 例2 ===\n";
print_r(ContentTypeDetector::parse($contentType2));
/*
Array (
    [media_type] => MULTIPART/FORM-DATA
    [charset] => 
    [boundary] => ----WebKitFormBoundary
)
*/

echo "\n=== JSON判定 ===\n";
var_dump(ContentTypeDetector::isMediaType($contentType3, 'application/json'));
// bool(true)

例5: ログエントリの解析

class LogEntryParser {
    /**
     * ログレベルを取得
     */
    public static function getLevel($logEntry) {
        // [ で始まるログレベル部分を探す
        $levelPart = stristr($logEntry, '[');
        
        if ($levelPart === false) {
            return null;
        }
        
        // ] までを取得
        $level = stristr($levelPart, ']', true);
        
        if ($level === false) {
            return null;
        }
        
        return trim($level, '[]');
    }
    
    /**
     * タイムスタンプを取得
     */
    public static function getTimestamp($logEntry) {
        // 最初の [ ] で囲まれた部分を取得
        $firstBracket = stristr($logEntry, '[');
        
        if ($firstBracket === false) {
            return null;
        }
        
        $timestamp = stristr($firstBracket, ']', true);
        
        if ($timestamp === false) {
            return null;
        }
        
        return trim($timestamp, '[]');
    }
    
    /**
     * メッセージ部分を取得
     */
    public static function getMessage($logEntry) {
        // 最後の ] 以降を取得
        $lastBracket = strrpos($logEntry, ']');
        
        if ($lastBracket === false) {
            return $logEntry;
        }
        
        return trim(substr($logEntry, $lastBracket + 1));
    }
    
    /**
     * 特定のレベルのログか確認
     */
    public static function isLevel($logEntry, $targetLevel) {
        $level = self::getLevel($logEntry);
        
        if ($level === null) {
            return false;
        }
        
        return strcasecmp($level, $targetLevel) === 0;
    }
}

// 使用例
$log1 = "[2024-02-23 10:30:45] [ERROR] Database connection failed";
$log2 = "[INFO] User logged in successfully";
$log3 = "[2024-02-23 10:31:00] [WARNING] Memory usage high";

echo "ログ1:\n";
echo "  タイムスタンプ: " . LogEntryParser::getTimestamp($log1) . "\n";
echo "  レベル: " . LogEntryParser::getLevel($log1) . "\n";
echo "  メッセージ: " . LogEntryParser::getMessage($log1) . "\n";

echo "\nエラーログ判定:\n";
var_dump(LogEntryParser::isLevel($log1, 'error'));  // true
var_dump(LogEntryParser::isLevel($log2, 'error'));  // false

例6: HTTPヘッダーの解析

class HttpHeaderParser {
    /**
     * ヘッダー名を取得
     */
    public static function getHeaderName($header) {
        return stristr($header, ':', true);
    }
    
    /**
     * ヘッダー値を取得
     */
    public static function getHeaderValue($header) {
        $value = stristr($header, ':');
        
        if ($value === false) {
            return '';
        }
        
        return trim(substr($value, 1));  // : を除去
    }
    
    /**
     * 特定のヘッダーか確認
     */
    public static function isHeader($header, $targetName) {
        $name = self::getHeaderName($header);
        
        if ($name === false) {
            return false;
        }
        
        return strcasecmp($name, $targetName) === 0;
    }
    
    /**
     * ヘッダー配列から特定のヘッダーを検索
     */
    public static function findHeader($headers, $targetName) {
        foreach ($headers as $header) {
            if (self::isHeader($header, $targetName)) {
                return self::getHeaderValue($header);
            }
        }
        
        return null;
    }
    
    /**
     * すべてのヘッダーを連想配列に変換
     */
    public static function parseHeaders($headers) {
        $parsed = [];
        
        foreach ($headers as $header) {
            $name = self::getHeaderName($header);
            $value = self::getHeaderValue($header);
            
            if ($name !== false) {
                $parsed[strtolower($name)] = $value;
            }
        }
        
        return $parsed;
    }
}

// 使用例
$headers = [
    "Content-Type: APPLICATION/JSON; charset=utf-8",
    "CONTENT-LENGTH: 1234",
    "Authorization: Bearer token123",
    "Cache-Control: no-cache"
];

echo "Content-Type: " . HttpHeaderParser::findHeader($headers, 'content-type') . "\n";
// APPLICATION/JSON; charset=utf-8

echo "Content-Length: " . HttpHeaderParser::findHeader($headers, 'CONTENT-LENGTH') . "\n";
// 1234

echo "\n全ヘッダー:\n";
print_r(HttpHeaderParser::parseHeaders($headers));

例7: ファイルタイプの判定

class FileTypeChecker {
    private static $mimeTypes = [
        'jpg' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'png' => 'image/png',
        'gif' => 'image/gif',
        'pdf' => 'application/pdf',
        'doc' => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'xls' => 'application/vnd.ms-excel',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    ];
    
    /**
     * 拡張子からMIMEタイプを取得
     */
    public static function getMimeType($filename) {
        $ext = PathHelper::getExtension($filename);
        $lowerExt = strtolower($ext);
        
        return self::$mimeTypes[$lowerExt] ?? 'application/octet-stream';
    }
    
    /**
     * 画像ファイルか確認
     */
    public static function isImage($filename) {
        $mimeType = self::getMimeType($filename);
        return stristr($mimeType, 'image/') !== false;
    }
    
    /**
     * ドキュメントファイルか確認
     */
    public static function isDocument($filename) {
        $ext = strtolower(PathHelper::getExtension($filename));
        $docExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
        
        return in_array($ext, $docExtensions);
    }
    
    /**
     * 許可された拡張子か確認
     */
    public static function isAllowedExtension($filename, $allowedExtensions) {
        $ext = PathHelper::getExtension($filename);
        
        foreach ($allowedExtensions as $allowed) {
            if (strcasecmp($ext, $allowed) === 0) {
                return true;
            }
        }
        
        return false;
    }
}

// 使用例
$files = [
    'photo.JPG',
    'document.PDF',
    'spreadsheet.XLSX',
    'video.MP4'
];

foreach ($files as $file) {
    echo "{$file}:\n";
    echo "  MIMEタイプ: " . FileTypeChecker::getMimeType($file) . "\n";
    echo "  画像: " . (FileTypeChecker::isImage($file) ? 'はい' : 'いいえ') . "\n";
    echo "  ドキュメント: " . (FileTypeChecker::isDocument($file) ? 'はい' : 'いいえ') . "\n";
    echo "\n";
}

$allowed = ['jpg', 'png', 'pdf'];
echo "許可された拡張子チェック:\n";
foreach ($files as $file) {
    $isAllowed = FileTypeChecker::isAllowedExtension($file, $allowed);
    echo "{$file}: " . ($isAllowed ? '許可' : '不許可') . "\n";
}

strchr()との関係

// stristr()とstrchr()は大文字小文字の扱いが異なるだけ
$email = "User@EXAMPLE.COM";

// strchr(): strstr()のエイリアス(大文字小文字を区別)
$result1 = strchr($email, '@');
echo $result1 . "\n";  // @EXAMPLE.COM

// stristr(): 大文字小文字を区別しない
$result2 = stristr($email, '@');
echo $result2 . "\n";  // @EXAMPLE.COM

// 大文字小文字が異なる場合
$result3 = strchr($email, 'user');
var_dump($result3);  // bool(false)

$result4 = stristr($email, 'user');
echo $result4 . "\n";  // User@EXAMPLE.COM

パフォーマンスの考慮

// 大量の文字列検索
$text = str_repeat("Lorem ipsum DOLOR sit amet. ", 1000);
$keyword = "dolor";

// stristr()
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    stristr($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 "stristr(): {$time1}秒\n";
echo "preg_match(): {$time2}秒\n";

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

まとめ

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

できること:

  • 大文字小文字を区別しない部分文字列の取得
  • 指定文字列以降の取得
  • 指定文字列より前の取得(第3引数)

strstr()との違い:

  • strstr(): 大文字小文字を区別
  • stristr(): 大文字小文字を区別しない

推奨される使用場面:

  • メールアドレスの解析
  • URL・パスの処理
  • ヘッダーの解析
  • ファイル拡張子の取得
  • ログエントリの解析
  • Content-Typeの処理

注意点:

  • falseの判定には=== falseを使用
  • マルチバイト文字にはmb_stristr()を検討
  • 最初の出現位置のみ

関連関数:

  • strstr(): 大文字小文字を区別
  • strchr(): strstr()のエイリアス
  • strrchr(): 最後の出現位置から取得
  • stripos(): 位置のみ取得(大文字小文字無視)
  • mb_stristr(): マルチバイト対応版

使用例のまとめ:

  • メールアドレス: ドメイン・ローカル部分の抽出
  • URL: プロトコル・ホスト・パスの解析
  • ファイルパス: 拡張子・ディレクトリの取得
  • ヘッダー: 名前と値の分離

stristr()は、大文字小文字を気にせずに文字列を分割・抽出したい場合に非常に便利です。ユーザー入力の処理や柔軟なパース処理に活用しましょう!

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