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

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

More than 3 years have passed since last update.

はじめに

業務で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

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

スクリーンショット 2017-08-31 2.46.05.png

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 レイアウトの作成

naoki85
Web系のエンジニアです。 RubyやPHPを主に書いています。
https://scrapbox.io/naoki85/
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