丸善丸の内本店で開催されている「プロが教える!読んでおきたい言語書籍フェア」に行ってきた

https://c2.staticflickr.com/4/3942/33056973633_c2034a8a6b_c.jpg

4月1日から丸善丸の内本店 4Fのロビー横の一角で、「達人選書!! プロが教える!読んでおきたい言語書籍フェア」が開催されています。これは、丸善丸の内本店とコンピュータ出版販売研究機構(CPU)の共同企画で、各言語コミュニティからおすすめ本を5冊選出してもらって、それに言語の紹介を加えるパネルを用意することで、販促につなげようという内容です。

t-wada こと和田卓人さんから、PHPで誰かやってもらえませんかというお誘いがあって、面白そうな企画だったので自分で引き受けることにしました。

コミュニティが解説を書いているプログラミング言語は以下の9つ。

あとCPUが書籍を選出している言語が4つあります。

展示場所に行ってみた

今日時間があったので、東京駅近くの丸善丸の内本店まで行ってみました。

https://c1.staticflickr.com/3/2876/33870375275_ac25ff94cd_b.jpg 展示場所は丸の内本店4Fの技術書棚近くのロビー横。

https://c1.staticflickr.com/3/2830/33485729600_5b06ec674a_z.jpg こんな形でパネルと書籍が展示されています。

https://c2.staticflickr.com/4/3940/33485729390_241d8be524_z.jpg

Ruby 高橋さんのパネル

https://c1.staticflickr.com/3/2905/33026486864_831f92db49_z.jpg

小山のパネル

https://c1.staticflickr.com/3/2840/33026486744_11696c7ecb_z.jpg

Python 鈴木さんのパネル

https://c1.staticflickr.com/3/2852/33870373335_aabe9f4584_z.jpg

Perl/Go 牧さんのパネル

https://c1.staticflickr.com/3/2879/33829273186_4735f4e41c_z.jpg

Scala 水島さんのパネル

https://c2.staticflickr.com/4/3727/33870371765_c386204668_z.jpg

HTML 吉川さんのパネル

https://c1.staticflickr.com/3/2921/33870372895_b0b70e7068_z.jpg

CPU の推薦書籍たち

https://c2.staticflickr.com/4/3856/33829273706_bacf64f7f0_z.jpg

言語の解説文が載っている小冊子はご自由にお持ち下さいでした。

私の紹介文

このフェア、Web的に見える部分にはコンテンツが存在しないようです。このままだと数年も経てばきっと検索に引っかからなくなる気がするので、せめて自分が書いた紹介文をここに記載しておくことにします。

あと本の紹介の際は

  • 1冊は初心者用のものを入れる
  • 1冊は言語に依らないお勧め本を入れる

という縛りがありました。

PHPの紹介

PHPの発端は、1995年にRasmus Rerdorf氏がC言語で書いたCGI用のツール集です。当時はウェブがまだ生まれたばかりで、ちょっとしたことをウェブサーバにやらせるための便利なツールとして徐々に広まっていきました。

ウェブに特化したプログラミング言語としてのPHPの位置づけが明確になったのは、1998年のPHP 3からです。他の言語から有用な構文をどんどん取り込んでいく一種の無節操さは、プログラミング言語に厳格さを求める人達からは嫌われましたが、「何となく書いてもそれなりに動く」というPHPのとっつきやすさがウェブプログラミングの敷居を大きく下げたのも確かです。

近年ではPHPにもモジュール指向の波が到来し、PHPライブラリのパッケージマネージャComposerを用いて多くのライブラリを利用し、また自分にあったフレームワークを使いつつ、効率的に開発を進めていくのがトレンドになっています。未だに乱立しているフレームワークも、徐々にモジュールベースに書き直され始めており、Composerを軸とした共通基盤ができつつあります。

2015年にリリースされたPHP 7は、内部構造の抜本的な改善によって大幅に性能を向上させています。今後は厳密な型チェックを可能にする方向で構文が強化される流れです。

お勧め本

確かな力が身につくPHP「超」入門

確かな力が身につくPHP「超」入門

確かな力が身につくPHP「超」入門

ウェブの動作の仕組みから、PHPの文法、データベースの連携までわかりやすく解説されています。

いまどきのアルゴリズムを使いこなすPHPプログラミング[開発テクニック]

PHPアルゴリズムを学ぶという大変珍しい書籍です。

PHPはどのように動くのか

