2009年6月アーカイブ

Ethnaの魅力を語る

自由度が高い

Ruby On Railsのように縛りがきつくないので、自由に自分の規約を作ったり、自作ライブラリと組み合わせて使うことができます。
また、そのような開発スタイルに向いています。

例えばADODBをラップして自作OR/Mapperを作るとか、lime.phpと組み合わせてPerl風のTDD開発をやるとか、そんな楽しみ方があります。
他のフレームワークではなかなか体験できないと思います。

ソースコードが読みやすい

フレームワークを開発している人がみな日本人で、コメントが日本語です。
私のような素人プログラマには、これはものすごく大きい。

また、ファイルやクラスの数が多くないので、印刷して電車の中で読めちゃいます。

あのGreeが使っている

上司や取引先を説得するときに、この一言は強いです。
「あのGree(時価総額1千億円)も使ってるんですよ!」と言えば、経営者層は安心します。
Ruby On RailsとかCakePHPとか言っても、プログラマじゃない人にはピンとこないですから。

覚えることが少ない

一つ目と関係しますが、学習コストが低いです。
個人的な体験ですが、プロジェクトの途中で新しいプログラマが参加してきたとき、1日ペアプログラミングで教えたら、2日目からはその人ひとりで開発できるようになっていました。
(その方がたまたま優秀だったという要因はありますが。)
_trigger_WWWは長いので、ばっさり要約してみます。
Ethna_Controller::_trigger_WWW (要約版)

function _trigger_WWW($default_action_name = "", $fallback_action_name = "")
{
    // アクション名の取得
    $action_name = $this->_getActionName(
            $default_action_name, $fallback_action_name
    );

    // アクション定義の取得
    $action_obj =& $this->_getAction($action_name);

    $this->action_name = $action_name;

    $backend =& $this->getBackend();

    // アクションフォームを呼び出し
    $form_name = $this->getActionFormName($action_name);
    $this->action_form =& new $form_name($this);
    $this->action_form->setFormVars();

    $backend->setActionForm($this->action_form);

    // セッションスタート
    $session =& $this->getSession();
    $session->restore();

    // アクション実行
    $forward_name = $backend->perform($action_name);

    // ビュー呼び出し・実行
    if ($forward_name != null) {
        $view_class_name = $this->getViewClassName($forward_name);

        $this->view =& new $view_class_name(
            $backend, $forward_name, $this->_getForwardPath($forward_name)
        );

        $this->view->preforward();
        $this->view->forward();
    }

    return 0;
}
実際には使用されていないコードを切り捨て、本質的なところだけ残すとこのようになります。
すごいシンプルになりましたね。
これがEthnaの「背骨」にあたる部分です。

要約版とは言っても、このコードの状態で十分りっぱに機能します。
実際私が携わっている商用Webサイトでは、この状態でも完全に動きます。

さて、_trigger_WWW内で行われていることが、より明確に見えてきましたね。

  • アクションフォームの呼び出し(インスタンス化)、初期化
  • アクションクラスの呼び出し、実行
  • ビュークラスの呼び出し(インスタンス化)、実行
  • プログラム全体の終了

Ehtna_Controllerはこのくらいにして、次回以降、個別のクラスの中をのぞいて見ましょう。
PHPをコマドライン(cli)から起動すると、表題のようなエラーが出ることがあります。
Fatal error: Call to undefined function pg_connect() 
ブラウザから動かしたらちゃんと動くのにおかしいなーと思っていろいろ調べたら、原因がわかりました。

原因

phpは、Apacheと連動して動く場合と、コマンドライン(cli)から起動して動く場合で、別々のphp.iniファイルを参照しているようです。

私の場合は、
  • Apacheモジュールとして動作する場合: c:\windows\php.ini
  • コマンドラインのコマンドとして動作する場合: c:\php5\php.ini
となっていたようです。
前者ではphp_pgsql.dll(PostgreSQL接続用モジュール)が読み込まれる設定になっていたのに、後者では何も設定していなかったので、上記のエラーが出た次第です。

対処法

コマンドライン(cli)用のphp.iniを、下記のように書き換えれば、動くと思います。
;extension=php_pgsql.dll
↓
extension=php_pgsql.dll

php_pgsql.dllが読み込まれているかを確認する方法

