CakePHPでRails Tutorialをやってみる〜その2 ほぼ静的なページの作成〜


はじめに

業務でCakePHPを使用することになったので、勉強を始めました。
本を買おうかとも思ったのですが、折角なのでRails TutorialをCakePHPで書き換えながら勉強をしようかと思いました。

自分の整理と、他の方たちへの参考になればと思い、記事にさせていただきます。
とばせる部分はとばしているので、全て実施するわけではありません。

コントローラーの生成

CakePHPにはbakeコマンドという、ファイルを生成してくれるコマンドがあります。
Cake(ケーキ)をbake(焼く)というのはなかなか洒落ているのではないかと思っています。
Bakeでコード生成

今回はコントローラーだけ生成したいと思います。
StaticPagesControllerという名前にします。
一緒にテストコードも生成してくれます。

$ php bin/cake.php bake controller StaticPages

Creating file /home/ubuntu/workspace/cakephp_de_rails_tutorial/src/Controller/StaticPagesController.php
Wrote `/home/ubuntu/workspace/cakephp_de_rails_tutorial/src/Controller/StaticPagesController.php`

生成されたコントローラーにはRESTなindexのようなメソッドが記載されているので、これを消して必要なものを追記していきます。
今回、StaticPagesにはhomehelpaboutcontactを追加します。

StaticPagesController.php
<?php
namespace App\Controller;

use App\Controller\AppController;

/**
 * StaticPages Controller
 *
 *
 * @method \App\Model\Entity\StaticPage[] paginate($object = null, array $settings = [])
 */
class StaticPagesController extends AppController
{

    public function home()
    {

    }

    public function help()
    {

    }

    public function about()
    {

    }

    public function contact()
    {

    }
}

ルーティングの設定

URLからコントローラーのアクションを紐づけるルーティングを設定します。
ファイルはconfig/routes.phpです。
ルーティング

ルートパスと各メソッドの設定をします。

