ニフティクラウド mobile backendのエバンジェリストをすることになりました

今月よりニフティクラウド mobile backendエバンジェリストを仕事でやらせていただくことになりました。

mb.cloud.nifty.com

これはMOOGIFT中津川さんがやられている DevRel というサービスのお手伝いの一環で、週に2日ほど西新宿のニフティさんにお伺いして作業しています。

devrel.jp

エバンジェリストと言っても、まだサービスそのものの中身を勉強中の身であり、エバンジェリスト活動として表舞台に立ち始めるのは早くて来月くらいからだと考えています。

とりあえずニフティクラウドmobile backend (NCMB) の Android SDKの次バージョンの開発をお手伝いしつつ、中身について深く理解していくというのが当面のミッションです。Android開発は大昔に少し触れたくらいで、本格的にやるのは初めての経験なので、新しい知識をどんどん脳の隙間にに突っ込んでいってパンクしかけながら踏みこたえるという、新しい技術を勉強するときにありがちな感覚を楽しく味わっています。

NCMB共々、今後ともよろしくお願いします。

Phalconとはなにか: Phalcon Advent Calendar 1日目

f:id:koyhoge:20141202110711p:plain

初日から締め切りぶっちしてますが、Phalcon Advent Calendar 2014 - Qiitaの1日目なわけです。

最近は某所のお仕事で Phalcon を使っていまして、結構面白いフレームワークだなぁと思いながらも、案外とクセが強いというか、使い方のノウハウがまだあまり共有されていないと感じたので、ついついアドベントカレンダーを立ち上げてしまいました。参加者が集まらなくても、最悪は少人数でぼちぼちやれば良いなぁと考えていたのですが、存外に好評なようで、開始時点で 13 エントリーも集まっています。

あらためて Phalcon とはなにか

PhalconPHP 5.3 以降を対象とするウェブアプリケーションフレームワークです。


High performance PHP framework

その最大の特徴は、コア部分がひとつの PHP 機能拡張として C 言語で書かれており、そのため PHP の文法解析/オペコードへの変換をすっ飛ばして実行されます。それが「最速 PHP フレームワーク」と自ら称している所以となっています。

他にも色々と興味深い特徴を持っていて、

などなど、コア部分を機能拡張として実装する必要からか、他のコンポーネントの利用は少なく、すべて自前で実装しているフルスタックフレームワークとなっています。

なお、Phalcon 本体は PHP 機能拡張として実装されていますが、それを使用するアプリも機能拡張として書かなければいけないわけではなく、通常通り PHPフレームワークを継承/利用してコードを書いていく形になります。

Phalcon の設計の特徴

フレームワーク設計的な視点で Phalcon をみた時に、一番の特徴は

DI を中心にしてすべてを設計している

ということだと思います。

Phalcon の DI は、Phalcon\DI というクラスを実装し、それに機能をすべて登録する一種のデータベースとして利用する形式になっています。主要なクラスはすべて Phalcon\DiInterface というインターフェースを implements しており、DI を軸に各種の機能を呼び出すことで、各クラス間の依存を可能な限り小さくする方針を取っています。このため一世代前のフレームワークにありがちな、コントローラクラスにすべてを詰め込んだいわゆる「ファットコントローラ」に陥ることを防ぐのを容易にしています。

DI を軸にやり取りする以上、基本的な機能を提供するサービスにはすべて Interface が用意されており、実装を独自に差し替えることが可能になっています。ただその Interface が現在の Phalcon の実装に引きずられる形で定義されており、用途によっては汎用性が足らず、自分の望むような拡張ができないこともままあります。

現在の開発状況

Phalcon の現在の安定版は 2014年9月にリリースされた 1.3.3 2014年10月にリリースされた 1.3.4 で、これはいくつかの問題点を修正したメンテナンスリリースです。(追記: @kenji_s さんに 1.3.4 がリリースされているとご指摘いただきました。いつもありがとうございます。)

これとは別に現在開発進行中なのが 2.0.0 で 2014年10月16日にベータ3がリリースされたところです。Phalcon 2 の大きな特徴は、Zephir を使ってコアのコードをすべて書き直したことです。 Zephir は型付きの PHP とでも呼べるようなプログラミング言語で、コードを Zend API を使った C 言語に変換しコンパイルすることで、PHP と機能拡張の形でリンクできるネイティブコードを作成することができます。Phalcon を Zephir ベースで書き直したことにより、クラスの型チェックがより高速になったと報告されています。

Phalcon は速度命だけどそれだけじゃない

Phalcon が開発された発想の基本は、PHP の巨大なフルスタックフレームワークで速度の足を引っ張る部分を、根本から解決するにはどうしたら良いかという思考の末にできあがったものだと思います。コアのすべてを機能拡張として C 言語で実装するという選択は、手軽にコードを記述できる PHP のメリットを失わせるものにも思えますが、フレームワーク設計をしっかりと行いフルスタックを作り切ることで、結果的に高性能かつ用途の問わないフレームワークができあがりました。

機能拡張として実装している以上、設計変更のコストは純粋な PHP フレームワークに比べてやはりどうしても高くなります。そのため Phalcon の設計思想は、すべてのクラス設計において統一感があるように感じられます。人によっては多少の好き嫌いはあるにせよ、統一感のある思想のもとに設計されたある意味では美しいフレームワークは、触っていても心地よさを感じるものです。

サイボウズ式に「モバイルフロンティア」の紹介文を書きました

tech@サイボウズ式」の風穴さんから、エンジニアの方々にアドベントカレンダー的に書籍の紹介記事をお願する企画があるので、小山さんもぜひ何か書いて下さい、と頼まれたが11月の末。何について書こうかといろいろ考えた挙句、やはり今年の書籍といえばこれだよなと、「モバイルフロンティア よりよいモバイルUXを生み出すためのデザインガイド」を選びました。

「モバイルフロンティア」──techな人にお勧めする「意外」な一冊(11) | サイボウズ式


いや、これ本当に良い本なので、みなさんぜひ読んで下さい。


モバイルフロンティア よりよいモバイルUXを生み出すためのデザインガイド

モバイルフロンティア よりよいモバイルUXを生み出すためのデザインガイド

アジャイルメディア・ネットワークを2013年12月一杯で退職することになりました

アジャイルメディア・ネットワーク株式会社に2010年9月に入社してから3年と少し、サーバサイド・クライアントサイド・インフラエンジニアとして働いてきましたが、今月いっぱいで退職することになりました。入社時のエントリを今見かえして、当時はCTOの福田さんしかエンジニアがいなくて、とにかく何でもやったなぁと思い出します。

アジャイルメディア・ネットワークに入社しました - Blog::koyhoge


今後については、なにせ急に決まった退職話なので、またフリーランスに戻る事以外は全くのノープランです。2014年冒頭は、仕事のお話を探しつつも、図らずして時間の余裕ができたので、忙しさを理由にしてキャッチアップできていなかった各種技術の習得に時間を割きたいと思います。


もし何かお仕事のお話があれば、メール/twitter等でお気軽にご連絡下さいませ。

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