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

とあるプログラマの備忘録

都内某所に住むプログラマが自分用に備忘録を残すという趣旨のブログです。はてなダイアリーから移動しました!

SamuraiFw 講習会 その一

前回までに行ったことで、SamraiFWを動かすことができてるはずです。
さて、これから私か勉強がてら作るのは家計簿システムです。

実際なんでもいいのですが、前に自前で作成したことがあったのですが、
それはPHPでべたで書いていました。

それを今回SamuraiFWで再実装して、公開まで持っていければ言いなぁと思っています。

今日やるのは、

PHP開発のためにvsftpdをインストール
・specの説明
・テンポラリーDBの作成
・specの作成
・modelの作成です。

では早速いきやしょう。

  • PHP開発のためにvsftpdをインストール

そういえば前回までにVMwareにFedora13をインスコして、
その上にPHP + MYSQL + Apache + phpMyAdminを乗っけて、
もろもろ設定を終わらせたんですけど、
FTP忘れてましたw(エヘッ

ってコトでインストールします。

# yum install vsftpd

起動と次回からの自動起動設定

# /etc/init.d/vsftpd start
# chkconfig vsftpd on

そしたらWindowsに適当なFTPソフトを入れて、つないで見ましょう。
(ちなみに僕はWinScpを愛用しています。)

  • specの説明

specとは、僕もあまり理解していませんが、用は最初にテスト項目書(仕様)をプログラム上で書き込んで
それに沿って実装を施していく実装方法。ということらしいです。

SamuraiFWではこの実装方法を推奨してます。

上記のような実装方法のことを「テスト駆動開発」というとのこと。
本来というかPHP開発の多くの現場は作ってそれにバグが出たら直す。って感じですが、
それだとClassとかの内容がその場しのぎで増えてデグル可能性が大きくなるって言うデメリットを
できるだけ防ぐ実装方法ということです。

  • テンポラリーDBの作成

まぁお堅いことは後にして、spec用はテストなんで、実際のDBを使うのは気が引けます。
ってコトでphpMyAdminからtmpdbを作成します。

そんじゃフラウザからphpMyAdminにはいって、作成。

別にこれから作るのでテーブルの作成とかはいりません。

samuraiの使うDBの設定をします。
/home/raharu/work/kakeibo/config/activegateway/activegateway.production.yml
ここにSamuraiFWのDB設定ファイルがあるので、それを編集します。

「specを仕様する際はコチラ」見たいなコメントがある下の一行を編集します

dsn : 'mysql://[DBユーザー]:[パスワード]@localhost/tmpdb'

tmpdbはさっき作成したDBです。

  • specの作成

さて、さっきちょっと説明したSPECですが、百聞は一見にしかず。ってことで作ってみましょう。

現在Samuraiではspecを作成してくれるコマンドは未実装とのコト

なので、/home/test/work/kakeibo/spec

直下に作成します。
ネーミングルールがあるので、気をつけてねー。

今回作成するのは
DescribeUserManager.php

内容は以下見たいに作成します。

<?php
class DescribeIncomeManager extends PHPSpec_Context
{
//ここにspecを書いていく。

}

このspecを実行するには以下のコマンドを打ちます。

$ sammurai spec

おぅ!?NOTICE?
少し編集が必要なようです。

一階層上の
Initialization.php
を以下の用に編集。

<?php
/**
 * SPEC用の初期化ファイル
 *
 * すべてのSPECで必要な前提処理をここに記述してください。
 * beforeSuperAllみたいなものです。
 * 
 * @package    Package
 * @subpackage Action
 * @copyright  Foo Project
 * @author     Foo Bar <foo@bar.jp>
 */

//Container初期化
$Container = Samurai::getContainer();
$Container->import('component/action/samurai.dicon');

//AG設定
Samurai_Loader::load('library/ActiveGateway/ActiveGatewayManager.class.php');
$AGManager = ActiveGatewayManager::singleton();
$AGManager->import(Samurai_Loader::getPath('config/activegateway/activegateway.yml'));
$AGManager->import(Samurai_Loader::getPath('config/activegateway/activegateway.production.yml'));
//$AGManager->import(Samurai_Loader::getPath('config/activegateway/activegateway.development.yml'));
//$AGManager->import(Samurai_Loader::getPath('config/activegateway/activegateway.sandbox.yml'));
$AG = $AGManager->getActiveGateway('sandbox');
$Container->registerComponent('AG', $AG);

そんじゃスペックに戻って、
内容を書きます。

<?php
class DescribeIncomeManager extends PHPSpec_Context
{
    //ここにspecを書いていく。
    //取得
    /*it~~で始まるメソッド名をspecは認識します。なので今回はitGetメソッドを作成*/
    public function itGet(){
    
        //UserManagerのgetメソッドからidが1のものをもってきたら、
        $res = $this->UserMnager->get(1);
        
        //返り値はNULLではなく。
        $this->spec($res)->shouldNot->beNull;
        //また$resオブジェクトのidには1が入ってこないといけない。
        $this->spec($res->id)->should->be(1);
    }
}

わざとコメントを書いてみました。が、
当たり前ですね、でもこれがspecというものだそうです。
投げたものが処理を挟んで帰ってくる。
これが帰ってこなかったら、もう仕様段階でアウト。ッてことです。

一回実行してみましょう。

$ samurai spec

もちろん、ERRORまたはEXEPTIONになります。
なんせUserMnagerクラスをまだインスタンス化すらしてないので。
Undifが出るのは当然。なのでここのUserManagerを満足させるためにmodelを作成します。

$ samurai add-component kakeibo_user_manager --model

最後に--modelを付けることによってsamuraiModelを継承したクラスが作成されます。
利点としてはActiveGatawayを自動で引っ張ってきてくれるので、
記述が楽になりますねー。

作成される場所は以下のパス。
/home/test/work/kakeibo/component/kakeibo/user


例です。
getメソッドは$idを元に
userテーブルから1レコードを持ってきます。

<?php
/**
 * [[機能説明]]
 *
 * @package    Package
 * @copyright  Foo Project
 * @author     Foo Bar <foo@bar.jp>
 */
class Kakeibo_User_Manager extends Samurai_Model
{
    /**
     * コンストラクタ
     *
     * @access     public
     */
    public function __construct()
    {

    }

    public function get($id){
        return $this->AG->find('user',$id);
    }

}


そしたらdiconファイルを作成します。

/home/raharu/work/kakeibo/component/action
直下に

$ vi samurai.dicon

今回はユーザーテーブルにアクセスするようのmodelと紐付けるので、
以下のように書きます。
内容的には「UserManager」ってエイリアスをつけてるイメージ?
/home/raharu/work/kakeibo/component/actionの
/kakeibo/user/Manager.phpをここで定義しています。

UserManager : 
    class : Kakeibo_User_Manager

まだspecとは紐付いてませんよー。なんでまたspecに戻って以下の様に編集します。

<?php
class DescribeIncomeManager extends PHPSpec_Context
{

    //classの定義
    public $UserManager;

    //ここにspecを書いていく。
    //取得
    public function itGet(){
    
        //UserManagerのgetメソッドからidが1のものをもってきたら、
        $res = $this->UserMnager->get(1);
        
        //返り値はNULLではなく。
        $this->spec($res)->shouldNot->beNull;
        //また$resオブジェクトのidには1が入ってこないといけない。
        $this->spec($res->id)->should->be(1);
    }
    
    
    /*beforはメソッドごとに動く*/
    public function before(){
        
        //この記述でやっとmodelと繋がります。
        $this->UserManager = Samurai::getContainer()->getComponent('UserManager');
    }
    
}

これでやっとspecとmodelが紐付いてUserManagerクラスが働くようになりました。

そんじゃもう一回実行して見ましょー。

$ samurai spec

どうでしょう?以下みたいなものがでてきませんか?

.


Finished in 0.12685585021973 seconds

user manager
  -get
  
1 examples, 0 failures, 0 errors

こんなんだったかな?

これが基本形です。

その他ERRORが出たり、または期待していた値と違うものが帰ってきた場合には以下のようになります。

F


Finished in 0.12685585021973 seconds

user manager
  -get (FAIL)
  
1 examples, 0 failures, 0 errors

Failures:

1)
'user manager get' FAILED
expected 2, got 1 (using be())