config/routes.php
Router::scope('/', function (RouteBuilder $routes) {
    /**
     * Here, we are connecting '/' (base path) to a controller called 'Pages',
     * its action called 'display', and we pass a param to select the view file
     * to use (in this case, src/Template/Pages/home.ctp)...
     */
-   $routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
+   $routes->connect('/', ['controller' => 'StaticPages', 'action' => 'home']);

    /**
     * ...and connect the rest of 'Pages' controller's URLs.
     */
+   $routes->connect('/home',    ['controller' => 'StaticPages', 'action' => 'home']);
+   $routes->connect('/help',    ['controller' => 'StaticPages', 'action' => 'help']);
+   $routes->connect('/about',   ['controller' => 'StaticPages', 'action' => 'about']);
+   $routes->connect('/contact', ['controller' => 'StaticPages', 'action' => 'contact']);

テンプレートの作成

それぞれのテンプレートを作成します。
とりあえずのテストなので何でも良いかと思いますが、Rails Tutorialのものをそのまま使用しようと思います。
(Railsではないのにリンクができてしまっていますが。。。)

なお、各ファイルはsrc/Templateの下にStaticPagesというディレクトリを作成し、その中で作っていきます。

src/Template/StaticPages/home.ctp
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
  sample application.
</p>
src/Template/StaticPages/help.ctp
<h1>Help</h1>
<p>
  Get help on the Ruby on Rails Tutorial at the
  <a href="https://railstutorial.jp/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
  <a href="https://railstutorial.jp/#ebook"><em>Ruby on Rails Tutorial</em>
  book</a>.
</p>
src/Template/StaticPages/about.ctp
<h1>About</h1>
<p>
  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
  is a <a href="https://railstutorial.jp/#ebook">book</a> and
  <a href="https://railstutorial.jp/#screencast">screencast</a>
  to teach web development with
  <a href="http://rubyonrails.org/">Ruby on Rails</a>.
  This is the sample application for the tutorial.
</p>
src/Template/StaticPages/contact.ctp
<h1>Contact</h1>
<p>
  Contact the Ruby on Rails Tutorial about the sample app at the
  <a href="https://railstutorial.jp/contact">contact page</a>.
</p>

表示確認

サーバーを起動して動作を確認します。

$ bin/cake server -H $IP -p $PORT

下図のようになっていれば問題ないと思います。

PHPUnitでテストコードを書く

PHPUnitのインストール

ここまでのテストコードを書いてみたいと思います。
CakePHPではPHPUnitがサポートされているようなので、そちらを使用します。
テスト

プロジェクトディレクトリのcomposer.jsonを編集します。
suggestの中にあるphpunitと、ついでにcakephp-codesnifferrequire-devに移します。
cakephp-codesnifferは、コーディング規約に沿っているか確認してくれるものです。
特に記事にはしませんが、開発環境であれば合っても良いのかな、と思います。
(Rubyでいうrubocopみたいなものですかね。。。)
https://github.com/cakephp/cakephp-codesniffer

composer.json
"require-dev": {
    "psy/psysh": "@stable",
    "cakephp/debug_kit": "~3.2",
    "cakephp/bake": "~1.1",
+   "phpunit/phpunit": "*",
+   "cakephp/cakephp-codesniffer": "*"
},
"suggest": {
    "markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.",
    "dereuromark/cakephp-ide-helper": "After baking your code, this keeps your annotations in sync with the code evolving from there on for maximum IDE and PHPStan compatibility.",
-   "phpunit/phpunit": "Allows automated tests to be run without system-wide install.",
-   "cakephp/cakephp-codesniffer": "Allows to check the code against the coding standards used in CakePHP."
},

この状態で、Composerをアップデートします。

$ sudo php composer.phar update

これで、phpunitコマンドが使用できるかと思います。
試しに下記が起動できるか確認します。
(恐らくエラーになるかと思います。)

$ vendor/bin/phpunit

テストコードの作成

先ほどbakeで生成されたStaticPagesControllerTest.phpというファイルがあるので、そちらを修正していきます。

tests/TestCase/Controller/StaticPagesControllerTest.php
<?php
namespace App\Test\TestCase\Controller;

use App\Controller\StaticPagesController;
use Cake\TestSuite\IntegrationTestCase;

/**
 * App\Controller\StaticPagesController Test Case
 */
class StaticPagesControllerTest extends IntegrationTestCase
{

    /**
     * Test home method
     *
     * @return void
     */
    public function testHome()
    {
        $this->get('/home');
        $this->assertTemplate('StaticPages/home');
    }

    /**
     * Test help method
     *
     * @return void
     */
    public function testHelp()
    {
        $this->get('/help');
        $this->assertTemplate('StaticPages/help');
    }

    /**
     * Test about method
     *
     * @return void
     */
    public function testAbout()
    {
        $this->get('/about');
        $this->assertTemplate('StaticPages/about');
    }

    /**
     * Test contact method
     *
     * @return void
     */
    public function testContact()
    {
        $this->get('/contact');
        $this->assertTemplate('StaticPages/contact');
    }
}

基本的に静的なページなので、テストでやっていることは同じです。

  • getで対象のURLにアクセスする。
  • 表示されているテンプレートが正しいか確認する(assertTemplate)。

IntegrationTestCaseを継承していると便利なアサーションメソッドを使用できます。

下記のコマンドで対象のファイルだけテストできます。
GREENになればOKです。

$ vendor/bin/phpunit tests/TestCase/Controller/StaticPagesControllerTest.php

また、テスト単位で実行するには、filterオプションが使用できます。
filterに一致するテストのみ実行してくれます。

# Homeがつくテストだけ実行
$ vendor/bin/phpunit --filter="/Home/" tests/TestCase/Controller/StaticPagesControllerTest.php

vendor/bin/phpunitだけ実行すると、全てのテストを実行できます。
ただ、今回の場合、PagesControllerTest.phpが引っかかってしまいます。
そのため、コメントアウトするか、消してしまっても良いと思います。

少し動的なページ

タイトルタグの中身をページごとに変わるようにします。
headタグなどは、src/Template/Layout/default.ctpに書かれております。

まずはテストコードを追記してみる

Rails Tutorialのようにテスト駆動開発(TDD)っぽく、まずはテストから修正します。
今回はHome | Ruby on Rails Tutorial Sample Appのような文言がちゃんと含まれているか確認します。

共通文言はライフサイクルコールバックsetUpを使用し、テスト前にプロパティにセットします。

tests/TestCase/Controller/StaticPagesControllerTest.php
<?php
namespace App\Test\TestCase\Controller;

use App\Controller\StaticPagesController;
use Cake\TestSuite\IntegrationTestCase;

class StaticPagesControllerTest extends IntegrationTestCase
{
+   public $base_title;

+   public function setUp()
+   {
+       parent::setUp();
+       $this->base_title = 'Ruby on Rails Tutorial Sample App';
+   }

    public function testHome()
    {
        $this->get('/home');
        $this->assertTemplate('StaticPages/home');
+       $this->assertResponseContains("Home | {$this->base_title}");
    }

    public function testHelp()
    {
        $this->get('/help');
        $this->assertTemplate('StaticPages/help');
+       $this->assertResponseContains("Help | {$this->base_title}");
    }

    public function testAbout()
    {
        $this->get('/about');
        $this->assertTemplate('StaticPages/about');
+       $this->assertResponseContains("About | {$this->base_title}");
    }

    public function testContact()
    {
        $this->get('/contact');
        $this->assertTemplate('StaticPages/contact');
+       $this->assertResponseContains("Contact | {$this->base_title}");
    }
}

これでテストを実行すると、エラーになります。

テンプレートの修正

コントローラーから値を受け取り、動的にタイトルタグを変更したいと思います。

src/Template/Layout/default.ctp
<title>
-   <?= $cakeDescription ?>:
-   <?= $this->fetch('title') ?>
+   <?= "{$title} | Ruby on Rails Tutorial Sample App" ?>
</title>

コントローラーの修正

コントローラーからsetメソッドを使用して値を渡します。

src/Controller/StaticPagesController.php
<?php
namespace App\Controller;

use App\Controller\AppController;

class StaticPagesController extends AppController
{

    public function home()
    {
+       $this->set('title', 'Home');
    }

    public function help()
    {
+       $this->set('title', 'Help');
    }

    public function about()
    {
+       $this->set('title', 'About');
    }

    public function contact()
    {
+       $this->set('title', 'Contact');
    }
}

これでテストが通ったかと思います。

さいごに

色々端折ってしまいましたが、第3章をやってみました。
PHPUnitはRubyのminitestに近い感じで使えそうです。

次回は第4章をとばして、5章をやりたいと思います。
次回:その3 レイアウトの作成