Help us understand the problem. What is going on with this article?

Phalconで実装する際に躓いたことのetc

jpgについて その3 でも書こうかと思っていたが、需要無いだろうし、ちょっと書くには準備が必要なので、今回は直近で触って齷齪したphpのフレームワーク「phalcon」のちょっとしたことを書いていこうかと思います。

Phalconでコード書いてて思ったことはまず記事の少なさと、記事があっても古くてリンク切れってのが散見されたこと。あと、各環境でのDIとかの書き方によって、そのまま適用するのが難しいってこともあった・・・
cakeとかに比べてベンチマークで20倍の速度とかあるらしいけど、こうまでしてPHPを使う必要ってあるんかなーってのが正直な感想です_(:3」∠)_

※この記事の執筆者の環境は PHP7.2 Phalcon 3.4.3

Nginx + Phalcon

最初らへんルーティングはされるのにパラメータが取れねぇ(;・∀・)
ってなってました。
ちゃんとNginx側のconfに

        location / {
            try_files $uri $uri/ @rewrite;
        }

        location @rewrite {
            rewrite ^/(.*)$ /index.php?_url=/$1;
        }

みたいな感じで「_url=xxxx」なrewriteを挟んであげる必要がありました。

ルーティングに記載してないアクセスは404にしたい

設定してないURLにアクセスが来た際には問答無用で404を表示したいなーってことで、ControllerBaseあたりに書くのかと思ってたら、DIのdispatcherで握りつぶす感じでした。
diを登録しているであろう service.php あたりで

use Phalcon\Dispatcher;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Mvc\Dispatcher\Exception as DispatchException;

$di->setShared('dispatcher', function () {
        // Create an EventsManager
        $eventsManager = new EventsManager();

        // Attach a listener
        $eventsManager->attach(
            'dispatch:beforeException',
            function (Event $event, $dispatcher, Exception $exception) {
                // Handle 404 exceptions
                if ($exception instanceof DispatchException) {
                    $dispatcher->forward(
                        [
                            'controller' => 'index',
                            'action'     => 'notFound',
                        ]
                    );

                    return false;
                }

                // Alternative way, controller or action doesn't exist
                switch ($exception->getCode()) {
                    case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                    case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
                        $dispatcher->forward(
                            [
                                'controller' => 'index',
                                'action'     => 'notFound',
                            ]
                        );

                        return false;
                }
            }
        );
        $dispatcher = new MvcDispatcher();
        // Bind the EventsManager to the dispatcher
        $dispatcher->setEventsManager($eventsManager);

        return $dispatcher;
    }
);

とし、あとは IndexController に notFoundAction で 404 を表示するページを設置する感じ。

composer を使いたい

その他のPHPのパッケージを併せて使いたいので、composerを利用する。
phalcon dev-tool でphalconのモジュールを作成すると PSR-4 には則らない形でディレクトリが掘られるが、そこの管理とcomposerの管理は切り離して考えて良いみたい。
通常通り、composer.jsonとcomposer.lockをgit管理下に置き、phalcon側のloaderを読み出しているindex.phpあたりに

    /**
     * Include Autoloader
     */
    include APP_PATH . '/config/loader.php';
    include BASE_PATH . '/vendor/autoload.php';

な感じで phalcon側のloaderと並列して配置します。

formの使い勝手

フロント側のvolt templateと併せてphalconのデフォルトのform機能が用意されており、単純なformとして利用する分には、必要なformのelementの用意もvalidationも記載できるので十分利用できそうではある。
が、Ajax通信やモーダル表示、validationなどをフロントにやらせようとjava scriptをごりごり書き始めると、classやidの管理が分離されてしまうため、とても取り扱いづらいものになってしまった。

フロントでjsをごりごりしたい場合には、フレームワークを入れつつ単純なPHP側からの値渡しぐらいの用途に留めるのが良さそう。

Controllerをネストさせたい

少々ソースコードが大きくなってくるとサブディレクトリを掘って、その中にControllerを配置したいこともあるかと思います。
(元の設計時点で分けるならphalconのmodule使えやってことで良いと思いますが、整理のためにサブディレクトリを切りたい、そんな要望です。)

どうやらphalconでControllerをサブディレクトリ下に置くにはnamespaceをきるしか方法が無い模様。(それ以外の方法があるなら教えてえろい人)
moduleで切り分ける場合にもがっつりnamespaceきるし、そういう思想なのかもしれない。

簡単な例として、ユーザーのパスワードをリセットするControllerをネストさせてみたいとします。
ディレクトリの構造としてはこんな感じ(例だからねw)

