2012年11月5日月曜日

複雑なInteractionを関数で実装する

先週は、dci(data, context, and interaction)の、特にインターアクションという
言葉からの連想で15行程度の関数を書いた。

そこから、継続とかの難しい言葉を覚えて、更に書き換えていたのだが…
難しい。

ウェブサイトのページをコントロールするので、
ページの順番、入力のバリデーション、DB登録の制御、特にCSRF対策、などをきっちりと行おうと思うと、それなりに複雑になってしまった。

せいぜいif文で2レベルの深さにも関わらず、だ。

で、そのコード。
関数、ではなくて、あるクラス内のメソード。

entityというデータについて、
 ・form1:フォーム1の表示、load1:入力の読み取り、バリデーション、
 ・form2:フォーム2の表示、load2:入力の読み取り、バリデーション、
 ・confirm:確認画面の表示、
 ・save:そしてDBに登録する処理。

ただし確認画面の表示で、入力内容が気に入らなければ、フォーム1あるいは2を再表示して再入力して、入力に問題がなければ確認画面に戻る、という仕様を実装してみた。
(まだ実行してないので動くかどうかは知らない)
($viewやcontextとは何かとかも気にしない)


/**
 * @param string $control
 * @param view $view
 * @return \view
 */
function entityAdd( $control, $view )
{
    // get entity
    $entity = $this->restore( 'entity' );
    $state  = $this->getState();
    if( !$state ) {
        $entity = $this->contextGet( 'entity' );
        $this->register( 'entity', $entity );
        $this->setState( array( 'form1', 'form2', 'confirm', 'save', 'done' ) );
    }
    $role = $this->applyContext( $entity, 'loadable' );
    // form1
    if( $control == 'form1' || $state == 'form1' ) {
        $this->nextStateIf( 'form1' );
        return $view->showForm1( $entity );
    }
    // load1
    if( $control == 'load1' ) $role->load( 'load1' );
    
    if( !$role->verify( 'load1' ) ) return $view->showForm1( $entity );
    // form2
    if( $control == 'form2' || $state == 'form2' ) {
        $this->nextStateIf( 'form2' );
        return $view->showForm2( $entity );
    }
    // load2
    if( $control == 'load2' ) $role->load( 'load2' );
    
    if( !$role->verify( 'load2' ) ) return $view->showForm1( $entity );

    if( $control == 'save' && $state == 'confirm' ) $this->nextState();
    
    // confirm
    if( $state == 'confirm' ) {
        return $view->showConfirm( $entity );
    }
    
    // save
    if( $state == 'save' ) {
        $role = $this->applyContext( $entity, 'active' );
        $role->insert();
        $this->nextState();
    }
    // done
    return $view->showDone( $entity );
}

複雑な理由は、制御変数が2つもあるから。
ひとつは$state、セッションに保持していて「〜〜まで実行」を制御する。
一方は、$control、フォームからの入力値で「〜〜から実行」あるいは「〜〜を実行」を制御する。

ステートが出てくるので、ステートパターンの出番だと思うが、
最初の発想が、処理が複数のクラスの分割されてしまって理解が難しくなる、というOOの問題の提起があって、対処方法として関数で逐次処理するという提案だった。なので、複雑なのでOOでクラスに分割して・・・では解決にならないw

とはいえ、理解しやすいコードとは到底言えない。

2012年11月1日木曜日

PHPUnitからPostgreSQLを使ってテストしたら「FATAL: remaining connection slots are reserved...」

PHPUnitでPostgreSQLを使ったテストを書いてみた。
個々のテストは動くのだけど、テストを一つにまとめてSuiteにすると

FATAL:  remaining connection slots are reserved for non-replication superuser connections
とエラーが続出した。

Googleで調べてみると、Rails、Hibernateなどでも似たような症状が報告されていたけど、解決法が見つからない。

「max_connectionを増やす」と良い、
と書いてあったのを見かけたが、増やすと今度は別のエラーで通らない。というかPostgreSQLが起動しなかったので論外。

9.0から9.2系にアップグレードしたけど、効果なし。
phpPgAdminが未対応で、そこでまた修正が出てきたり。

しかたがないのでPHPUnitの書き方を変更して対応。
単に、setupでPdoを生成してたのを、setUpBeforeClass内で行うように変更した。staticメソードなので、ちょっと修正は必要だったが。

class TestUsingPgSql extends \PHPUnit_Framework_TestCase {
   function setUp() {
        $pdo = new \Pdo( 'some dsn' );
    }
}

class TestUsingPgSql extends \PHPUnit_Framework_TestCase
{
   static function setUpBeforeClass() {
        $pdo = new \Pdo( 'some dsn' );
    }
    public function setUp() {
    }
}

単独でもsuite内で走らせても、問題なし。

しかし、PostgreSQLを使ったテストは3ファイルのみ。
もっとテストが増えてきたら対応できるのだろうか?