[PHP]strpos関数を完全解説!文字列の位置を検索する方法

PHP

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

strpos関数とは?

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

文字列検索の基本となる関数で、部分文字列の存在確認、位置の取得、文字列の分割など、様々な用途で使用されます!

基本的な構文

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

基本的な使用例

シンプルな検索

$text = "Hello World";

// 部分文字列の位置を検索
$pos = strpos($text, "World");
echo $pos . "\n";  // 6

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

// 1文字の検索
$pos = strpos($text, "o");
echo $pos . "\n";  // 4(最初の'o'の位置)

falseと0の区別(重要!)

$text = "Hello World";

// 先頭にある場合(位置0)
$pos = strpos($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を検索
$pos1 = strpos($text, "apple");
echo "最初: {$pos1}\n";  // 0

// 2番目のappleを検索(最初の位置+1から検索)
$pos2 = strpos($text, "apple", $pos1 + 1);
echo "2番目: {$pos2}\n";  // 13

// 途中から検索
$pos3 = strpos($text, "banana", 10);
var_dump($pos3);  // bool(false)(10より前にある)

大文字小文字の区別

$text = "Hello World";

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

$pos2 = strpos($text, "World");
echo $pos2 . "\n";  // 6

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

実践的な使用例

例1: 文字列の存在確認

class StringChecker {
    /**
     * 文字列が含まれているかチェック
     */
    public static function contains($haystack, $needle) {
        return strpos($haystack, $needle) !== false;
    }
    
    /**
     * 文字列で始まるかチェック
     */
    public static function startsWith($haystack, $needle) {
        return strpos($haystack, $needle) === 0;
    }
    
    /**
     * 文字列で終わるかチェック
     */
    public static function endsWith($haystack, $needle) {
        $length = strlen($needle);
        
        if ($length === 0) {
            return true;
        }
        
        return substr($haystack, -$length) === $needle;
    }
    
    /**
     * 複数の文字列のいずれかが含まれているかチェック
     */
    public static function containsAny($haystack, $needles) {
        foreach ($needles as $needle) {
            if (strpos($haystack, $needle) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 複数の文字列すべてが含まれているかチェック
     */
    public static function containsAll($haystack, $needles) {
        foreach ($needles as $needle) {
            if (strpos($haystack, $needle) === false) {
                return false;
            }
        }
        
        return true;
    }
}

// 使用例
$text = "The quick brown fox jumps over the lazy dog";

echo "=== 存在確認 ===\n";
var_dump(StringChecker::contains($text, "fox"));      // true
var_dump(StringChecker::contains($text, "cat"));      // false

echo "\n=== 前方一致 ===\n";
var_dump(StringChecker::startsWith($text, "The"));    // true
var_dump(StringChecker::startsWith($text, "quick")); // false

echo "\n=== 後方一致 ===\n";
var_dump(StringChecker::endsWith($text, "dog"));     // true
var_dump(StringChecker::endsWith($text, "fox"));     // false

echo "\n=== いずれかを含む ===\n";
var_dump(StringChecker::containsAny($text, ["cat", "fox", "bird"])); // true

echo "\n=== すべてを含む ===\n";
var_dump(StringChecker::containsAll($text, ["fox", "dog"]));         // true
var_dump(StringChecker::containsAll($text, ["fox", "cat"]));         // false

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

class StringExtractor {
    /**
     * 指定文字列の前の部分を取得
     */
    public static function getBefore($text, $delimiter) {
        $pos = strpos($text, $delimiter);
        
        if ($pos === false) {
            return $text;
        }
        
        return substr($text, 0, $pos);
    }
    
    /**
     * 指定文字列の後の部分を取得
     */
    public static function getAfter($text, $delimiter) {
        $pos = strpos($text, $delimiter);
        
        if ($pos === false) {
            return '';
        }
        
        return substr($text, $pos + strlen($delimiter));
    }
    
    /**
     * 2つの文字列に挟まれた部分を取得
     */
    public static function getBetween($text, $start, $end) {
        $startPos = strpos($text, $start);
        
        if ($startPos === false) {
            return '';
        }
        
        $startPos += strlen($start);
        $endPos = strpos($text, $end, $startPos);
        
        if ($endPos === false) {
            return '';
        }
        
        return substr($text, $startPos, $endPos - $startPos);
    }
    
    /**
     * すべての出現位置を取得
     */
    public static function findAll($text, $needle) {
        $positions = [];
        $offset = 0;
        
        while (($pos = strpos($text, $needle, $offset)) !== false) {
            $positions[] = $pos;
            $offset = $pos + 1;
        }
        
        return $positions;
    }
    
    /**
     * n番目の出現位置を取得
     */
    public static function findNth($text, $needle, $n) {
        $offset = 0;
        $count = 0;
        
        while (($pos = strpos($text, $needle, $offset)) !== false) {
            $count++;
            
            if ($count === $n) {
                return $pos;
            }
            
            $offset = $pos + 1;
        }
        
        return false;
    }
}

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

echo "=== 前後の分割 ===\n";
echo "@ の前: " . StringExtractor::getBefore($email, '@') . "\n";  // user
echo "@ の後: " . StringExtractor::getAfter($email, '@') . "\n";   // example.com

$html = '<a href="http://example.com">Link</a>';
echo "\n=== 挟まれた部分 ===\n";
echo StringExtractor::getBetween($html, 'href="', '"') . "\n";  // http://example.com
echo StringExtractor::getBetween($html, '>', '<') . "\n";       // Link

$text = "apple banana apple orange apple";
echo "\n=== すべての位置 ===\n";
$positions = StringExtractor::findAll($text, "apple");
print_r($positions);  // [0, 13, 27]

echo "\n=== n番目の位置 ===\n";
echo "2番目のapple: " . StringExtractor::findNth($text, "apple", 2) . "\n";  // 13

例3: URL解析

class UrlParser {
    /**
     * プロトコルを抽出
     */
    public static function extractProtocol($url) {
        $pos = strpos($url, '://');
        
        if ($pos === false) {
            return null;
        }
        
        return substr($url, 0, $pos);
    }
    
    /**
     * ドメインを抽出
     */
    public static function extractDomain($url) {
        // プロトコルを除去
        $start = strpos($url, '://');
        
        if ($start !== false) {
            $url = substr($url, $start + 3);
        }
        
        // パスを除去
        $end = strpos($url, '/');
        
        if ($end !== false) {
            $url = substr($url, 0, $end);
        }
        
        // ポート番号を除去
        $portPos = strpos($url, ':');
        
        if ($portPos !== false) {
            $url = substr($url, 0, $portPos);
        }
        
        return $url;
    }
    
    /**
     * パスを抽出
     */
    public static function extractPath($url) {
        $protocolEnd = strpos($url, '://');
        
        if ($protocolEnd !== false) {
            $url = substr($url, $protocolEnd + 3);
        }
        
        $pathStart = strpos($url, '/');
        
        if ($pathStart === false) {
            return '/';
        }
        
        $queryStart = strpos($url, '?', $pathStart);
        
        if ($queryStart !== false) {
            return substr($url, $pathStart, $queryStart - $pathStart);
        }
        
        $fragmentStart = strpos($url, '#', $pathStart);
        
        if ($fragmentStart !== false) {
            return substr($url, $pathStart, $fragmentStart - $pathStart);
        }
        
        return substr($url, $pathStart);
    }
    
    /**
     * クエリパラメータを抽出
     */
    public static function extractQueryParams($url) {
        $queryStart = strpos($url, '?');
        
        if ($queryStart === false) {
            return [];
        }
        
        $fragmentStart = strpos($url, '#', $queryStart);
        
        if ($fragmentStart !== false) {
            $query = substr($url, $queryStart + 1, $fragmentStart - $queryStart - 1);
        } else {
            $query = substr($url, $queryStart + 1);
        }
        
        $params = [];
        $pairs = explode('&', $query);
        
        foreach ($pairs as $pair) {
            $equalPos = strpos($pair, '=');
            
            if ($equalPos !== false) {
                $key = substr($pair, 0, $equalPos);
                $value = substr($pair, $equalPos + 1);
                $params[$key] = urldecode($value);
            } else {
                $params[$pair] = '';
            }
        }
        
        return $params;
    }
}

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

echo "=== URL解析 ===\n";
echo "プロトコル: " . UrlParser::extractProtocol($url) . "\n";
// https

echo "ドメイン: " . UrlParser::extractDomain($url) . "\n";
// example.com

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

echo "\nクエリパラメータ:\n";
$params = UrlParser::extractQueryParams($url);
print_r($params);
// [key => value, foo => bar]

例4: メールアドレスの検証と抽出

class EmailProcessor {
    /**
     * メールアドレスが有効な形式か簡易チェック
     */
    public static function isValidFormat($email) {
        $atPos = strpos($email, '@');
        
        if ($atPos === false || $atPos === 0) {
            return false;
        }
        
        $dotPos = strpos($email, '.', $atPos);
        
        if ($dotPos === false || $dotPos === strlen($email) - 1) {
            return false;
        }
        
        return true;
    }
    
    /**
     * ローカル部分を抽出
     */
    public static function extractLocal($email) {
        $atPos = strpos($email, '@');
        
        if ($atPos === false) {
            return null;
        }
        
        return substr($email, 0, $atPos);
    }
    
    /**
     * ドメイン部分を抽出
     */
    public static function extractDomain($email) {
        $atPos = strpos($email, '@');
        
        if ($atPos === false) {
            return null;
        }
        
        return substr($email, $atPos + 1);
    }
    
    /**
     * TLDを抽出
     */
    public static function extractTLD($email) {
        $domain = self::extractDomain($email);
        
        if ($domain === null) {
            return null;
        }
        
        $lastDotPos = strrpos($domain, '.');
        
        if ($lastDotPos === false) {
            return null;
        }
        
        return substr($domain, $lastDotPos + 1);
    }
    
    /**
     * テキストからメールアドレスを抽出
     */
    public static function extractFromText($text) {
        $emails = [];
        $atPos = 0;
        
        while (($atPos = strpos($text, '@', $atPos)) !== false) {
            // @の前を遡る
            $start = $atPos;
            while ($start > 0 && preg_match('/[a-zA-Z0-9._-]/', $text[$start - 1])) {
                $start--;
            }
            
            // @の後を進む
            $end = $atPos;
            while ($end < strlen($text) - 1 && preg_match('/[a-zA-Z0-9._-]/', $text[$end + 1])) {
                $end++;
            }
            
            $email = substr($text, $start, $end - $start + 1);
            
            if (self::isValidFormat($email)) {
                $emails[] = $email;
            }
            
            $atPos++;
        }
        
        return array_unique($emails);
    }
}

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

echo "=== メール解析 ===\n";
var_dump(EmailProcessor::isValidFormat($email));  // true
echo "ローカル: " . EmailProcessor::extractLocal($email) . "\n";    // john.doe
echo "ドメイン: " . EmailProcessor::extractDomain($email) . "\n";   // example.com
echo "TLD: " . EmailProcessor::extractTLD($email) . "\n";           // com

$text = "Contact us at info@example.com or support@test.org for help.";
echo "\n=== テキストから抽出 ===\n";
$emails = EmailProcessor::extractFromText($text);
print_r($emails);
// [info@example.com, support@test.org]

例5: ファイルパス処理

class PathProcessor {
    /**
     * ファイル名を取得
     */
    public static function getFilename($path) {
        $slashPos = max(strrpos($path, '/'), strrpos($path, '\\'));
        
        if ($slashPos === false) {
            return $path;
        }
        
        return substr($path, $slashPos + 1);
    }
    
    /**
     * 拡張子を取得
     */
    public static function getExtension($path) {
        $filename = self::getFilename($path);
        $dotPos = strrpos($filename, '.');
        
        if ($dotPos === false) {
            return '';
        }
        
        return substr($filename, $dotPos + 1);
    }
    
    /**
     * ディレクトリ部分を取得
     */
    public static function getDirectory($path) {
        $slashPos = max(strrpos($path, '/'), strrpos($path, '\\'));
        
        if ($slashPos === false) {
            return '';
        }
        
        return substr($path, 0, $slashPos);
    }
    
    /**
     * 特定のディレクトリが含まれているかチェック
     */
    public static function containsDirectory($path, $directory) {
        return strpos($path, $directory) !== false;
    }
    
    /**
     * 相対パスか判定
     */
    public static function isRelative($path) {
        // Windowsの絶対パス(C:\...)
        if (strlen($path) >= 2 && $path[1] === ':') {
            return false;
        }
        
        // Unixの絶対パス(/...)
        if (strpos($path, '/') === 0) {
            return false;
        }
        
        return true;
    }
}

// 使用例
$paths = [
    '/var/www/html/index.php',
    'C:\\Users\\Documents\\file.txt',
    'images/photo.jpg',
    'document.pdf'
];

echo "=== パス解析 ===\n";
foreach ($paths as $path) {
    echo "\nパス: {$path}\n";
    echo "  ファイル名: " . PathProcessor::getFilename($path) . "\n";
    echo "  拡張子: " . PathProcessor::getExtension($path) . "\n";
    echo "  ディレクトリ: " . PathProcessor::getDirectory($path) . "\n";
    echo "  相対パス: " . (PathProcessor::isRelative($path) ? 'Yes' : 'No') . "\n";
}

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

class TextSearcher {
    /**
     * キーワードをハイライト
     */
    public static function highlight($text, $keyword, $before = '<mark>', $after = '</mark>') {
        $result = '';
        $offset = 0;
        
        while (($pos = strpos($text, $keyword, $offset)) !== false) {
            // キーワードの前までを追加
            $result .= substr($text, $offset, $pos - $offset);
            
            // ハイライトされたキーワードを追加
            $result .= $before . substr($text, $pos, strlen($keyword)) . $after;
            
            $offset = $pos + strlen($keyword);
        }
        
        // 残りを追加
        $result .= substr($text, $offset);
        
        return $result;
    }
    
    /**
     * キーワードの出現回数をカウント
     */
    public static function countOccurrences($text, $keyword) {
        $count = 0;
        $offset = 0;
        
        while (($pos = strpos($text, $keyword, $offset)) !== false) {
            $count++;
            $offset = $pos + 1;
        }
        
        return $count;
    }
    
    /**
     * 複数のキーワードを検索
     */
    public static function findKeywords($text, $keywords) {
        $found = [];
        
        foreach ($keywords as $keyword) {
            $positions = [];
            $offset = 0;
            
            while (($pos = strpos($text, $keyword, $offset)) !== false) {
                $positions[] = $pos;
                $offset = $pos + 1;
            }
            
            if (!empty($positions)) {
                $found[$keyword] = $positions;
            }
        }
        
        return $found;
    }
    
    /**
     * コンテキスト付きで検索結果を表示
     */
    public static function searchWithContext($text, $keyword, $contextLength = 20) {
        $results = [];
        $offset = 0;
        
        while (($pos = strpos($text, $keyword, $offset)) !== false) {
            $start = max(0, $pos - $contextLength);
            $end = min(strlen($text), $pos + strlen($keyword) + $contextLength);
            
            $before = substr($text, $start, $pos - $start);
            $match = substr($text, $pos, strlen($keyword));
            $after = substr($text, $pos + strlen($keyword), $end - $pos - strlen($keyword));
            
            $results[] = [
                'position' => $pos,
                'before' => ($start > 0 ? '...' : '') . $before,
                'match' => $match,
                'after' => $after . ($end < strlen($text) ? '...' : '')
            ];
            
            $offset = $pos + 1;
        }
        
        return $results;
    }
}

// 使用例
$text = "PHP is a popular programming language. Many developers love PHP.";

echo "=== ハイライト ===\n";
echo TextSearcher::highlight($text, "PHP") . "\n";
// <mark>PHP</mark> is a popular programming language. Many developers love <mark>PHP</mark>.

echo "\n=== 出現回数 ===\n";
echo "PHPの出現回数: " . TextSearcher::countOccurrences($text, "PHP") . "\n";  // 2

$keywords = ["PHP", "programming", "developers"];
echo "\n=== 複数キーワード検索 ===\n";
$found = TextSearcher::findKeywords($text, $keywords);
print_r($found);

echo "\n=== コンテキスト付き検索 ===\n";
$results = TextSearcher::searchWithContext($text, "PHP", 15);
foreach ($results as $result) {
    echo "{$result['before']}[{$result['match']}]{$result['after']}\n";
}

例7: データ検証

class DataValidator {
    /**
     * SQLインジェクションの危険なパターンをチェック
     */
    public static function hasSqlInjectionRisk($input) {
        $dangerousPatterns = [
            '--', ';', 'DROP', 'DELETE', 'INSERT', 'UPDATE',
            'UNION', 'SELECT', 'exec(', 'script>'
        ];
        
        foreach ($dangerousPatterns as $pattern) {
            if (stripos($input, $pattern) !== false) {
                return [
                    'risk' => true,
                    'pattern' => $pattern,
                    'position' => stripos($input, $pattern)
                ];
            }
        }
        
        return ['risk' => false];
    }
    
    /**
     * URLが許可されたドメインかチェック
     */
    public static function isAllowedDomain($url, $allowedDomains) {
        foreach ($allowedDomains as $domain) {
            if (strpos($url, $domain) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 禁止ワードが含まれているかチェック
     */
    public static function containsBannedWords($text, $bannedWords) {
        $found = [];
        
        foreach ($bannedWords as $word) {
            if (stripos($text, $word) !== false) {
                $found[] = $word;
            }
        }
        
        return $found;
    }
}

// 使用例
echo "=== SQLインジェクションチェック ===\n";
$inputs = [
    "normal text",
    "SELECT * FROM users",
    "user input --comment"
];

foreach ($inputs as $input) {
    $result = DataValidator::hasSqlInjectionRisk($input);
    echo "{$input}: " . ($result['risk'] ? "危険 ({$result['pattern']})" : "安全") . "\n";
}

echo "\n=== ドメインチェック ===\n";
$allowedDomains = ['example.com', 'test.com'];
$urls = [
    'https://example.com/page',
    'https://malicious.com/page'
];

foreach ($urls as $url) {
    $allowed = DataValidator::isAllowedDomain($url, $allowedDomains);
    echo "{$url}: " . ($allowed ? '許可' : '拒否') . "\n";
}

パフォーマンスの考慮

// 大量のテキストで検索
$text = str_repeat("Lorem ipsum dolor sit amet. ", 10000);
$keyword = "dolor";

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

// preg_match()
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    preg_match('/' . preg_quote($keyword, '/') . '/', $text);
}
$time2 = microtime(true) - $start;

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

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

// strpos()が最も高速

まとめ

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

できること:

  • 部分文字列の位置を検索
  • 文字列の存在確認
  • 複数回の出現位置を検索

重要な注意点:

  • false0の区別に必ず!== falseを使用
  • 最初の出現位置のみ返す
  • 大文字小文字を区別する

推奨される使用場面:

  • 文字列の存在確認
  • 前方一致・後方一致チェック
  • URL・メールアドレスの解析
  • テキスト検索
  • ファイルパス処理

関連関数:

  • stripos(): 大文字小文字を区別しない検索
  • strrpos(): 最後の出現位置を検索
  • strstr(): 位置以降の文字列を取得
  • str_contains(): 存在確認のみ(PHP 8.0+)
  • str_starts_with(): 前方一致(PHP 8.0+)
  • str_ends_with(): 後方一致(PHP 8.0+)

パフォーマンス:

  • 単純な検索では正規表現より高速
  • 大量のテキストでも効率的

strpos()は文字列処理の基本中の基本です。false0の区別に注意しながら、適切に使いこなしましょう!

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