トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

Phalcon

PHPの数あるフレームワークの中で、かなり高速なフレームワーク。
C拡張でPHPに組み込まれる形で提供されるため、普通にソース丸見えな他のフレームワークとは異なる。

php-framework-benchmark
https://github.com/kenjis/php-framework-benchmark

インストール(raspbian)

raspbian(wheezy)にインストールしてみる。

Download Phalcon
https://phalconphp.com/ja/download

apt-getでインストールできるのはDebian 6.0(squeeze)用みたいなのでソースからコンパイル。
Phalcon-2.0.8のコンパイルはPHP-5.3.1で可能だが、バグとかセキュリティ的にPHP-5.3.11以降が推奨らしい。
ちなみにwheezyはPHP-5.4.45でOK。

# apt-get install php5-dev php5-mysql gcc libpcre3-dev git make

# git clone --depth=1 git://github.com/phalcon/cphalcon.git
# cd cphalcon/build
# ./install

raspbianでは/usr/lib/php5/20100525+lfsに拡張が入っていて、phalcon.soもここにできる。
実機がPi1だと完了まで30分ぐらい。

nginx + php5-fpm構成なので、php5-fpmのルールに倣ってiniを作成。めどいから先にインスコしてたmysql.iniをコピる。

# cd /etc/php5/mods-available
# cp mysql.ini phalcon.ini

# vim phalcon.ini
; configuration for php Phalcon module
; priority=20
extension=phalcon.so

有効にする。

# cd /etc/php5/fpm/conf.d
# ln -s ../../mods-available/phalcon.ini 30-phalcon.ini
# /etc/init.d/php5-fpm restart

あとはドキュメントルートのindex.phpにphpinfo()あたりを書いて出力にphalconがあればOK。

インストール(XAMPP for Windows

まずXAMPPのVCとPHPのバージョンを確認。
phpinfo()で最初のPHPバージョンとZend Extension BuildやPHP Extension Buildあたりを見る。
例としてTS,VC11とあればThread Safe版のVusial Cでコンパイルされたもの。
VCでコンパイルされたものを実行するにはVCのruntimeが必要だけど、VC9はWindows XPまで
VC11はWindows Vista以降って認識で良いと思う。

WindowsXAMPPは32bit用なのでx86のdll、NTSはNon Thread Safe版で(NTSが付かないもの)を選ぶこと。
あとは先に調べたPHPのバージョン(5.5とか5.6のあたり)と、VC(9か11)を合わせていればOK。

Download Phalcon for Windows
https://phalconphp.com/ja/download/windows

dllを/xampp/php/ext/php_phalcon.dllに配置し、/xampp/php/php.iniに追記する。

extension=php_phalcon.dll

チュートリアル

tutorial
https://docs.phalconphp.com/en/latest/reference/tutorial.html

チュートリアルのとおりのディレクトリ構成を作り、ファイルにソースをコピペしていけば動作確認できるのは素晴らしい。

しかしSignupへのアクセスから動かなくなる。

http://localhost/signup

へアクセスしてもSignupControllerのindexActionが呼ばれない。というかIndexControllerにfooActionを作っても呼び出せなかった(汗)。

とりあえずindex.phpに

use Phalcon\Mvc\Router;

$di->set('router', function() {
    $router = new Router();
    $router->setUriSource(Router::URI_SOURCE_SERVER_REQUEST_URI);
    $router->handle();
    return $router;
});

を追加したら動くようになった。

デフォルトではApacheでの動作を想定して.htaccessで

RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L]

としてあり、nginxで同等の設定をしなかったから動かなかったようだ(汗)。

DI\FactoryDefault

Phalcon\DIは空の状態から注入。
Phalcon\DI\FactoryDefaultは必要そうなものが最初から注入されている。以下

router / Phalcon\Mvc\Router / shared
dispatcher / Phalcon\Mvc\Dispatcher / shared
url / Phalcon\Mvc\Url / shared
modelsManager / Phalcon\Mvc\Model\Manager / shared
modelsMetadata / Phalcon\Mvc\Model\MetaData\Memory / shared
response / Phalcon\Http\Response / shared
cookies / Phalcon\Http\Response\Cookies / shared
request / Phalcon\Http\Request / shared
filter / Phalcon\Filter / shared
escaper / Phalcon\Escaper / shared
security / Phalcon\Security / shared
crypt / Phalcon\Crypt / shared
annotations / Phalcon\Annotations\Adapter\Memory / shared
flash / Phalcon\Flash\Direct / shared
flashSession / Phalcon\Flash\Session / shared
tag / Phalcon\Tag / shared
session / Phalcon\Session\Adapter\Files / shared
sessionBag / Phalcon\Session\Bag
eventsManager / Phalcon\Events\Manager / shared
transactionManager / Phalcon\Mvc\Model\Transaction\Manager / shared
assets / Phalcon\Assets\Manager / shared

