[メモ] symfony/sfPayloadFilterChainPluginによる文字列のフィルタ保存

sfPayloadFilterChainPlugin

掲示板とかで、表示時にフィルタをかけるんじゃなくて、保存時にまずい文字列とかを見つけて伏字にしたりするためのsymfony プラグイン。

インストール方法

> symfony plugin-install http://plugins.symfony-project.com/sfPayloadFilterChainPlugin

設定ファイル

$ cd frontend/config/pfc
$ edit frontend/config/pfc/profiles.yml
post_comment: [censor_bad_words, hide_phone_number]

$ edit frontend/config/pfc/filters.yml

  censor_bad_words:
    enabled: on
    class:   myCensorshipFilter
    params:
      bad_words:        [死ね, 死ねばいいのに, 氏ね]
      replacement_word: "王大人に死亡確認されればいいのに"
  hide_phone_number:
    enabled: on
    class:   akkyHidePhoneNumberFilter
    params:
      replacement_word: "***-****-****"

payloadでフィルタするテキストを受け渡す作りで、説明にあるように単純なpayloadクラスを作っておく

lib/payload/myTextPayload.class.php

[code lang=”php”]
class myTextPayload
{
public function getText()
{
return $this->text;
}
public function setText($text)
{
$this->text = $text;
}
}
[/code]

あとはフィルタクラスを実装して配置。autoloadさせるので置いたら”symfony cc”を呼ぶこと

lib/filter/akkyHidePhoneNumberFilter.class.php

[code lang=”php”]
/**
* hide Japan’s phone number from text
*
*/
class akkyHidePhoneNumberFilter extends sfFilter
{
// not a perfect solution
// see http://blog.livedoor.jp/nipotan/archives/17526053.html
const PATTERN = ‘/(?:0\d{1,4}[-.]?)?\d{1,4}[-.]?\d{4}/’;
/**
* Make phone number unreadable.
*
* @param string $text
* @return string
*/
public function execute($chain, $payload)
{
$replacementWord
= $this->getParameter(‘replacement_word’, ‘xxx-xxxx-xxxx’);

// Retrieve text from payload
$text = $payload->getText();

$censoredText = preg_replace(self::PATTERN, $replacementWord, $text);

// Replace payload text
$payload->setText($censoredText);

// Execute next filter in chain
$chain->execute($payload);
}
}
[/code]

あとは、実際に書き込みの発生するところで、手動(アクション側)または自動(モデル側)でこのfilterを呼び出すことで、まずい単語にマスクをかけるフィルタなどを、フィールド毎に適用させることができます。

