読者です 読者をやめる 読者になる 読者になる

忍び歩く男 - SLYWALKER

大阪のこっそりPHPer

PHPカンファレンス関西2015は大丈夫なのか? #phpkansai

この記事は「 PHPカンファレンス関西2015 - 関西最大のPHPイベントは5月30日(土)開催! 」の リレーブログ のエントリーです。

昨日は、@tanakahisateru さん はやいで! つよいで! ワテらのPHP!! #phpkansai - なんたらノート第三期ベータ でした。

f:id:slywalker:20140413164900j:plain

初物尽くしの今開催、はたして…

私は第1回開催から毎回実行委員として参加させていただいていますが、第5回を迎える今年の開催は初めての試みが多く行われています。

  • 新実行委員長
  • 初会場
  • 初有料化

ただでさえ開催に向けて多くの課題の解決をしていかねばならないスケジュールに加え、今までのノウハウが活かせない環境への対応。年々高まる参加者のクオリティへの期待。

はたして今開催は無事終えることができるのだろうか?

そもそも第1回はすべてがチャレンジ

そもそものはじまりは、「関西にもPHPカンファレンスを!」との熱い気持ちからでした。「東京には負けてられへん!」「関西でもPHPerたちの交流を場を!」「有名なあの人の話が聴きたい!」それぞれの思いの違いはあれど、スタッフ一丸となって無事開催でき、参加者と盛り上がることができました。

開催を重ねるごとに新しいスタッフや参加者が

そもそも、新しいことをやろうと集まったスタッフと参加者のみなさんです。カンファレンスの内容も常に新鮮なテーマであふれています。常連の参加者も初めての参加者もオススメの情報です。

そして、開催を重ねるごとに新しいスタッフや参加者が増えていき、年に一度のPHPer大交流会となっています。

結論: PHPカンファレンス関西2015は大丈夫です!

常に進化するPHP、日々チャレンジのIT業界において冒頭であげたようなことは何の心配もないことでした。

今年は、福岡で記念すべき初のPHPカンファレンス福岡2015も開催され、日本中でPHP熱が高まっての開催です。当日に風邪などひかぬようにしっかりと体調管理に気をつけましょう。数少ない1日中PHPで楽しめる日ですよ!

唯一心配があるとすれば、私がCakePHPのセッション担当ということでしょうか…

次のリレーブログは?

あれ?だれだろう?

PHP FANN (Fast Artificial Neural Network) を使って儲けてみる

PHP

f:id:slywalker:20150224124759j:plain

データ解析を勉強するにあたって、何かモチベーションになるものはないかと思っていました。そんなとき、「儲かったらいいんじゃね?」との考えにいたりました。

そこでお題にえらんだものが「競馬予想」です。とんでもねーなと思っていたところ、データ解析のお題としては「金融」とならんでベタのお題ということが判明。入力となるデータと答えの値がはっきりしているので、お題として適切なんだそうです。

今回は、かれこれ20年前に大学でかじったニューラルネットワークを使ってのディープラーニング(かっこいい響き)で予測してみました。

前置き

ニューラルネットワークに詳しくありません。学生のとき、まじめに勉強していればよかったと後悔してる状態です。

根っからのPHPerなのでPHPを使います。PHPにはPHP-FANNというのがPECLにあるのでそれを使います。

PHP: FANN - Manual

概要はこのスライドが参考になりました。

PHP による機械学習

FANNについての日本語ドキュメントはこちらをみさせていただきました。

Momma's Wiki: FANN - Fast Artificial Neural Network Librar...

インストール

HomeBrewを使ってインストールします。PHPは5.6がインストールされている前提です。

$ brew install homebrew/science/fann
$ pecl install fann

サンプルで動作テスト

PHP: XOR training - Manual

こちらのサンプルコードをコピペして、まず動作するかを確認します。

入力データ

幸い競馬では自力で指数化しなくても、世の中に「なんちゃら指数」というものがあふれています。これをかき集めて、勝ち馬のパターンを抽出しようと思いました。(競馬新聞のデータから、おっさんが勝ち馬を赤ペンで印をつけるイメージ)

データ集めにはこれ

fabpot/goutte - Packagist

$ php composer.phar require fabpot/goutte:~2.0

PHPに優秀なクローラーが登場しています。

入力データの規格化