コマンドラインから、
>php -i
と打って、"PostgreSQL"または"pgsql"という文字が出てくればOKです。
Windows上で、Ethna+PostgresqlでWebアプリを作っていて、コマンドライン用のアクションスクリプトを生成して、さあ実行しようとすると、こんなエラーが出ることがあります。

エラー

>php -f foo.php
Fatal error: Call to a member function execute() on a non-object in Ethna_DB_ADOdb.php on line 210

原因

Ethnaのせいではありません。

恐らく、コマンドラインから起動したPHPで、pg_connect()が使えないのが原因です。
それは、コマンドライン用のphp.ini(Web用のphp.iniとは別物)の中で"php_pgsql.dll"が読み込まれていないのが原因です。
解決法はこちら↓
Ethnaコマンドを使おうとすると、こんなエラーが出ることがあります。
私の場合は必ずこれが出ます。
これが出ると、add-actionとか何もできなくなって一瞬憂鬱になりますが、簡単に回避できるのでご安心を。

エラー

>ethna add-action ...なんとかかんとか
Fatal error: Cannot redeclare class Ethna in ... Ethna.php on line 424 

原因

Ethnaのライブラリ一式が、同じコンピュータ内に2重で存在している。
私の場合、
  • PEARチャンネルでインストールしたEthna (C:\php5\PEAR\Ethnaみたいな場所にある)
  • 既に作ったプロジェクトのlibフォルダ内のEthna (C:\www\myproject\lib\Ethna)
という形で、Ethnaのソースコードが複数の場所に存在していたのが原因のようです。

対処法

ethnaコマンドの実体であるethna.batを書き換えたら動きました。
環境によって違うので、あくまでも参考ということでお願いします。

"rem"というのはコメントアウトのことです。(phpでの"//"に相当)

@echo off

rem
rem   ethna.bat
rem
rem   simple command line gateway
rem
rem   $Id: ethna.bat 457 2007-02-07 11:14:39Z ichii386 $
rem

if "%OS%"=="Windows_NT" @setlocal

if NOT "%PHP_PEAR_INSTALL_DIR%" == "" (
rem set DEFAULT_ETHNA_HOME= %PHP_PEAR_INSTALL_DIR%\Ethna
set DEFAULT_ETHNA_HOME=.\lib\Ethna
) ELSE (
set DEFAULT_ETHNA_HOME=%~dp0
)

goto init
goto cleanup

:init

if "%ETHNA_HOME%" == "" set ETHNA_HOME=%DEFAULT_ETHNA_HOME%
set DEFAULT_ETHNA_HOME=

if "%PHP_COMMAND%" == "" goto no_phpcommand
if "%PHP_CLASSPATH%" == "" goto set_classpath

goto run
goto cleanup

:run
rem IF EXIST "C:\php5\pear\Ethna" (
rem %PHP_COMMAND% -d html_errors=off -qC "C:\php5\pear\Ethna\bin\ethna_handle.php" %1 %2 %3 %4 %5 %6 %7 %8 %9
rem ) ELSE (
%PHP_COMMAND% -d html_errors=off -qC "%ETHNA_HOME%\bin\ethna_handle.php" %1 %2 %3 %4 %5 %6 %7 %8 %9
rem )
goto cleanup

:no_phpcommand
set PHP_COMMAND=php.exe
goto init

:set_classpath
set PHP_CLASSPATH=%ETHNA_HOME%\class
goto init

:cleanup
if "%OS%"=="Windows_NT" @endlocal
REM pause
ポイントは、"C:\php5\PEAR\Ethna"のような場所を見に行かないようにすること、"C:\www\myproject\lib\Ethna\bin\ethna_handle.php"を起動するように強制させることです。

なお、この書き方の場合は、必ずコマンドプロンプト上のカレントディレクトリを"C:\www\myproject\"にしてからEthnaコマンドを叩く必要があります。(ちょっと不便)
なぜこのようにしてるかというと、PC内にEthnaプロジェクトが3つも4つもあるからです。

どんだけEthna漬けやねん。。
Ethna_Controller::_trigger_WWW