Errors:

上のメッセージは2を期待していたのに1がかっえて来たぞゴラァ!ってコトです。

これらをmodelに作成する予定(仕様)のspecを用意すると以下のようになります。

<?php
class DescribeUserManager extends PHPSpec_Context
{

    public $UserManager;

    //ユーザー取得(find)
    public function itGet(){
        $res = $this->UserManager->get(1);
        $this->spec($res)->shouldNot->beNull();
        $this->spec($res->id)->should->be(2);
    }

    //ユーザー取得(findDitail)
    public function itFinduser(){

        $cond = $this->UserManager->getCondition();
        $conf->where->name = "spec";
        $cond->where->password = "specpass";

        $res = $this->UserManager->finduser($cond);
        $this->spec($res->name)->should->be("spec");
        $this->spec($res->password)->should->be("specpass");
    }

    //ユーザー追加(add)
    public function itAdd(){

        $addobj =  new stdClass();
        $addobj->name = "spec2";
        $addobj->password = "specpass2";

        $user = $this->UserManager->add($addobj);
    }

    //編集(edit)
    public function itEdit(){

        $editarr = array('name' => 'specedit','password' => 'speceditpass');
        $user = $this->UserManager->get(1);
        $this->spec($user->name)->should->be('specedit');
        $this->spec($user->password)->should->be('specpassword');
    }