PHPはどのように動くのか ~PHPコアから読み解く仕組みと定石

PHPはどのように動くのか ~PHPコアから読み解く仕組みと定石

PHPが内部でどのように動作しているのか、その内部構造を解説している貴重な本です。

Laravel リファレンス

Laravel リファレンス[Ver.5.1 LTS 対応] Web職人好みの新世代PHPフレームワーク

Laravel リファレンス[Ver.5.1 LTS 対応] Web職人好みの新世代PHPフレームワーク

近年大躍進しているPHPフレームワークの解説本です。

スティーブズ (1)

スティーブズ 1 (ビッグコミックス)

スティーブズ 1 (ビッグコミックス)

コンピューターが個人の手に届き始めた頃の熱いやつらの物語。最初から決まってることなんか無い。

自己紹介

フリーランスエンジニア。Webシステムの開発を行うかたわら、日本PHPユーザ会の運営に関わる。毎年開催される「PHPカンファレンス」には2003年よりスタッフとして関わる。共著書に「超・極める! PHP」「PHPエンジニア養成読本」「Laravelエンジニア養成読本」など。好きな作家は藤井太洋、好きな声優は杉田智和

おまけ

その他枠は少し悩んで、一般的なエンジニア向け名著と言われるものはきっと誰かが出してくるだろうと思ったので、ちょっと遊んで「スティーブズ」を選びました。蓋を開けてみたら、こちらが想定していた名著はあまり選ばれてなかったわけですが、まあいいやということでそのままスティーブズで行くことにしましたw

技術書に限らず出版業界は苦しい状況がずっと続いているので、このように複数の出版社と書店を横断した取り組みが行われるのは素晴らしいことだと思います。4月は新入社員としてエンジニアの門をくぐる人も多数いると思われるので、こういうことがきっかけでエンジニアリングの面白さに気づいてくれれば言うことはないです。

フェアはゴールデンウィークくらいまでは開催中だそうです。お近くに寄った際は覗いてみて頂けるとありがたいです。

日本のPHPコミュニティのあゆみ

ソフトウェアデザイン2016年9月号が発売されました。

この号の第2特集でPHPが取り上げられていて、私もコミュニティについて2Pほど執筆しています。

ソフトウェアデザイン編集長の池本(@XR230)さんから企画案をいただきまして、PHPのslackチャンネルで執筆者を募集して、実現された記事となっています。

私が執筆した第4章のコミュニティについての文章は、各グループの紹介にほぼ終始していて、あまり読み応えのある内容とはいえません。自分が締め切りすぎまでもたもたしたせいでページ数が減った結果、前半に用意していた導入部を全部ボツにするという判断で、現在のものになったのでした。

ということで、せっかく執筆した前半導入部なので、ここで公開することにします。

日本のPHPコミュニティのあゆみ

ソフトウェアデザイン2016年9月号 PHP特集ボツ原稿

コミュニティの発端

PHPに関するコミュニティらしきものが日本に生まれたのは、1997年のPHP-jpメーリングリストに遡ることができるでしょう。当時のPHPのバージョンは PHP2.0、「漢字patch」と呼ばれるものを各人が当てて日本語を使用していました。そのままでは日本語を扱えないものに対して、有志の人々が協力して日本語を扱えるように作業をし、そこでコミュニティが形作られていくのは90年代の日本のソフトウェアシーンとしてはごく当たり前の光景でした。

その後、PHP3.0.7に対して日本語を扱えるようにしたものを、日本のコミュニティとして独自に配布を始め、日本語も問題なく使用できるウェブプログラミング言語として評判になっていきます。そこからさらにコミュニティは発展して、2000年に「日本PHPユーザー会」の発足へとつながっていきます。この時はまだ自分はユーザ会には関わっていなかったですが、当時のニュース記事などを読むときちんとメディアを呼んで記者発表会が行われたようです。日本PHPユーザ会は、2000年7月2日に日本で最初のPHPカンファレンスを開催し、その後毎年欠かすことなく開催を続けています。

メーリングリストからブログへ

2000年代前半は、コミュニティのやりとりの中心はもっぱらメーリングリスト(ML)でした。普段MLでやり取りしている人達と、年に数回行われるPHPカンファレンスやLinuxConferenceなどのリアルイベントで顔を合わせて話すことで関係が深くなっていったということは、この頃なら誰しも経験していると思います。MLの流量も今とは比較にならないほどで、多いときは1日に100通以上が流れていたと思います。