function _trigger_WWW($default_action_name = "", $fallback_action_name = "")
{
    // アクション名の取得
    $action_name = $this->_getActionName($default_action_name, $fallback_action_name);

    // マネージャ実行チェック
    $this->_ethnaManagerEnabledCheck($action_name);

    // アクション定義の取得
    $action_obj =& $this->_getAction($action_name);
    if (is_null($action_obj)) {
        if ($fallback_action_name != "") {
            $this->logger->log(LOG_DEBUG, 'undefined action [%s] -> try fallback action [%s]', $action_name, $fallback_action_name);
            $action_obj =& $this->_getAction($fallback_action_name);
        }
        if (is_null($action_obj)) {
            return Ethna::raiseError("undefined action [%s]", E_APP_UNDEFINED_ACTION, $action_name);
        } else {
            $action_name = $fallback_action_name;
        }
    }

    // アクション実行前フィルタ
    for ($i = 0; $i < count($this->filter_chain); $i++) {
        $r = $this->filter_chain[$i]->preActionFilter($action_name);
        if ($r != null) {
            $this->logger->log(LOG_DEBUG, 'action [%s] -> [%s] by %s', $action_name, $r, get_class($this->filter_chain[$i]));
            $action_name = $r;
        }
    }
    $this->action_name = $action_name;

    // 言語設定
    $this->_setLanguage($this->language, $this->system_encoding, $this->client_encoding);

    // オブジェクト生成
    $backend =& $this->getBackend();

    $form_name = $this->getActionFormName($action_name);
    $this->action_form =& new $form_name($this);
    $this->action_form->setFormVars();

    // バックエンド処理実行
    $backend->setActionForm($this->action_form);

    $session =& $this->getSession();
    $session->restore();
    $forward_name = $backend->perform($action_name);

    // アクション実行後フィルタ
    for ($i = count($this->filter_chain) - 1; $i >= 0; $i--) {
        $r = $this->filter_chain[$i]->postActionFilter($action_name, $forward_name);
        if ($r != null) {
            $this->logger->log(LOG_DEBUG, 'forward [%s] -> [%s] by %s', $forward_name, $r, get_class($this->filter_chain[$i]));
            $forward_name = $r;
        }
    }

    // コントローラで遷移先を決定する(オプション)
    $forward_name = $this->_sortForward($action_name, $forward_name);

    if ($forward_name != null) {
        $view_class_name = $this->getViewClassName($forward_name);
        $this->view =& new $view_class_name($backend, $forward_name, $this->_getForwardPath($forward_name));
        $this->view->preforward();
        $this->view->forward();
    }

    return 0;
}


この関数こそがEthnaのメイン処理の部分です。
Ethnaのプログラムの流れがすべてここに凝縮されています。

ここでは、次のような重要なことが行われています。
  • アクションフォームの呼び出し(インスタンス化)
  • アクションクラスの呼び出し(インスタンス化)
  • ビュークラスの呼び出し(インスタンス化)
  • プログラム全体の終了

ここを読み解けば、Ethnaの全体像を把握することができます。
Ethnaで作ったアプリが動かないときは、ここでecho('hello')とか仕込んでおけば、プログラムがどこまで実行されたかわかります。

なお、この関数は最後に0を返しますが、この0はどこにも行きません。
呼び出し元のtrigger()関数のなかで空しく捨てられます。
そしてプログラム全体が終了します。

まとめると、

Ethnaのプログラム実行処理は、index.phpから始まって、trigger()で終わる。

と言えます。
そしてほとんどの処理は_trigger_WWW()の中で行われています。

次回以降、この_trigger_WWW()を詳しく見ていきましょう。

coLinuxとは

coLinuxは、Windows上でLinuxを動かせるツールです。
Ubuntu、Debianなどの本物のLinuxを、あなたが今お使いのWindowsの上で動かすことができます。
ノートPCなどに入れれば、自分専用のLinuxを持ち運べて快適です。

近所のカフェでLinuxをバリバリ使う自分を想像してみてください。
かっこよくないですか?

そんな環境が、たったの10分で手に入るのです。
すごくないですか?

そう思ったら、レッツトライ!

coLinuxをダウンロード

  • http://www.colinux.org/ coLinuxのサイトに行きます。
  • colinux-01.JPG
  • "Downloads (binary and source)"というページに行きます。

  • colinux-02.JPG

  • "coLinux-stable"の"download"をクリックします。
  • filenameが"coLinux-0.7.4.exe"というのをダウンロードしてください。
  • 普通に実行してインストールが始まります。

