Dietcubeの使い方を軽く解説する

  • 14
    いいね
  • 0
    コメント
  • この記事はPHP Advent Calendar 2016における18日目の記事です
  • 遅刻して大変恐縮です、すみませんでした

この記事の目的

  • DietcubeというPHP製フレームワークの基礎的な使い方を丁寧に説明することです
  • PHP5.6.18で動作確認をしています

Dietcubeとは

  • DietcubeとはOSSなMVC PHPフレームワークです
  • 株式会社メルカリが公開しています

setup

  • 上記のレポジトリとは別にDietcubeのskeltonが公開されており、そちらを利用すればサクッとアプリケーションを作ることが出来ます

では早速やってみましょう

  • 適当なディレクトリで以下のコマンドを打ちます
composer create-project dietcube/project -s dev sample-project
  • 以下のようなメッセージが出れば準備完了です

Screen Shot 2016-12-24 at 18.15.12.png

  • メッセージに従ってコマンドを打ち、ブラウザで確認してみましょう

Screen Shot 2016-12-24 at 19.52.00.png

ディレクトリ構成

  • 基本的に /app 以下のファイルを編集していくことになります

Screen Shot 2016-12-24 at 19.58.07.png

アプリケーションの基礎

routingを切る

  • まずは簡単に新しいページを作ってみましょう
  • app/Route.phpを以下のように変更します
app/Route.php
diff --git a/app/Route.php b/app/Route.php
index 8234df6..e11a458 100644
--- a/app/Route.php
+++ b/app/Route.php
@@ -14,6 +14,7 @@ class Route implements RouteInterface
     {
         return [
             ['GET', '/', 'Top::index'],
+            ['GET', '/hello', 'Hello::index'],
         ];
     }
 }
  • 配列の中身は順に「http method」「path」「コントローラー名::アクション名」を表しています
  • 上記で追加した一行の内容をブラウザで見れるようにするため、HelloControllerindexアクションを作成します

controllerを用意する

  • HelloControllerを以下のように作成します
app/Controller/HelloController.php
<?php

namespace SampleProject\Controller;

use Dietcube\Controller;

class HelloController extends Controller
{
    public function index()
    {
        return $this->render('hello/hello_world', [
            'message' => 'Hello, World !',
        ]);
    }
}
  • $this->render();第一引数には、使いたいviewテンプレートを、第二引数にはviewで使いたい変数名と変数の内容を渡してあげます
  • ではhelloディレクトリの中にあるhello_worldview.html.twigテンプレートを作成しましょう

viewテンプレートを作る

  • viewのテンプレートエンジンにはTwigが使われています
  • 後に詳しく説明するので、一旦この内容だけでファイルを作ってしまいます
app/template/hello/hello_world.html.twig
{{ message }}

作ったページを確認する

  • http://0.0.0.0:8999/helloへアクセスすれば、以下のような画面が見れるはずです

Screen Shot 2016-12-24 at 20.49.46.png

  • これで新しいページを作成することが出来ました
  • viewに渡されたmessageという名前の変数の内容が、ちゃんと展開されていることがわかります

Serviceクラスの使い方

  • 新しいページを作れるようになり、そのページに対してデータを渡して表示できるようになりました
  • では、表示するデータ自体の扱いをDietcube的にどうするべきかを考えてみましょう

例:おみくじの処理

  • 例えば、おみくじを作ることにします
  • 今日の日付が
    • 3の倍数の時:大吉
    • 11の倍数の時:凶
    • 上記に当てはまらない時:吉
  • というロジックを書くことにします
app/Service/OmikujiService.php
<?php

namespace SampleProject\Service;

use Dietcube\Components\LoggerAwareTrait;

class OmikujiService
{
    use LoggerAwareTrait;

    public function getResult($today)
    {
        if (0 === $today % 3) {
            return '大吉';
        }

        if (0 === $today % 11) {
            return '凶';
        }

        return '吉';
    }
}

controllerからおみくじの処理を呼び出す

  • ServiceクラスはPimpleというDIコンテナによってそれぞれ管理されます
  • 先程作ったOmikujiServiceservice.omikujiという名前でコンテナに格納し、HelloControllerからgetResult()呼んでみます
app/Application.php
diff --git a/app/Application.php b/app/Application.php
index 72c29bd..b19d5d1 100644
--- a/app/Application.php
+++ b/app/Application.php
@@ -7,6 +7,7 @@ namespace SampleProject;

 use Dietcube\Application as DCApplication;
 use Pimple\Container;
+use SampleProject\Service\OmikujiService;
 use SampleProject\Service\SampleService;

 class Application extends DCApplication
@@ -25,5 +26,12 @@ class Application extends DCApplication

             return $sample_service;
         };
+
+        $container['service.omikuji'] = function ($container)  {
+            $sample_service = new OmikujiService();
+            $sample_service->setLogger($container['logger']);
+
+            return $sample_service;
+        };
     }
 }
app/Controller/HelloController.php
diff --git a/app/Controller/HelloController.php b/app/Controller/HelloController.php
index 80d9bff..4f0ced1 100644
--- a/app/Controller/HelloController.php
+++ b/app/Controller/HelloController.php
@@ -3,15 +3,18 @@
 namespace SampleProject\Controller;

 use Dietcube\Controller;
+use SampleProject\Service\OmikujiService;

 class HelloController extends Controller
 {
     public function index()
     {
+        /** @var OmikujiService $omikuji_service */
+        $omikuji_service = $this->get('service.omikuji');
+        $today = (new \DateTime())->format('d');

         return $this->render('hello/hello_world', [
-            'message' => 'Hello, World !',
+            'message' => 'あなたの今日の運勢は' . $omikuji_service->getResult($today) . 'です。',
         ]);
     }
 }
  • http://0.0.0.0:8999/helloへアクセスすれば、以下のような画面で、現在の日付に応じた結果が出力されるはずです
    • 執筆現時点は12月24日なので3で割り切れ、大吉が表示されています