今回の仕様では、あるレースに出馬する1頭の各指数のパターンを解析して、3着以内の確率を求めます。

指数データというのは、JRAに登録されているすべての馬の中でどのくらいの能力値といったものなので、各レース毎にそのレースでどのくらいの能力値かといったように規格化します。規格化のためには偏差値を使用しました。

PHPで偏差値を算出するには、以下の拡張をインストールします。

$ brew install php56-stats

偏差値の算出例

<?php

$values = [1, 2, 3, 4, 5];

$std = stats_standard_deviation($values);
$avg = array_sum($values) / count($values);

$results = [];
foreach ($values as $value) {
    $results[] = 10 * ($value - $avg) / $std + 50;
}

var_export($results);

?>

array (
  0 => 35.857864376269049,
  1 => 42.928932188134524,
  2 => 50,
  3 => 57.071067811865476,
  4 => 64.142135623730951,
)

過去データの学習

ニューラルネットワークに過去データを学習させます。試行錯誤の結果、評価関数は FANN_ELLIOT_SYMMETRIC がよかったです。一般的には FANN_SIGMOID_SYMMETRIC が使われることが多いみたいです。

入力データの範囲は一般的に[-1から1]らしく、今回は[0から1]となっています。また答えは[-1,0,1]を与えることが多く、今回は[0,1](5着以内は1、それ以外は0)としました。

学習回数は、MSEの推移をみながら決定しました。

<?php

$layers = [3, 2, 1];
$ann = fann_create_standard_array(count($layers), $layers);

if ($ann) {
    fann_set_activation_function_hidden($ann, FANN_ELLIOT_SYMMETRIC);
    fann_set_activation_function_output($ann, FANN_ELLIOT_SYMMETRIC);

    $sql = 'SELECT index1, index2, index3, order FROM keiba_indexes';
    for ($i = 0; $i < 10; $i++) { 
        fann_reset_MSE($ann);

        $query = $this->query($sql);
        while ($row = $query->fetch()) {
            $order = $row['order'];
            unset($row['order']);

            $out = ($order <= 5) ? [1] : [0];
            fann_train($ann, array_values($row), $out);
        }

        $mse = fann_get_MSE($ann);
        echo $mse . PHP_EOL;

        $query->closeCursor();
        fann_save($ann, 'keiba_float.net');
    }
}

?>

予想データの解析

金曜の夜、土曜の夜になると次の日のレースの出馬表が発表されます。その後指数データも公開されるためので入手して規格化しておきます。

あとは、学習によって得られた関数で数値を算出します。

<?php

$ann = fann_create_from_file('keiba_float.net');

if ($ann) {
    $results = [];
    $sql = 'SELECT index1, index2, index3 FROM keiba_indexes';
    $query = $this->query($sql);
    while ($row = $query->fetch()) {
        $results[] = fann_run($ann, array_values($data));
    }

    var_export($results);
}

?>

算出された数値を降順で並び替えれば、勝つ確率の高い順になります。

気になる成果は?

まぁ、単純に数値を出しただけでは儲かるわけがありません。 購入レースのフィルタリングや、購入券種の検討、資金管理ルールが必要でした。

では、ジャーン!

日程 的中率 回収率
2/14,2/15 54.17% 207.28%
2/21,2/22 40.00% 122.70%

とりあえず、競馬には勝ってます。なぜか残高が増えてませんが…

欲望駆動開発でモノはできたのですが、購入の際は機械的に欲望を断ち切って行ったほうがよいようですね(涙)

CakePHP2でも今どきの戦い方ができるんですよ

CakePHP

f:id:slywalker:20100906153334j:plain

この記事は、CakePHP Advent Calendar 2014 - Qiita 17日の記事になります。

みなさんご無沙汰しております。今年はCli Applicationの開発ばっかりしてました。(CakePHPで)

さて、CakePHP3待ちどうしいですね。Stable版がクリスマスには間に合うかと去年辺り思っていたのですが、この調子で開発が進めばバレンタインデーの頃でしょうか?

冒険しちゃってもいいプロジェクトであれば、今から使っても問題ないと思いますよ。っといってもアンパイのCakePHP2を選んじゃいますよね!

ララベったり、イーったりしてしまいそうな雰囲気ですが、過去のコード資産やノウハウといったところで仕方なくCakePHP2をつかうか…とかなってませんか?