そこから大きく変わってきたのは2004年頃にブログが流行ってきてからです。簡単な方法で広く世界に情報発信できるブログというツールは、その界隈での有名人を多く生み出すことになりました。PHPコミュニティでもその傾向はあり、PHPに関する情報を発信し続けるブログとそのコメント欄というコミュニケーション手段が加わることで、人のつながりが多層になったように思います。

情報をブログで発信する人が増えてくると、それをまとめて読みたい要望も生まれてきて、特定ジャンルの各ブログのRSSを一箇所に集約して配信する「プラネット」と呼ばれるサービスが流行したこともありました。

そしてSNS

今で言うSNSが最初に使われ始めたのは2004年頃でした。mixiGreeが日本発のSNSとして相次いでサービスを開始したのも2004年ですし、海外サービス勢もその後次々に日本に上陸してきました。しかしSNSが流行し始めた当初は、ITエンジニアコミュニティではさほど影響を与えていなかったように思います。

この流れが変化したのは2007年頃からのTwitterの流行だと思われます。Twitterは最初マイクロブログと呼ばれていたように基本的にはオープンな場で、人々の情報発信の敷居を大幅に下げました。ブログほど堅苦しくなく、日々の疑問やコードを書いていて感じたちょっとしたことなどがどんどん投稿されていき、そこから様々なコミュニケーションが生まれました。Twitterが広く一般に使われ始めるのは2010年頃からですが、それ以前よりIT関係者はこの新しいツールを上手く使いこなしていたように思います。

追って日本に上陸したFacebookも、ITエンジニアコミュニティにじわじわ浸透し、現在では一部のPHPコミュニティの主要コミュニケーション拠点にまでなっています。

あいまいになるコミュニティ

ブログやSNSがコミュニケーションの中心になることによって、ITエンジニアコミュニティに大きな変化が表れました。それは個人のブランド力の増大と、それに呼応した組織への帰属意識の希薄化です。2000年前後のエンジニアコミュニティは、組織や団体という看板を必要としました。そのメンバーの一員になるという帰属意識が、人々を結びつけるのに大きな影響力を持っていたのです。しかしブログやSNSでの活動は、それを書いているのが「どこのだれ」ではなく、ただ単に「だれ」ということのみが注目される傾向が強く、組織としてのコミュニティの役割は一面ではすでに失われつつあります。

個人が特定の組織に帰属することなく、その人それぞれの興味ある対象ごとに広くゆるく繋がっている状態、それが現在のITコミュニティの実態だと言えるでしょう。そういう意味では、GitHub上の各プロジェクトや、各Q&Aサイトも今風のコミュニティの一形態と見ることもできます。

PHP BLT #4 で PHP の興味深い挙動を知った

photo by Dana McMahan

昨日は PHP BLT #4 でメルカリに行ってきた。

phpblt.connpass.com

そこで @uzulla さんが雑談的に発表された内容に面白いものがありまして。

たしかこんな感じのやつ。

$values = [
  'a' => null,
  'b' => 'abc',
];
foreach ($values as $key => &$value) {
    $value[$key] = '123';
    // 本当は $key[$value] = '123' の意図
}

ここで間違えて $key と $value を入れ替えて記述したのはいいけど、$value が null の時にはエラーにならなくて、abc の時に初めて

PHP Warning: Illegal string offset 'b' in /some/path/hoge.php on line X

なんてのが出るという話。

値が null の変数は未定義と等しく扱われるので、そこで変数の生成と代入が同時に行われる。null は未定義と等しいのは以下の例をみても分かる。

$a = null;
var_dump(isset($a)); // 結果は bool(false)

で、先の uzulla さんのコードを、エラーを出さないようにキーが 'b' の要素を抜いて実行すると、当然エラーにはならず $values は以下のようになる。

$values = [
    'a' => null,
];
foreach ($values as $key => &$value) {
    $value[$key] = '123';
}
var_dump($values);

実行結果:

array(1) {
  ["a"]=>
  &array(1) {
    ["a"]=>
    string(3) "123"
  }
}

あれ? なんで配列が入れ子になるんだろうと不思議に思ったので、色々試してみたところ挙動が理解できた。

この場合 foreach でループを回す際の $value に & がついて参照になっていることがキモで、ループ内で新たな配列が作られた後、それが $values キー 'a' の値として上書きされているということになる。

分かりやすく代替コードで示すとこう。

