[PHP]str_contains関数を完全解説!文字列に部分文字列が含まれるか確認する方法

PHP

こんにちは!今回は、PHPの標準関数であるstr_contains()について詳しく解説していきます。文字列内に特定の部分文字列が含まれているかを簡単にチェックできる、PHP 8.0で追加された新しい関数です!

str_contains関数とは?

str_contains()関数は、文字列内に指定した部分文字列が含まれているかを判定する関数です。

PHP 8.0で追加された関数で、従来のstrpos() !== falseというイディオムをより直感的に書けるようになりました!

基本的な構文

str_contains(string $haystack, string $needle): bool
  • $haystack: 検索対象の文字列
  • $needle: 検索する部分文字列
  • 戻り値: 含まれている場合はtrue、含まれていない場合はfalse

基本的な使用例

シンプルな検索

// 基本的な使用(PHP 8.0+)
$text = "Hello World";

if (str_contains($text, "World")) {
    echo "「World」が含まれています\n";
}
// 出力: 「World」が含まれています

if (str_contains($text, "PHP")) {
    echo "「PHP」が含まれています\n";
} else {
    echo "「PHP」は含まれていません\n";
}
// 出力: 「PHP」は含まれていません

大文字小文字の区別

$text = "Hello World";

// 大文字小文字は区別される
var_dump(str_contains($text, "world"));  // false
var_dump(str_contains($text, "World"));  // true

// 大文字小文字を区別しない場合は小文字に統一
$text_lower = strtolower($text);
var_dump(str_contains($text_lower, strtolower("WORLD")));  // true

空文字列の扱い

$text = "Hello World";

// 空文字列は常にtrueを返す
var_dump(str_contains($text, ""));  // true

// 空の文字列内で検索
var_dump(str_contains("", "test"));  // false
var_dump(str_contains("", ""));      // true

先頭や末尾の検索

$text = "Hello World";

// 先頭にある
var_dump(str_contains($text, "Hello"));  // true

// 末尾にある
var_dump(str_contains($text, "World"));  // true

// 中間にある
var_dump(str_contains($text, "lo Wo"));  // true

PHP 8.0未満での代替実装

// PHP 8.0未満用のポリフィル
if (!function_exists('str_contains')) {
    function str_contains($haystack, $needle) {
        return $needle !== '' && strpos($haystack, $needle) !== false;
    }
}

// 使用例
$text = "Hello World";
var_dump(str_contains($text, "World"));  // true

実践的な使用例

例1: テキスト検索システム

class TextSearcher {
    /**
     * キーワード検索
     */
    public static function search($text, $keyword, $caseSensitive = true) {
        if (!$caseSensitive) {
            $text = strtolower($text);
            $keyword = strtolower($keyword);
        }
        
        return str_contains($text, $keyword);
    }
    