でも、案外CakePHP2は頑張ってるんですよ!

Friends Of Cake

Friends Of Cake

ここに集まったデベロッパーたちは、CakePHP2が他のフレームワークに引き離されている部分を精力的に差を縮めるプロジェクトを開発しています。

彼らのプロジェクトを利用してCakePHP2で今どきの戦いに挑みましょう!

FriendsOfCake/awesome-cakephp

FriendsOfCake/awesome-cakephp

戦いにおいて情報は最も重要であることは言うまでもないでしょう。CakePHPでの戦いをサポートしてくれる有能なプラグインやチップスのリンク集がここにあります。

やみくもに情報を探しまわるのではなく、まず彼らのアドバイスに従ってみましょう。

FriendsOfCake/app-template

FriendsOfCake/app-template

CakePHPってZipをダウンロードしてインストールするんやろ?w テラ縄文w」などど言われて枕を涙で濡らした経験はありませんか?CakePHP2でも今どきのインストールがちゃんとできるんです!

composer -sdev create-project friendsofcake/app-template ProjectName

見事に最新鋭です。

cd ProjectName
app/Console/cake server -p 8888

http://localhost:8888/ にアクセスすればいつもの画面です。

それだけにとどまらず、CakePHP Advent Calendar 2014 の他の記事にも紹介されているPaaSへの対応が準備万端の状態でインストールされます。

あまり詳しく書くとこの記事の公開が18日になってしまうそうなので割愛しますが、READMEをしっかり読めば大丈夫です。

CakePHP3ではますますPluginの存在が重要に

他にも以前紹介したCrudPluginやCakePHP3対応のプラグインも早々に開発が進んでいます。

#CakePHP Bakerは「FriendOfCake/crud」をもっと使おうよ - 忍び歩く男 - SLYWALKER

FriendsOfCake/crud

CakePHP3ではフレームワーク自体はシンプルに、機能はプラグインで追加していくとの方針なので、良質なプラグインをどうやって見つけるかが重要になってきますね。

もちろん見つけるだけでなく、作ってもいいですね!

f:id:slywalker:20121103210603j:plain

来年は、Bakerにとっていい年になりそうです。

2014年 注目すべきPHPフレームワークは? #phpkansai

PHP 勉強会

f:id:slywalker:20140610044800p:plain

この記事は「PHPカンファレンス関西2014」のリレーブログのエントリーです。
PHPカンファレンス関西2014」の開催日までリレー方式でブログを繋いでいきます。

PHPカンファレンス関西2014

前回の記事は、nano_eightさんのPHPカンファレンス関西2014が開催されます! - ぶろぐでした。


新しいフレームワークにチャレンジ! - 時期が良い

f:id:slywalker:20140610044038j:plain

PHPフレームワークを取り巻く環境は、PHPのバージンアップなどで変化していっています。PHP5.3のサポート切れを機に、各フレームワークがPHP5.4以降または5.5以降のみをサポート対象として大きく変化しています。

PHP5.4以降、PHPオブジェクト指向言語としてほぼ完成してきています。設計に対する言語側の制約がなくなってきており、さまざまなデザインパターンを素直なかたちで実装できるようになってきました。

それにともない、フレームワーク側も今までのバッドノウハウ的なコードを一新し、よりソフトウエアの問題解決に則した実装となってきています。

ということで、新しいフレームワークを試してみるには非常によいタイミングです。この変化の波に乗ってみてはいかがでしょうか?

キーワードは「DIコンテナ」、「Composer」

f:id:slywalker:20140610044108j:plain

いまどきのフレームワークDIコンテナがあり、 Composerでインストールできるようになっているものが多いようです。

DIコンテナとは、依存性の注入を実現するための便利な道具です。 Composerでインストールしたパッケージをコンテナに格納して、フレームワーク内で使うといったスタイルが主流となっています。これによって各オブジェクトが疎結合となり、テストがしやすくなっています。

また ComposerPackagistの登場によりプロジェクトごとのパッケージ管理が飛躍的に簡単になったことは、フレームワーク側にも強く影響しています。

フレームワーク4本勝負

PHPカンファレンス関西2014」では下記のPHPフレームワーク4つをとりあげ、それぞれの特徴や今後の方向性を発表していただきます。

はじめてフレームワークを使ってみる人や他のフレームワークが気になる人は、是非お見逃しなく!