controllers/ControllerBase
controllers/UserController
controllers/user/PasswordController
views/user/create.volt
views/user/password/reset.volt

ControllerBaseやUserControllerはそのまま利用できますが、userディレクトリにネストしたPasswordControllerは以下のような形で記述。
(namespaceだから自分が分かりやすいようにきってね)

namespace User\Controllers;

class PasswordController extends \ControllerBase
{
    public function resetAction()
    {
        // 処理
    }
}

その上でphalconのloaderにnamespaceの情報をロードするよう追記。
($config->application->controllersDir の部分は自分の環境用ので置換してね)

$loader->registerNamespaces(
    [
        'User\Controllers' => $config->application->controllersDir.'user/'
    ]
);

で、最後にrouterに設定したnamespaceの情報を含めてmountしまふ。

use Phalcon\Cli\Router\Route;
use Phalcon\Mvc\Router;
use Phalcon\Mvc\Router\Group as RouterGroup;

$user_password = new RouterGroup([
    'namespace' => 'User\Controllers',
    'controller' => 'password'
]);
$user_password->setPrefix('/user/password');
$user_password->addGet('/reset', ['action' => 'reset']);
$router->mount($user_password);

まぁ、なんて面倒くさい(;・∀・)
追加で、views/user/password 配下の reset.volt を自動でactionのviewとして関連付けるには、diのvolt周りを設定しているところに

$di->setShared('view', function () {
    $config = $this->getConfig();

    $view = new View();
    $view->setDI($this);
    $view->setViewsDir([
        $config->application->viewsDir,
        $config->application->viewsDir.'user/'
    ]);

    $view->registerEngines([
        '.volt' => function ($view) {
            $config = $this->getConfig();

            $volt = new VoltEngine($view, $this);

            $volt->setOptions([
                'compiledPath' => $config->cache->cacheDir,
                'compiledSeparator' => '_',
                'compileAlways' => $config->cache->compileAlways
            ]);

            return $volt;
        },
        '.phtml' => PhpEngine::class

    ]);

    return $view;
});

な感じで setViewsDir に userディレクトリ配下を追加してあげる必要がある模様。
ただ、コントローラー名やアクション名がかぶる場合は正しく自動判定してくれないため、上記方法では無くネストさせたコントローラー側で

public function afterExecuteRoute()
{
    $views_dir = $this->view->getViewsDir();
    $this->view->setViewsDir([
        $views_dir,
        $views_dir.'user/'
    ]);
}

のような感じでviewsのディレクトリの後付け設定を行うべし。

※ 以下 2020/02/14 追記分

メールの送信内容にvoltを使いたい

voltテンプレートを使ってメールの内容を動的に生成することについては他の記事を参考にしてもらいたいが、困ったのは既存のDIのviewを使ってメールの内容を生成した後、通常のフロントエンド側のviewを生成した際にCSS等が効かなくなることだった。

どこかに載っているように既存のviewをcloneしてrenderし、getContentしても結果は同じ・・・苦肉の策でメール送信後は別のビューだけのページにリダイレクトするみたいなことをしてましたが、別個のSimple Viewにメールテンプレートのレンダリングをまかせることで回避できますた。
既存のviewのDIに並列してsimple_viewなDIを設定。

use Phalcon\Mvc\View\Simple;
use Phalcon\Mvc\View\Engine\Php as PhpEngine;
use Phalcon\Mvc\View\Engine\Volt as VoltEngine;

$di->set('simple_view', function() {
    $config = $this->getConfig();
    $view = new Simple();
    $view->setDI($this);
    $view->setViewsDir($config->application->viewsDir);
    $view->registerEngines([
        '.volt' => function ($view, $di) use ($config) {
            $volt = new VoltEngine($view, $di);
            $volt->setOptions([
                // options
            ]);
            return $volt;
        },
        '.phtml' => function ($view, $di) use ($config) {
            return new PhpEngine($view, $di);
        },
    ]);
    return $view;
});

emailsというディレクトリ配下のvoltを使って、メールのbodyをレンダリングするときはこんな感じ

public function makeMailBodyByTemplate($template_name, $params)
{
    if (empty($template_name)) {
        return false;
    }
    $view = $this->simple_view;
    $view->render('emails/'.$template_name, $params);
    return $view->getContent();
}

これでメール送信の際にわざわざリダイレクトしなくても良くなりまふ。


なんか他にも結構引っかかったところがあった気がするけど、とりあえずこんなもんで・・・
phalconと戦っていて、最終的に行き着くのは、公式ドキュメントでも記事でもなく、Cのソースコードなのだわ\(^o^)/

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away