より良い環境を求めて このページをアンテナに追加 RSSフィード

2013-06-28

[][][] PHPの自作フレームワーク現状まとめ

https://github.com/nishimura/laiz2

基本方針

  • DB設計最優先
  • HTML構造優先
  • コード量を少なく

大抵の処理はPage(Action)クラスに書く。いわゆるAction肥大化。

もし「この二つのテーブルは大抵joinして取得している」という状況になったらModelを作っても良いが、大抵の場合は一画面ごとに処理がユニークなので共通化できない。

アプリ側はフレームワークの何かを継承してクラスを作ることが基本的にないのでテストする場合はActionクラスをテストすれば良い。


プレゼンテーションロジックがほとんどでビジネスロジックはあまりないという考えに基づく。

DB設計と画面遷移でほぼシステムの設計は決まっていて、コードに書くビジネスロジック的なものはおまけ。


アプリを作る流れ

ふつう
  1. ER図を書く
  2. SQLを書いて初期DBを作る
  3. HTMLを書く
  4. PHPを書く
  1. ER図を変更する
  2. migrationでDBを変更する
  3. PHPHTMLを変更する

DB設計優先。

HTML優先。

PHPDBHTMLつなぐ手段。


デザイン優先
  1. htmlcss、画像などのセットをもらう
  2. PHPを書く、htmlに変数を埋める

htmlpathなどの構造は変えない。

元々ある構造にPHPを割り当てていく。



細かい話

  • DB設計最優先:フレームワークの制約でDB設計が変わることのないように
    • 主キー自由
    • リレーション自由
    • DB変更はPHPコードで&バージョン管理
  • HTML構造優先:フレームワークの制約でHTML構造が変わることのないように
  • コード量を少なく:自動で出来るところは自動でやる
    • インスタンスの生成はタイプヒンティングから自動で
    • DBのテーブル情報をPHPに対応させるところは自動で
    • フォームから送られてくる各データをオブジェクトへ詰め替えるのは自動で
    • checkbox、selectboxなどの初期値設定は自動で


次に実装するかもしれない機能

  • 自動化を進めて更にコード量を少なく
    • DB制約から最低限のバリデーションを自動で
    • HTML5のフォーム入力チェック構文からバリデーションを自動で
  • 構造を変える
    • フレームワーク自体の構造を綺麗にする
    • フォーム処理の流れがまだしっくり来ない
      • もっと単純に簡単にしたい

2013-04-16

[][][] PHPフレームワークを作った

ソースはここ: https://github.com/nishimura/laiz2

composerの使い方を見るのも兼ねてサンプルアプリを置いたのですぐにインストールできるはず。

https://github.com/nishimura/laiz-sample-task

composer.phar create-project laiz/laiz-sample-task laiz-sample-task
cd laiz-sample-task
mkdir logs cache
chmod o+w logs cache

やりたかったことは

https://github.com/nishimura/laiz-sample-task/blob/master/src/Laiz/Sample/Task/Page/Dir/Information.php

ほぼこのファイルに集約した。

<?php
...
class Information
{
    /**
     * @Converter(["upper", PlusLengthConverter])
     */
    public $plainText;

    /** @var bool */
    public $flag;

    /**
     * @var MyModel
     * @Converter(["name" => "wordseparatortocamelcase",
     *             "value" => ["wordseparatortodash", "upper"]])
     */
    public $model;
...


アノテーションによるフィルターで型変換。

フォームとDBの操作は

https://github.com/nishimura/laiz-sample-task/blob/master/src/Laiz/Sample/Task/Page/Task.php

こんな感じ。

<?php
...
class Task
{
    public $action;
    public $pager;
    public $TASKS;

    /**
     * @var TransactionToken
     * @Validator("transactiontoken.ini")
     */
    public $transaction;

    /**
     * @var Vo_Task
     * @Validator("task.ini")
     */
    public $task;

    /**
     * @Validator("check.ini")
     * @var bool
     */
    public $check;

    public function index(Db $db)
    {
        $iterator = $db->from('Task')
            ->order(array('subject', 'taskId'))
            ->iterator();

        $pager = new Pager($iterator, 5);
        $this->pager = $pager->getHtml();
        $this->TASKS = $iterator;
    }

    public function info()
    {
        // nothing
    }