もっとつっこんで聞きたい人は、懇親会で登壇者を捕まえて質問攻めにしてみましょう。

私もCakePHP担当で登壇しますので、お手柔らかによろしくお願いします。


次のエントリーは、こちらです!
どうにもならない日々@mkkn

#CakePHP 爆速でAPIを実装するチュートリアル

f:id:slywalker:20131115100808j:plain

スマートフォンアプリのバックエンドや、JSフレームワークのバックエンドとして、JSONやXMLを返すAPIをサーバサイドで実装する機会は多いと思います。

今回は、ComposerとCakePHP2.4、FriendsOfCake/crudを使って爆速で実装してみます。

できあがりは、これ

slywalker/cakephp-app-api_sample

CakePHPのインストール

まず、プロジェクトのディレクトリにcomposer.jsonをつくります

composer.json

{
    "require": {
        "pear-cakephp/cakephp": "2.4.*"
    },
    "config": {
        "vendor-dir": "Vendor/"
    },
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.cakephp.org"
        }
    ]
}

つづいてcomposer.pharのダウンロードとパッケージのインストール

$ curl -s http://getcomposer.org/installer | php
$ php composer.phar install

そして、プロジェクトをBake

$ Vendor/bin/cake bake project $PWD --empty

ブラウザでアクセスすると…

f:id:slywalker:20131115102539p:plain

はい。いつもの画面登場!

ただ、このままではCAKE_CORE_INCLUDE_PATH絶対パスになっているので、デプロイした際にダメになるので書き換えます。

webroot/index.php, webroot/test.php

define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . APP_DIR . DS . 'Vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');

忘れがちなのがConsole/cake.php

Console/cake.php

25c25
<   $root = dirname(dirname(dirname(__FILE__)));
---
>    $app = dirname(dirname(__FILE__));
29c29
<   ini_set('include_path', $root . PATH_SEPARATOR . __CAKE_PATH__ . PATH_SEPARATOR . ini_get('include_path'));
---
>    ini_set('include_path', $app . $ds . 'Vendor' . $ds . 'pear-pear.cakephp.org' . $ds . 'CakePHP' . PATH_SEPARATOR . ini_get('include_path'));
35c35
< unset($paths, $path, $dispatcher, $root, $ds);
---
> unset($paths, $path, $dispatcher, $app, $ds);

最後に、database.phpをbake

$ Console/cake bake db_config

プラグインのインストール

今回の目玉、FriendsOfCake/crudcakephp/debug_kitを入れておきましょう。composer.jsonに書き足します。

composer.json

{
    "require": {
        "pear-cakephp/cakephp": "2.4.*",
        "cakephp/debug_kit": "~2.2",
        "FriendsOfCake/crud": "3.*"
    },
    "config": {
        "vendor-dir": "Vendor/"
    },
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.cakephp.org"
        }
    ]
}

Composerでインストールします。

$ php composer.phar update

CakePHPがプラグインを読み込むように設定しときましょう。

Config/bootstrap.php

CakePlugin::loadAll();

Crudプラグインの設定他

さて、Crudプラグインを使えるように設定していきましょう。今回は詳しいとこは省くので、「ん?」と思ったら公式ドキュメントを参照してください。

今回は、.jsonのアクセスでJSONのレスポンス、.xmlのアクセスでXMLのレスポンスを返すようにしましょう。ということでroutes.phpにこれを追加。

Config/routes.php

Router::parseExtensions('json', 'xml');

そして、AppController.phpをゴリゴリ…

Controller/AppController.php

<?php
App::uses('Controller', 'Controller');
App::uses('CrudControllerTrait', 'Crud.Lib'); // これ書いといて

class AppController extends Controller {

    use CrudControllerTrait; // トレイト使うよ!なんてモダン

    public $components = [
        'Session',
        'RequestHandler', // これが拡張子で処理をわけてくれるのさ
        'Paginator' => [
            'paramType' => 'querystring' // APIっぽくクエリ形式
        ],
        'DebugKit.Toolbar' => [
            'panels' => ['Crud.Crud'] // Crud用のパネルがあるのさ 
        ],
        'Crud.Crud' => [
            'actions' => ['index'], // とりあえずindexのアクションだけ
            'listeners' => ['Api'] // ApiListenerを使うよ
        ]
    ];

}

よしっ!準備は整った!