    /**
     * 複数キーワード検索(AND)
     */
    public static function searchAll($text, $keywords, $caseSensitive = true) {
        if (!$caseSensitive) {
            $text = strtolower($text);
        }
        
        foreach ($keywords as $keyword) {
            $searchKeyword = $caseSensitive ? $keyword : strtolower($keyword);
            
            if (!str_contains($text, $searchKeyword)) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * 複数キーワード検索(OR)
     */
    public static function searchAny($text, $keywords, $caseSensitive = true) {
        if (!$caseSensitive) {
            $text = strtolower($text);
        }
        
        foreach ($keywords as $keyword) {
            $searchKeyword = $caseSensitive ? $keyword : strtolower($keyword);
            
            if (str_contains($text, $searchKeyword)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * マッチしたキーワードを取得
     */
    public static function getMatches($text, $keywords, $caseSensitive = true) {
        $matches = [];
        $searchText = $caseSensitive ? $text : strtolower($text);
        
        foreach ($keywords as $keyword) {
            $searchKeyword = $caseSensitive ? $keyword : strtolower($keyword);
            
            if (str_contains($searchText, $searchKeyword)) {
                $matches[] = $keyword;
            }
        }
        
        return $matches;
    }
    
    /**
     * ハイライト表示
     */
    public static function highlight($text, $keyword, $tag = 'mark') {
        if (!str_contains(strtolower($text), strtolower($keyword))) {
            return $text;
        }
        
        // 大文字小文字を保持しながらハイライト
        return preg_replace(
            '/(' . preg_quote($keyword, '/') . ')/i',
            "<{$tag}>$1</{$tag}>",
            $text
        );
    }
    
    /**
     * フィルタリング
     */
    public static function filter($items, $keyword, $caseSensitive = true) {
        return array_filter($items, function($item) use ($keyword, $caseSensitive) {
            return self::search($item, $keyword, $caseSensitive);
        });
    }
}

// 使用例
echo "=== テキスト検索 ===\n";

$text = "PHP is a popular programming language";

// 単一キーワード検索
var_dump(TextSearcher::search($text, "PHP"));            // true
var_dump(TextSearcher::search($text, "python"));         // false

// 大文字小文字を区別しない検索
var_dump(TextSearcher::search($text, "php", false));     // true

echo "\n=== 複数キーワード検索 ===\n";

// AND検索
$keywords = ['PHP', 'programming'];
var_dump(TextSearcher::searchAll($text, $keywords));     // true

$keywords = ['PHP', 'python'];
var_dump(TextSearcher::searchAll($text, $keywords));     // false

// OR検索
$keywords = ['python', 'ruby', 'PHP'];
var_dump(TextSearcher::searchAny($text, $keywords));     // true

echo "\n=== マッチしたキーワード ===\n";
$keywords = ['PHP', 'Python', 'programming', 'Java'];
$matches = TextSearcher::getMatches($text, $keywords);
print_r($matches);
// Array ( [0] => PHP [1] => programming )

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

echo "\n=== フィルタリング ===\n";
$items = [
    'PHP Tutorial',
    'Python Guide',
    'PHP Best Practices',
    'JavaScript Basics'
];

$filtered = TextSearcher::filter($items, 'PHP');
print_r($filtered);

例2: バリデーション

class StringValidator {
    /**
     * 禁止ワードチェック
     */
    public static function containsBannedWords($text, $bannedWords) {
        $text = strtolower($text);
        
        foreach ($bannedWords as $word) {
            if (str_contains($text, strtolower($word))) {
                return [
                    'valid' => false,
                    'banned_word' => $word
                ];
            }
        }
        
        return ['valid' => true];
    }
    
    /**
     * 必須キーワードチェック
     */
    public static function containsRequiredWords($text, $requiredWords) {
        $text = strtolower($text);
        $missing = [];
        
        foreach ($requiredWords as $word) {
            if (!str_contains($text, strtolower($word))) {
                $missing[] = $word;
            }
        }
        
        return [
            'valid' => empty($missing),
            'missing' => $missing
        ];
    }
    
    /**
     * URLが含まれているかチェック
     */
    public static function containsUrl($text) {
        $patterns = ['http://', 'https://', 'www.'];
        
        foreach ($patterns as $pattern) {
            if (str_contains(strtolower($text), $pattern)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * メールアドレスが含まれているかチェック
     */
    public static function containsEmail($text) {
        return str_contains($text, '@');
    }
    
    /**
     * HTMLタグが含まれているかチェック
     */
    public static function containsHtml($text) {
        return str_contains($text, '<') && str_contains($text, '>');
    }
    
    /**
     * 特殊文字が含まれているかチェック
     */
    public static function containsSpecialChars($text, $specialChars = '!@#$%^&*()') {
        for ($i = 0; $i < strlen($specialChars); $i++) {
            if (str_contains($text, $specialChars[$i])) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 数字が含まれているかチェック
     */
    public static function containsNumber($text) {
        for ($i = 0; $i <= 9; $i++) {
            if (str_contains($text, (string)$i)) {
                return true;
            }
        }
        
        return false;
    }
}

// 使用例
echo "=== バリデーション ===\n";

// 禁止ワードチェック
$text = "This is a spam message";
$bannedWords = ['spam', 'casino', 'viagra'];
$result = StringValidator::containsBannedWords($text, $bannedWords);
echo "禁止ワード: " . ($result['valid'] ? 'OK' : 'NG') . "\n";
if (!$result['valid']) {
    echo "  検出: {$result['banned_word']}\n";
}

// 必須キーワードチェック
echo "\n=== 必須キーワード ===\n";
$text = "PHP is great";
$requiredWords = ['PHP', 'programming'];
$result = StringValidator::containsRequiredWords($text, $requiredWords);
echo "必須キーワード: " . ($result['valid'] ? 'OK' : 'NG') . "\n";
if (!$result['valid']) {
    echo "  不足: " . implode(', ', $result['missing']) . "\n";
}

// URL含有チェック
echo "\n=== URL含有 ===\n";
$texts = [
    'Visit our website at https://example.com',
    'No URL here',
    'Check www.example.com'
];

foreach ($texts as $text) {
    $hasUrl = StringValidator::containsUrl($text) ? 'あり' : 'なし';
    echo "{$text}: {$hasUrl}\n";
}

// その他のチェック
echo "\n=== その他のチェック ===\n";
$text = "Contact us at info@example.com!";
echo "メール含有: " . (StringValidator::containsEmail($text) ? 'Yes' : 'No') . "\n";
echo "特殊文字含有: " . (StringValidator::containsSpecialChars($text) ? 'Yes' : 'No') . "\n";
echo "数字含有: " . (StringValidator::containsNumber($text) ? 'Yes' : 'No') . "\n";

$html = "<p>Hello World</p>";
echo "HTML含有: " . (StringValidator::containsHtml($html) ? 'Yes' : 'No') . "\n";

例3: コンテンツフィルタリング

class ContentFilter {
    /**
     * カテゴリ判定
     */
    public static function detectCategory($text) {
        $categories = [
            'technology' => ['computer', 'software', 'programming', 'tech'],
            'sports' => ['football', 'basketball', 'soccer', 'tennis'],
            'food' => ['recipe', 'cooking', 'restaurant', 'food'],
            'travel' => ['vacation', 'hotel', 'flight', 'travel']
        ];
        
        $text = strtolower($text);
        $detected = [];
        
        foreach ($categories as $category => $keywords) {
            foreach ($keywords as $keyword) {
                if (str_contains($text, $keyword)) {
                    $detected[] = $category;
                    break;
                }
            }
        }
        
        return array_unique($detected);
    }
    
    /**
     * センシティブコンテンツチェック
     */
    public static function isSensitive($text) {
        $sensitiveWords = ['violence', 'hate', 'explicit'];
        $text = strtolower($text);
        
        foreach ($sensitiveWords as $word) {
            if (str_contains($text, $word)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 広告コンテンツチェック
     */
    public static function isAdvertisement($text) {
        $adIndicators = ['buy now', 'limited offer', 'click here', 'subscribe'];
        $text = strtolower($text);
        
        $count = 0;
        foreach ($adIndicators as $indicator) {
            if (str_contains($text, $indicator)) {
                $count++;
            }
        }
        
        return $count >= 2;  // 2つ以上該当で広告と判定
    }
    
    /**
     * 言語検出(簡易版)
     */
    public static function detectLanguage($text) {
        $patterns = [
            'japanese' => ['です', 'ます', 'こんにちは'],
            'chinese' => ['你好', '谢谢', '中国'],
            'korean' => ['안녕', '감사', '한국'],
            'spanish' => ['hola', 'gracias', 'por favor'],
            'french' => ['bonjour', 'merci', 's\'il vous plaît']
        ];
        
        foreach ($patterns as $language => $keywords) {
            foreach ($keywords as $keyword) {
                if (str_contains($text, $keyword)) {
                    return $language;
                }
            }
        }
        
        return 'english';  // デフォルト
    }
    
    /**
     * コンテンツタイプ判定
     */
    public static function getContentType($text) {
        $text = strtolower($text);
        
        if (str_contains($text, '?')) {
            return 'question';
        } elseif (str_contains($text, '!')) {
            return 'exclamation';
        } elseif (str_contains($text, 'http://') || str_contains($text, 'https://')) {
            return 'link';
        } elseif (str_contains($text, '@')) {
            return 'mention';
        } else {
            return 'statement';
        }
    }
}

// 使用例
echo "=== コンテンツフィルタリング ===\n";

// カテゴリ判定
$text = "I love programming and creating software applications";
$categories = ContentFilter::detectCategory($text);
echo "カテゴリ: " . implode(', ', $categories) . "\n";

// センシティブコンテンツ
echo "\n=== センシティブチェック ===\n";
$texts = [
    'This is a normal message',
    'Contains hate speech',
    'Family friendly content'
];

foreach ($texts as $text) {
    $isSensitive = ContentFilter::isSensitive($text) ? 'Yes' : 'No';
    echo "{$text}: {$isSensitive}\n";
}

// 広告判定
echo "\n=== 広告判定 ===\n";
$text = "Buy now! Limited offer - click here to subscribe";
$isAd = ContentFilter::isAdvertisement($text) ? '広告' : '通常';
echo "{$text}: {$isAd}\n";

// 言語検出
echo "\n=== 言語検出 ===\n";
$texts = [
    'Hello world',
    'こんにちは世界',
    'Hola mundo',
    '你好世界'
];

foreach ($texts as $text) {
    $lang = ContentFilter::detectLanguage($text);
    echo "{$text}: {$lang}\n";
}

// コンテンツタイプ
echo "\n=== コンテンツタイプ ===\n";
$texts = [
    'What is PHP?',
    'This is amazing!',
    'Check this out: https://example.com',
    'Hey @john, look at this'
];

foreach ($texts as $text) {
    $type = ContentFilter::getContentType($text);
    echo "{$text}: {$type}\n";
}

例4: ルーティング判定

class RouteChecker {
    /**
     * パスがパターンに一致するか
     */
    public static function matches($path, $pattern) {
        // 簡易的なパターンマッチング
        $pattern = str_replace('*', '', $pattern);
        return str_contains($path, $pattern);
    }
    
    /**
     * APIエンドポイントか判定
     */
    public static function isApiRoute($path) {
        return str_contains($path, '/api/');
    }
    
    /**
     * 管理画面か判定
     */
    public static function isAdminRoute($path) {
        return str_contains($path, '/admin/');
    }
    
    /**
     * 静的ファイルか判定
     */
    public static function isStaticFile($path) {
        $extensions = ['.css', '.js', '.jpg', '.png', '.gif', '.svg'];
        
        foreach ($extensions as $ext) {
            if (str_contains($path, $ext)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 保護されたルートか判定
     */
    public static function isProtectedRoute($path, $protectedPaths) {
        foreach ($protectedPaths as $protected) {
            if (str_contains($path, $protected)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * ロケール情報を含むか判定
     */
    public static function hasLocale($path) {
        $locales = ['/en/', '/ja/', '/es/', '/fr/', '/de/'];
        
        foreach ($locales as $locale) {
            if (str_contains($path, $locale)) {
                return true;
            }
        }
        
        return false;
    }
}

// 使用例
echo "=== ルーティング判定 ===\n";

$paths = [
    '/api/users/123',
    '/admin/dashboard',
    '/css/style.css',
    '/en/products',
    '/profile/settings'
];

foreach ($paths as $path) {
    echo "\nパス: {$path}\n";
    echo "  API: " . (RouteChecker::isApiRoute($path) ? 'Yes' : 'No') . "\n";
    echo "  管理画面: " . (RouteChecker::isAdminRoute($path) ? 'Yes' : 'No') . "\n";
    echo "  静的ファイル: " . (RouteChecker::isStaticFile($path) ? 'Yes' : 'No') . "\n";
    echo "  ロケール: " . (RouteChecker::hasLocale($path) ? 'Yes' : 'No') . "\n";
}

// 保護ルートチェック
echo "\n=== 保護ルートチェック ===\n";
$protectedPaths = ['/admin/', '/api/private/', '/user/settings'];
$testPaths = ['/api/private/data', '/public/info', '/admin/users'];

foreach ($testPaths as $path) {
    $isProtected = RouteChecker::isProtectedRoute($path, $protectedPaths) ? '保護' : '公開';
    echo "{$path}: {$isProtected}\n";
}

例5: ファイル名チェック

class FileNameChecker {
    /**
     * 特定の拡張子か判定
     */
    public static function hasExtension($filename, $extension) {
        if ($extension[0] !== '.') {
            $extension = '.' . $extension;
        }
        
        return str_contains(strtolower($filename), strtolower($extension));
    }
    
    /**
     * 画像ファイルか判定
     */
    public static function isImage($filename) {
        $extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
        $filename = strtolower($filename);
        
        foreach ($extensions as $ext) {
            if (str_contains($filename, $ext)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * ドキュメントファイルか判定
     */
    public static function isDocument($filename) {
        $extensions = ['.pdf', '.doc', '.docx', '.txt', '.rtf', '.odt'];
        $filename = strtolower($filename);
        
        foreach ($extensions as $ext) {
            if (str_contains($filename, $ext)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * バックアップファイルか判定
     */
    public static function isBackup($filename) {
        $indicators = ['.bak', '.backup', '.old', '~'];
        $filename = strtolower($filename);
        
        foreach ($indicators as $indicator) {
            if (str_contains($filename, $indicator)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 一時ファイルか判定
     */
    public static function isTemporary($filename) {
        $indicators = ['.tmp', '.temp', 'tmp_', 'temp_'];
        $filename = strtolower($filename);
        
        foreach ($indicators as $indicator) {
            if (str_contains($filename, $indicator)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 隠しファイルか判定
     */
    public static function isHidden($filename) {
        $basename = basename($filename);
        return str_contains($basename, '.') && $basename[0] === '.';
    }
    
    /**
     * 危険なファイルか判定
     */
    public static function isDangerous($filename) {
        $dangerous = ['.exe', '.bat', '.cmd', '.sh', '.php', '.js'];
        $filename = strtolower($filename);
        
        foreach ($dangerous as $ext) {
            if (str_contains($filename, $ext)) {
                return true;
            }
        }
        
        return false;
    }
}

// 使用例
echo "=== ファイル名チェック ===\n";

$files = [
    'document.pdf',
    'photo.jpg',
    'script.php',
    'data.bak',
    '.htaccess',
    'tmp_file.txt',
    'report.docx'
];

foreach ($files as $file) {
    echo "\nファイル: {$file}\n";
    echo "  画像: " . (FileNameChecker::isImage($file) ? 'Yes' : 'No') . "\n";
    echo "  ドキュメント: " . (FileNameChecker::isDocument($file) ? 'Yes' : 'No') . "\n";
    echo "  バックアップ: " . (FileNameChecker::isBackup($file) ? 'Yes' : 'No') . "\n";
    echo "  一時ファイル: " . (FileNameChecker::isTemporary($file) ? 'Yes' : 'No') . "\n";
    echo "  隠しファイル: " . (FileNameChecker::isHidden($file) ? 'Yes' : 'No') . "\n";
    echo "  危険: " . (FileNameChecker::isDangerous($file) ? 'Yes' : 'No') . "\n";
}

例6: データ検証

class DataChecker {
    /**
     * JSONデータか判定
     */
    public static function looksLikeJson($data) {
        $data = trim($data);
        return (str_contains($data, '{') && str_contains($data, '}')) ||
               (str_contains($data, '[') && str_contains($data, ']'));
    }
    
    /**
     * XMLデータか判定
     */
    public static function looksLikeXml($data) {
        return str_contains($data, '<?xml') || 
               (str_contains($data, '<') && str_contains($data, '</'));
    }
    
    /**
     * HTMLデータか判定
     */
    public static function looksLikeHtml($data) {
        $htmlTags = ['<html', '<body', '<div', '<p>', '<head'];
        $data = strtolower($data);
        
        foreach ($htmlTags as $tag) {
            if (str_contains($data, $tag)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Base64エンコードされているか判定
     */
    public static function looksLikeBase64($data) {
        // 簡易チェック
        return !str_contains($data, ' ') && 
               (str_contains($data, '==') || strlen($data) % 4 === 0);
    }
    
    /**
     * 日付文字列か判定
     */
    public static function looksLikeDate($data) {
        return str_contains($data, '-') || str_contains($data, '/');
    }
}

// 使用例
echo "=== データ検証 ===\n";

$samples = [
    '{"name": "John", "age": 30}',
    '<?xml version="1.0"?><root></root>',
    '<html><body>Hello</body></html>',
    'SGVsbG8gV29ybGQ=',
    '2024-01-15',
    'Just plain text'
];

foreach ($samples as $sample) {
    echo "\nデータ: " . substr($sample, 0, 30) . "...\n";
    echo "  JSON: " . (DataChecker::looksLikeJson($sample) ? 'Yes' : 'No') . "\n";
    echo "  XML: " . (DataChecker::looksLikeXml($sample) ? 'Yes' : 'No') . "\n";
    echo "  HTML: " . (DataChecker::looksLikeHtml($sample) ? 'Yes' : 'No') . "\n";
    echo "  Base64: " . (DataChecker::looksLikeBase64($sample) ? 'Yes' : 'No') . "\n";
    echo "  日付: " . (DataChecker::looksLikeDate($sample) ? 'Yes' : 'No') . "\n";
}

従来の方法との比較

$text = "Hello World";
$needle = "World";

// 新しい方法(PHP 8.0+)
if (str_contains($text, $needle)) {
    echo "含まれています\n";
}

// 従来の方法
if (strpos($text, $needle) !== false) {
    echo "含まれています\n";
}

// 利点:
// - より直感的
// - false/0の混同がない
// - コードが読みやすい

まとめ

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

できること:

  • 部分文字列の存在確認
  • シンプルで直感的な判定
  • ブール値を返す

推奨される使用場面:

  • テキスト検索
  • バリデーション
  • コンテンツフィルタリング
  • ルーティング判定
  • ファイル名チェック

利点:

  • 直感的で読みやすい
  • false/0の混同がない
  • PHP 8.0で標準化

注意点:

  • PHP 8.0以降でのみ使用可能
  • 大文字小文字を区別する
  • 空文字列は常にtrue

関連関数:

  • str_starts_with(): 先頭一致
  • str_ends_with(): 末尾一致
  • strpos(): 位置を返す
  • stripos(): 大文字小文字無視の位置

使い分け:

// 含まれるか判定
str_contains($text, $needle)

// 先頭一致
str_starts_with($text, $prefix)

// 末尾一致
str_ends_with($text, $suffix)

// 位置が必要
strpos($text, $needle)

str_contains()は、PHP 8.0で追加された非常に便利な関数です。従来のstrpos() !== falseよりも直感的で読みやすいコードが書けるので、PHP 8.0以降を使用している場合は積極的に活用しましょう!

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