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

# はじめに
業務でCakePHPを使用することになったので、勉強を始めました。
本を買おうかとも思ったのですが、折角なので[Rails Tutorial](https://railstutorial.jp/)をCakePHPで書き換えながら勉強をしようかと思いました。

* 前回：[CakePHPでRails Tutorialをやってみる〜その１ Cloud9の設定〜](http://qiita.com/naoki85/items/f4bfabc941381ca5ff35)

自分の整理と、他の方たちへの参考になればと思い、記事にさせていただきます。
とばせる部分はとばしているので、全て実施するわけではありません。
# コントローラーの生成
CakePHPには`bake`コマンドという、ファイルを生成してくれるコマンドがあります。
Cake（ケーキ）をbake（焼く）というのはなかなか洒落ているのではないかと思っています。
[Bakeでコード生成](https://book.cakephp.org/3.0/ja/bake/usage.html)

今回はコントローラーだけ生成したいと思います。
`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`には`home`、`help`、`about`、`contact`を追加します。

```php: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`です。
[ルーティング](https://book.cakephp.org/3.0/ja/development/routing.html)

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

```diff: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](https://railstutorial.jp/chapters/static_pages?version=5.0#sec-custom_static_pages)のものをそのまま使用しようと思います。
（Railsではないのにリンクができてしまっていますが。。。）

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

```html: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>
```
```html: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>
```
```html: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>
```
```html: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
```
下図のようになっていれば問題ないと思います。

![スクリーンショット 2017-08-31 2.46.05.png](https://qiita-image-store.s3.amazonaws.com/0/118817/f41056ec-2f56-338f-9695-b2d74c3bbf2a.png)

# PHPUnitでテストコードを書く
## PHPUnitのインストール
ここまでのテストコードを書いてみたいと思います。
CakePHPではPHPUnitがサポートされているようなので、そちらを使用します。
[テスト](https://book.cakephp.org/3.0/ja/development/testing.html)

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

```diff: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`というファイルがあるので、そちらを修正していきます。

```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`を継承していると便利な[アサーションメソッド](https://book.cakephp.org/3.0/ja/development/testing.html#id26)を使用できます。

下記のコマンドで対象のファイルだけテストできます。
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](https://railstutorial.jp/chapters/static_pages?version=5.0#aside-when_to_test)のようにテスト駆動開発（TDD）っぽく、まずはテストから修正します。
今回は**Home | Ruby on Rails Tutorial Sample App**のような文言がちゃんと含まれているか確認します。

共通文言は[ライフサイクルコールバック](https://book.cakephp.org/3.0/ja/development/testing.html#id11)の`setUp`を使用し、テスト前にプロパティにセットします。

```diff: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}");
    }
}
```
これでテストを実行すると、エラーになります。
## テンプレートの修正
コントローラーから値を受け取り、動的にタイトルタグを変更したいと思います。

```diff:src/Template/Layout/default.ctp
<title>
-   <?= $cakeDescription ?>:
-   <?= $this->fetch('title') ?>
+   <?= "{$title} | Ruby on Rails Tutorial Sample App" ?>
</title>
```
## コントローラーの修正
コントローラーから[set](https://book.cakephp.org/3.0/ja/controllers.html#id4)メソッドを使用して値を渡します。

```diff: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章をやりたいと思います。
次回：[その３ レイアウトの作成](http://qiita.com/naoki85/items/d4bc9e333f2f5b2bcff3)