[PHP]strstr関数を完全解説!部分文字列を検索して取得する方法

PHP

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

strstr関数とは?

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

strchr()の別名(エイリアス)でもあり、文字列の分割や部分抽出に便利です。メールアドレスやURLの解析、データの抽出など、様々な用途で使用されます!

基本的な構文

strstr(
    string $haystack,
    string $needle,
    bool $before_needle = false
): string|false
  • $haystack: 検索対象の文字列
  • $needle: 検索する部分文字列
  • $before_needle: trueの場合、見つかった位置より前の部分を返す(PHP 5.3.0+)
  • 戻り値:
    • 見つかった場合: その位置から最後まで(または前まで)の部分文字列
    • 見つからない場合: false

基本的な使用例

シンプルな使用例

$email = "user@example.com";

// @以降を取得
$domain = strstr($email, '@');
echo $domain . "\n";
// 出力: @example.com

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

文字列と1文字の検索

$text = "Hello World";

// 文字列で検索
$result = strstr($text, "World");
echo $result . "\n";  // World

// 1文字で検索
$result = strstr($text, 'o');
echo $result . "\n";  // o World(最初の'o'から)

見つからない場合

$text = "Hello World";

// 含まれていない文字列を検索
$result = strstr($text, "PHP");
var_dump($result);  // bool(false)

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

strchr()との関係

$text = "apple@banana@orange";

// strstr()とstrchr()は同じ
$result1 = strstr($text, '@');
$result2 = strchr($text, '@');

echo $result1 . "\n";  // @banana@orange
echo $result2 . "\n";  // @banana@orange

var_dump($result1 === $result2);  // true

実践的な使用例

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

class EmailParser {
    /**
     * ドメイン部分を取得
     */
    public static function getDomain($email) {
        $domain = strstr($email, '@');
        
        if ($domain === false) {
            return null;
        }
        
        return substr($domain, 1);  // @を除去
    }
    
    /**
     * ローカル部分を取得
     */
    public static function getLocalPart($email) {
        return strstr($email, '@', true);
    }
    
