[PHP]strchr関数を完全解説!文字列内で文字を検索する方法

PHP

こんにちは!今回は、PHPの標準関数であるstrchr()について詳しく解説していきます。文字列内で特定の文字を検索し、その文字以降の部分文字列を取得できる便利な関数です!

strchr関数とは?

strchr()関数は、文字列内で特定の文字が最初に現れる位置を検索し、その文字から文字列の最後までを返す関数です。

実はstrchr()strstr()のエイリアス(別名)で、全く同じ動作をします。文字列内の部分文字列を抽出する際に便利です!

基本的な構文

strchr(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 = strchr($email, '@');
echo $domain . "\n";
// 出力: @example.com

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

文字列での検索

$text = "Hello World! Welcome to PHP.";

// 'World' 以降を取得
$result = strchr($text, 'World');
echo $result . "\n";
// 出力: World! Welcome to PHP.

// 'World' より前を取得
$before = strchr($text, 'World', true);
echo $before . "\n";
// 出力: Hello 

見つからない場合

$text = "Hello World";

$result = strchr($text, 'xyz');

if ($result === false) {
    echo "文字列が見つかりません\n";
} else {
    echo "見つかりました: {$result}\n";
}
// 出力: 文字列が見つかりません

実践的な使用例

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

class EmailParser {
    public static function extractDomain($email) {
        // @ 以降を取得してドメイン部分を抽出
        $domainPart = strchr($email, '@');
        
        if ($domainPart === false) {
            return null;
        }
        
        // @ を除去
        return substr($domainPart, 1);
    }
    
    public static function extractUsername($email) {
        // @ より前を取得
        return strchr($email, '@', true);
    }
    
    public static function maskEmail($email) {
        $username = strchr($email, '@', true);
        $domain = strchr($email, '@');
        
        if ($username === false || $domain === false) {
            return $email;
        }
        
        // ユーザー名の一部をマスク
        $visibleLength = min(3, strlen($username));
        $masked = substr($username, 0, $visibleLength) . 
                  str_repeat('*', strlen($username) - $visibleLength);
        
        return $masked . $domain;
    }
    
    public static function getTopLevelDomain($email) {
        $domain = self::extractDomain($email);
        
        if ($domain === null) {
            return null;
        }
        
        // 最後の . 以降を取得
        $tld = strchr($domain, '.');
        
        if ($tld === false) {
            return null;
        }
        
        return substr($tld, 1);  // . を除去
    }
    
    public static function isCompanyEmail($email, $companyDomain) {
        $domain = self::extractDomain($email);
        
        return $domain !== null && 
               strcasecmp($domain, $companyDomain) === 0;
    }
}

// 使用例
$email = "john.doe@example.com";

echo "ドメイン: " . EmailParser::extractDomain($email) . "\n";
// 出力: example.com

echo "ユーザー名: " . EmailParser::extractUsername($email) . "\n";
// 出力: john.doe

echo "マスク済み: " . EmailParser::maskEmail($email) . "\n";
// 出力: joh*****@example.com

echo "TLD: " . EmailParser::getTopLevelDomain($email) . "\n";
// 出力: com

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

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

class PathParser {
    public static function getExtension($filename) {
        // 最後の . 以降を取得
        $ext = strchr($filename, '.');
        
        if ($ext === false) {
            return '';
        }
        
        return substr($ext, 1);  // . を除去
    }
    
    public static function getFilename($path) {
        // 最後の / または \ 以降を取得
        $filename = strchr($path, '/');
        
        if ($filename === false) {
            $filename = strchr($path, '\\');
        }
        
        if ($filename === false) {
            return $path;
        }
        
        return substr($filename, 1);  // / または \ を除去
    }
    
    public static function getDirectory($path) {
        // 最後の / または \ より前を取得
        $lastSlash = strrpos($path, '/');
        $lastBackslash = strrpos($path, '\\');
        
        $pos = max($lastSlash, $lastBackslash);
        
        if ($pos === false) {
            return '';
        }
        
        return substr($path, 0, $pos);
    }
    
    public static function changeExtension($filename, $newExtension) {
        $dotPos = strrpos($filename, '.');
        
        if ($dotPos === false) {
            return $filename . '.' . $newExtension;
        }
        
        return substr($filename, 0, $dotPos) . '.' . $newExtension;
    }
    
    public static function removeExtension($filename) {
        $ext = strchr($filename, '.');
        
        if ($ext === false) {
            return $filename;
        }
        
        return strchr($filename, '.', true);
    }
}

// 使用例
$path = "/var/www/html/images/photo.jpg";

echo "拡張子: " . PathParser::getExtension($path) . "\n";
// 出力: jpg

echo "ファイル名: " . PathParser::getFilename($path) . "\n";
// 出力: photo.jpg

echo "ディレクトリ: " . PathParser::getDirectory($path) . "\n";
// 出力: /var/www/html/images

echo "拡張子変更: " . PathParser::changeExtension($path, 'png') . "\n";
// 出力: /var/www/html/images/photo.png

echo "拡張子削除: " . PathParser::removeExtension('document.pdf') . "\n";
// 出力: document

例3: URLの解析

class UrlParser {
    public static function extractProtocol($url) {
        // :// より前を取得
        $protocol = strchr($url, '://', true);
        
        return $protocol !== false ? $protocol : null;
    }
    
    public static function extractHost($url) {
        // プロトコル部分を削除
        $afterProtocol = strchr($url, '://');
        
        if ($afterProtocol === false) {
            $afterProtocol = $url;
        } else {
            $afterProtocol = substr($afterProtocol, 3);  // :// を削除
        }
        
        // / より前を取得(パスがある場合)
        $host = strchr($afterProtocol, '/', true);
        
        if ($host === false) {
            $host = $afterProtocol;
        }
        
        return $host;
    }
    
    public static function extractPath($url) {
        // プロトコルとホストを削除
        $afterProtocol = strchr($url, '://');
        
        if ($afterProtocol === false) {
            $afterProtocol = $url;
        } else {
            $afterProtocol = substr($afterProtocol, 3);
        }
        
        // 最初の / 以降を取得
        $path = strchr($afterProtocol, '/');
        
        if ($path === false) {
            return '/';
        }
        
        // クエリ文字列がある場合は除去
        $pathWithoutQuery = strchr($path, '?', true);
        
        return $pathWithoutQuery !== false ? $pathWithoutQuery : $path;
    }
    
    public static function extractQueryString($url) {
        // ? 以降を取得
        $query = strchr($url, '?');
        
        if ($query === false) {
            return '';
        }
        
        // # がある場合は除去(フラグメント)
        $queryWithoutFragment = strchr($query, '#', true);
        
        return substr(
            $queryWithoutFragment !== false ? $queryWithoutFragment : $query, 
            1
        );  // ? を除去
    }
    
    public static function extractFragment($url) {
        // # 以降を取得
        $fragment = strchr($url, '#');
        
        if ($fragment === false) {
            return '';
        }
        
        return substr($fragment, 1);  // # を除去
    }
}

// 使用例
$url = "https://example.com:8080/path/to/page?key=value&foo=bar#section";

echo "プロトコル: " . UrlParser::extractProtocol($url) . "\n";
// 出力: https

echo "ホスト: " . UrlParser::extractHost($url) . "\n";
// 出力: example.com:8080

echo "パス: " . UrlParser::extractPath($url) . "\n";
// 出力: /path/to/page

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

echo "フラグメント: " . UrlParser::extractFragment($url) . "\n";
// 出力: section

例4: CSVデータの処理

class CsvParser {
    public static function parseSimpleLine($line, $delimiter = ',') {
        $values = [];
        $current = $line;
        
        while (strlen($current) > 0) {
            $nextDelimiter = strchr($current, $delimiter);
            
            if ($nextDelimiter === false) {
                // 最後の値
                $values[] = trim($current);
                break;
            }
            
            // デリミタより前の値を取得
            $value = strchr($current, $delimiter, true);
            $values[] = trim($value);
            
            // デリミタの次から継続
            $current = substr($nextDelimiter, 1);
        }
        
        return $values;
    }
    
    public static function extractColumn($lines, $columnIndex, $delimiter = ',') {
        $columnData = [];
        
        foreach ($lines as $line) {
            $values = self::parseSimpleLine($line, $delimiter);
            
            if (isset($values[$columnIndex])) {
                $columnData[] = $values[$columnIndex];
            }
        }
        
        return $columnData;
    }
}

// 使用例
$csvLine = "田中,太郎,30,東京";
$values = CsvParser::parseSimpleLine($csvLine);
print_r($values);
// Array ( [0] => 田中 [1] => 太郎 [2] => 30 [3] => 東京 )

$csvLines = [
    "名前,年齢,都市",
    "田中,30,東京",
    "佐藤,25,大阪",
    "鈴木,35,名古屋"
];

$names = CsvParser::extractColumn($csvLines, 0);
print_r($names);
// Array ( [0] => 名前 [1] => 田中 [2] => 佐藤 [3] => 鈴木 )

例5: ログの解析

class LogParser {
    public static function parseApacheLog($line) {
        // IPアドレスを抽出(最初のスペースまで)
        $ip = strchr($line, ' ', true);
        
        // タイムスタンプを抽出([ と ] の間)
        $timestampStart = strchr($line, '[');
        $timestamp = strchr($timestampStart, ']', true);
        $timestamp = substr($timestamp, 1);  // [ を除去
        
        // HTTPメソッドとパスを抽出(" の間)
        $requestStart = strchr($line, '"');
        $request = strchr(substr($requestStart, 1), '"', true);
        
        // ステータスコードを抽出
        $afterRequest = strchr($line, '" ');
        $statusPart = substr($afterRequest, 2);  // " を除去
        $status = (int)strchr($statusPart, ' ', true);
        
        return [
            'ip' => $ip,
            'timestamp' => $timestamp,
            'request' => $request,
            'status' => $status
        ];
    }
    
    public static function parseCustomLog($line) {
        // [日時] レベル: メッセージ 形式
        
        // タイムスタンプを抽出
        $timestampPart = strchr($line, '[');
        $timestamp = strchr($timestampPart, ']', true);
        $timestamp = substr($timestamp, 1);
        
        // レベルを抽出
        $afterTimestamp = strchr($line, '] ');
        $levelPart = substr($afterTimestamp, 2);
        $level = strchr($levelPart, ':', true);
        
        // メッセージを抽出
        $message = strchr($levelPart, ': ');
        $message = substr($message, 2);
        
        return [
            'timestamp' => $timestamp,
            'level' => trim($level),
            'message' => trim($message)
        ];
    }
}

// 使用例
$apacheLog = '192.168.1.1 - - [13/Feb/2024:10:30:45 +0900] "GET /index.html HTTP/1.1" 200 1234';
$parsed = LogParser::parseApacheLog($apacheLog);
print_r($parsed);

$customLog = '[2024-02-13 10:30:45] ERROR: Database connection failed';
$parsed = LogParser::parseCustomLog($customLog);
print_r($parsed);

例6: コマンドライン引数の処理

class CommandParser {
    public static function parseCommand($commandLine) {
        // コマンド名を抽出(最初のスペースまで)
        $command = strchr($commandLine, ' ', true);
        
        if ($command === false) {
            $command = $commandLine;
            $args = '';
        } else {
            // 引数部分を取得
            $args = strchr($commandLine, ' ');
            $args = trim($args);
        }
        
        return [
            'command' => $command,
            'arguments' => $args
        ];
    }
    
    public static function extractOption($args, $option) {
        // --option=value 形式を検索
        $optionPattern = "--{$option}=";
        $optionPos = strpos($args, $optionPattern);
        
        if ($optionPos === false) {
            return null;
        }
        
        $afterOption = substr($args, $optionPos + strlen($optionPattern));
        
        // 次のスペースまたは文字列の最後まで
        $value = strchr($afterOption, ' ', true);
        
        return $value !== false ? $value : $afterOption;
    }
    
    public static function extractFlag($args, $flag) {
        // -f または --flag 形式を検索
        $shortFlag = "-{$flag}";
        $longFlag = "--{$flag}";
        
        return strpos($args, $shortFlag) !== false || 
               strpos($args, $longFlag) !== false;
    }
}

// 使用例
$commandLine = "git commit -m \"Initial commit\" --author=\"John Doe\"";

$parsed = CommandParser::parseCommand($commandLine);
print_r($parsed);
// command => git, arguments => commit -m "Initial commit" --author="John Doe"

$args = 'process --input=data.txt --output=result.txt --verbose';

echo "input: " . CommandParser::extractOption($args, 'input') . "\n";
// 出力: data.txt

echo "output: " . CommandParser::extractOption($args, 'output') . "\n";
// 出力: result.txt

var_dump(CommandParser::extractFlag($args, 'verbose'));
// 出力: bool(true)

例7: HTML/XMLタグの処理

class TagParser {
    public static function extractTagContent($html, $tagName) {
        $openTag = "<{$tagName}>";
        $closeTag = "</{$tagName}>";
        
        // 開始タグを検索
        $afterOpen = strchr($html, $openTag);
        
        if ($afterOpen === false) {
            return null;
        }
        
        // 開始タグを除去
        $content = substr($afterOpen, strlen($openTag));
        
        // 終了タグより前を取得
        $result = strchr($content, $closeTag, true);
        
        return $result !== false ? $result : null;
    }
    
    public static function extractAttribute($tag, $attribute) {
        // attribute="value" または attribute='value' 形式
        $pattern = "{$attribute}=\"";
        $afterAttr = strchr($tag, $pattern);
        
        if ($afterAttr === false) {
            $pattern = "{$attribute}='";
            $afterAttr = strchr($tag, $pattern);
        }
        
        if ($afterAttr === false) {
            return null;
        }
        
        // 引用符を除去
        $valueStart = substr($afterAttr, strlen($pattern));
        
        // 次の引用符まで
        $value = strchr($valueStart, '"', true);
        
        if ($value === false) {
            $value = strchr($valueStart, "'", true);
        }
        
        return $value;
    }
    
    public static function removeHtmlTags($html) {
        $result = $html;
        
        while (($tagStart = strchr($result, '<')) !== false) {
            $tagEnd = strchr($tagStart, '>');
            
            if ($tagEnd === false) {
                break;
            }
            
            // タグの前と後を結合
            $before = strchr($result, '<', true);
            $after = substr($tagEnd, 1);
            
            $result = $before . $after;
        }
        
        return $result;
    }
}

// 使用例
$html = '<div class="container"><p>Hello World</p></div>';

echo TagParser::extractTagContent($html, 'p') . "\n";
// 出力: Hello World

$tag = '<img src="photo.jpg" alt="My Photo" width="200">';

echo "src: " . TagParser::extractAttribute($tag, 'src') . "\n";
// 出力: photo.jpg

echo "alt: " . TagParser::extractAttribute($tag, 'alt') . "\n";
// 出力: My Photo

echo TagParser::removeHtmlTags('<p>Hello <b>World</b>!</p>') . "\n";
// 出力: Hello World!

strstr()との関係

// strchr()とstrstr()は完全に同じ
$email = "user@example.com";

$result1 = strchr($email, '@');
$result2 = strstr($email, '@');

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

// どちらを使っても同じ結果
echo strchr($email, '@') . "\n";   // @example.com
echo strstr($email, '@') . "\n";   // @example.com

注意点と制限事項

最初に見つかった位置のみ

$text = "apple banana apple orange";

// 最初の 'apple' 以降を取得
$result = strchr($text, 'apple');
echo $result . "\n";
// 出力: apple banana apple orange

// 2番目の 'apple' は検索されない

大文字小文字の区別

$text = "Hello World";

// 大文字小文字を区別する
$result1 = strchr($text, 'world');  // false(見つからない)
$result2 = strchr($text, 'World');  // World(見つかる)

var_dump($result1);  // bool(false)
echo $result2 . "\n"; // World

// 大文字小文字を無視する場合は stristr()
$result3 = stristr($text, 'world');
echo $result3 . "\n"; // World

マルチバイト文字の扱い

$text = "こんにちは世界";

// strchr()はバイト単位で動作
$result = strchr($text, '世界');
echo $result . "\n";  // 世界

// マルチバイト対応が必要な場合は mb_strstr()
$result = mb_strstr($text, '世界');
echo $result . "\n";  // 世界

パフォーマンスの考慮

// 方法1: strchr()
$email = "user@example.com";
$domain = strchr($email, '@');

// 方法2: explode()
$parts = explode('@', $email);
$domain = '@' . $parts[1];

// 方法3: substr() + strpos()
$pos = strpos($email, '@');
$domain = substr($email, $pos);

// strchr()は最もシンプルで、単純な検索には効率的

まとめ

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

できること:

  • 文字列内で文字を検索
  • 見つかった位置以降の部分文字列を取得
  • 見つかった位置より前の部分文字列を取得(第3引数)

strstr()との関係:

  • strchr()strstr()の完全なエイリアス
  • 機能的に全く同じ

推奨される使用場面:

  • メールアドレスの解析
  • ファイルパスの処理
  • URLの解析
  • ログファイルの解析
  • 区切り文字での分割

関連関数:

  • strstr(): strchr()と同じ
  • stristr(): 大文字小文字を区別しない検索
  • strrchr(): 最後に現れた位置から検索
  • strpos(): 位置(インデックス)を返す
  • mb_strstr(): マルチバイト対応版

注意点:

  • 最初に見つかった位置のみ
  • 大文字小文字を区別する
  • マルチバイト文字列には注意が必要
  • 見つからない場合はfalseを返す

strchr()は文字列の一部を抽出する際に便利な関数です。特にメールアドレスやファイルパスなど、特定の区切り文字で分割されたデータを扱う場合に効果的です!

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