    public function add(Db $db, AuthenticationService $auth, $valid = null)
    {
        $this->action = "Create New Task";
        $this->editInternal($db, $auth, $valid, 'Task was created.');
        return 'task_edit.html';
    }
    public function edit(Db $db, AuthenticationService $auth, $valid = null)
    {
        $this->action = "Edit the Task";
        if ($valid === true)
            $this->task->updatedAt = date('Y-m-d H:i:s');
        $this->editInternal($db, $auth, $valid, 'Task was edited.');
    }
    private function editInternal($db, $auth, $valid, $msg)
    {
        if ($valid === null){
            $this->task->userName = $auth->getIdentity();
        }else if ($valid === true){
            try {
                $db->save($this->task);
                throw new RedirectMessageException('/task_info.html?task[taskId]=' . $this->task->taskId,
                                                   $msg,
                                                   Message::SUCCESS);
            }catch (Exception $e){
                Message::add($e->getMessage(), Message::ERROR);
            }
        }
    }
    public function delete(Db $db)
    {
        try {
            $db->delete($this->task);
            throw new RedirectMessageException('/task.html',
                                               'Task was deleted.',
                                               Message::SUCCESS);
        }catch (Exception $e){
            Message::add($e->getMessage(), Message::ERROR);
            return;
        }
    }
}


今まで使っていた自作フレームワークと今回のフレームワークの違い

  • 旧バージョン
  • 新バージョン
    • 全体的な考え方はDI+アノテーション
      • Zend\DI
      • 特に参考にしたフレームワーク無し
      • Zend Framework2 を使っているので、そういう感じの設定になる
      • composer.phar を使っているので、割とマシな名前空間に強制される
    • CRUD 1クラス
      • または1アクション1クラス
    • 全体的な設定ファイルがいくつか
    • URLhtmlメイン
    • 独自ORM(Tsukiyo、S2JDBC風)
      • DB設計重視、複数プライマリキーの外部結合など対応
    • 独自テンプレートエンジン(Yokaze、今のところ機能縮小版FlexyHTMLタグ解析は少しやってるのでフォームに対応したいところ)

主な設定(?)と機能

  • アクション|ページクラスは一つのnamespace以下に収める
  • @var アノテーションを書くと型変換する
    • ORMのVOの型を書くと、リクエストにプライマリーキーがある場合はDBからデータを取得し、さらにリクエスト変数で上書きする
  • @Converter アノテーションで変換
    • trimとか
  • @Validatorアノテーションで入力チェック
    • 実体はiniファイルに書く
    • validかどうかは三値で取得する。null=動作なし、false=invalid、true=valid
  • アクションフィルター的なものはURLに対する正規表現で書く
    • 一ヶ所に集約
  • Zend\Di に対する設定はdi.iniに書く

その他

  • モデルやサービス的なことは何もしない
    • 自分でApp\Modelなどのnamespaceを作る
  • メールとかファイルとか権限管理はまだ無い
    • ログイン処理はサンプルとしてiniファイル版とDB版がある
  • Wicketのように例外でリダイレクトする

現状はこんなところ。

2009-12-14

[][] BazaarからGitに移行した

少しだけ使っていたLaunchpadはやめてGithubに変更。

Launchpadってmixiみたいに段々見にくくなってるような…。


http://github.com/nishimura/laiz

取り敢えずローカルのリポジトリとLANのリポジトリとgithubとこんがらがりながら使ってみている。


もともとはMapleとguessworkを合わせた感じだったんだけれど、今は…うーん。

無駄に複雑にしてしまったかもしれない。PHPでコンポーネント指向は難しい。

作りかけのややこしい機能を削っていかないと。

オブジェクト指向が分かってないっぽい作りになってるのが多々あるかも。Javaっぽくしようとして失敗的な。

バランス感覚が必要なんかね。

2008-02-05

[][] アクションのテスト

やりたいこと。