foreach ($values as $key => &$value) {
    $tmp[$key] = '123';
    $value = $tmp;
}

参照をやめれば、当然ながら要素の上書きは起こらない。

$values = [
    'a' => null,
];
foreach ($values as $key => $value) {
    $value[$key] = '123';
}
var_dump($values);

実行結果:

array(1) {
  ["a"]=>
  NULL
}

FuelPHPでMongoDBをちょびっと便利に使う

MongoDB Advent Calendar 2013の14日目です。まぁ途中で一度途切れているので気楽に行きましょうw


さて、このエントリはここ連続で続いている FuelPHP ネタでもあります。

MongoDBでSQL的なシーケンスをどうするか

FuelPHPでMongoDBを使うには、Coreに含まれている Mongo_Dbクラスを使うのが普通だと思います。基本的なメソッドはひと通り用意されていて、通常使う文にはまあ困らないでしょう。ただしちょっと突っ込んだことをやろうとすると、そのままでは使えなくて何らかの拡張を自分ですることになるのもよくあることです。


MongoDBをアプリケーションデータの格納に使うとして、SQL的なシーケンス(SEQUENCE)が欲しいことがあります。MySQLでいうところのSERIAL属性と同様で、一意な値を自動採番していくれる仕組みです。むろんMongoDBでもちょっとした仕掛けで実現可能で、そのための手法はちょっとググれば簡単に見つかります。ただし FuelPHP の Mongo_Db クラスでは、PHP の Mongo オブジェクトがプライベート変数になっていて直接アクセスできるメソッドもないので、直接アクセスするには Mongo_Db クラスを継承したクラスを用意する必要があります。そうやって作ったのが、以下の Util_MongoPlus クラスです。

<?php

class Util_MongoPlus extends \Fuel\Core\Mongo_Db
{
    protected $seq_collection = 'sequences';

    public function getRawMongo()
    {
        return $this->db;
    }

    public function seqNext($name)
    {
        $query = array(
            '_id' => $name,
            );
        $update = array(
            '$inc' => array('seq' => 1),
            );
        $options = array(
            'new' => true,
            'upsert' => true,
            );

        $result = $this->db->{$this->seq_collection}->findAndModify(
            $query,
            $update,
            null,
            $options
            );
        return $result['seq'];
    }
} 

直接 Mongo オブジェクトにアクセスする getRawMongo メソッドと、シーケンスを実現する seqNext メソッドを持っています。シーケンスは sequences というコレクションを作り、そこにシーケンス名がキーのレコードを作成して、アトミックにインクリメントを行うことで実現しています。


※ とここまで書いていて、Mongo_Db::get_collection メソッドを用いれば任意のコレクションにアクセスできるので、直接 Mongo オブジェクトを使う必要がないことに気がつきました。まあいいやw

MongoDBをベースにしたモデルのベースクラスを作る

で、ここまでできたらもういっそ Model のベースクラスまで作ってしまえということで、できたのが以下です。

<?php
use Fuel\Core\Date;

class Model_Mongobase
{
    static protected $table_name = null;
    static protected $uniq_id = null;

    static protected $timestamp = null;

    static public function getMongo()
    {
        return \Util_MongoPlus::instance();
    }

    static public function getTable()
    {
        return static::$table_name;
    }

    static public function getSeqName()
    {
        // dummy
        return null;
    }

    static public function newId()
    {
        $mongo = static::getMongo();
        $seq = static::getSeqName();
        if (empty($seq)) {
            throw new Exception('empty sequence');
        }
        return $mongo->seqNext($seq);
    }

    // base methods
    static public function get($id)
    {
        if (static::$uniq_id === null) {
            throw new Exception('Unique key is not found');
        }

        $mongo = static::getMongo(); 
        $conds = array(
            static::$uniq_id => (int)$id,
            );
        $result = $mongo->get_where(static::getTable(), $conds);
        if (count($result) === 0) {
            throw new Exception('Item not found');
        }
        return $result[0];
    }

    static public function all($options = null)
    {
        $conds = array_val($options, 'conditions');
        $order_by = array_val($options, 'order_by');
        $use_cursor = array_val($options, 'use_cursor', false);

        $mongo = static::getMongo();
        if (!empty($conds)) {
            $mongo->where($conds);
        }
        if (!empty($order_by)) {
            $mongo->order_by($order_by);
        }
        if ($use_cursor) {
            $result = $mongo->get_cursor(static::getTable());
        } else {
            $result = $mongo->get(static::getTable());
        }
        return $result;
    }