データの準備

今回はMySQL使うということでよろしくお願いします。サンプルデータがgithubのレポジトリ内のConfig/Schema/cakeapi.sql.gzにあるのでインポートしてください。

ちなみに、ちょっと凝ったことをしようと思ったのでデータは東京駅を中心としたランダムの位置情報1万件です。geometry型を使ってます。

Model

さくっと

Model/Geometry.php

<?php
App::uses('AppModel', 'Model');
App::uses('Sanitize', 'Utility');

class Geometry extends AppModel {

    public $virtualFields = [
        'lat' => 'Y(`latlng`)',
        'lng' => 'X(`latlng`)'
    ];

    public function conditionCenter($queryParams) {
        $queryParams = Sanitize::clean($queryParams) + [
            'lat' => null,
            'lng' => null
        ];

        if (
            !is_numeric($queryParams['lat']) ||
            !is_numeric($queryParams['lng'])
        ) {
            return [];
        }

        return ["MBRContains(
          GeomFromText(
              Concat(
                  'LineString(',
                  {$queryParams['lng']} + 1,
                  ' ',
                  {$queryParams['lat']} + 1,
                  ',',
                  {$queryParams['lng']} - 1,
                  ' ',
                  {$queryParams['lat']} - 1,
                  ')'
              )
          ),
          latlng
      )"];
    }

}

緯度経度は、バーチャルフィールドを使います。中心からの範囲検索ようにメソッドを用意してます。

Controller

うりゃっと

Controller/Geometries.php

<?php
App::uses('AppController', 'Controller');

class GeometriesController extends AppController {

    public function beforeFilter() {
        $this->Crud->on('beforePaginate', function(CakeEvent $event) {
            $model = $event->subject->model;
            $request = $event->subject->request;

            $event->subject->paginator->settings += [
                'conditions' => [
                    $model->conditionCenter($request->query)
                ]
            ];
        });

        parent::beforeFilter();
    }

}

はい。これだけです…

「は?」と思ったら公式ドキュメントですからね!

レスポンス

ではでは、アクセスしてみましょう。

http://localhost/geometries.json
{
    "success": true,
    "data": [
        {
            "Geometry": {
                "id": "1",
                "latlng": null,
                "lat": "84.001196",
                "lng": "191.951974"
            }
        },
        {
            "Geometry": {
                "id": "2",
                "latlng": null,
                "lat": "51.617372",
                "lng": "162.921083"
            }
        },
        ....
    ]
}

おおー!20件表示されてますね。これはPaginateのデフォルトだからですね。

ちなみに、

http://localhost/geometries.json?limit=1
{
    "success": true,
    "data": [
        {
            "Geometry": {
                "id": "1",
                "latlng": null,
                "lat": "84.001196",
                "lng": "191.951974"
            }
        }
    ]
}

おおー!効いてる効いてる。

やっぱり、全体の件数とか、何ページ目とか知りたいですよね。そんなときはApiPaginationListenerを追加するんです。

Controller/AppController.php

<?php
class AppController extends Controller {

    use CrudControllerTrait;

    public $components = [
        'Crud.Crud' => [
            'actions' => ['index'],
            'listeners' => [
                'Api', 
                'ApiPagination'  // これ!
            ]
        ]
    ];

}

さて、どうだ?

http://localhost/geometries.json?page=2&limit=3
{
    "success": true,
    "data": [
        {
            "Geometry": {
                "id": "4",
                "latlng": null,
                "lat": "83.012136",
                "lng": "165.754295"
            }
        },
        {
            "Geometry": {
                "id": "5",
                "latlng": null,
                "lat": "123.59616",
                "lng": "201.408059"
            }
        },
        {
            "Geometry": {
                "id": "6",
                "latlng": null,
                "lat": "80.112906",
                "lng": "177.030496"
            }
        }
    ],
    "pagination": {
        "page_count": 3334,
        "current_page": 2,
        "has_next_page": true,
        "has_prev_page": true,
        "count": 10000,
        "limit": 3
    }
}

へー(×3)。これはAPIの受け手にやさしいですよね。

でも、CakePHPが返すデータ配列ってモデル名がついてて、なんかいやですよね。そんなときは、ApiTransformationListenerを使ってください!

Controller/AppController.php

<?php
class AppController extends Controller {

    use CrudControllerTrait;

