こんにちは!今回は、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()関数の特徴をまとめると:
できること:
- 部分文字列の位置を検索
- 文字列の存在確認
- 複数回の出現位置を検索
重要な注意点:
falseと0の区別に必ず!== falseを使用- 最初の出現位置のみ返す
- 大文字小文字を区別する
推奨される使用場面:
- 文字列の存在確認
- 前方一致・後方一致チェック
- URL・メールアドレスの解析
- テキスト検索
- ファイルパス処理
関連関数:
stripos(): 大文字小文字を区別しない検索strrpos(): 最後の出現位置を検索strstr(): 位置以降の文字列を取得str_contains(): 存在確認のみ(PHP 8.0+)str_starts_with(): 前方一致(PHP 8.0+)str_ends_with(): 後方一致(PHP 8.0+)
パフォーマンス:
- 単純な検索では正規表現より高速
- 大量のテキストでも効率的
strpos()は文字列処理の基本中の基本です。falseと0の区別に注意しながら、適切に使いこなしましょう!