image

  • 処理をcontrollerにべた書きせず、Serviceクラスとcontainerで適切に管理することによって、fatなcontrollerを作りにくくする仕組みが、最初からDietcubeには備わっています

Serviceクラスのテストを書く

  • このおみくじは本当に正しく自分たちの想定通りの結果を返すのでしょうか?
  • phpunitを用いてテストを書いてみることにします
tests/Service/OmikujiServiceTest.php
<?php

namespace SampleProject\Service;

class OmikujiServiceTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @param string $expect
     * @param int $day
     * @dataProvider dataProvider
     */
    public function testGetResult($expect, $day)
    {
        $omikuji_service = new OmikujiService();
        $this->assertEquals($expect, $omikuji_service->getResult($day));
    }

    public function dataProvider()
    {
        return [
            ['大吉', 12],
            ['凶', 11],
            ['吉', 10],
        ];
    }
}
  • project rootで./vendor/phpunit/phpunit/phpunit tests/Service/OmikujiServiceTest.phpなどと実行してみます

Screen Shot 2016-12-25 at 20.10.57.png

  • 無事、このおみくじのテストが実行できました
  • このように適切にServiceクラスにビジネスロジックが押し込まれていれば、テストも書きやすくなります

logを吐く

  • ServiceクラスでLoggerAwareTraitをuseしているので、$this->loggerと書けば使えるようになっています。
    • Traitについてのリファレンスはこちら
  • loggerにはMonologが使われています
  • logを吐く場所やlevelの設定はapp/config/config{_develop}.phpなどのconfigファイルで行なえます

動的なroutingを作る

  • 例えば、誕生日を元に、先程のおみくじのロジックを利用するとしたら以下のようになります
diff --git a/app/Route.php b/app/Route.php
index e11a458..47830b6 100644
--- a/app/Route.php
+++ b/app/Route.php
@@ -15,6 +15,7 @@ class Route implements RouteInterface
         return [
             ['GET', '/', 'Top::index'],
             ['GET', '/hello', 'Hello::index'],
+            ['GET', '/birthday/{month}/{day}', 'Hello::birthday'],
         ];
     }
 }
diff --git a/app/Controller/HelloController.php b/app/Controller/HelloController.php
index 4f0ced1..544396d 100644
--- a/app/Controller/HelloController.php
+++ b/app/Controller/HelloController.php
@@ -17,4 +17,14 @@ class HelloController extends Controller
             'message' => 'あなたの今日の運勢は' . $omikuji_service->getResult($today) . 'です。',
         ]);
     }
+
+    public function birthday($month, $day)
+    {
+        /** @var OmikujiService $omikuji_service */
+        $omikuji_service = $this->get('service.omikuji');
+
+        return $this->render('hello/hello_world', [
+            'message' => "あなたの誕生日、${month}月${day}日の運勢は" . $omikuji_service->getResult($month + $day) . 'です。',
+        ]);
+    }
 }

Screen Shot 2016-12-25 at 20.36.05.png

  • 同一Controller内の別のmethod内、また別のController内でも既存のロジックが再利用しやすいようになっています。

viewファイルの継承

  • viewファイルも再利用しましょう
  • Twigの継承の機能を使います
    • ググればたくさん使用例が出てくると思うので詳細な解説はしません
diff --git a/app/template/index.html.twig b/app/template/index.html.twig
index 125db8e..f6cb677 100644
--- a/app/template/index.html.twig
+++ b/app/template/index.html.twig
@@ -54,10 +54,10 @@
   <body>

     <div class="main">
-
-      <h1>SampleProject</h1>
-      <p>{{ sample_hello }}</p>
-
+      {% block main %}
+        <h1>SampleProject</h1>
+        <p>{{ sample_hello }}</p>
+      {% endblock main %}
     </div>

     <footer class="footer">
diff --git a/app/template/hello/hello_world.html.twig b/app/template/hello/hello_world.html.twig
index 58f848b..9dec404 100644
--- a/app/template/hello/hello_world.html.twig
+++ b/app/template/hello/hello_world.html.twig
@@ -1 +1,6 @@
-{{ message }}
\ No newline at end of file
+{% extends 'index.html.twig' %}
+
+{% block main %}
+  <h1>おみくじ</h1>
+  <p>{{ message }}</p>
+{% endblock main %}
  • 入れ替えたい部分をblockとして定義し、テンプレートを継承した上で新しいブロックを定義します
  • こんな感じになりました

Screen Shot 2016-12-25 at 20.45.18.png

Controller.php

  • jsonでレスポンスを返す、redirectをする、POSTパラメータを受け取る、query stringを取り出す、などはどうやって実現するのでしょうか?
  • 各Controllerの継承元のController.phpに存在するmethodを上手く使えば問題なく実装できます
  • 薄くて読みやすいのでぜひ一度読んでみるといいかと思われます

終わりに

  • 基礎的な部分を一通り紹介してみました
  • 各々のプロダクトに向けてカスタマイズし易い構成という印象を持ちます
    • フルスタックなフレームワークを使うほどではないけど、ある程度必要なものが備わっていてほしい、という場合に使いやすいフレームワークなのではないでしょうか
  • 本当はsessionやDB、validationやlogin機能などに関する解説も書ければよかったのですが、結構重そうなのでまた別の機会に…
  • 本記事で書いたコードはsampleとしてGitHubに上げたので、興味のある方はcommit logなどを追ってみて下さい
この投稿は PHP Advent Calendar 201618日目の記事です。