PHPで汎用的な伏字クラスを作成しました

電話番号やメールアドレスとかに加えて、放送禁止用語とか、個人情報等、伏字って案外使用するケースが多いのですが、正規表現も混ぜて対応すると意外と面倒なものです。そんな個人的需要を経て様々なプロジェクトに使いまわせる汎用的なクラスを作りました。PHP 5.3以上で動作確認済です。

# 注意

このクラスはエスケープやサニタイズといった処理をしていないので、$_POST等外部の入力を受け取るときや、HTMLとして文中に吐き出すときはご注意ください。

伏字置換は入力や設定の仕方によって精度が変わるので、絶対の信用を担保するものではありません。ご使用する場合は自己責任でお願いいたします。

# 機能

+ 置換する文字列は __普通の文字列__ と __正規表現__ のどちらにも対応しています
+ 伏字後の文字数を __伏字前と同じ文字数に合わせる__ or __固定文字数にする__ の二つから選択可能です
+ 伏字の置換文字列を自由に変えられます
+ クラスに予め伏字設定ができるのに加え、いざ文字列置換を行うタイミングでその場限りの伏字設定ができます

# クラス全貌

以下がクラスの全貌です。

```Unprintable.php

<?php
class Unprintable
{

  /**
   * 伏字にした後の置換文字列
   * 置換後はこの文字列を一文字と数え代替される
   * @var string
   */
  private $substitution;

  /**
   * 伏字置換時の文字数
   * 0にしておくと置換前と同じ文字数になる
   * @var int
   */
  private $length;

  /**
   * 伏字にしたい文字列の配列
   * 'sentence'が伏字文字列, 'regex'が正規表現かどうかの真偽値
   * @var array
   */
  private $sentences;

  /**
   * コンストラクタ
   * 伏字置換文字列・文字数の設定
   * @param string $substitution
   * @param int $length
   */
  public function __construct($substitution = '*', $length = 0)
  {
    $this->substitution  = is_string($substitution) && $substitution !== '' ? (string)$substitution : '*';
    $this->length = is_int($length) && $length >= 0 ? $length : 0;
  }

  /**
   * $string内に存在する$matchを全て置換して返す
   * @param string $match
   * @param string $string
   * @return string
   */
  private function  _replace($match, $string)
  {
    //文字数を計って代替文字を作成し全て置換して返す
    return str_replace($match, str_repeat($this->substitution, $this->length ? $this->length : mb_strlen($match)), $string);
  }

  /**
   * 伏字にする文字を追加する
   * $sentencesは配列（一次元）か文字列を受け付ける
   * $regexをTUREに設定すると正規表現として処理する
   * @param array|string $sentences
   * @param bool $regex
   * @return void
   */
  public function add_sentence($sentences, $regex = FALSE)
  {
    //文字列で来ていたら配列化
    if ( ! is_array($sentences))
    {
      $sentences = array($sentences);
    }

    //追加
    foreach ($sentences as $sentence)
    {
      $this->sentences[] = array(
        'sentence' => (string)$sentence,
        'regex' => (bool)$regex
      );
    }
  }

  /**
   * $stringで渡した文字列を現在の伏字設定を基に加工して返す
   * $flashConfigはその場限りの追加設定で、配列の中に'sentence'と'regex'を持った
   * 配列を並べた値を指定する
   * @param string $string
   * @param array $flash_config
   * @return string
   */
  public function convert_string($string, $flash_config = array())
  {
    //返す文字列の初期設定
    $result = (string)$string;

    //設定済伏字設定と一時伏字設定を合体してループ処理
    $sentences = array_merge($this->sentences, $flash_config);
    foreach ($sentences as $sentence)
    {
      //regexとsentenceがあるか検査
      if ( ! isset($sentence['regex']) ||  ! isset($sentence['sentence']))
      {
        die('伏字設定が正しくありません');
      }

      //正規表現で処理するかどうか
      if ($sentence['regex'])
      {
        //マッチする文字列の取得兼ループ
        $limit = 100;
        while (preg_match('{'.$sentence['sentence'].'}', $result, $match) && $limit > 0)
        {
          //正規表現置換
          $result = $this->_replace($match[0], $result);
          --$limit;
        }
        continue;
      }

      //通常置換
      $result = $this->_replace($sentence['sentence'], $result);
    }

    return $result;
  }

}


```

# 使い方

突然ですが、ネットにはびこる出会い厨の野望を阻止しましょう。

あるユーザーがあなたの作ったチャットフォームを使って相手に自分の個人情報を渡そうとしています。