    public $components = [
        'Crud.Crud' => [
            'actions' => ['index'],
            'listeners' => [
                'Api', 
                'ApiPagination', 
                'ApiTransformation  // これ!
          ]
      ]
  ];

}

どうなるかな?

http://localhost/geometries.json?page=2&limit=3
{
    "success": true,
    "data": [
        {
            "id": 4,
            "latlng": null,
            "lat": 83.012136,
            "lng": 165.754295
        },
        {
            "id": 5,
            "latlng": null,
            "lat": 123.59616,
            "lng": 201.408059
        },
        {
            "id": 6,
            "latlng": null,
            "lat": 80.112906,
            "lng": 177.030496
        }
    ],
    "pagination": {
        "page_count": 3334,
        "current_page": 2,
        "has_next_page": true,
        "has_prev_page": true,
        "count": 10000,
        "limit": 3
    }
}

わぁお!もう最高!

あ、肝心の絞込検索やってなかったですね。

http://localhost/geometries.json?lat=35.67832667&lng=139.77044378
{
    "success": true,
    "data": [
        {
            "id": 2583,
            "latlng": null,
            "lat": 35.790109,
            "lng": 140.713021
        },
        {
            "id": 5111,
            "latlng": null,
            "lat": 35.759589,
            "lng": 140.428571
        },
        {
            "id": 6944,
            "latlng": null,
            "lat": 36.627709,
            "lng": 140.225557
        }
    ],
    "pagination": {
        "page_count": 1,
        "current_page": 1,
        "has_next_page": false,
        "has_prev_page": false,
        "count": 3,
        "limit": 20
    }
}

はい!見事に絞れております!

最後に

Crudプラグインは、GETだけじゃなくてPOSTやPUT、UPDATE、DELETEにも対応してるので、夢が広がりまくりングですよ。

と、こんなエントリーが書けるくらいフリーになって暇しているエンジニアがここにいるので、なにかお仕事ください!

CakePHPの実装指導やパフォーマンス改善、実際のコーディングなどなど、お待ちしております。連絡先はslywalker (Yasuo Harada)にメールアドレスが記載してあります。

#KOF2013 関西オープンフォーラム2013でPackagistへの登録について発表しました

f:id:slywalker:20131110125518j:plain

KOF2013:関西オープンフォーラム2013

11月8日から9日に開催されましたKOF2013:関西オープンフォーラム2013の中で、関西PHP勉強会を行いました。

Composer を活用しよう - 関西PHP勉強会 in KOF - | Kansai PHP Users Group | KOF2013:関西オープンフォーラム2013

Packagistへの登録とおすすめパッケージ

わたしの発表は次のスライドです。

自作のライブラリをオープンソースとして公開している方は少ないですが、オレオレライブラリ置き場としてもPackagistを活用してみてはいかがかと思います。

今回の勉強会には、自作ライブラリを作っている方は少なかったのですが、コードをホスティングしてシェアできる環境が整ってきた現在、積極的に活用してもらったらいいなぁと思っています。

#CakePHP Bakerは「FriendOfCake/crud」をもっと使おうよ

f:id:slywalker:20131105100843p:plain

Introduction - FriendsOfCake/crud

「FriendOfCake/crud」ってなに?

とりあえずこのスライドを見て欲しい。

f:id:slywalker:20131105101726p:plain

まぁ要するに、今までのごちゃごちゃしてたControllerがスッキリして、API化させるのもめっちゃ楽になるよ!っていうPluginです。

わたしが注目してるところはココ!

今までは、Controllerでの処理をPlugin化するのは難儀だった。Controllerから呼び出されるViewはApplicationごとに違ってくるし、むりやりComponent化してみたけどしっくりこない感じで、どう実装しようか悩んでいたところこのPluginが登場!

Class EditCrudAction | FriendsOfCake/crud (develop)

上のコードを見て欲しいんだけど、Controllerでの処理の流れはCrudActionを継承したEditCrudActionに書かれている。リクエストがGET、POST、PUTのときで何をするかが書かれている。

使うときは、Controller側でRedirect先や、FlashMessegeやら、Queryやらを設定してあげる。

つまり、設定はControllerにまかせて、純粋に振る舞いだけをCrudActionを継承したClassに書いていけばいいのだ!

わたしは説明下手だから、とにかくドキュメント読んで使ってみてくださいな。