    static public function save($data)
    {
        if (static::$uniq_id === null) {
            $result = static::saveWithNonuniq($data);
        } else {
            $result = static::saveWithUniq($data);
        }
        return $result;
    }
    static public function saveWithNonuniq($data)
    {
        // call hook
        static::beforeInsert($data);

        $mongo = static::getMongo();
        return $mongo->insert(static::getTable(), $data);
    }

    static public function saveWithUniq($data)
    {
        $mongo = static::getMongo();

        $id_name = static::$uniq_id;

        $id = array_val($data, $id_name);
        if (empty($id)) {
            $id = static::newId();
            $data[$id_name] = $id;

            static::beforeInsert($data);
            $result = $mongo->insert(static::getTable(), $data);
        } else {
            $conds = array(
                $id_name => (int)$id,
                );

            static::beforeUpdate($data);
            $result = $mongo->where($conds)->update(static::getTable(), $data);
        }
        return $result;
    }

    static public function deleteWithUniq($id)
    {
        $mongo = static::getMongo();

        if (static::$uniq_id === null) {
            $id_key = '_id';
        } else {
            $id_key = static::$uniq_id;
        }
        $conds = array(
            $id_key => (int)$id,
            );
        return $mongo->where($conds)->delete(static::getTable());
    }

    static public function deleteAll($conds)
    {
        $mongo = static::getMongo();

        return $mongo->where($conds)->delete_all(static::getTable());
    }

    static protected function beforeInsert(&$data)
    {
        $hook = array_val(static::$timestamp, 'before_insert');
        if (empty($hook)) {
            return;
        }
        $key = array_val($hook, 'key');
        static::handleTimestamp($data, $key);
    }

    static protected function beforeUpdate(&$data)
    {
        $hook = array_val(static::$timestamp, 'before_update');
        if (empty($hook)) {
            return;
        }
        $key = array_val($hook, 'key');
        static::handleTimestamp($data, $key);
    }

    static public function handleTimestamp(&$data, $key)
    {
        foreach ($key as $k) {
            $data[$k] = Date::forge()->format('mysql');
        }
    }
}

あー、いつものarray_val()を使ってますね。ここで紹介している自作の便利関数です。


使用するには、例えば以下の様な Model_Mongobase を継承したクラスを作って

<?php

class Model_Item extends Model_Mongobase
{
    static protected $table_name = 'item';
    static protected $sequence = 'item';
    static protected $uniq_id = 'id';

    static protected $timestamp = array(
        'before_insert' => array(
            'key' => array('created_at', 'updated_at'),
            ),
        'before_update' => array(
            'key' => array('updated_at'),
            ),
        );

    static public function getSeqName()
    {
        return static::$sequence;
    }
}

あとは任意のデータを格納するだけです。

<?php
  :
    Model_Item::save(array('fuga' => 'hoge'));

継承したクラスで

static protected $uniq_id = 'id';

のように $uniq_id プロパティが定義されていれば、それに対してシーケンスを適用してくれます。


いつもなら Package にまとめて github で公開したりするんですが、これに関してはちょっとどういうまとめ方が良いのか悩んでいるところもあって、とりあえず生ソースをブログで公開することにしました。


明日のアドカレは@さんです。

FuelPHPのViewの自動エスケープについて

前回のエントリ「JavaScript側にPHP変数を簡単にまるごと渡す方法 #FuelPHPAdvent2013 - Blog::koyhoge」について、PHPjson_encode()関数は標準ではエスケープ処理は行わないのでXSS脆弱性があるのではないか、という指摘をいただきました。

json_encode()のエスケープオプション

確かにPHPのマニュアルには、各種文字にエスケープ対応するオプションが存在します。

PHP: json_encode - Manual

この場合で言えば

    return sprintf($fmt, $name, json_encode($val)); 

を以下のようにエスケープオプションを追加するべきということです。

    return sprintf($fmt, $name, json_encode($val, JSON_HEX_TAG |JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP)); 

ただこのコードを書いた時に自動的にエスケープ処理がかかることを確認していたので、どこでそれが行われるかは深く調べずに、json_encodeのオプションを省いたという経緯がありました。

自動エスケープは\Fuel\Core\Viewの機能だった

その後fuelphp.jpグループで@さんに指摘されて、Parserパッケージの標準設定で 'auto_encode’ が true になっているおかげでテンプレートに渡される変数が自動でエスケープされていた事がわかりました。