> こんにちわ！　山田　太郎です！　僕の連絡先はhoge@example.comだよ！　電話番号なら090-0000-0000にかけてきてね！　09001234567でも大丈夫だよ！　もしよかったら東京に遊びにきなよ^^　暇だったらここじゃなくてhttps://qiita.com/でもっとお話ししようよ！

この平文を伏字だらけにしましょう。

```php

//クラスを読み込む
include_once 'Unprintable.php';

//伏字を'×'、伏字後の文字数を3文字にする
$unprintable = new Unprintable('×', 3);

//都道府県を伏字
$unprintable->add_sentence(array('北海', '青森', '岩手', '宮城', '秋田', '山形', '福島', '茨城', '栃木', '群馬', '埼玉', '千葉', '東京', '神奈川', '新潟', '富山', '石川', '福井', '山梨', '長野', '岐阜', '静岡', '愛知', '三重', '滋賀', '京都', '大阪', '兵庫', '奈良', '和歌山', '鳥取', '島根', '岡山', '広島', '山口', '徳島', '香川', '愛媛', '高知', '福岡', '佐賀', '長崎', '熊本', '大分', '宮崎', '鹿児島', '沖縄'));

//メールアドレスを伏字
$unprintable->add_sentence('([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+', TRUE);

//電話番号（-有り）を伏字
$unprintable->add_sentence('[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}', TRUE);

//http or https始まりのURLを伏字
$unprintable->add_sentence('(https?|ftp)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)', TRUE);

//原文
$txt = 'こんにちわ！　山田　太郎です！　僕の連絡先はhoge@example.comだよ！　電話番号なら090-0000-0000にかけてきてね！　09001234567でも大丈夫だよ！　もしよかったら東京に遊びにきなよ^^　暇だったらここじゃなくてhttps://qiita.com/Go-Nojiでもっとお話ししようよ！';

//置換して出力
echo $unprintable->convert_string($txt);

```

> こんにちわ！　山田　太郎です！　僕の連絡先は×××だよ！　電話番号なら×××にかけてきてね！　09001234567でも大丈夫だよ！　もしよかったら×××に遊びにきなよ^^　暇だったらここじゃなくて×××でもっとお話ししようよ！

微妙に野望を打ち砕けましたね。

事前にユーザーの本名が分かっていればそこも伏字にできます。その場限りの設定に追加してみましょう。

```php

$last_name = '山田';
$first_name = '太郎';

echo $unprintable->convert_string($txt, array(
  array('sentence' => $last_name, 'regex' => FALSE),
  array('sentence' => $first_name , 'regex' => FALSE)
));

echo $unprintable->convert_string($txt, array(
  array('sentence' => $last_name, 'regex' => FALSE)
));

echo $unprintable->convert_string($txt, array(
  array('sentence' => $first_name , 'regex' => FALSE)
));

```

> こんにちわ！　×××　×××です！　僕の連絡先は×××だよ！　電話番号なら×××にかけてきてね！　09001234567でも大丈夫だよ！　もしよかったら×××に遊びにきなよ^^　暇だったらここじゃなくて×××でもっとお話ししようよ！

> こんにちわ！　×××　太郎です！　僕の連絡先は×××だよ！　電話番号なら×××にかけてきてね！　09001234567でも大丈夫だよ！　もしよかったら×××に遊びにきなよ^^　暇だったらここじゃなくて×××でもっとお話ししようよ！

> こんにちわ！　山田　×××です！　僕の連絡先は×××だよ！　電話番号なら×××にかけてきてね！　09001234567でも大丈夫だよ！　もしよかったら×××に遊びにきなよ^^　暇だったらここじゃなくて×××でもっとお話ししようよ！

``convert_string``の第二引数に設定できるのはその場限りの設定なので、事前に設定したメールアドレス等の置換は全体に適用されますが、姓と名はバラバラに置換されています。

今度は違うケースとして、正規表現としては少々過激ですが、ある程度連続する数値、つまりハイフン無しの電話番号も伏字にしてみましょう。``regex``を``TRUE``にすると正規表現として解釈されます。

```php

echo $unprintable->convert_string($txt, array(
  array('sentence' => '[0-9]{10,11}', 'regex' => TRUE)
));

```

> こんにちわ！　山田　太郎です！　僕の連絡先は×××だよ！　電話番号なら×××にかけてきてね！　×××でも大丈夫だよ！　もしよかったら×××に遊びにきなよ^^　暇だったらここじゃなくて×××でもっとお話ししようよ！

こんな感じで伏字設定が簡単に適用できます。さらば出会い厨。