  • アクションを実行した戻り値のチェック
  • テンプレート用変数のチェック
  • DB操作のチェック(スタブ?
  • ダミー変数でのHTML表示
    • ループ変数などがあると、実際に動かすまでデザインできない

そのメソッドをどのように呼びたいのかということをDocコメントに規約どおりに書く

http://d.hatena.ne.jp/kunit/20080205#1202142580

これってどうなんだろう。

私はエディタの自動整形を使いたいのでコメントじゃない方がいいってぼんやり考えている。



こんなイメージ。

<?php
class ViewUserData
{
    /** @var int */
    public $userId;

    /** @var Laiz_Vo_User */
    public $user;

    /** @var string */
    public $errorMessage;

    public function execute(DaoFactory $factory)
    {
        if (!is_numeric($this->userId)){
            $this->message = 'ユーザIDを指定してください';
            return 'error';
        }

        $userDao = $factory->get('user');
        $user = $userDao->getVo($this->userId);
        if (!$user){
            $this->message = 'ユーザが存在しません';
            return 'error';
        }

        // 表示用
        $this->user = $user;
    }

    public testNoUserId()
    {
        // ここでフレームワークを起動してアクションメソッドの実行
        $ret = Laiz::testAction($this); // ←これの方法を考える

        // 設定ファイルでアサート(可能なのか?もしくは面倒じゃないのか?
    }

    public testNoUser(Request $req)
    {
        $req->add('userId', 1);
        // ここでフレームワークを起動してアクションメソッドの実行
        ・・・
    }

    public testSuccess(DaoFactoryStub $factory)
    {
        $this->userId = 1; // 別バージョン

        // ここでフレームワークを起動してアクションメソッドの実行
        $ret = $this->execute($factory); // とかもできる?
    }
}

何だかまとまらない。

フレームワークの設定、リクエスト変数(ActionName?test=trueなど)、セッション変数などでテストかどうかを切り替える。

全てのアクションのテストをしたい場合が面倒で重そうだ。


setup()やtearDown()もいいんだけど、セッションの値が不正な場合とかをチェックしようとするとそれぞれのテストメソッドに初期設定や初期化処理を書かないといけない気がする。クラスに一つずつで上手いことできるんだろうか。テストメソッドが10個あってそのうちの5個で使うとか。・・・まぁ普通に関数を書けばいいか。


まだまだ曖昧な考えだけどアクションのテストは是非作りたい。

2008-02-04

[] フレームワークのメール処理

php.iniでmbstringの設定を何もしていないときに日本語メールが正しく処理されないのを解決しなければ。

フレームワーク起動直後にmb_language()を呼ぶのが適当だろうか。


タイムゾーンの設定(date_default_timezone_set())と、日本で使う上での必須の設定ってそれくらいだっけ・・。

2007-12-24

[][] O/Rマッパーに疑似ビュー機能を追加した

今までDBにビューを作っていたものを、やっぱりDBをいちいち変えるのが面倒なのでormで対応することにした。


myview.sql

select col1, col2 
from t1
%s
order by col3
limit 1

こんなselect文があるとすると

<?php

class MyAction
{
    public $num;
    public $vo;

    public function execute(DaoFactory $factory)
    {
        $dao = $factory->get();
        $where = 'where col3 > ?'; // 実際は条件によってwhere句が複雑に変わる場合とかに利用
        $this->vo = $dao->getViewVo('myview', $this->num, $where);
    }
}

という感じの処理。

もちろん動的な部分(ここでは$where)が無ければ第3引数は要らないし、変数を利用しないのであれば第2引数も要らない。


そんなに複雑なことはしていない。

http://laiz-source.googlecode.com/svn/Laiz/branches/TRY-3.0/laiz/webapp/components/Laiz/OrMapper.php

これの下の方のviewと書いてある部分。


bindViewの部分はイテレータ と連携するためのものだから、実質70行くらいか。



思い付きで書いたので、まずい構造があるかもしれない。後で見直す。

データの更新とか複数の行を取得する処理も後で。まぁ複数行表示はイテレータ書けばできるわけだから、



他のフレームワーク(というかO/Rマッピングツール?)では、イテレータとか使えるんだろうか。

最近orm+iterator抜きでは無理になってきた。

2007-11-12

[] O/Rマッパーにビューに機能が欲しくなってきた

DBの更新ってめんどくさいよね。


システムを動かしながらソースは更新できる。

システムを動かしながらDBは更新できる。


これを同時にやれば、DB修正ありの更新をシステム停止せずにできる。ミスらなければ。



で、新しいDBデータ一覧表示が欲しくなった場合。

開発環境でビュー作って、テストサーバでビュー作って、本番環境でビュー作って。

ソースコミットしてアップデートして。


これがめんどくさい。

修正が続くとDBの更新を忘れてエラーになったり。

だからテキストファイルにビューのようなselect文書いて、subversionでまとめて更新したい。

既存のormだとそういうのあるんだろうなー。



考え方メモ。

  1. 特定のディレクトリ(例えばcomponents/orm/views)に ビュー名.ini ファイルを置く
  2. select文べた書きする
  3. $orm->get(ビュー名) でビューDAO取得。