モンモンブログ

技術的な話など

ローカルでの git push 時にサーバ上で自動で git pull

ローカルで git push した時にサーバ上で自動で git pull してくれるスクリプト gitpull_server.py を公開しました。

とりあえず Github, Backlog に対応しています。

使い方、注意点などは README に書きましたのでそちらをご覧下さい。



みんな大好き Python 製だよ。

うふふ、Python

_(:3」∠)_

_(    )_ コロリン

_(:3」∠)_ コロリン

Symfony2 のログを見やすく設定する

symfony2 のログってデフォルトだと余計なものが多くて見難いです。

例えばこんなです。

$ tail -f app/logs/dev.log
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\Security\Http\Firewall::onKernelRequest". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Bundle\AsseticBundle\EventListener\RequestListener::onKernelRequest". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.request" to listener "Hoe\HoeBundle\Hoe\EventListener\tvSeoRequestListener::onKernelRequest". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.controller" to listener "Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector::onKernelController". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.controller" to listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener::onKernelController". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.controller" to listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener::onKernelController". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.controller" to listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener::onKernelController". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.controller" to listener "Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::onKernelController". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.view" to listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener::onKernelView". [] []
[2013-08-08 21:53:41] event.DEBUG: Listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener::onKernelView" stopped propagation of the event "kernel.view". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\Security\Http\Firewall\ContextListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\Security\Http\RememberMe\ResponseListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Bridge\Monolog\Handler\FirePHPHandler::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\CacheListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Hoe\HoeBundle\Hoe\EventListener\tvAnalyticsTrackerListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\ResponseListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\StreamedResponseListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener::onKernelView" stopped propagation of the event "kernel.view". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\Security\Http\Firewall\ContextListener::onKernelResponse". [] []
[2013-08-08 21:53:41] security.DEBUG: Write SecurityContext in the session [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\Security\Http\RememberMe\ResponseListener::onKernelResponse". [] []
[2013-08-08 21:53:41] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Bridge\Monolog\Handler\FirePHPHandler::onKernelResponse". [] []
...
  • ほぼ不要な "Notified event" 行だらけで、重要な行が埋もれてしまう
  • ログの末尾に必ずついてる空カッコ "[] []" がウザい

です。

見やすくなるよう設定をいじってみました。

"Notified event" をログしない

symfony2 のロギングには標準で Monolog を使ってます。 で、Monolog によるロギングはいくつかのチャネルからなり、そのうち event チャネルが "Notified event" ログを出力してるようです。

設定ファイルを修正して、この "event" チャネルをログしないように(event チャネル以外をログするように)設定します。

$ vim app/config/config_dev.yml
monolog:
    handlers:
        main:
            type:  stream
            path:  "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug
            # この行を追加。ビックリマーク "!" で「event 以外」って意味になる
            channels: !event

末尾の "[] []" を出力しない

ログのフォーマットは Monolog のフォーマッタ、LineFormatter が担当しています。 LineFormatter はデフォルトで↓のようなフォーマットで出力します(https://github.com/Seldaek/monolog/blob/master/src/Monolog/Formatter/LineFormatter.php#L24)。

"[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"

この末尾の %context%, %extra% に相当するデータが空な場合に、ログに "[] []"(空配列の JSON 表現)と吐き出されちゃうようです。じゃあ設定変更して余分(?)な %context% %extra% を省いちゃえばいーじゃん?

$ vim app/config/config_dev.yml
monolog:
    handlers:
        main:
            type:  stream
            path:  "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug
            channels: !event
            # main ハンドラで使うフォーマッタを指定
            formatter: my_formatter

# フォーマッタ設定を追加
services:
    my_formatter:
        class: Monolog\Formatter\LineFormatter
        arguments:
            # フォーマットを定義
            - "[%%datetime%%] %%channel%%.%%level_name%%: %%message%%\n"

ただ、僕は %context% や %extra% が何なのかよく分からんままやってるので注意です。 もし %context% %extra% をログに全く出さなくしちゃうのに抵抗があるなら、こいつらの位置を末尾から %message% の前あたりに移動すれば、空カッコ "[] []" も目立たなくなっていいのでは。例えばこんな感じ?

            # フォーマットを定義
            - "[%%datetime%%] %%channel%%.%%level_name%%: %%context%% %%extra%%: %%message%%\n"

どうでしょうか。

参考:

php5.3 以前で mixin 的なものを実装

php5.4 以降であれば Trait を使って mixin 的なことは実現できますが(※)、 php5.3 以前で mixin したい場合はどうするか。

いろいろ考えましたが、一番シンプルで使いやすいかなーという方法を紹介します。

※mixin と trait の違いとかよう分かってへんけどこまけぇこたぁいいんだよ!(AA略

ここが素敵

  • インスタンスメソッドも静的メソッドも mixin 出来ます(ただし静的メソッドの mixin は php5.3.0 以降でのみ可能)。
  • もちろん mixin したメソッドに引数を渡すことも出来ます。
  • mixin したインスタンスメソッドの中から、対象クラスの public なメンバ変数にアクセス出来ます。
  • 複数の mixin クラスを同時に mixin 出来ます。

まずはコード

サンプルコードです。

Trait が導入される php5.4 より前の php で mixin 的なことを実装してみた。

これを実行するとこう。

$ php mixin.php
// $foo->mixin_instance_method
これは MixinClass のインスタンスメソッドです yahoooooo! 2013-10-11
Foo の public メンバ変数にアクセスすることも出来ます。 999

// $foo->mixin2_instance_method
これは MixinClass2 のインスタンスメソッドです

// $foo->instance_method
これは Foo 自身のインスタンスメソッドです

// $foo->parent_instance_method
これは親クラス Bar のインスタンスメソッドです

// Foo::mixin_static_method
これは MixinClass の静的メソッドです yahoooooo! 2013-10-11

// Foo::mixin2_static_method
これは MixinClass2 の静的メソッドです

// Foo::static_method
これは Foo 自身の静的メソッドです

// Foo::parent_static_method
これは親クラス Bar の静的メソッドです

     *      *
  *     +
     n ∧_∧ n  IINE!
 + (ヨ(* ´∀`)E)
      Y     Y    *

いいねー。

解説

では解説です。インスタンスメソッド版 mixin と静的メソッド版 mixin の2つに分けて説明します。

1. インスタンスメソッド版 mixin

さっきのコードから、インスタンスメソッドの mixin に関わるとこだけ切り出すとこう。

インスタンスメソッドの mixin

1-1. mixin クラスを作成

まずは mixin クラスを作りましょう。

ほぼ、普通にクラス定義するだけですけど一点注意。 インスタンスメソッドの第1引数は $that とか $self とかの名前にして下さい。 ここに mixin 対象クラスのインスタンスが渡されてきます。これを通じて mixin 対象クラスの public なメンバ変数にアクセス出来るようになります。

// このように呼ぶメソッドであれば、
$foo->message("hello.");

// 定義はこうではなく、
public function message($message) { /* (´・ω・`) */ }

// こうです。
public function message($that, $message) { /* (・∀・)イイネ!! */ }

// 引数なしで呼ぶメソッドであっても、
$foo->bar();

// 定義はこうではなく、
public function bar() { /* (´・ω・`) */ }

// こうです。
public function bar($that) { /* (・∀・)イイネ!! */ }

1-2. mixin 対象クラスを修正

mixin 対象クラスをいじって __construct と __call をオーバーライドします。

__construct で MixinClass のインスタンスを生成してメンバ変数に持っておきます。

    public function __construct()
    {
        $this->mixin1 = new MixinClass();
    }

__call では、渡されてきたメソッド名 $method_name が MixinClass のインスタンスメソッドとして実行可能か調べて、そうあれば実行。 実行する際、第1引数に $this を渡します。これがさっき mixin クラス側でメソッドの第1引数とした $that (or $self) です。

    public function __call($method_name, $args)
    {
        // Foo 自身を表す $this を引数リストの先頭に追加します
        array_unshift($args, $this);
        // $this->mixin1->$method_name() が実行可能か調べる
        if (is_callable(array(&$this->mixin1, $method_name))) {
            // MixinClass#$method_name を実行
            return call_user_func_array(array(&$this->mixin1, $method_name), $args);
        } else {
            // そんなインスタンスメソッドはどこにも見つかりませんでした。。。
            throw new BadMethodCallException();
        }
    }

2. 静的メソッド版 mixin

静的メソッドの mixin に関わるとこだけ切り出すとこう。

静的メソッドの mixin

2-1. mixin クラスを作成

静的メソッドを持つクラスをふつーに定義するだけです。

2-2. mixin 対象クラスを修正

mixin 対象クラスでは __callStatic をオーバーライドします。MixinClass::$method_name が実行可能かどうか調べて、あれば実行。

    public static function __callStatic($method_name, $args)
    {
        if (is_callable("MixinClass::$method_name")) {
            // MixinClass::$method_name を実行
            return call_user_func_array("MixinClass::$method_name", $args);
        } else {
            // そんな静的メソッドはどこにも見つかりませんでした。。。
            throw new BadMethodCallException();
        }
    }

おしまい。

お解り頂けたかしら_(:3」∠)_

(jQuery) select 要素の選択結果で別の select 要素の選択肢を絞り込む jQuery プラグイン "Select Narrowing Plugin"

ある select の選択結果で、別の select の選択肢を絞り込む jQuery プラグインを作りました。
こういうの、「Hierselect」(hierarchy + select, 階層select)っていうらしいです。

ググると同じ目的のライブラリはいくつか見つかるけども、

サーバサイドの言語に依存してたり、階層の数が限定されてたり、単純な階層関係しか定義できなかったり、ちょっと不便。
なので新しく作りました。

このプラグインのウリ↓

  • サーバサイドの実装なしに、JS と HTML だけで動作します。サーバサイドが PHP だろうが Ruby だろうが Java だろうが関係なく動きます。素敵。
  • 単純な「親→子」だけでなく、「親→子→孫→ひ孫…」と、何階層でも連鎖させられます。国→エリア→都市、とか。
  • 「親→子1&子2」のように、1つの親 select に複数の子 select を持たせられます。
  • 「親1&親2→子」のように、複数の親 select の選択結果により子 select の選択肢を絞り込んだり出来ます。例えば select1 で色を、select2 で形をそれぞれ選択して、select3 の選択肢を絞り込んだりとか。
  • これらを組み合わせて、いくらでも複雑な階層関係を表現出来ます。出来るはず。あまり複雑な階層関係で実験したことないですけど。。。

プラグインの使い方ですが、

HTML をこんな風に書いて、

<!-- 親select:食品カテゴリ -->
<select id="category">
    <option value="">-- Food Category --</option>
    <option value="meat">Meat</option>
    <option value="vegetable">Vegetable</option>
    <option value="fruit">Fruit</option>
</select>
<!-- 子select:食品 -->
<select id="food">
    <option value="">-- Food --</option>
    <option value="beef" data-category="meat">Beef</option>
    <option value="pork" data-category="meat">Pork</option>
    <option value="chicken" data-category="meat">Chicken</option>
    <option value="lettuce" data-category="vegetable">Lettuce</option>
    <option value="carrot" data-category="vegetable">Carrot</option>
    <option value="tomato" data-category="vegetable">Tomato</option>
    <option value="apple" data-category="fruit">Apple</option>
    <option value="banana" data-category="fruit">Banana</option>
    <option value="melon" data-category="fruit">Melon</option>
</select>

jQuery ライブラリをこんな風に呼ぶだけです。

<script type="text/javascript">
$(function () {
    $("#category").narrows("#food");    // #category は #food を絞り込む
});
</script>

親 select でどの option を選択したら子 select でどの option が選択肢になるのか?ていうのは子 select のデータ属性で定義します。上の例でいうと、

    <option value="beef" data-category="meat">Beef</option>

データ属性 data-category="meat" により「id="category" な親 select で value="meat" が選択された場合にこの option を表示」って表現しています。

詳細は github の README に書いたので見てね。

jQuery Select Narrowing Plugin

あとサンプルはこちら。

sample.html

あっ。そういえば Windows で動作確認してないです_(:3」∠)_ ちょっとまっててよん。 Windows でも動きました。IE6、結構古めのFirefox(笑。バージョン忘れました)で動作確認。

(追記)いろんな OS のいろんなブラウザにスクリーンショット取って貰えるサービス Browsershotssample.htmlスクリーンショットをまとめて取って、Jasmine のテストが通ってるか(画面下の緑のバーが出ているか)ざっくり眺めて確認しましたが、大体のブラウザで動作してるみたいでした。(画面下まで写ってなくてテスト結果が見えないのはありましたが少なくともテスト失敗を表す赤いバーは1つも見えませんでした。)Jasmine + Browsershots ってすごい便利!

(php) 日付を1日ずつインクリメントして出力

php

2013-01-01, 2013-01-02, ..., 2013-12-31

って具合に、日付をインクリメントしながら文字列として出力したいような場合 DateTime クラス + DateInterval クラス による実装と、
date 関数 + strtotime 関数 による実装と、
2通り考えられるかなーと思います。
どっちのが速いんだ? って思ったので実験してみました。

結論から言っちゃうと date 関数 + strtotime 関数 のが歴然と速かった。3倍〜4倍くらい速かった。まあそんなもんかもね。。。

実験コード

<?php
// 実験回数
$times = 1000;

// DateTime + DateInterval で実験
print "*** DateTime + DateInterval ***\n";
$time1_msec = ceil(microtime(true)*1000);
for ($i = $times; $i > 0; $i--) {
    $start_at = new DateTime('2013-01-01');    // 開始日
    $end_at = new DateTime('2013-12-31');      // 終了日
    $_1day = new DateInterval('P1D');          // 「1日」を表す DateInterval 型インスタンス
    $date = $start_at;
    while ((int)($date->diff($end_at)->format('%R%a')) >= 0) {
        $date_string = $date->format('Y-m-d');
        //print "$date_string\n";
        $date->add($_1day);     // 1日インクリメント
    }
}
$time2_msec = ceil(microtime(true)*1000);
printf("%d msec\n", ($time2_msec - $time1_msec));

// date + strtotime で実験
print "*** date + strtotime ***\n";
$time1_msec = ceil(microtime(true)*1000);
for ($i = $times; $i > 0; $i--) {
    $start_at_string = '2013-01-01';    // 開始日
    $end_at_string = '2013-12-31';      // 終了日
    $start_at_unixtime = strtotime($start_at_string);
    $end_at_unixtime = strtotime($end_at_string);
    $date_unixtime = $start_at_unixtime;
    while ($date_unixtime <= $end_at_unixtime) {
        $date_string = date('Y-m-d', $date_unixtime);
        //print "$date_string\n";
        $date_unixtime += 86400;    // 1日インクリメント
    }
}
$time2_msec = ceil(microtime(true)*1000);
printf("%d msec\n", ($time2_msec - $time1_msec));

実行結果。3回叩いてこんな感じ。

$ php date-iteration.php
*** DateTime + DateInterval ***
3353 msec
*** date + strtotime ***
949 msec

$ php date-iteration.php
*** DateTime + DateInterval ***
4058 msec
*** date + strtotime ***
1113 msec

$ php php-date-iteration.php
*** DateTime + DateInterval ***
3371 msec
*** date + strtotime ***
962 msec

いじょ