こんにちは!今回は、PHPの標準関数であるstrip_tags()について詳しく解説していきます。HTML・XMLタグを文字列から削除できる、セキュリティやテキスト処理に重要な関数です!
strip_tags関数とは?
strip_tags()関数は、文字列からHTMLおよびPHPタグを削除する関数です。
ユーザー入力のサニタイズ、プレーンテキストの抽出、メール送信用のテキスト生成など、様々な場面で活用できます!
基本的な構文
strip_tags(string $string, array|string|null $allowed_tags = null): string
- $string: 処理対象の文字列
- $allowed_tags: 削除しないタグのリスト(オプション)
- 戻り値: タグが削除された文字列
基本的な使用例
シンプルなタグ削除
$html = "<p>Hello <b>World</b>!</p>";
// すべてのタグを削除
$text = strip_tags($html);
echo $text . "\n";
// 出力: Hello World!
$html = "<h1>タイトル</h1><p>本文です。</p>";
echo strip_tags($html) . "\n";
// 出力: タイトル本文です。
特定のタグを許可
$html = "<p>これは<b>太字</b>で<i>斜体</i>です。</p>";
// <b>タグのみ許可(PHP 7.4未満の書き方)
$text = strip_tags($html, "<b>");
echo $text . "\n";
// 出力: これは<b>太字</b>で斜体です。
// 複数のタグを許可
$text = strip_tags($html, "<b><i>");
echo $text . "\n";
// 出力: これは<b>太字</b>で<i>斜体</i>です。
// PHP 7.4以降:配列で指定
$text = strip_tags($html, ['b', 'i']);
echo $text . "\n";
// 出力: これは<b>太字</b>で<i>斜体</i>です。
改行の扱い
$html = "<p>段落1</p><p>段落2</p>";
// タグを削除すると改行が失われる
$text = strip_tags($html);
echo $text . "\n";
// 出力: 段落1段落2
// タグを改行に置換してから削除
$html = str_replace(['<p>', '</p>', '<br>', '<br/>'], "\n", $html);
$text = strip_tags($html);
echo $text . "\n";
// 出力:
// 段落1
// 段落2
実践的な使用例
例1: ユーザー入力のサニタイズ
class InputSanitizer {
/**
* HTMLタグを完全に削除
*/
public static function sanitizeText($input) {
return strip_tags($input);
}
/**
* 安全なHTMLタグのみ許可
*/
public static function sanitizeHtml($input) {
$allowedTags = ['p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li'];
$cleaned = strip_tags($input, $allowedTags);
// さらにaタグのhref属性をチェック
return self::sanitizeLinks($cleaned);
}
/**
* リンクのサニタイズ
*/
private static function sanitizeLinks($html) {
// aタグのhrefをチェック(簡易版)
$pattern = '/<a\s+([^>]*?)href\s*=\s*["\']([^"\']*)["\']([^>]*)>/i';
$html = preg_replace_callback($pattern, function($matches) {
$href = $matches[2];
// javascriptスキームを削除
if (stripos($href, 'javascript:') === 0) {
return '';
}
// 安全なプロトコルのみ許可
if (!preg_match('/^(https?|mailto):/', $href)) {
$href = '';
}
if (empty($href)) {
return '';
}
return '<a ' . $matches[1] . 'href="' . htmlspecialchars($href) . '"' . $matches[3] . '>';
}, $html);
return $html;
}
/**
* プレーンテキスト用のサニタイズ
*/
public static function toPlainText($html) {
// ブロック要素を改行に
$html = preg_replace('/<(p|div|h[1-6]|li)[^>]*>/i', "\n", $html);
$html = preg_replace('/<\/(p|div|h[1-6]|li)>/i', "\n", $html);
$html = preg_replace('/<br\s*\/?>/i', "\n", $html);
// すべてのタグを削除
$text = strip_tags($html);
// 連続する改行を整理
$text = preg_replace("/\n+/", "\n", $text);
return trim($text);
}
/**
* タイトル用のサニタイズ
*/
public static function sanitizeTitle($input, $maxLength = 100) {
// すべてのタグを削除
$title = strip_tags($input);
// HTMLエンティティをデコード
$title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
// 長さ制限
if (mb_strlen($title) > $maxLength) {
$title = mb_substr($title, 0, $maxLength) . '...';
}
return trim($title);
}
}
// 使用例
$userInput = "<script>alert('XSS')</script><p>正常な<b>テキスト</b></p>";
echo "完全削除:\n";
echo InputSanitizer::sanitizeText($userInput) . "\n";
// 出力: alert('XSS')正常なテキスト
echo "\n安全なHTMLのみ:\n";
echo InputSanitizer::sanitizeHtml($userInput) . "\n";
// 出力: <p>正常な<strong>テキスト</strong></p>
$html = "<h1>タイトル</h1><p>段落1</p><p>段落2</p>";
echo "\nプレーンテキスト:\n";
echo InputSanitizer::toPlainText($html) . "\n";
/*
出力:
タイトル
段落1
段落2
*/
$longTitle = "<h1>これは非常に長いタイトルで、HTMLタグも含まれています</h1>";
echo "\nタイトル:\n";
echo InputSanitizer::sanitizeTitle($longTitle, 30) . "\n";
// 出力: これは非常に長いタイトルで、HTMLタグも含...
例2: メール送信用テキスト生成
class EmailTextGenerator {
/**
* HTMLからプレーンテキストを生成
*/
public static function htmlToPlainText($html) {
// リンクを処理(テキストとURLを両方表示)
$html = preg_replace(
'/<a\s+[^>]*href\s*=\s*["\']([^"\']*)["\'][^>]*>(.*?)<\/a>/is',
'$2 [$1]',
$html
);
// リストを処理
$html = preg_replace('/<li[^>]*>/i', "\n• ", $html);
$html = preg_replace('/<\/li>/i', '', $html);
// 見出しを処理
$html = preg_replace('/<h([1-6])[^>]*>/i', "\n\n", $html);
$html = preg_replace('/<\/h[1-6]>/i', "\n", $html);
// 段落とブロック要素を改行に
$html = preg_replace('/<(p|div|blockquote)[^>]*>/i', "\n\n", $html);
$html = preg_replace('/<\/(p|div|blockquote)>/i', '', $html);
$html = preg_replace('/<br\s*\/?>/i', "\n", $html);
// 強調を処理
$html = preg_replace('/<(strong|b)[^>]*>(.*?)<\/(strong|b)>/is', '*$2*', $html);
$html = preg_replace('/<(em|i)[^>]*>(.*?)<\/(em|i)>/is', '_$2_', $html);
// すべてのタグを削除
$text = strip_tags($html);
// HTMLエンティティをデコード
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// 空白行を整理
$text = preg_replace("/\n\n+/", "\n\n", $text);
return trim($text);
}
/**
* 短い要約を生成
*/
public static function generateSummary($html, $maxLength = 200) {
$text = self::htmlToPlainText($html);
if (mb_strlen($text) <= $maxLength) {
return $text;
}
// 単語の途中で切らないように
$text = mb_substr($text, 0, $maxLength);
$lastSpace = mb_strrpos($text, ' ');
if ($lastSpace !== false) {
$text = mb_substr($text, 0, $lastSpace);
}
return $text . '...';
}
/**
* メール本文を生成
*/
public static function generateEmailBody($html, $includeFooter = true) {
$text = self::htmlToPlainText($html);
if ($includeFooter) {
$text .= "\n\n" . str_repeat('-', 50) . "\n";
$text .= "このメールは自動送信されています。\n";
}
return $text;
}
}
// 使用例
$html = <<<HTML
<h1>ニュースレター</h1>
<p>こんにちは!今月の<strong>特別オファー</strong>をご紹介します。</p>
<ul>
<li>商品A:30%オフ</li>
<li>商品B:50%オフ</li>
</ul>
<p>詳細は<a href="https://example.com/offer">こちら</a>をご覧ください。</p>
HTML;
echo "プレーンテキスト:\n";
echo EmailTextGenerator::htmlToPlainText($html) . "\n";
/*
ニュースレター
こんにちは!今月の*特別オファー*をご紹介します。
• 商品A:30%オフ
• 商品B:50%オフ
詳細はこちら [https://example.com/offer]をご覧ください。
*/
echo "\n要約:\n";
echo EmailTextGenerator::generateSummary($html, 50) . "\n";
// 出力: ニュースレター こんにちは!今月の*特別オファー*をご...
echo "\nメール本文:\n";
echo EmailTextGenerator::generateEmailBody($html) . "\n";
例3: コンテンツ抽出
class ContentExtractor {
/**
* 記事の本文を抽出
*/
public static function extractArticleText($html) {
// スクリプトとスタイルを削除
$html = preg_replace('/<script[^>]*>.*?<\/script>/is', '', $html);
$html = preg_replace('/<style[^>]*>.*?<\/style>/is', '', $html);
// コメントを削除
$html = preg_replace('/<!--.*?-->/s', '', $html);
// ブロック要素を改行に
$html = preg_replace('/<(p|div|h[1-6]|li)[^>]*>/i', "\n", $html);
// すべてのタグを削除
$text = strip_tags($html);
// HTMLエンティティをデコード
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// 空白を整理
$text = preg_replace("/\n+/", "\n", $text);
$text = preg_replace("/ +/", " ", $text);
return trim($text);
}
/**
* メタディスクリプションを生成
*/
public static function generateMetaDescription($html, $length = 160) {
$text = self::extractArticleText($html);
// 最初の段落を取得
$paragraphs = explode("\n", $text);
$firstParagraph = '';
foreach ($paragraphs as $para) {
$para = trim($para);
if (!empty($para) && mb_strlen($para) > 20) {
$firstParagraph = $para;
break;
}
}
if (empty($firstParagraph)) {
$firstParagraph = $text;
}
// 長さ制限
if (mb_strlen($firstParagraph) > $length) {
$firstParagraph = mb_substr($firstParagraph, 0, $length);
$lastSpace = mb_strrpos($firstParagraph, ' ');
if ($lastSpace !== false) {
$firstParagraph = mb_substr($firstParagraph, 0, $lastSpace);
}
$firstParagraph .= '...';
}
return $firstParagraph;
}
/**
* 見出しを抽出
*/
public static function extractHeadings($html) {
$headings = [];
preg_match_all('/<h([1-6])[^>]*>(.*?)<\/h\1>/is', $html, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$level = (int)$match[1];
$text = strip_tags($match[2]);
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
$headings[] = [
'level' => $level,
'text' => trim($text)
];
}
return $headings;
}
/**
* 単語数をカウント
*/
public static function countWords($html) {
$text = self::extractArticleText($html);
// 日本語の場合は文字数
if (preg_match('/[\x{4E00}-\x{9FFF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $text)) {
return mb_strlen($text);
}
// 英語の場合は単語数
$words = preg_split('/\s+/', $text, -1, PREG_SPLIT_NO_EMPTY);
return count($words);
}
}
// 使用例
$article = <<<HTML
<article>
<h1>記事タイトル</h1>
<p>これは記事の最初の段落です。重要な情報が含まれています。</p>
<h2>セクション1</h2>
<p>セクション1の内容です。</p>
<h2>セクション2</h2>
<p>セクション2の内容です。</p>
</article>
HTML;
echo "本文:\n";
echo ContentExtractor::extractArticleText($article) . "\n";
echo "\nメタディスクリプション:\n";
echo ContentExtractor::generateMetaDescription($article) . "\n";
echo "\n見出し:\n";
$headings = ContentExtractor::extractHeadings($article);
foreach ($headings as $heading) {
echo str_repeat(' ', $heading['level'] - 1) . $heading['text'] . "\n";
}
echo "\n単語数: " . ContentExtractor::countWords($article) . "\n";
例4: フィード生成
class FeedGenerator {
/**
* RSSフィード用のテキストを生成
*/
public static function generateRssDescription($html, $maxLength = 500) {
// 画像を代替テキストに置換
$html = preg_replace(
'/<img[^>]+alt\s*=\s*["\']([^"\']*)["\'][^>]*>/i',
'[$1]',
$html
);
// すべてのタグを削除
$text = strip_tags($html);
// HTMLエンティティをデコード
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// 空白を正規化
$text = preg_replace('/\s+/', ' ', $text);
$text = trim($text);
// 長さ制限
if (mb_strlen($text) > $maxLength) {
$text = mb_substr($text, 0, $maxLength);
$lastSpace = mb_strrpos($text, ' ');
if ($lastSpace !== false) {
$text = mb_substr($text, 0, $lastSpace);
}
$text .= '...';
}
return $text;
}
/**
* JSONフィード用のテキストを生成
*/
public static function generateJsonContent($html) {
// HTMLを最小限のタグに変換
$allowedTags = ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li'];
$text = strip_tags($html, $allowedTags);
// 改行を整理
$text = preg_replace("/\n+/", "\n", $text);
return trim($text);
}
}
// 使用例
$content = <<<HTML
<article>
<h1>記事タイトル</h1>
<img src="photo.jpg" alt="写真">
<p>これは記事の本文です。<strong>重要な情報</strong>が含まれています。</p>
<p>詳細は<a href="https://example.com">こちら</a>をご覧ください。</p>
</article>
HTML;
echo "RSS用:\n";
echo FeedGenerator::generateRssDescription($content) . "\n";
// 記事タイトル [写真] これは記事の本文です。重要な情報が含まれています...
echo "\nJSON用:\n";
echo FeedGenerator::generateJsonContent($content) . "\n";
// <p>これは記事の本文です。<strong>重要な情報</strong>が含まれています。</p>
例5: 検索インデックス作成
class SearchIndexer {
/**
* 検索用のテキストを抽出
*/
public static function extractSearchText($html) {
// スクリプト、スタイル、コメントを削除
$html = preg_replace('/<(script|style)[^>]*>.*?<\/\1>/is', '', $html);
$html = preg_replace('/<!--.*?-->/s', '', $html);
// すべてのタグを削除
$text = strip_tags($html);
// HTMLエンティティをデコード
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// 小文字に変換
$text = mb_strtolower($text);
// 記号を削除
$text = preg_replace('/[^\p{L}\p{N}\s]/u', ' ', $text);
// 空白を正規化
$text = preg_replace('/\s+/', ' ', $text);
return trim($text);
}
/**
* キーワードを抽出
*/
public static function extractKeywords($html, $minLength = 3, $maxCount = 20) {
$text = self::extractSearchText($html);
// 単語に分割
$words = preg_split('/\s+/', $text, -1, PREG_SPLIT_NO_EMPTY);
// ストップワードを除外
$stopWords = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for'];
$keywords = [];
foreach ($words as $word) {
if (mb_strlen($word) >= $minLength && !in_array($word, $stopWords)) {
$keywords[] = $word;
}
}
// 頻度をカウント
$frequency = array_count_values($keywords);
arsort($frequency);
// 上位のキーワードを取得
return array_slice(array_keys($frequency), 0, $maxCount);
}
/**
* 検索用インデックスデータを作成
*/
public static function createIndex($id, $title, $content) {
$titleText = strip_tags($title);
$contentText = self::extractSearchText($content);
$keywords = self::extractKeywords($content);
return [
'id' => $id,
'title' => $titleText,
'content' => mb_substr($contentText, 0, 200),
'keywords' => $keywords,
'full_text' => $contentText
];
}
}
// 使用例
$html = <<<HTML
<article>
<h1>PHPプログラミング入門</h1>
<p>PHPは人気のあるプログラミング言語です。Webアプリケーション開発に広く使用されています。</p>
<p>PHPの基本的な構文を学びましょう。変数、関数、クラスなどの概念を理解することが重要です。</p>
</article>
HTML;
echo "検索用テキスト:\n";
echo SearchIndexer::extractSearchText($html) . "\n";
echo "\nキーワード:\n";
$keywords = SearchIndexer::extractKeywords($html);
print_r($keywords);
echo "\nインデックスデータ:\n";
$index = SearchIndexer::createIndex(1, '<h1>PHPプログラミング入門</h1>', $html);
print_r($index);
例6: プレビュー生成
class PreviewGenerator {
/**
* 記事プレビューを生成
*/
public static function generatePreview($html, $length = 200, $includeImage = true) {
$preview = [];
// タイトルを抽出
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $html, $match)) {
$preview['title'] = strip_tags($match[1]);
}
// 画像を抽出
if ($includeImage && preg_match('/<img[^>]+src\s*=\s*["\']([^"\']+)["\'][^>]*>/i', $html, $match)) {
$preview['image'] = $match[1];
}
// 本文を抽出
$text = ContentExtractor::extractArticleText($html);
if (mb_strlen($text) > $length) {
$text = mb_substr($text, 0, $length);
$lastSpace = mb_strrpos($text, ' ');
if ($lastSpace !== false) {
$text = mb_substr($text, 0, $lastSpace);
}
$text .= '...';
}
$preview['excerpt'] = $text;
return $preview;
}
/**
* カード形式のプレビューHTML生成
*/
public static function generateCardHtml($html) {
$preview = self::generatePreview($html);
$card = '<div class="preview-card">';
if (isset($preview['image'])) {
$card .= '<img src="' . htmlspecialchars($preview['image']) . '" alt="Preview">';
}
if (isset($preview['title'])) {
$card .= '<h3>' . htmlspecialchars($preview['title']) . '</h3>';
}
$card .= '<p>' . htmlspecialchars($preview['excerpt']) . '</p>';
$card .= '</div>';
return $card;
}
}
// 使用例
$article = <<<HTML
<article>
<h1>最新テクノロジートレンド</h1>
<img src="tech.jpg" alt="テクノロジー">
<p>2024年の最新テクノロジートレンドをご紹介します。</p>
<p>人工知能、機械学習、ブロックチェーンなど、様々な技術が進化しています。</p>
</article>
HTML;
$preview = PreviewGenerator::generatePreview($article);
print_r($preview);
/*
Array (
[title] => 最新テクノロジートレンド
[image] => tech.jpg
[excerpt] => 2024年の最新テクノロジートレンドをご紹介します。 人工知能、機械学習、ブロックチェーンなど、様々な技術が進化しています。
)
*/
例7: ソーシャルメディア共有
class SocialShareGenerator {
/**
* Twitter用テキスト生成
*/
public static function generateTweetText($html, $maxLength = 280) {
// タイトルを取得
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $html, $match)) {
$title = strip_tags($match[1]);
} else {
$title = strip_tags($html);
}
// HTMLエンティティをデコード
$title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
// 長さ制限(URLとハッシュタグのスペースを考慮)
$availableLength = $maxLength - 30; // URL用に予約
if (mb_strlen($title) > $availableLength) {
$title = mb_substr($title, 0, $availableLength - 3) . '...';
}
return $title;
}
/**
* Facebook用ディスクリプション生成
*/
public static function generateFacebookDescription($html, $maxLength = 300) {
$text = ContentExtractor::extractArticleText($html);
if (mb_strlen($text) > $maxLength) {
$text = mb_substr($text, 0, $maxLength);
$lastPeriod = mb_strrpos($text, '。');
$lastDot = mb_strrpos($text, '.');
$lastBreak = max($lastPeriod, $lastDot);
if ($lastBreak !== false && $lastBreak > $maxLength * 0.7) {
$text = mb_substr($text, 0, $lastBreak + 1);
} else {
$text .= '...';
}
}
return $text;
}
/**
* OGPメタタグ生成
*/
public static function generateOgpTags($html, $url) {
$tags = [];
// タイトル
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $html, $match)) {
$tags['og:title'] = strip_tags($match[1]);
}
// ディスクリプション
$tags['og:description'] = self::generateFacebookDescription($html);
// 画像
if (preg_match('/<img[^>]+src\s*=\s*["\']([^"\']+)["\'][^>]*>/i', $html, $match)) {
$tags['og:image'] = $match[1];
}
// URL
$tags['og:url'] = $url;
return $tags;
}
}
// 使用例
$content = <<<HTML
<h1>PHP 8.3の新機能</h1>
<img src="php83.jpg" alt="PHP 8.3">
<p>PHP 8.3では、多くの新機能と改善が追加されました。パフォーマンスの向上や新しい構文のサポートなど、開発者にとって有益な変更が含まれています。</p>
HTML;
echo "Twitter用:\n";
echo SocialShareGenerator::generateTweetText($content) . "\n";
echo "\nFacebook用:\n";
echo SocialShareGenerator::generateFacebookDescription($content) . "\n";
echo "\nOGPタグ:\n";
$ogp = SocialShareGenerator::generateOgpTags($content, 'https://example.com/article');
foreach ($ogp as $property => $content) {
echo '<meta property="' . $property . '" content="' . htmlspecialchars($content) . '">' . "\n";
}
注意点と制限事項
セキュリティ上の注意
// strip_tags()だけではXSS対策として不十分
$input = '<img src=x onerror=alert("XSS")>';
// タグは削除されるが、属性内のコードは実行される可能性がある
$cleaned = strip_tags($input, '<img>');
echo $cleaned; // <img src=x onerror=alert("XSS")>
// 完全なサニタイズには追加の処理が必要
$safe = htmlspecialchars(strip_tags($input), ENT_QUOTES, 'UTF-8');
echo $safe; // 安全
不完全なタグの扱い
// 閉じタグがない場合
$html = "<p>テキスト";
echo strip_tags($html); // "テキスト"
// 入れ子が正しくない場合
$html = "<b><i>テキスト</b></i>";
echo strip_tags($html); // "テキスト"(エラーにはならない)
パフォーマンス
// 大量のテキスト処理
$largeHtml = str_repeat("<p>段落</p>", 10000);
$start = microtime(true);
$result = strip_tags($largeHtml);
$time = microtime(true) - $start;
echo "処理時間: {$time}秒\n";
echo "元のサイズ: " . strlen($largeHtml) . " bytes\n";
echo "結果のサイズ: " . strlen($result) . " bytes\n";
より安全な代替手段
// HTMLPurifierを使用(より強力)
// require 'vendor/autoload.php';
// $config = HTMLPurifier_Config::createDefault();
// $purifier = new HTMLPurifier($config);
// $clean = $purifier->purify($dirtyHtml);
// DOMDocumentを使用
function stripTagsWithDom($html) {
$dom = new DOMDocument();
@$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
return $dom->textContent;
}
// 使用例
$html = "<p>Hello <b>World</b>!</p>";
echo stripTagsWithDom($html); // "Hello World!"
まとめ
strip_tags()関数の特徴をまとめると:
できること:
- HTMLとPHPタグの削除
- 特定のタグのみ許可
- プレーンテキストの抽出
推奨される使用場面:
- ユーザー入力のサニタイズ
- メール本文の生成
- 検索インデックスの作成
- プレビューテキストの生成
- ソーシャルメディア共有用テキスト
注意点:
- セキュリティ対策としては不十分
- 改行が失われる
- 属性は削除されない
- 不完全なHTMLも処理される
より安全な方法:
htmlspecialchars()と組み合わせる- HTMLPurifierなどのライブラリを使用
- DOMDocumentで解析
関連関数:
htmlspecialchars(): HTMLエンティティをエスケープhtml_entity_decode(): HTMLエンティティをデコードhtmlspecialchars_decode(): エスケープを元に戻す
strip_tags()は便利な関数ですが、セキュリティが重要な場面では追加の対策が必要です。用途に応じて適切に使い分けましょう!