sessionBagだけsharedじゃない。

$di = new Phalcon\DI\FactoryDefault();
$router = $di->getShared('router');
$router->setUriSource(Phalcon\Mvc\Router::URI_SOURCE_SERVER_REQUEST_URI);

みたいな使い方。

APIリファレンス

Phalcon API Documentation
https://api.phalconphp.com/

オリジナルソース

挙動がわからなければ、ソースを読めば良いじゃない。

cphalcon
https://github.com/phillipmadsen/cphalcon

ハマりどころ

Phalcon 2.0.8で確認。

 キャメルケースの処理

コントローラはfoo-barならFooBarControllerとなる。
アクションはfoo-barならルーターではじかれfoo_barはfoo_barActionとなる。

アクション名のキャメルケース化
https://docs.phalconphp.com/ja/latest/reference/dispatching.html#id5
ルーティング
https://docs.phalconphp.com/ja/latest/reference/routing.html

 Page Not Found

PhalconでPage Not Foundしようとすると

Routerでアンマッチ → Page Not Found
↓
RouterでマッチしたがControllerが存在しない → Page Not Found
RouterでマッチしたがActionが存在しない → Page Not Found
↓
RouterでマッチしController::Actionは存在するが、テンプレートがない → Page Not Found

と何回もチェックしないといけない。

ルーターの初期設定の挙動

http://localhost/foo/hoge/page/1
↓
http://localhost/[controller:]/[action:]/[params:]...

コントローラー名は

foobar または foo-bar または foo_bar
↓
○ FoobarController
○ FooBarController

だけど
http://localhost/foobar → views/foobar
http://localhost/foo-bar → views/foo-bar
http://localhost/foo_bar → views/foo_bar

まぁPHP自体がクラス名、メソッド(または関数)名の大文字小文字を区別しないので、挙動としてはハイフンやアンダーバーを除去してコントローラ名とする。これは納得。
viewに関してはURLのそのままフォルダ名を探すので、存在しなければ空白ページを返す。これが非常にわかりづらい。

続いてアクション名に関しては

foo-bar
↓
デフォルトのコントローラ、アクションが呼ばれる(IndexController、indexAction)

foo_bar
↓
○foo_barAction
×fooBarAction

ハイフンに関しては意味不明。普通トップページは最初に作ってるので表示されてしまいエラーにすらならない。
アンダーバーについては除去されず、そのままアクション名となる。存在しなければhandlerのexceptionとなる。

ということで調べた。

ルーティング
https://docs.phalconphp.com/ja/latest/reference/routing.html
/:controller   /([a-zA-Z0-9\_\-]+)
/:action       /([a-zA-Z0-9\_]+)

これが初期設定で

コントローラ名がアンマッチ → デフォルトのコントローラ、アクション
アクション名が アンマッチ → デフォルトのコントローラ、アクション

コントローラ名がマッチ、クラスが存在しない     → handler exception
アクション名が マッチ、アクションが存在しない → handler exception

となるが、ユーザーがwebページのリンクをクリックしているような場合は、製作者がミスしないかぎりアンマッチはない。
しかし、直接URLを入力してくるユーザーやロボットの総当りなど、普通に考えたらアンマッチだろうが存在しなかろうがPage Not Foundが普通なのに、そうはなっていない。

チュートリアルのinvoでは

$di->set('dispatcher' => function () use($id) {
    $eventsManager = new EventsManager;
    :
    $eventsManager->attach('dispatch:beforeException', new NotFoundPlugin);
    :
    return $dispatcher;
});

class NotFoundPlugin extends Plugin
{
    public function beforeException(Event $event, MvcDispatcher $dispatcher, Exception $exception)
    {
        if ($exception instanceof DispatcherException) {
            switch ($exception->getCode()) {
                case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND: // ← コントローラが存在しない
                case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:  // ← アクションが存在しない
                    $dispatcher->forward(array(
                        'controller' => 'errors',
                        'action' => 'show404'
                    ));
                    return false;
            }
        }

        $dispatcher->forward(array(
            'controller' => 'errors',
            'action'     => 'show500'
        ));
        return false;
    }
}

というようになっており、ルーターにマッチしない場合はデフォルト(トップページ)を表示する。