coLinuxをインストール

  • インストール先は"C:¥colinux"に設定。(デフォルトのままでもOKです。)
  • Root Filesystem Image Downloadのところにチェックを入れて、Ubuntuを選択。
  • TAP-Win32 というドライバをインストールするか聞かれるので、インストール。
  • Ubuntuのイメージファイル(Ubuntu-7.10.ext3.2GB.7z)がダウンロードされたことを確認

Ubuntuイメージファイルを解凍

  • 7zファイルを解凍するには、"7zip"というツールが必要です。
  • http://sevenzip.sourceforge.jp/download.html から入手してインストール。
  • 先ほどの"Ubuntu-7.10.ext3.2GB.7z" を解凍します。
  • 解凍すると"Ubuntu-7.10.ext3.2gb.fs"と"swap128.fs"という2つのファイルができるので、それらをcoLinuxフォルダに移動
  • confファイルを作る

    • coLinuxフォルダにある、example.confをコピーしてubuntu.confに名前変更
    • ubuntu.confを開いて、下記のように書き換えます。

    • 20行目付近
      cobd0="c:\coLinux\root_fs"
      
      ↓(Ubuntuイメージファイルを指定)
      
      cobd0="c:\coLinux\Ubuntu-7.10.ext3.2gb.fs"
      
      23行目付近
      #cobd1="c:\coLinux\swap_device"
      
      ↓(コメントを外す)
      
      cobd1="c:\coLinux\swap_device"
      
      23行目付近
      #eth1=tuntap
      
      ↓
      
      eth0=tuntap
      

    起動!

    コマンドプロンプロでこのように入力します。
    C:\coLinux>colinux-daemon.exe @ubuntu.conf
    Cooperative Linux Consoleが立ち上がります。

    colinux-03.JPG
    "ubuntu login:"と出たら、rootでログインします。パスワードもrootです。

    colinux-04.JPG
    見事ログインできたら成功です。
    ね、簡単でしょ?
    おめでとう!!
    あたなも今日からLinuxユーザです。


    ※ネットワークの設定は後日解説します。
    Ethna_Controller::trigger

        function trigger($default_action_name = "", $fallback_action_name = "", $enable_filter = true)
        {
            // フィルターの生成
            if ($enable_filter) {
                $this->_createFilterChain();
            }
    
            // 実行前フィルタ
            for ($i = 0; $i < count($this->filter_chain); $i++) {
                $r = $this->filter_chain[$i]->preFilter();
                if (Ethna::isError($r)) {
                    return $r;
                }
            }
    
            // trigger
            switch ($this->getGateway()) {
            case GATEWAY_WWW:
                $this->_trigger_WWW($default_action_name, $fallback_action_name);
                break;
            case GATEWAY_CLI:
                $this->_trigger_CLI($default_action_name);
                break;
            case GATEWAY_XMLRPC:
                $this->_trigger_XMLRPC();
                break;
            case GATEWAY_SOAP:
                $this->_trigger_SOAP();
                break;
            }
    
            // 実行後フィルタ
            for ($i = count($this->filter_chain) - 1; $i >= 0; $i--) {
                $r = $this->filter_chain[$i]->postFilter();
                if (Ethna::isError($r)) {
                    return $r;
                }
            }
        }
    
    この関数、ちょっと長いので短くしてみましょう。

    実はちょっとはしょりましたが、$this->getGateway() は "GATEWAY_WWW"という文字列を返します。
    これでswitch文は簡単になります。


    また連載1回目でも触れましたが、この連載では「フィルタ、CLI,XMLRPC, SOAPなどには触れない」ことにしています。
    よって、「なんとかフィルタ」と書いてある部分はばっさり削除できます。
    実際、Sampleプロジェクトではフィルタを定義してないので、この部分は不要です。

    不要部分を削除するとこうなります。
        function trigger($default_action_name = "", $fallback_action_name = "", $enable_filter = true)
        {
                $this->_trigger_WWW($default_action_name, $fallback_action_name);
        }
    

    実際の値を放り込むとこうなります。
        function trigger('index',  "",  true)
        {
                $this->_trigger_WWW('index',  "");
        }
    
    シンプルですね。
    _trigger_WWW()関数を実行しているだけのようです。

    つづく
    前回の続きより。
    Ethna_Controller.php
    
        function main('Sample_Controller', 'index', "")
        {
            $c =& new Sample_Controller;
            $c->trigger('index',  "");
        }
    
    $c には、Sample_Controllerクラスのオブジェクトが入ります。
    (オブジェクトが何かわからない人は、「データ」だと思ってください。オブジェクト=「関数付きデータ」です。)

    $c->trigger()で、Sample_Controllerのtrigger関数(メソッド)が実行されます。

    では、Sample_Controller.phpを開いて、trigger関数を探しましょう。

    ない。

    ということは、Ethna_Controllerのtrigger関数が( $cのメソッドとして )呼ばれるはずです。

    Ethna_Controller::triggerは・・・あった。
        function trigger($default_action_name = "", $fallback_action_name = "", $enable_filter = true)
        {
            // フィルターの生成
            if ($enable_filter) {
                $this->_createFilterChain();
            }
    
            // 実行前フィルタ
            for ($i = 0; $i < count($this->filter_chain); $i++) {
                $r = $this->filter_chain[$i]->preFilter();
                if (Ethna::isError($r)) {
                    return $r;
                }
            }
    
            // trigger
            switch ($this->getGateway()) {
            case GATEWAY_WWW:
                $this->_trigger_WWW($default_action_name, $fallback_action_name);
                break;
            case GATEWAY_CLI:
                $this->_trigger_CLI($default_action_name);
                break;
            case GATEWAY_XMLRPC:
                $this->_trigger_XMLRPC();
                break;
            case GATEWAY_SOAP:
                $this->_trigger_SOAP();
                break;
            }
    
            // 実行後フィルタ
            for ($i = count($this->filter_chain) - 1; $i >= 0; $i--) {
                $r = $this->filter_chain[$i]->postFilter();
                if (Ethna::isError($r)) {
                    return $r;
                }
            }
        }
    
    むむむ。。ちょっと長いですね。
    私のようなへっぽこプログラマは、長い関数を見ると尻込みしてしまいます。

    つづく

    Ethna_Controller.php

    いよいよ、Ethnaの本体とも言うべきEthna_Controllerです。
    前回、index.phpを見るとこう書いてありました。
    
    Sample_Controller::main('Sample_Controller', 'index');
    

    これは、こう書くのと同じです。
     (Sample_Controllerクラスにはmain関数が存在しないので、親クラスのmain関数が実行される。)
    
    Ethna_Controller::main('Sample_Controller', 'index');
    
    では、Ethna_Controllerを覗いてみましょう。

    さすがにEthnaの司令塔(コントローラ)というだけあって、ボリュームがあります。

    しかし今は気にしない。main()関数を探します。

    お、あった。

    
        function main($class_name, $action_name = "", $fallback_action_name = "")
        {
            $c =& new $class_name;
            $c->trigger($action_name, $fallback_action_name);
        }
    

    これに、先ほどの値が放り込まれてこうなります。

    
        function main('Sample_Controller', 'index', "")
        {
            $c =& new Sample_Controller;
            $c->trigger('index',  "");
        }
    
    dqneo-calendar.JPG

    DQNEO Calendar

    は、Googleカレンダー風の日付入力ツールです。
    JKL CalendarをGoogleカレンダー風にアレンジしたものです。
    JavaScriptが分らない人でも導入できるように、設置方法を簡単にしました。
    誰でも簡単に設置できると思います。

    デモ
    ←クリック


    (ここにカレンダーが表示されます。)
    ダウンロード
    dqneo-calendar-0.1.js   (右クリックで保存できます。)

    設置方法

    1.HTMLファイルの<head>~</head>タグ内で次のように書きます。
      <script src="dqneo-calendar-0.1.js" charset="utf-8"></script>
      <script>
        var cal1 = new DQNEO.Calendar("cal1");
      </script>
    
    2.<body>~</body>タグ内で、次のように書きます。
    <span>
     <input type="text" id="cal1" 
        onclick="cal1.onclick();" onchange="cal1.onchange();"><br/>
    </span>
    
    注1:"cal1"の部分は好きな文字列でかまいません。
    注2:<br/>タグ、<span>タグなどは必須ではありませんが、これがないと表示が崩れることがあります。
    (次期バージョンで改良予定)

    1画面に複数のカレンダーを表示したい場合

    1画面に複数表示することも簡単にできます。
     ~ 


    下記のように記述します。
      <script>
        var cal1 = new DQNEO.Calendar("cal1");
        var cal2 = new DQNEO.Calendar("cal2");
      </script>
    
    <table><tr>
    <td>
      <input type="text" id="cal1"
     onclick="cal1.onclick();" onchange="cal1.onchange();"> ~ 
    </td>
    <td>
      <input type="text" id="cal2"
     onclick="cal2.onclick();" onchange="cal2.onchange();">
    </td>
    </tr></table>
    

    設置例

    デモページ をご覧ください。

    補足

    JKL Calendarにある半透明化やフェードアウトの機能はありません。
    基本的に、JKL Calendarから機能を削ってより簡単にしています。

    謝辞

    JKL Calendarという素晴らしいツールを作られた川崎様に感謝いたします。
    Googleカレンダーで日付選択をするときに、ポップアップで出てくるミニカレンダーみたいなやつをHTML/CSSで作る方法。

    GoogleカレンダーのJavaScriptを解析して、静的HTML化してみました。
    CSSのお勉強にどうぞ。



    <html>
    <head>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
    <style>
    
    html{overflow-y:auto}
    body{font:small/normal Arial,Sans-serif}
    
    .cal-dayh{ font-size:78% }
    .cal-monthtable{
      width:100%;
      padding:0;
      border-bottom:1px #a2bbdd solid;
      -webkit-user-select:none;
      -khtml-user-select:none;
      table-layout:fixed;
    }
    .cal-cell{ text-align:center;padding:2px;font-family:Verdana,sans-serif;font-size:85%}
    .cal-heading { color:#112abb; font-weight:bold;vertical-align:middle} /* 年月の文字色 */
    .cal-cur{font-weight:bold;padding-bottom:4px;text-align:center;white-space:nowrap;font-size:100%}
    .cal-prev,.cal-next{font-size:100%;padding-bottom:6px;cursor:pointer}
    .cal-prev { text-align:right}
    .cal-next { text-align:left}
    .cal-weekday          { background:#fff }
    .cal-weekday-selected { background:#ace }
    .cal-weekend          { background:#eee }
    .cal-weekend-selected { background:#9bd }
    .cal-offmonth         { color:#888} /* その月の前後月に属する日の文字色*/
    .cal-day-left         { border-left:1px #a2bbdd solid}
    .cal-day-right        { border-right:1px #a2bbdd solid}
    .cal-dayh             { border-bottom:1px #a2bbdd solid}
    .cal-today,.cal-today-selected{
                            padding:1px;color:#fff;border:1px solid }
    .cal-today            { background:#9ab;border-color:#567 #abc #abc #567} /* 現在日の背景色 */
    .cal-today-selected   { background:#579;border-color:#246 #9bd #9bd #246} /* 現在日が選択された場合の背景色 */
    .cal-onhover          { background-color:#def;color:black}
    
    .cal-popup{
        background:#d9ffc3; /* カンレダー上部の背景色 */
        line-height:1em;
        width:12.45em;
        position:absolute;
        z-index:300
    }
    
    
    </style>
    
    </head>
    
    <body>
    
    <div style="top: 30px; left: 30px;" class="cal-popup">
    
    <table id="caltbl" class="cal-monthtable" style="-moz-user-select: none;" cellpadding="0" cellspacing="0">
    <tbody>
    <tr style="cursor: pointer;" class="cal-cell cal-heading" id=":dheader">
        <td id=":dprev" class="cal-cell cal-prev">«</td>
        <td colspan="5" id=":dcur" class="cal-cell cal-cur">2009年 8月</td>
        <td id=":dnext" class="cal-cell cal-next">»</td>
    </tr>
    <tr class="cal-days">
        <td class="cal-cell cal-dayh">日</td>
        <td class="cal-cell cal-dayh">月</td>
        <td class="cal-cell cal-dayh">火</td>
        <td class="cal-cell cal-dayh">水</td>
        <td class="cal-cell cal-dayh">木</td>
        <td class="cal-cell cal-dayh">金</td>
        <td class="cal-cell cal-dayh">土</td>
    </tr>
    <tr style="cursor: pointer;" id=":drow_0"><td id=":dday_20218" class="cal-cell cal-weekend cal-offmonth cal-day-left">26</td>
        <td id=":dday_20219" class="cal-cell cal-weekday cal-offmonth">27</td>
        <td id=":dday_20220" class="cal-cell cal-weekday cal-offmonth">28</td>
        <td id=":dday_20221" class="cal-cell cal-weekday cal-offmonth">29</td>
        <td id=":dday_20222" class="cal-cell cal-weekday cal-offmonth">30</td>
        <td id=":dday_20223" class="cal-cell cal-weekday cal-offmonth">31</td>
        <td id=":dday_20225" class="cal-cell cal-weekend cal-onmonth cal-day-right">1</td>
    </tr>
    <tr style="cursor: pointer;" id=":drow_1"><td id=":dday_20226" class="cal-cell cal-weekend cal-onmonth cal-day-left">2</td>
        <td id=":dday_20227" class="cal-cell cal-weekday cal-onmonth">3</td>
        <td id=":dday_20228" class="cal-cell cal-weekday cal-onmonth">4</td>
        <td id=":dday_20229" class="cal-cell cal-weekday cal-onmonth">5</td>
        <td id=":dday_20230" class="cal-cell cal-weekday cal-onmonth">6</td>
        <td id=":dday_20231" class="cal-cell cal-weekday cal-onmonth">7</td>
        <td id=":dday_20232" class="cal-cell cal-weekend cal-onmonth cal-day-right">8</td>
    </tr>
    <tr style="cursor: pointer;" id=":drow_2"><td id=":dday_20233" class="cal-cell cal-weekend cal-onmonth cal-day-left">9</td>    <td id=":dday_20234" class="cal-cell cal-weekday cal-onmonth">10</td>
        <td id=":dday_20235" class="cal-cell cal-weekday cal-onmonth">11</td>
        <td id=":dday_20236" class="cal-cell cal-weekday cal-onmonth">12</td>
        <td id=":dday_20237" class="cal-cell cal-weekday cal-onmonth">13</td>
        <td id=":dday_20238" class="cal-cell cal-weekday cal-onmonth">14</td>
        <td id=":dday_20239" class="cal-cell cal-weekend cal-onmonth cal-day-right">15</td>
    </tr>
    <tr style="cursor: pointer;" id=":drow_3"><td id=":dday_20240" class="cal-cell cal-weekend cal-onmonth cal-day-left">16</td>
        <td id=":dday_20241" class="cal-cell cal-weekday cal-onmonth">17</td>
        <td id=":dday_20242" class="cal-cell cal-weekday cal-onmonth cal-today">18</td>
        <td id=":dday_20243" class="cal-cell cal-weekday cal-onmonth">19</td>
        <td id=":dday_20244" class="cal-cell cal-weekday cal-onmonth">20</td>
        <td id=":dday_20245" class="cal-cell cal-weekday cal-onmonth cal-weekday-selected">21</td>
        <td id=":dday_20246" class="cal-cell cal-weekend cal-onmonth cal-day-right">22</td>
    </tr>
    <tr style="cursor: pointer;" id=":drow_4"><td id=":dday_20247" class="cal-cell cal-weekend cal-onmonth cal-day-left">23</td>
        <td id=":dday_20248" class="cal-cell cal-weekday cal-onmonth">24</td>
        <td id=":dday_20249" class="cal-cell cal-weekday cal-onmonth">25</td>
        <td id=":dday_20250" class="cal-cell cal-weekday cal-onmonth">26</td>
        <td id=":dday_20251" class="cal-cell cal-weekday cal-onmonth">27</td>
        <td id=":dday_20252" class="cal-cell cal-weekday cal-onmonth">28</td>
    <td id=":dday_20253" class="cal-cell cal-weekend cal-onmonth cal-day-right">29</td>
    </tr>
    <tr style="cursor: pointer;" id=":drow_5"><td id=":dday_20254" class="cal-cell cal-weekend cal-onmonth cal-day-left">30</td>
        <td id=":dday_20255" class="cal-cell cal-weekday cal-onmonth">31</td>
        <td id=":dday_20257" class="cal-cell cal-weekday cal-offmonth">1</td>
        <td id=":dday_20258" class="cal-cell cal-weekday cal-offmonth">2</td>
        <td id=":dday_20259" class="cal-cell cal-weekday cal-offmonth">3</td>
        <td id=":dday_20260" class="cal-cell cal-weekday cal-offmonth">4</td>
        <td id=":dday_20261" class="cal-cell cal-weekend cal-offmonth cal-day-right">5</td>
    </tr>
    <tr style="cursor: pointer;" id=":drow_6"><td id=":dday_20262" class="cal-cell cal-weekend cal-offmonth cal-day-left">6</td>
        <td id=":dday_20263" class="cal-cell cal-weekday cal-offmonth">7</td>
        <td id=":dday_20264" class="cal-cell cal-weekday cal-offmonth">8</td>
        <td id=":dday_20265" class="cal-cell cal-weekday cal-offmonth">9</td>
        <td id=":dday_20266" class="cal-cell cal-weekday cal-offmonth">10</td>
        <td id=":dday_20267" class="cal-cell cal-weekday cal-offmonth">11</td>
        <td id=":dday_20268" class="cal-cell cal-weekend cal-offmonth cal-day-right">12</td>
    </tr>
    </tbody>
    </table>
    
    </div>
    
    </body>
    </html>
    
    

    spp/Sample_Controller.php

    再びindex.phpを見てみましょう。
    <?php
    require_once 'C:¥xampp¥htdocs¥sample¥app/Sample_Controller.php';
    
    Sample_Controller::main('Sample_Controller', 'index');
    ?>
    
    Sample_Controllerのmain関数を呼び出しています。

    では、Sample_Controller.phpを開いて、Sample_Controllerクラスを探しましょう。

    おっ、ありました。
    
    class Sample_Controller extends Ethna_Controller
    {
    ...
    
    よしよし。
    では、main関数はと・・・

    あれ、ない。

    Sample_Controllerにmain関数はないようです。

    この場合、Sample_Controllerの親クラスであるEthna_Controllerのmain関数が呼び出されることになっています。

    次回は、Ethna_Controllerの中を覗いてみましょう。

    はじめに

    この連載では、Ethna2.3.6のソースコードとその読み方を解説します。

    Ethnaコマンドを使って"Sample"という名前のプロジェクトを作ったという前提で話を進めます。

    手元にEthnaがない方は、公式サイトを見てインストールしてみてください。
    Ethnaインストールガイド

    Windowsユーザの方はコレ↓で一発です。
    5分でまっさらなWindowsにEthnaをインストールしてHello Worldを表示する方法

    話を単純化するために、CLI,XML-RPC,SOAPなどには一切触れませんのでご了承ください。


    Ethnaのプログラム実行はどこから始まるのか


    EthnaでWebアプリを作るとき、通常はアクション、ビュー、テンプレートファイルの3セット(+DBクラスや自作ライブラリ)をバリバリ記述することになると思います。

    では、問題です。

    Ethnaで作ったWebアプリの実行はどこから始まるのでしょうか?



    こたえ:index.php



    全てはindex.phpから始まる

    これは重要です。

    そのアプリが、たとえ"hello world"と表示するだけの簡単なアプリであろうと、
    あるいは、何百人のユーザを抱える大規模なソーシャルネットワーキングSNSサービスであろうと、

    ユーザがindex.phpにアクセスしてきたなら、プログラムの実行はindex.phpから始まります。

    つまりここから始まります。

    <?php
    require_once 'C:¥xampp¥htdocs¥sample¥app/Sample_Controller.php';
    
    Sample_Controller::main('Sample_Controller', 'index');
    ?>
    
    これがEthnaの第一歩です。

    解説

    1行目では、Sampleプロジェクトのコントローラーファイルを読み込んでいます。
    コントローラというのは、メインのプログラムのことです。
    require_once 'C:¥xampp¥htdocs¥sample¥app/Sample_Controller.php';
    

    2行目では、読み込んだファイルの中の、Sample_Controllerクラスのmain関数を実行しています。
    Sample_Controller::main('Sample_Controller', 'index');

    さあここからが、Ethna内部への旅の始まりです。

    つづく

    余談:MVCなど忘れてしまえ

    公式サイトやEthna本によると、
    Ethnaとは、
    MVCモデル2とフロントコントローラパターンに基づいたスタンダードなウェブアプリケーションフレームワーク
    であると書かれています。

    んがっ、そんな話は忘れてしまいましょう。

    Ethnaとは、PHPで書かれたプログラムである。
    これで十分です。

    人気記事

    このアーカイブについて

    このページには、2009年6月に書かれたブログ記事が新しい順に公開されています。

    前のアーカイブは2009年5月です。

    次のアーカイブは2009年7月です。

    最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

    最近の人気記事