    /**
     * トップレベルドメインを取得
     */
    public static function getTLD($email) {
        $domain = self::getDomain($email);
        
        if ($domain === null) {
            return null;
        }
        
        $tld = strstr($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 strstr($domain, '.', true);
    }
    
    /**
     * メールアドレスをマスク
     */
    public static function maskEmail($email) {
        $local = self::getLocalPart($email);
        $domain = strstr($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;
    }
    
    /**
     * 企業メールか判定
     */
    public static function isCompanyEmail($email, $companyDomains) {
        $domain = self::getDomain($email);
        
        if ($domain === null) {
            return false;
        }
        
        foreach ($companyDomains as $companyDomain) {
            if (stripos($domain, $companyDomain) !== false) {
                return true;
            }
        }
        
        return false;
    }
}

// 使用例
$emails = [
    'john.doe@example.com',
    'admin@mail.company.co.jp',
    'user@test.org'
];

echo "=== メールアドレス解析 ===\n";
foreach ($emails as $email) {
    echo "\n{$email}:\n";
    echo "  ローカル部: " . EmailParser::getLocalPart($email) . "\n";
    echo "  ドメイン: " . EmailParser::getDomain($email) . "\n";
    echo "  ドメイン名: " . EmailParser::getDomainName($email) . "\n";
    echo "  TLD: " . EmailParser::getTLD($email) . "\n";
    echo "  マスク: " . EmailParser::maskEmail($email) . "\n";
}

$companyDomains = ['company.co.jp', 'example.com'];
echo "\n=== 企業メール判定 ===\n";
foreach ($emails as $email) {
    $isCompany = EmailParser::isCompanyEmail($email, $companyDomains);
    echo "{$email}: " . ($isCompany ? '企業' : '個人') . "\n";
}

例2: URL解析

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

// 使用例
$urls = [
    'https://example.com:8080/path/to/page?key=value&foo=bar#section',
    'http://test.org/index.html',
    'ftp://files.example.com/download'
];

echo "=== URL解析 ===\n";
foreach ($urls as $url) {
    echo "\nURL: {$url}\n";
    $parsed = UrlParser::parse($url);
    echo "  プロトコル: {$parsed['protocol']}\n";
    echo "  ホスト: {$parsed['host']}\n";
    echo "  パス: {$parsed['path']}\n";
    echo "  クエリ: {$parsed['query']}\n";
    echo "  フラグメント: {$parsed['fragment']}\n";
}

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

class PathParser {
    /**
     * 拡張子を取得
     */
    public static function getExtension($path) {
        $filename = basename($path);
        $ext = strstr($filename, '.');
        
        if ($ext === false) {
            return '';
        }
        
        return substr($ext, 1);  // .を除去
    }
    
    /**
     * ファイル名(拡張子除く)を取得
     */
    public static function getBasename($path) {
        $filename = basename($path);
        return strstr($filename, '.', true) ?: $filename;
    }
    
    /**
     * ディレクトリ部分とファイル名を分離
     */
    public static function splitPath($path) {
        $lastSlash = max(strrpos($path, '/'), strrpos($path, '\\'));
        
        if ($lastSlash === false) {
            return ['dir' => '', 'file' => $path];
        }
        
        return [
            'dir' => substr($path, 0, $lastSlash),
            'file' => substr($path, $lastSlash + 1)
        ];
    }
    
    /**
     * 拡張子を変更
     */
    public static function changeExtension($path, $newExtension) {
        $parts = self::splitPath($path);
        $basename = self::getBasename($parts['file']);
        
        if (!empty($newExtension) && $newExtension[0] !== '.') {
            $newExtension = '.' . $newExtension;
        }
        
        $newFilename = $basename . $newExtension;
        
        if (!empty($parts['dir'])) {
            return $parts['dir'] . '/' . $newFilename;
        }
        
        return $newFilename;
    }
}

// 使用例
$paths = [
    '/var/www/html/index.php',
    'C:\\Users\\Documents\\report.pdf',
    'image.backup.jpg',
    'README'
];

echo "=== パス解析 ===\n";
foreach ($paths as $path) {
    echo "\nパス: {$path}\n";
    echo "  拡張子: " . PathParser::getExtension($path) . "\n";
    echo "  ベース名: " . PathParser::getBasename($path) . "\n";
    
    $parts = PathParser::splitPath($path);
    echo "  ディレクトリ: " . $parts['dir'] . "\n";
    echo "  ファイル名: " . $parts['file'] . "\n";
}

echo "\n=== 拡張子変更 ===\n";
echo PathParser::changeExtension('/path/to/file.txt', 'md') . "\n";
// /path/to/file.md

例4: データ抽出

class DataExtractor {
    /**
     * 特定マーカー以降のデータを取得
     */
    public static function afterMarker($text, $marker) {
        $result = strstr($text, $marker);
        
        if ($result === false) {
            return null;
        }
        
        return substr($result, strlen($marker));
    }
    
    /**
     * 特定マーカーより前のデータを取得
     */
    public static function beforeMarker($text, $marker) {
        return strstr($text, $marker, true);
    }
    
    /**
     * 2つのマーカーに挟まれた部分を取得
     */
    public static function between($text, $start, $end) {
        $afterStart = strstr($text, $start);
        
        if ($afterStart === false) {
            return null;
        }
        
        $afterStart = substr($afterStart, strlen($start));
        $beforeEnd = strstr($afterStart, $end, true);
        
        return $beforeEnd !== false ? $beforeEnd : $afterStart;
    }
    
    /**
     * キー・値ペアを抽出
     */
    public static function extractKeyValue($line, $separator = '=') {
        $value = strstr($line, $separator);
        
        if ($value === false) {
            return null;
        }
        
        $key = strstr($line, $separator, true);
        
        return [
            'key' => trim($key),
            'value' => trim(substr($value, 1))
        ];
    }
    
    /**
     * HTMLタグの内容を抽出
     */
    public static function extractTagContent($html, $tag) {
        $openTag = "<{$tag}>";
        $closeTag = "</{$tag}>";
        
        $afterOpen = strstr($html, $openTag);
        
        if ($afterOpen === false) {
            return null;
        }
        
        $content = substr($afterOpen, strlen($openTag));
        $beforeClose = strstr($content, $closeTag, true);
        
        return $beforeClose !== false ? $beforeClose : $content;
    }
}

// 使用例
echo "=== データ抽出 ===\n";

$text = "Name: John Doe, Age: 30";
echo "': 以降: " . DataExtractor::afterMarker($text, ': ') . "\n";
// John Doe, Age: 30

$config = "database.host=localhost";
$kv = DataExtractor::extractKeyValue($config);
print_r($kv);
// ['key' => 'database.host', 'value' => 'localhost']

$html = "<title>Page Title</title>";
echo "\nタイトル: " . DataExtractor::extractTagContent($html, 'title') . "\n";
// Page Title

$text = "Start [content here] End";
echo "挟まれた部分: " . DataExtractor::between($text, '[', ']') . "\n";
// content here

例5: ログ解析

class LogParser {
    /**
     * ログレベルを抽出
     */
    public static function extractLevel($logLine) {
        // [ERROR] のような形式を想定
        $afterBracket = strstr($logLine, '[');
        
        if ($afterBracket === false) {
            return null;
        }
        
        $level = strstr($afterBracket, ']', true);
        
        if ($level === false) {
            return null;
        }
        
        return trim($level, '[]');
    }
    
    /**
     * メッセージ部分を抽出
     */
    public static function extractMessage($logLine) {
        // 最後の ] 以降がメッセージ
        $lastBracket = strrpos($logLine, ']');
        
        if ($lastBracket === false) {
            return $logLine;
        }
        
        return trim(substr($logLine, $lastBracket + 1));
    }
    
    /**
     * タイムスタンプを抽出
     */
    public static function extractTimestamp($logLine) {
        $afterBracket = strstr($logLine, '[');
        
        if ($afterBracket === false) {
            return null;
        }
        
        $timestamp = strstr($afterBracket, ']', true);
        
        return trim($timestamp, '[]');
    }
    
    /**
     * ログエントリを解析
     */
    public static function parse($logLine) {
        // [timestamp] [level] message 形式を想定
        $parts = [];
        $remaining = $logLine;
        
        // 1つ目の[]内容(タイムスタンプ)
        $afterBracket = strstr($remaining, '[');
        if ($afterBracket !== false) {
            $timestamp = strstr($afterBracket, ']', true);
            $parts['timestamp'] = trim($timestamp, '[]');
            $remaining = substr(strstr($afterBracket, ']'), 1);
        }
        
        // 2つ目の[]内容(レベル)
        $afterBracket = strstr($remaining, '[');
        if ($afterBracket !== false) {
            $level = strstr($afterBracket, ']', true);
            $parts['level'] = trim($level, '[]');
            $remaining = substr(strstr($afterBracket, ']'), 1);
        }
        
        // 残りがメッセージ
        $parts['message'] = trim($remaining);
        
        return $parts;
    }
}

// 使用例
$logs = [
    '[2024-02-23 10:30:00] [ERROR] Database connection failed',
    '[2024-02-23 10:31:00] [INFO] User logged in',
    '[2024-02-23 10:32:00] [WARN] High memory usage'
];

echo "=== ログ解析 ===\n";
foreach ($logs as $log) {
    echo "\n{$log}\n";
    $parsed = LogParser::parse($log);
    echo "  タイムスタンプ: {$parsed['timestamp']}\n";
    echo "  レベル: {$parsed['level']}\n";
    echo "  メッセージ: {$parsed['message']}\n";
}

例6: CSVとテキスト処理

class TextProcessor {
    /**
     * CSVフィールドを抽出(簡易版)
     */
    public static function extractCsvField($line, $fieldNumber) {
        $current = $line;
        
        for ($i = 0; $i < $fieldNumber; $i++) {
            $afterComma = strstr($current, ',');
            
            if ($afterComma === false) {
                return null;
            }
            
            $current = substr($afterComma, 1);
        }
        
        $field = strstr($current, ',', true);
        
        return $field !== false ? $field : $current;
    }
    
    /**
     * 文の始まりから特定の単語までを取得
     */
    public static function upToWord($text, $word) {
        return strstr($text, $word, true);
    }
    
    /**
     * 特定の単語以降を取得
     */
    public static function fromWord($text, $word) {
        return strstr($text, $word);
    }
    
    /**
     * 行を分割
     */
    public static function splitAtDelimiter($text, $delimiter) {
        $after = strstr($text, $delimiter);
        
        if ($after === false) {
            return [$text, ''];
        }
        
        $before = strstr($text, $delimiter, true);
        
        return [$before, substr($after, strlen($delimiter))];
    }
}

// 使用例
echo "=== テキスト処理 ===\n";

$csv = "apple,banana,orange,grape";
echo "2番目のフィールド: " . TextProcessor::extractCsvField($csv, 1) . "\n";
// banana

$sentence = "The quick brown fox jumps over the lazy dog";
echo "brownまで: " . TextProcessor::upToWord($sentence, "brown") . "\n";
// The quick

echo "jumps以降: " . TextProcessor::fromWord($sentence, "jumps") . "\n";
// jumps over the lazy dog

list($before, $after) = TextProcessor::splitAtDelimiter("key=value", "=");
echo "\n分割:\n";
echo "  前: {$before}\n";  // key
echo "  後: {$after}\n";   // value

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

class VersionIdProcessor {
    /**
     * バージョン番号のメジャー部分を取得
     */
    public static function getMajorVersion($version) {
        return strstr($version, '.', true) ?: $version;
    }
    
    /**
     * マイナーバージョン以降を取得
     */
    public static function getMinorAndPatch($version) {
        $result = strstr($version, '.');
        
        if ($result === false) {
            return '';
        }
        
        return substr($result, 1);
    }
    
    /**
     * IDのプレフィックスを取得
     */
    public static function getIdPrefix($id, $separator = '-') {
        return strstr($id, $separator, true) ?: $id;
    }
    
    /**
     * IDの番号部分を取得
     */
    public static function getIdNumber($id, $separator = '-') {
        $result = strstr($id, $separator);
        
        if ($result === false) {
            return '';
        }
        
        return substr($result, 1);
    }
}

// 使用例
$versions = ['1.2.3', '2.0', '10.5.8-beta'];

echo "=== バージョン処理 ===\n";
foreach ($versions as $version) {
    echo "\n{$version}:\n";
    echo "  メジャー: " . VersionIdProcessor::getMajorVersion($version) . "\n";
    echo "  マイナー以降: " . VersionIdProcessor::getMinorAndPatch($version) . "\n";
}

$ids = ['USER-12345', 'PROD-999', 'ORDER-ABC-789'];
echo "\n=== ID処理 ===\n";
foreach ($ids as $id) {
    echo "\n{$id}:\n";
    echo "  プレフィックス: " . VersionIdProcessor::getIdPrefix($id) . "\n";
    echo "  番号: " . VersionIdProcessor::getIdNumber($id) . "\n";
}

パフォーマンスの考慮

// 大量のテキストで部分文字列を検索
$text = str_repeat("apple banana orange ", 10000);
$keyword = "orange";

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

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

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

// strstr()は位置と抽出を一度に行うので効率的

まとめ

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

できること:

  • 部分文字列の検索と抽出を同時に実行
  • 検索位置以降の取得
  • 検索位置より前の取得(before_needleパラメータ)

strchr()との関係:

  • strstr()strchr()は完全に同じ
  • strchr()strstr()のエイリアス

推奨される使用場面:

  • メールアドレスの解析
  • URL・パスの処理
  • データの分割と抽出
  • ログ解析
  • 設定ファイルのパース
  • CSVデータの処理

注意点:

  • 見つからない場合はfalseを返す
  • 最初の出現位置のみ処理
  • 大文字小文字を区別する

関連関数:

  • strchr(): strstr()のエイリアス
  • stristr(): 大文字小文字を区別しないバージョン
  • strrchr(): 最後の出現位置から取得
  • strpos(): 位置(整数)のみ返す
  • substr(): 位置と長さで部分文字列を取得

パフォーマンス:

  • 位置の検索と抽出を一度に実行
  • strpos() + substr()より効率的な場合が多い

before_needleパラメータ:

$email = "user@example.com";
$username = strstr($email, '@', true);  // user
$domain = strstr($email, '@');           // @example.com

strstr()は、部分文字列の検索と抽出を同時に行いたい場合に非常に便利です。特にメールアドレスやURLの解析、データの分割処理で頻繁に使用される関数なので、しっかり使いこなしましょう!

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