なのでルーター側は

$router = $di->getShared('router');
$router->notFound(array(
    'controller' => 'errors',
    'action' => 'show404',
));

とする必要があるが、$router->notFound()すると$router->setDefaults()やルート無し'/'でアクセスされた場合のトップページ表示が死ぬので

$router = $di->getShared('router');
$router->add('/', array( // setDefaults()で補完されてもmatchしない為に追加
    'controller' => 'index',
    'action' => 'index',
));
$router->setDefaults(array( // urlで省略された際に補完する設定値
    'controller' => 'index',
    'action' => 'index',
));
$router->notFound(array( // routerにmatchしなかった場合
    'controller' => 'errors',
    'action' => 'show404',
));

としなければならない。

で、ラスト。invoでは

http://localhost/index/index  → トップページ
http://localhost/in-dex/index → Unauthorizedのページ

となる。

いままでの処理で行くと、indexとin-dexは同じIndexControllerの呼び出しなのでExceptionは発生しない。
ところが認証で表示されるページはURLから抽出されたin-dexのままなので、roleに設定されたaccess listのindexにマッチしない。
よってUnauthorizedのページ表示。

要するにrouterがcontrollerやactionとしてURLから抽出した部分を整形しないので、ちょっとした違いを吸収できず、下流の処理に影響が出る。なんか微妙に使えない(汗)。

ViewのRenderLevelとイベントview:notFoundView

結論から書くと、view:notFoundViewだったらPage Not Foundに転送しようとかめどい。

URLからコントローラ、ビューのおさらい。

/foobar/baz  → FooberControllerのbazAction → views/foobar/baz.phtml
/foo-bar/baz → FoobarControllerのbazAction → views/foo-bar/baz.phtml

というようになる。正確には

/foo-bar/baz → FooBarControllerのbazAction → views/foo-bar/baz.phtml

かもしれないが、PHPではクラス名等の大文字小文字を区別しない。

/foo-bar/baz  → FooberControllerのbazAction → views/foobar/baz.phtml

だと処理は正常に行われて画面は真っ白になる。

で、本題のRenderLevelってのは初期値で5になってて、これはViewの初期動作で

views/foobar/baz.phtml     → 1 主にコンテンツ部分
views/layouts/foobar.phtml → 3 主にメニューとかナビの部分
views/index.phtml          → 5 HTMLのHEADとかの部分

が呼ばれるが、ファイルが無ければ空白を返す。
5回のループでページソースの中心から外に向けて組み込んで出力する。

で、最初の話に戻って、ページは存在しないんだからPage Not Foundにしたいと思い、view:notFoundViewイベントに処理を書いたら無限ループ。
パスがない2、4は無視されるようですが、baz.phtmlのみってこともあるし。

$eventsManager->attach('view:notFoundView', function($event, $view) {
    if($view->getCurrentRenderLevel == 1) {
        // view rendering
        $view->start();
        $view->render('errors', 'e404');
        $view->finish();
        echo $view->getContent();
        exit;
    }
});
$view->setEventsManager($eventsManager);

とやればテンプレートを使っていない場合の処理を書けるが、コントローラでビューを使わない場合は

public function indexAction() {
    $this->view->disable();
    // 処理
}

としないといけない。

request

コントローラにて値を取得する場合。

$data = $this->request->getPost('data', array('trim', 'striptags'), '初期値');

フォームから送られるデータが多次元配列からなる場合には対応しない。
詳しくはcphalconからcphalcon/phalcon/http/request.zepで、getPost()とgetHelper()参照のこと。

メソッドと一番目の引数は

  • getPost('data') = $_POST['data']
  • getQuery('data') = $_GET['data']
  • get('data') = $_REQUEST['data']
  • getPut('data') = $_PUT['data']

二番目の引数はサニタイズで最初から用意されているものは

  • email
  • int ・・・ 0-9+-
  • float ・・・ 0-9+-.
  • alphanum ・・・ a-zA-Z0-9
  • string ・・・ HTMLタグをエスケープ
  • striptags ・・・ HTMLタグを除く
  • trim
  • lower
  • upper

array()を使うことにより複数設定可能。

三番目の引数は初期値を設定しておきたい場合。
サニタイズの必要は無いが初期値は設定したいなら

$page = $this->request->getQuery('page', null, 1);

dispatcher

/controller/action/param/param/param...

というようにURLにパラメータ(param)を設定した場合は

$id = $this->dispatcher->getParam(0, 'int', 1);

0は最初のparam、1は次のparam、2は次の次のparam... みたいな感じ。