Ethna: 2009年6月アーカイブ
自由度が高い
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 (要約版)
すごいシンプルになりましたね。
これがEthnaの「背骨」にあたる部分です。
要約版とは言っても、このコードの状態で十分りっぱに機能します。
実際私が携わっている商用Webサイトでは、この状態でも完全に動きます。
さて、_trigger_WWW内で行われていることが、より明確に見えてきましたね。
Ehtna_Controllerはこのくらいにして、次回以降、個別のクラスの中をのぞいて見ましょう。
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はこのくらいにして、次回以降、個別のクラスの中をのぞいて見ましょう。
Windows上で、Ethna+PostgresqlでWebアプリを作っていて、コマンドライン用のアクションスクリプトを生成して、さあ実行しようとすると、こんなエラーが出ることがあります。
恐らく、コマンドラインから起動したPHPで、pg_connect()が使えないのが原因です。
それは、コマンドライン用のphp.ini(Web用のphp.iniとは別物)の中で"php_pgsql.dll"が読み込まれていないのが原因です。
解決法はこちら↓
エラー
>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とか何もできなくなって一瞬憂鬱になりますが、簡単に回避できるのでご安心を。
私の場合、
環境によって違うので、あくまでも参考ということでお願いします。
"rem"というのはコメントアウトのことです。(phpでの"//"に相当)
なお、この書き方の場合は、必ずコマンドプロンプト上のカレントディレクトリを"C:\www\myproject\"にしてからEthnaコマンドを叩く必要があります。(ちょっと不便)
なぜこのようにしてるかというと、PC内にEthnaプロジェクトが3つも4つもあるからです。
どんだけ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.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
Ethnaのプログラムの流れがすべてここに凝縮されています。
ここでは、次のような重要なことが行われています。
ここを読み解けば、Ethnaの全体像を把握することができます。
Ethnaで作ったアプリが動かないときは、ここでecho('hello')とか仕込んでおけば、プログラムがどこまで実行されたかわかります。
なお、この関数は最後に0を返しますが、この0はどこにも行きません。
呼び出し元のtrigger()関数のなかで空しく捨てられます。
そしてプログラム全体が終了します。
まとめると、
そしてほとんどの処理は_trigger_WWW()の中で行われています。
次回以降、この_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()を詳しく見ていきましょう。
Ethna_Controller::trigger
実はちょっとはしょりましたが、$this->getGateway() は "GATEWAY_WWW"という文字列を返します。
これでswitch文は簡単になります。
また連載1回目でも触れましたが、この連載では「フィルタ、CLI,XMLRPC, SOAPなどには触れない」ことにしています。
よって、「なんとかフィルタ」と書いてある部分はばっさり削除できます。
実際、Sampleプロジェクトではフィルタを定義してないので、この部分は不要です。
不要部分を削除するとこうなります。
実際の値を放り込むとこうなります。
_trigger_WWW()関数を実行しているだけのようです。
つづく
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()関数を実行しているだけのようです。
つづく
前回の続きより。
(オブジェクトが何かわからない人は、「データ」だと思ってください。オブジェクト=「関数付きデータ」です。)
$c->trigger()で、Sample_Controllerのtrigger関数(メソッド)が実行されます。
では、Sample_Controller.phpを開いて、trigger関数を探しましょう。
ない。
ということは、Ethna_Controllerのtrigger関数が( $cのメソッドとして )呼ばれるはずです。
Ethna_Controller::triggerは・・・あった。
私のようなへっぽこプログラマは、長い関数を見ると尻込みしてしまいます。
つづく
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', "");
}
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とフロントコントローラパターンに基づいたスタンダードなウェブアプリケーションフレームワークであると書かれています。
んがっ、そんな話は忘れてしまいましょう。