JavaScript側にPHP変数を簡単にまるごと渡す方法 #FuelPHPAdvent2013
ハイ、昨日のオレに引き続きFuelPHP Advent Calendar 2013の6日目です。
今回の内容もまたTwig絡みです。実は昨日の記事は、本日の記事の前準備になっていたのでした。
JavaScript側にPHPのオブジェクトを渡したい
最近のWebアプリはUIのインタラクションが凝っていて、ブラウザ側のJavaScriptで色んな制御をすることも当たり前になってきました。jQueryや様々なjQueryプラグインを駆使して、ユーザに分かりやすく使いやすいサービスを提供することは、もはやウェブエンジニアとしては持っていて当然のスキルになっています。
そのようなUIを作っている際に、JavaScript側に動作パラメータの初期値を渡すのに値を一つ一つテンプレート記法で埋め込むのが面倒だったので、一発で渡せるTwig Extensionを作ったので紹介します。
data_bind関数
アイデアとしては、見えないHTML要素を作成してそのテキストに値をJSON化して突っ込もうという、まぁ普通に思いつきそうなものです。でもこれがやってみると思ったより便利で。
extension本体はこんなコードです。
<?php class Hoge_Twig_Extension extends Twig_Extension { : public function getFunctions() { return array( new Twig_SimpleFunction('data_bind', array($this, 'dataBind')), ); } public function dataBind($name, $val, $exclude = null) { if (is_object($val) && is_callable(array($val, 'to_array'))) { $val = $val->to_array(); } if (!empty($exclude)) { if (is_string($exclude)) { $exclude = array($exclude); } foreach ($exclude as $key) { unset($val[$key]); } } $fmt = '<div id="data-%s" class="hide">%s</div>'; return sprintf($fmt, $name, json_encode($val)); } }
div要素を不可視にするために、ここでは'hide'というclassを指定していますが、これはCSSで
.hide { display: none; }
的なものがあることを前提にしています。Bootstrapには含まれてますね。もちろん直接styleを書いてしまってもよいでしょう。
コントローラ側のアクションメソッドで以下のようにテンプレートに値を渡して、
<?php : function action_xxx() { : // $userinfo は情報が入ったObjectまたは連想配列 $this->template->user = $userinfo; }
テンプレート側ではこう記述します。
{{ data_bind('user', user) }}
JavaScript側でその値を使用するには、例えばjQueryだったら
var user = $.parseJSON($('#data-user').text());
と書くと、user変数にPHPで渡した値が入ります。
パラメータの解説
data_bind 関数は3つのパラメータを持ちます。
$name: 名前
HTML上で展開される名前です。'data-名前' がその要素のidになります。
$val: 変数
展開する変数です。Twigの変数になります。
$exclude: 排除するキー (省略可能)
変数を全部JS側に渡すのが楽とはいっても、ユーザ側に公開したくない内部プロパティが含まれているかもしれません。そういう場合には、第3引数にそのプロパティ名を渡すことであらかじめ削除した上で展開することができます。
単なる文字列として指定することもできますし、
{{ data_bind('user', user, 'password') }}
配列にして複数指定することもできます。
{{ data_bind('user', user, ['password', 'rank']) }}
ということでお手軽 tips でした。明日のアドベントカレンダーは@LandscapeSketchさんの[W] FuelPHP開発でローカルとWebで構造が変わっても対応できる小技 | Work Tool Smith [ワークツールスミス]です。
12/8 追記:
HTMLに埋め込む際のエスケープ処理に関しては、状況によってはjson_encode()にエスケープ用のオプションが必要です。またTwig_Function_Method はすでに古いインターフェースだと @kenji_s さんからご指摘を受けたので、現在推奨されている Twig_SimpleFunction に書き換えました。詳しくは以下をご覧ください。
FuelPHPのViewの自動エスケープについて - Blog::koyhoge
「PHPエンジニア養成読本」が9月13日に出版されます
来月9月13日(金)に技術評論社より「PHPエンジニア養成読本」というムックが発売されます。新原さんのエントリや増永さんのエントリがすでにホッテントリ入りしているので、もうご存知の方も多いかもしれません。大きく変わりつつある PHP 開発のイマドキの常識を、可能な限りピックアップした本です。
内容についてはすでに上記の2エントリで的確に解説されていますので、ここでは視点を変えて、きっかけを作った一人としてこのムックが生まれた背景などを書いておこうと思います。
企画の相談が来たのは4月中旬
私は以前にWEB+DB PRESSで「PHPこども電話相談室」というふざけたタイトルの連載をしていました。その時からお世話になっている技評の編集者の細谷さんから、「PHPエンジニア養成読本」というムックの企画があるので相談に乗ってもらえないかというメールが来たのは4月中旬でした。PHP を取り巻く開発環境が大きく変わりつつある中で、それらの情報がまとまっている出版物はほぼ無いという状況で、たぶん需要もあるだろうし出版する価値も大きいと思った私は、二つ返事で OK しました。
その後、わりかしのんびりとしたメールのやりとりでネタ出し等をおこなったあと、5月22日に細谷さんとお会いして打合せをしました。その場では色んな話をしましたが、今回のムックの出版日ターゲットとして、その時にはすでに開催日が決まっていた PHPカンファレンス2013 に合わせる形にしたい、という要望を聞きました。それを聞いた時に私は「そりゃ無理ですよー」と返事をしたのを覚えています。その時に自分が想定していた執筆者候補は、たいてい PHPカンファレンスのスタッフになっているので、カンファレンス前のテンパっている時期に執筆時記を重ねるのはリスク高え、と思ってました。
PHPカンファレンス関西の飲み会で新原さんに相談
その打合せの直後にPHPカンファレンス関西2013が開催されました。私は毎年参加しているので今回も参加したわけですが、カンファレンス前日の宴会で「このムックの執筆陣を関西 PHP コミュニティで確保できないか?」という相談を新原さんにしてみました。新原さんも幸い興味を持ってくれて、ここから一気に企画が具体化し始めます。
執筆者の Facebook グループが作られて、そこで企画内容を揉んだり、執筆原稿は github で管理することが決まったり、様々な物事がどんどん詰められていきました。github での原稿を共同執筆という経験は自分は初めてで、他の執筆陣から徐々に上がってくる原稿を見ながら、高まるプレッシャーを感じていました。
自分はというと、別口の原稿依頼がもう一本あってそちらに時間が取られたり、ちょっとサボり癖がでたりして、最初の〆切を大幅にオーバーしてからエンジンが回り始めて一気に書き上げました。ちなみにその別口の原稿というのが、@IT で公開された以下の記事です。
Symfony, FuelPHP の紹介記事を3ページずつ書きました
さてそういうわけで、他の方々に心配をかけながらも、無事に原稿は書き上がりました。Symfony と FuelPHP の紹介記事をそれぞれ 3 ページずつ執筆しました。
MVC がどうなっているとか、基本的なことはどのフレームワークも大して変わらないので、自分なりにそれぞれのフレームワークを使ってみて感じた、そのフレームワークならではの特徴を抽出して執筆したつもりです。その意味では、自分のパートは入門者には実用性は全くありませんが、そのフレームワークのキモの考え方をパッと知りたいというニーズには合ってるんじゃないかと思います。
PHPカンファレンス2013の会場で是非ご購入下さいw
PHPカンファレンス2013は、今年もWordCamp Tokyo 2013と共催で、大田区蒲田産業会館 PiO にて、9月14日(土) に開催されます。そうです、「PHPエンジニア養成読本」の発売日の翌日です。PHPCon には今年もジュンク堂さんが出展して書籍販売します。当然、本ムックも販売リストの中に入っています。会場内には私も含め執筆陣も多数いるはずですので、サインを集めるには最適かもしれません。
fuelphp-dynamoutilを公開した
さて、FuelPHP 勉強会 東京 vol.3 : ATND で発表したスライドの中で、FuelPHP で Amazon DynamoDB を使いやすくする SDK のラッパーを OSS で公開したいと予告していたのですが、先ほど github で公開しました。
ドキュメントはまだないです。クラス 4 つしかない小さなものなので、ソース読んだだけでも使い方は分かるかもしれません。
FuelPHP勉強会東京 vol.3に参加してきた
月の最後の日をPHPで簡単に知る方法
集計系の処理のプログラムを開発していると、その月の最後の日を求める必要がちょくちょくあったりしませんか? 私はあります。さてそういう時にどうやって求めるでしょう。答えは一発↓
<?php $d = date('Y-m-t');
これだけですw
書式「t」って何ですか?
恥ずかしながら date() 関数の書式に使える「t」の存在を今日まで知りませんでした。意味としては以下になっています。
t : 指定した月の日数。
t は DateTime クラスにも使えます。
例えば今年の各月に対して最終日を求めるのには、以下のようにします。
<?php $d = new DateTime(); $year = 2012; foreach (range(1, 12) as $month) { $d->setDate($year, $month, 1); echo $d->format('Y-m-t') . "\n"; }
従来はどうやっていた?
t フォーマットを知る前の従来のセオリーはこうでした。
- その月の最初の日(1日)を求める
- それに "+1 month" する
- それにさらに "-1 day" する
コードで書くとこんな感じ。
<?php $d = new DateTime(); $d->setDate($d->format('Y'), $d->format('m'), 1); $d->modify('+1 month -1 day'); echo $d->format('Y-m-d');
PEAR::Mail_mimeで日本語ファイル名の添付ファイルを送る方法
世の中もう大体 UTF-8 で OK かなと思ったら、メールの添付ファイル名はそうでもないっぽいです。UTF-8 + URLエンコードで添付した日本語ファイル名が正しく読めないメール環境があったので、ISO-2022-JP + base64 にしたら大丈夫でした。
これをするのにPEAR::Mail_mimeだと、ややこしい引数を渡さなければいけないのでそれのメモ。以下のソースは UTF-8 で書かれていることを前提にしています。
<?php require_once 'Mail.php'; // PEAR::Mail require_once 'Mail/mime.php'; // PEAR::Mail_mime $textbody = '日本語の本文。'; $filename = '日本語添付ファイル名.txt'; $filedata = '日本語添付ファイルの中身'; // 文字コードを ISO-2022-JP に変える mb_convert_variables('ISO-2022-JP', 'UTF-8', $textbody, $filename); // 各 mime パートの構築 $mime = new Mail_mime; $mime->setParam('text_charset', 'ISO-2022-JP'); $mime->setParam('text_encoding', '8bit'); $mime->setTxtBody($textbody); $mime->addAttachment($filedata // data ,'application/octet-stream' // content-type ,$filename // attached file name ,false // isfile ,'base64' // encoding ,'attachment' // disposition ,'' // charset ,'' // language ,'' // location ,'base64' // n_encoding ,'base64' // f_encoding ,'' // description ,'ISO-2022-JP' // h_charset ); $subject = '日本語表題'; $jis = mb_convert_encoding($subject, 'ISO-2022-JP', 'UTF-8'); $encsubj = mb_encode_mimeheader($jis, 'ISO-2022-JP', 'B'); $recpts = 'example@example.jp'; $headers = array( 'From' => 'webapp@example.jp'; 'To' => $recpts, 'Subject' => $encsubj, ); $headers = $mime->headers($headers); $body = $mime->get(); // メール送信には SMTP を直接使うのが好きw $mail = Mail::factory('smtp', array('host' => 'localhost')); $mail->send($recpts, $headers, $body);
子プロセス制御ふたたび : PHP Advent Calendar jp 2011 Day 8
はい、7日目の @scriptwork さんのエントリ「DateTimeクラスの落とし穴と対策 : PHP Advent Calendar jp 2011 Day 7」から引き続いて、PHP Advent Calendar jp 2011の8日目なわけです。
今回は何を書こうかずいぶん悩んで、ちょうど昨晩開催されたPHP忘年会2011@関東でネタ募集したところ、@sizuhikoさんが口走ったphpQueryネタをパクるという案もあったのですが、やはり正攻法で手持ちのネタでいくことにしました。
子プロセスfork
このはてなダイアリーでの数少ないPHPヒットネタとして「pcntl extensionを使って一定個数の子プロセスに作業させる方法 - Blog::koyhoge」という記事がありまして、公開した2007年以来ぼちぼちとずっとアクセスを稼いでくれております。
ただこの時に書いたサンプルコードも、話を単純にするためにグローバル空間にベタ書きですし、今となってはあまり良いサンプルとは呼べません。実は今年になって、とある目的できちんとクラス化したものが作ったのでそれを紹介しようと思います。
きっかけは Amazon SQS+SDB のワーカー
その目的というのは、AMN の広告配信ログサーバを改善する際の途中成果として作成した、キューからデータを取ってきて DB に入れるワーカー処理です。11月15日に開催された「第7回 MongoDB 勉強会 in Tokyo」で発表した以下のスライドで、そこに至る背景と経緯を説明しています。
ProcessForker クラス
では早速コードを見ていきましょう。
<?php class ProcessForker { // defaults protected $_options = array( 'max_children' => 10, // max number of child processes 'process_limit' => 0, // return when this count tasks are finished 'loop_task' => false, // reuse tasks 'sleep' => 0, // microseconds 'single_execution' => null, ); protected $_idx_task = 0; public function __construct($options = null) { if (!empty($options)) { $this->_options = array_merge($this->_options, $options); } } public function fetchTask(&$tasks) { if ($this->_options['loop_task']) { $idx = $this->_idx_task; $task = $tasks[$idx]; // circulation in tasks ++$idx; if ($idx >= count($tasks)) { $idx = 0; } $this->_idx_task = $idx; } else { $task = array_shift($tasks); } return $task; } public function run($tasks) { // number of current running child processes $nchildren = 0; // number of finished task $nfinished = 0; for (;;) { if (empty($tasks)) { break; } $maxproc = $this->_options['process_limit']; if (($maxproc > 0) && ($maxproc <= $nfinished)) { break; } if ($nchildren <= $this->_options['max_children']) { $task = $this->fetchTask($tasks); $pid = pcntl_fork(); if ($pid === -1) { throw new Exception('pcntl_fork faild'); } else if ($pid) { // parent process ++$nchildren; } else { $exit_code = 0; // child process $func = $task[0]; $arg = $task[1]; if (!is_array($arg)) { $arg = array($arg); } try { call_user_func_array($func, $arg); } catch (Exception $e) { $exit_code = -1; } // care singleExecution: // child process must not unlock $se = $this->_options['single_execution']; if ($se !== null) { $se->setDoUnlock(false); } exit($exit_code); } $sleep = $this->_options['sleep']; if ($sleep > 0) { usleep($sleep); } } else { $pid = pcntl_waitpid(0, $status, 0); --$nchildren; ++$nfinished; } } } }
前回は決め打ちだった各種パラメータや実際に実行する処理を、すべて外部から渡せるようにしてあります。
使用例
この ProcessForker クラスは以下のように使用します。
<?php require_once 'ProcessForker.php'; function hoge($str) { srand(); $rand = rand(1, 5); echo "$rand: $str\n"; sleep($rand); } $opts = array( 'max_children' => 4, 'process_limit' => 50, 'loop_task' => true, ); $pf = new ProcessForker($opts); $tasks = array( array('hoge', 'a'), array('hoge', 'b'), ); $pf->run($tasks);
まずはオプションを指定してProcessForkerオブジェクトを作ります。それぞのれオプションの意味は以下になります。
max_children | 子プロセスの最大同時実行数 |
process_limit | いくつのタスクを処理したら終了するか |
loop_task | 渡されたタスクを繰り返すかどうか |
sleep | 親プロセスが処理を行うたびにスリープする時間(us) |
single_execution | singleExecutionオブジェクト(後述) |
この例では、
- 最大4つの子プロセスを起動し (max_children => 4)
- 全部で50回の処理を行い (process_limit => 50)
- タスクを繰り返しながら (loop_task => true)
実行するという意味になります。
タスク指定
子プロセスとして処理するタスクは、配列の形で渡します。
array(<callable>, <引数>)
callable はPHPの is_callable() でtrueとなるもので、以下のどれかになります。
- 関数名文字列: 'hoge'
- オブジェクトとメソッド名の配列: array($obj, 'hoge')
- クラス名とメソッド名の配列: array('Hoge', 'hoge')
- (PHP-5.3 以降は)無名関数やクロージャ
タスクへの引数は、ひとつの場合はそのままで構いませんが、複数の場合はそれも配列にして渡します。
繰り返し処理
loop_task オプションが true になっていると、渡されたタスクをすべて処理し終えてても終了せずに、またタスクリストの先頭から処理を行います。同じ処理を何度も繰り返したいときは、タスクを1つだけ用意して loop_task を true にすれば良いです。
single_execution 対応
常にプロセスが動いていて欲しい場合は cron で毎分キックするということをよくやりますが、しかし大元の親プロセスが複数立ち上がってしまうのは好ましくありません。そこで @bto さんが作られた single_execution を利用して、同時起動チェックを行いました。しかし単に ProcessForker と single_execution を組み合わせるだけではどうも上手く動いてくれません。
調べたところ ProcessForker から起動された子プロセスが、プロセス終了時に singleExecution の管理しているロックファイルを削除してしまっていることが解りました。single_execution はそこから更に子プロセスが fork されることなど想定していないので当然ですね。
ということで、single_execution.php にパッチを当ててプロセス終了時のロック解除を選択できるように変更しました。ProcessForker 作成時の single_execution オプションに singleExecution オブジェクトを渡せば、子プロセスの場合はロックを解除せずに終了するようになっています。改変した single_execution.php は以下に置いてあります。
これですでにプロセスが動作しているかどうか気にせずに、cronからスクリプトを定期的に起動するだけで、複数プロセスで動的に処理を行なってくれるようになりました。
明日の Advent Calendar は @takuya_1st さんです。