[code lang=”php”]
class messageActions extends sfActions
{
public function executePost()
{
$payloadComment = new myTextPayload();
$payloadComment->setText($this->getRequestParameter(‘comment’));
// Filter payload
$chain = new pfcPayloadFilterChain();
$chain->loadProfile(‘post_comment’);
$chain->setPayload($payloadComment);
$chain->execute();
$this->comment = $payloadComment->getText();

(このあとpropelオブジェクトにセットして保存とか)
[/code]

で、入力フィールドのこういうのが

秋元(030-5555-5555)死ね

こうなる

秋元(***-****-****)王大人に死亡確認されればいいのに

アクションごとに適用できるフィルタをきめ細かく変更できるところと、適用するfilterをyamlで調整したり、可変のパラメータを設定できたりするところが利点なんでしょうか。もうちょっと使ってみようかと思っています。

Windows+PHP+memcached+symfony

PHPマニュアル memcache

Windows用のmemcachedもバイナリパッケージで入手できるもんですね。手元の開発環境でどうしようかと思ったら、なんでもあるもんだ。

memcached-win32 Windows用のmemcached。サービス版とコマンド版両用 バイナリ版をzipで展開して適当な場所に置く

peclのWindows用バイナリ配布pecl4win からmemecahed拡張をダウンロード。5.2.5用はないので5.2.1用を。php-5.2.5/extとかに置く。

php.iniを編集して、extention=php_memcache.dll を追加。(Apache再起動)(このバイナリのdllにはmemcacheで”d”はついてないので注意)

sfMemcachePluginを、自分のsymfonyで作ったプロジェクトに入れる。

> symfony plugin-install http://plugins.symfony-project.com/sfMemcachePlugin

関数キャッシュのmemcached化の使用レポートin日本語を参考に、設定を行なう。

config_handlers.ymlとmemcache.ymlを、(アプリ名)/config/ にコピー。memcache.ymlの中のポート番号などは自分が動かしているmemcachedの設定に合わせて変更する。

conf/factories.ymlで、ViewをキャッシュするクラスをsfMemcacheCacheに置換する。

view_cache:
  class: sfMemcacheCache

もちろん、自分のsymfony appはテンプレートキャッシュを使う設定になってないといけない。開発環境でもcacheをオンにする設定は、たとえば、(apps)/settings.ymlにて

all:
  .settings:
    cache:     on

(apps)/cache.ymlにて

all:
  enabled:     on
  with_layout: false
  lifetime:    86400

など。(実際にはこんな乱暴に全actionにキャッシュをかけちゃだめ)

あとは、設定を反映させるために、symfonyキャッシュをクリア

> symfony cc

で、実行する。devモードの場合、一回目表示される水色の「キャッシュがなかったので作ったよ」マークが二回目の表示で黄色になればOK。

実行した結果、memcachedが本当に使われているかどうかを確認するには、memcachedの内容を確認するにはにあるようなスクリプトでmemcachedの状態を表示します。

[追記] クライアント側のライブラリだからdがついてない、とelfさんに指摘いただきました。考えてみればそうですね。

symfonyのフォームでYahoo UIの綺麗なボタンを使う

状況

  • フォームのボタンをブラウザのデフォルトじゃなく、もうちょっと良いデザインにしたい
  • Yahoo UIのボタンは、CSSでキレイにデザインされている。CSSや画像ファイルもyahoo.comのを使えるのでサーバにもやさしい
  • symfonyで作ったフォームからYUI buttonを使いたい

symfonyでのフォームのsubmitについては、link_to()、button_to()、submit_image_tag()の3つがFormHelperに用意されています。

  • submit_tag() は、ブラウザデフォルトのボタンを書くのでフォームでそのまま動く
  • button_to() type=”button”は作れるけど、formの値を送るには自分でjavascript書く必要あり
  • submit_image_tag() 画像用意すればキレイなsubmit用のボタンが使える

YUI buttonは、type=”button”を使うことを前提にしているのですが、type=”button”でformのサブミットをさせる(ボタンを押したらフォームの各値がリクエストに含まれる)ようにするためには、ボタンが所属するフォームを駆動するようなJavascriptが必要となります。

以上から、次のようにすることで、symfony+YUIでのCSSデザインボタンを作ることができました。

HTMLのほうは、2番目のパラメータにtypeを渡すことで、submit_tag()にもtype=”body”を上書きで指定することができます。

<?php echo submit_tag('実行する', array('type' => 'button', 'id' => 'compareButton')); ?>

YUI buttonのJavascriptでの定義のところでは、この「フォームを駆動するボタン」という設定ができるので、これを指定します。

<script type="text/javascript">
var oButton = new YAHOO.widget.Button(
  "compareButton",
  { 
    checked: false,
    type: "submit"
  }
);
</script>

type: “submit”がそれにあたります。

あとは、YUI buttonに必要な定義をします。

なんらかのスタイルをあてる。YUIで提供されているスキンか、自分で改造したスキンを指定することも可能

<body class="yui-skin-sam">

symfonyのconfig/view.ymlでは、関係のあるYUIのCSS/JSを読ませるようにします。

  stylesheets:    [main, http://yui.yahooapis.com/2.4.1/build/grids/grids-min.css, http://yui.yahooapis.com/2.4.1/build/button/assets/skins/sam/button.css]

  javascripts:    [http://yui.yahooapis.com/2.4.1/build/yahoo-dom-event/yahoo-dom-event.js, http://yui.yahooapis.com/2.4.1/build/element/element-beta-min.js, http://yui.yahooapis.com/2.4.1/build/button/button.js]

このへんは、YUIのバージョン変わったり、圧縮版を使うかとか、yahoo.comの提供してくれているファイルを使うか、ローカルにコピーしたファイルを使うかとかで変わってくるでしょう。

symfony, YUIで作ったキレイ目ボタン

symfonyで、view.ymlで定義したサイトタイトルをテンプレート中から呼び出す方法

view.ymlのdefault/metas/titleでサイトワイドのタイトルを定義しているのに、layout.phpや_header.phpでタイトルを手打ちするのは嫌だなあ、ということで、どこから辿ってview.ymlのデータが取れるのかを探しました。

layout.php等で、

sfContext::getInstance()->getResponse()->getTitle()

を呼ぶと、view.ymlの中のタイトルが取れます。これを<h1>とかに放り込めばOK。

SimpleXMLElementをunserializeするとエラーになる

simplexmlで取得したオブジェクトをシリアライズしても、元に戻せない。

$gottenItems = simplexml_load_file($serviceUrl);
$hoge = unserialize(serialize($gottenItems));

エラーはこれ

Warning: unserialize() [function.unserialize]: Node no longer exists in *.php on line *

simplexml_load_stringのマニュアルのコメントでも回避策とか提案されている。

なんでこんなエラーに出会ったかというと、symfonyの関数キャッシュを使おうとしたら、対象の関数の返すのがsimplexml_load_file()の返すオブジェクトそのままだったからで、symfonyの中でエラーになった。

以下のコードは、検索結果をファイルにキャッシュする関数をsymfonyで書いたもの。symfonyのキャッシュフォルダ以下に生成させることで、キャッシュコントロールをsymfony管理下に置いている。

public static function search($words, $offset = 0, $results = 10) {
	$function_cache_dir = sfConfig::get('sf_cache_dir').'/mysearch';
	$fc = new sfFunctionCache($function_cache_dir);
	$results = $fc->call('doSearch', $words, $offset, $results);
	return $results;
}

コメントにあった回避策取るよりは、そもそもシリアライズするポイントが間違ってる、ということかなと。

XMLのままキャッシュに入れるか、SimpleXMLElementから必要なものを取り出してその配列やオブジェクトをserializeするように変更しよう。