コネヒト開発者ブログ

コネヒト開発者ブログ

もしDHHさんがCakePHPのコントローラーを書いたら

f:id:itosho525:20170601124347p:plain

こんにちは!乃木坂46の西野七瀬さんと誕生日が同じサーバーサイドエンジニアの@itoshoです。

僕は元々PHPerなのですが、ここ1〜2年くらいRubyを書く機会が多かったこともあり、最近はRubyでいいな!と思った考え方や技術をPHPに輸入することにハマっています。

そこで今日は少し古い記事になるのですが DHHはどのようにRailsのコントローラを書くのか を参考にして、Ruby on Rails(以下Rails)の産みの親であるDHHさんのコントローラーの書き方をCakePHPに取り入れてみたいと思います。

DHHさんのコントローラーの書き方

そこに至るまでの考えなどの詳細は元記事をご覧いただければと思いますが、要約すると、

RESTの原則に従う場合、コントローラはデフォルトのCRUDアクションだけを使い、その他のアクションは新たに専用のコントローラを作成する。

という考え方になります。

DHHさん流コントローラーのメリット

基本的には以下の2つがメリットになるかと思います。

  • コントローラーがスリムになる
    • フルスタックなMVCフレームワークは往々にしてコントローラーがファットになりがちですが、デフォルトのCRUDアクションのみを利用するため、ファットコントローラーを構造的に防止することが出来ます。
  • ルーティングのルールが明確になる
    • カスタムアクションの命名規則が開発者によってバラバラになったり、そもそもそれを防止するための命名規則のガイドラインを作成したりするコストがなくなるので、ルーティングの設計コストを下げることが出来ます。

DHHさんの考えをCakePHPに適用してみる

では、早速DHHさんになりきって、CakePHPのコントローラーを書いていきましょう。 例として、元記事にもあったメールの受信箱を管理するコントローラを書いてみたいと思います。 尚、CakePHPのバージョンは3.4系を利用しており、サンプルコードのためphpDocumentorの記述などは省略しています。

コントローラークラス編

まず、デフォルトのCRUDアクションを持つコントローラークラスを書いていきます。

<?php

namespace App\Controller;

class InboxesController extends AppController
{
    public function index()
    {
        // 一覧を取得する処理
    }

    public function view($id = null)
    {
        // 詳細を取得する処理
    }

    public function add()
    {
        // 新規追加する処理
    }

    public function edit($id = null)
    {
        // 更新する処理
    }

    public function delete($id)
    {
        // 削除する処理
    }
}

Railsのデフォルトアクションは index, show, new, edit, create, update, destroy ですが、CakePHPの場合は index, view, add, edit, delete になります。

ここで新たに未送信のメール一覧が閲覧できる機能を追加しようとなった場合、よくやる実装としては、以下のようなアクションをInboxesControllerに追加するやり方ではないでしょうか。

<?php

public function pendings()
{
    // 未送信の一覧を表示する処理
}

もちろん index アクション内で出し分けるというやり方もあると思いますが、新しいアクションを追加するというアイディア自体はそれほどおかしいものではないと思います。

しかし、DHHさんはこの場合、新しいアクションではなく、以下のような新しいコントローラークラスを作成します。

<?php

namespace App\Controller\Inboxes;

use App\Controller\AppController;

class PendingsController extends AppController
{
    public function index()
    {
        // 未送信の一覧を取得する処理
    }
}

namespace App\Controller\Inboxes でサブリソースのような形で名前空間を切り、その中にコントローラークラスを作成して、デフォルトのCRUDアクションである index アクションを定義します。 ちなみに PendingsController はControllerディレクトリ内にInboxesディレクトリを作成して、その配下に格納しています。

ルーティング編

コントローラークラスが出来たので、次はルーティングの設定を行います。 今回は以下のようなREST的なルーティングが出来るようにしたいと思います。

HTTPメソッド URL 対応するコントローラアクション
GET /inboxes InboxesController::index()
GET /inboxes/123 InboxesController::view(123)
POST /inboxes InboxesController::add()
PUT(PATCH) /inboxes/123 InboxesController::edit(123)
DELETE /inboxes/123 InboxesController::delete(123)
GET /inboxes/pendings PendingsController::index()

このルーティングを実現するには routes.php 内で、

<?php

$routes->connect(
    '/inboxes',
    ['controller' => 'Inboxes', 'action' => 'index', '_method' => 'GET']
);

のようなナイーブな書き方をアクション毎に定義することでも実現出来るのですが、CakePHPでは以下のように書き方でもっとシンプルに定義することが出来ます。

<?php

$routes->resources('Inboxes');
Router::prefix('inboxes', [], function ($routes) {
    $routes->resources('Pendings');
});

このようにシンプルに書けるのもRESTに沿ったデフォルトのアクションを利用しているお陰かなと思います。

以上でDHHさん流コントローラーの完成です!

まとめ

もちろん、このやり方が全てのWebアプリケーションにとっての銀の弾丸にはなり得ませんし、RailsとCakePHPでは考え方が異なる部分もあるので、DHHさんがやっているから!という理由だけで導入するのは危険だと思います。 とは言え、少なくともRESTベースなWebAPIの開発などでは、このルールを導入するメリットはとても大きいのではないかと個人的には感じているので、これから積極的に導入していければなと考えています。

そして、PHP vs Rubyみたいな不毛な宗教戦争はやめて、今後もお互いの言語のよいところを取り入れながら、よりよいプロダクトつくっていきたいと思う次第であります。

参考サイト

jeromedalbert.com postd.cc tech.kitchhike.com qiita.com