fuel/packages/parser/config/parser.php

の以下の部分ですね。

<?php
:
        'View_Twig' => array(
                'auto_encode' => true,
:

この auto_encode 設定は、\Fuel\Core\View のコンストラクタに $auto_filter として渡され、結果的に\Fuel\Core\Security::clean() が呼び出されます。つまりTwig Extensionに渡される際にはすでにエスケープ済になっていたわけですね。


PHP 変数を JSON にして JavaScript に渡す仕組みは、別に FuelPHP でなくても使用できますので、その場合は XSS に注意して json_encode にオプションを随時追加して下さい。

JSON の埋め込み方の問題

他にもfuelphp.jpグループでは@さんより、HTML 要素にテキストとして JSON を書き出すよりは、要素の data-option 属性として埋め込んだ方が良いのではないかとの指摘を受けました。

  <div class="hidden">{"fuga":"hoge"}</div>

ではなく

  <div class="hidden" data-option='{"fuga":"hoge"}'></div>

とせよということですね。ふむー、これはちょっと試してみたいと思います。

Twigのクラスが古かった

元記事とその前の記事で用いた以下のクラスはすでに古く、2.0でなくなる予定だと@さんに指摘いただきました。

  • Twig_Filter_Function
  • Twig_Function_Method

これは気が付いてなかったので、Twig_SimpleFunction, Twig_SimpleFilter を使うように元記事を修正しました。

その他の反応への返事

はてブより。

id:teppeis $nameもjson_encode()もエスケープが足りないです。危険。

http://b.hatena.ne.jp/teppeis/20131207#bookmark-172246146

json_encodeについては上記に書いたとおり。$nameはテンプレートに直接記述されるので、そこに外部からの変数が渡される事態は、コード全体を見直したほうが良いレベルだと思うのですがどうでしょう?

id:thujikun JSON形式のコードをJSの変数に直接代入する方が楽な気が。。。ひとつグローバル変数使うことにはなるけども。

http://b.hatena.ne.jp/thujikun/20131208#bookmark-172246146

JavaScriptにテンプレートエンジンを通して変数展開を埋め込む方が、自分的にはあり得ないです。HTMLに埋め込み JS を直接記述することは現在は全くやっていません。

id:fakechan PHPのレガシーっぷりに驚きを隠せない。というか、こういう場合はREST APIを作って「js側から」Ajaxでアクセスすればいいのでは。Ajaxのロードが終わるまでは「ロード中...」とかかぶせて。

http://b.hatena.ne.jp/fakechan/20131208#bookmark-172246146

いやこれとPHPのレガシーは関係ないでしょ。PHPディスりたい病にかかっているようですね。何でもRESTでAjaxすれば良いやというのは、JS 側の処理を無駄に複雑にするだけではありませんか?

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 でした。明日のアドベントカレンダーは@さんの[W] FuelPHP開発でローカルとWebで構造が変わっても対応できる小技 | Work Tool Smith [ワークツールスミス]です。



12/8 追記:
HTMLに埋め込む際のエスケープ処理に関しては、状況によってはjson_encode()にエスケープ用のオプションが必要です。またTwig_Function_Method はすでに古いインターフェースだと @ さんからご指摘を受けたので、現在推奨されている 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ページずつ書きました

さてそういうわけで、他の方々に心配をかけながらも、無事に原稿は書き上がりました。SymfonyFuelPHP の紹介記事をそれぞれ 3 ページずつ執筆しました。


MVC がどうなっているとか、基本的なことはどのフレームワークも大して変わらないので、自分なりにそれぞれのフレームワークを使ってみて感じた、そのフレームワークならではの特徴を抽出して執筆したつもりです。その意味では、自分のパートは入門者には実用性は全くありませんが、そのフレームワークのキモの考え方をパッと知りたいというニーズには合ってるんじゃないかと思います。

PHPカンファレンス2013の会場で是非ご購入下さいw

PHPカンファレンス2013は、今年もWordCamp Tokyo 2013と共催で、大田区蒲田産業会館 PiO にて、9月14日(土) に開催されます。そうです、「PHPエンジニア養成読本」の発売日の翌日です。PHPCon には今年もジュンク堂さんが出展して書籍販売します。当然、本ムックも販売リストの中に入っています。会場内には私も含め執筆陣も多数いるはずですので、サインを集めるには最適かもしれません。