    //削除(delete)
    public function itDelete(){

        $this->UserManager->delete(1);
        $deluser = $this->userManager->get(1);
        $this->spec($deluser)->should->beNull();
    }

        $this->spec($user->password)->should->be('specpassword');
    }

    //削除(delete)
    public function itDelete(){

        $this->UserManager->delete(1);
        $deluser = $this->userManager->get(1);
        $this->spec($deluser)->should->beNull();
    }

    public function before(){
        $this->UserManager = Samurai::getContainer()->getComponent('UserManager');
    }
}

specを実行させると以下みたいになる。

FEEEE

Finished in 0.093230962753296 seconds

user manager
  -get (FAIL)
  -finduser (ERROR)
  -add (ERROR)
  -edit (ERROR)
  -delete (ERROR)
  
  5 examples, 1 failure, 4 errors

Failures:

1)
'user manager get' FAILED
expected 2, got 1 (using be())

Errors:
1)
'user manager add' ERROR
exception 'PHPSpec_Runner_ErrorException' with message 'undefined method.' in /usr/share/pear/Samurai/component/samurai/Model.class.php:259
Stack trace:
#0 [internal function]: PHPSpec_ErrorHandler(512, 'undefined metho...', '/usr/share/pear...', 259, Array)
#1 /usr/share/pear/Samurai/component/samurai/Model.class.php(259): trigger_error('undefined metho...', 512)
#2 [internal function]: Samurai_Model->__call('add', Array)
#3 /home/raharu/work/kakeibo/spec/kakeibo/DescribeUserManager.php(33): Kakeibo_User_Manager->add(Object(stdClass))
#4 /usr/share/pear/PHPSpec/Runner/Example.php(85): DescribeUserManager->itAdd()
#5 /usr/share/pear/PHPSpec/Runner/Collection.php(73): PHPSpec_Runner_Example->execute()
#6 /usr/share/pear/PHPSpec/Runner/Base.php(51): PHPSpec_Runner_Collection->execute(Object(PHPSpec_Runner_Result))
#7 /usr/share/pear/PHPSpec/Runner/Base.php(44): PHPSpec_Runner_Base->executeExamples()
#8 /usr/share/pear/PHPSpec/Runner.php(54): PHPSpec_Runner_Base::execute(Object(PHPSpec_Runner_Collection), Object(PHPSpec_Runner_Result))
#9 /usr/share/pear/Samurai/component/action/samurai/Spec.class.php(74): PHPSpec_Runner::run(Object(stdClass))
#10 /usr/share/pear/Samurai/component/samurai/ActionChain.class.php(118): Action_Samurai_Spec->execute()
#11 /usr/share/pear/Samurai/component/filter/Action.class.php(87): Samurai_ActionChain->executeAction(Object(Action_Samurai_Spec), '')
#12 /usr/share/pear/Samurai/component/samurai/Filter.class.php(79): Filter_Action->_prefilter()
#13 /usr/share/pear/Samurai/component/samurai/FilterChain.class.php(116): Samurai_Filter->execute()
#14 /usr/share/pear/Samurai/component/samurai/Filter.class.php(116): Samurai_FilterChain->execute()
#15 /usr/share/pear/Samurai/component/samurai/Filter.class.php(80): Samurai_Filter->_chainfilter()
#16 /usr/share/pear/Samurai/component/samurai/FilterChain.class.php(116): Samurai_Filter->execute()
#17 /usr/share/pear/Samurai/component/samurai/Filter.class.php(116): Samurai_FilterChain->execute()
#18 /usr/share/pear/Samurai/component/samurai/Filter.class.php(80): Samurai_Filter->_chainfilter()
#19 /usr/share/pear/Samurai/component/samurai/FilterChain.class.php(116): Samurai_Filter->execute()
#20 /usr/share/pear/Samurai/component/samurai/Filter.class.php(116): Samurai_FilterChain->execute()
#21 /usr/share/pear/Samurai/component/samurai/Filter.class.php(80): Samurai_Filter->_chainfilter()
#22 /usr/share/pear/Samurai/component/samurai/FilterChain.class.php(116): Samurai_Filter->execute()
#23 /usr/share/pear/Samurai/component/samurai/Controller.class.php(116): Samurai_FilterChain->execute()
#24 /usr/share/pear/Samurai/generator/generator.php(56): Samurai_Controller->execute()
#25 {main}

こんなエラーがずらーっとでる。
まだmodelを実装してませんからね。
このspecの期待値が帰ってくることを目標にmodelを実装していくのが。
テスト駆動開発」ということらしいっすわ。

わかりやすく書きたいがために長くなりましたが、
ちょっと見づらいっすね(汗