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

CakePHPでpugる

More than 1 year has passed since last update.

この記事は、FORK Advent Calendar 2018 の1日目の記事です。

もうアドベントカレンダーの季節ですか。
時間の流れが早すぎて困りますね。。

私は普段CakePHPにSmartyを組み込んで開発しているのですが、
ビューのテンプレートとして組み込む前のHTMLは、pugで書いたのをコンパイルしたものだったりするので、
pugをそのまま使えちゃった方が良いよね?と思って色々試した記録です。

ライブラリ探し

ググったら、速攻でCakePHP用のものを見つけました。
https://github.com/elquimista/cakephp-jade
が、GitHubを見ると3年前で止まっている。。

次に見つけたのはコレ。
https://github.com/pug-php/pug
動きも活発そうだし、良さそうですね。
これを利用したLaravelとかSymfonyなどのフレームワーク用のやつもあるし!
あれ、、CakePHPのがない。

ということで、2つ目のライブラリを継承しつつ、
1つ目のコードをパクって参考にして、ちょっと作ってみました。

PugView(簡易版)

出来上がったのがこちらです。

<?php
namespace App\View;

use Cake\View\View;
use Pug\Pug;

class PugView extends View
{
    protected $_ext = '.pug';
    protected $pug;

    public function initialize()
    {
        $this->pug = new Pug([
            'pretty' => true,
            'cache' => CACHE . 'views' . DS,
        ]);

        // レイアウトを使わないようにする
        $this->setLayout('');
    }

    protected function _render($viewFile, $data = [])
    {
        if (empty($data)) {
            $data = $this->viewVars;
        }

        $data = array_merge(
            $data,
            [
                'view' => $this,
            ]
        );

        return $this->pug->renderFile($viewFile, $data);
    }
}

ほとんど何も書いてないですね!
上記のソースを src/View/PugView.php として保存し、使いたいコントローラーで、

$this->viewBuilder()->setClassName('Pug');

としてやればOKです!

簡易版の仕様

  • pugファイルは通常通り src/Template/ に配置する
  • 拡張子は .pug でOK
  • .ctp は使わない
  • CakePHPのレイアウト、エレメントは使えないので、pug の extends や include を使おう
  • テンプレートファイルでは、$this の代わりに $view を使おう
  • ヘルパーは使える

お試し

簡易版が出来たので、お問い合わせフォームを想定して使ってみましょう。

テンプレートファイルの構成

src/Template/
    _parts/             -- extends や include で使うやつ
        _header.pug     -- ヘッダーのパーツ
        _footer.pug     -- フッターのパーツ
        _layout.pug     -- ベースとなるレイアウト
    Contact/
        complete.pug   -- 完了画面
        confirm.pug    -- 確認画面
        index.pug      -- 入力画面

_parts/_layout.pug

レイアウトはとりあえずシンプルに。

doctype
html(lang="ja")
  head
    meta(charset="utf-8")
    title!=title
    link(rel="stylesheet", href="/common/base.css")

  body
    include _header

    block content

    include _footer

Contact/input.pug

Formヘルパー使わずにベタで書いてみます。

extends ../_parts/_layout

block content

  form(method="post" action!=$view->Url->build(['action' => 'confirm']))
    .form_column
      label.form_label(for="company")
        |お名前
        span.form_required ※必須
      .form_control
        input.form_input(type="text" placeholder="山田太郎" name="name" value=$input.name)
        if $errors.name
          p.form_error
           = $errors.name

    .form_column
      label.form_label
        |メールアドレス
        span.form_required ※必須
      .form_control
        input.form_input(type="email" placeholder="user@example.com" name="email" value=$input.email)
        if $errors.email
          p.form_error
            = $errors.email

    .form_column
      label.form_label
        | 問い合わせ内容
        span.form_required ※必須
      .form_control
        textarea.form_input(name="content")
          = $input.content
        if $errors.content
          p.form_error
            = $errors.content

    .formBtnWrap
      button(type="submit") 入力内容のご確認

変数は下記をセットしているものとします。
$input : POSTデータ
$errors : バリデーションエラー(※デフォルトだと2次元配列になって扱いづらいので、下記のようにフォーマットしています)

$errors = [
  'name'    => 'お名前を入力してください',
  'email'   => 'メールアドレスを入力してください',
  'content' => 'お問い合わせ内容を入力してください',
];

出力結果

何も入力せずにフォームを送信したときのHTMLソースはこんな感じになりました。
うまく行ってますね!

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title></title>
    <link rel="stylesheet" href="/common/base.css">
  </head>
  <body>
    <header>
      <div class="header">ヘッダー</div>
    </header>
    <form method="post" action="/contact/confirm">
      <div class="form_column">
        <label class="form_label" for="company">お名前<span class="form_required">※必須</span></label>
        <div class="form_control"><input class="form_input" type="text" placeholder="山田太郎" name="name" value="">          <p class="form_error">お名前を入力してください</p>
</div>
      </div>
      <div class="form_column">
        <label class="form_label">メールアドレス<span class="form_required">※必須</span></label>
        <div class="form_control"><input class="form_input" type="email" placeholder="user@example.com" name="email" value="">          <p class="form_error">メールアドレスを入力してください</p>
</div>
      </div>
      <div class="form_column">
        <label class="form_label">問い合わせ内容<span class="form_required">※必須</span></label>
        <div class="form_control">
          <textarea class="form_input" name="content"></textarea>
          <p class="form_error">お問い合わせ内容を入力してください</p>
        </div>
      </div>
      <div class="formBtnWrap">
        <button type="submit">入力内容のご確認</button>
      </div>
    </form>
    <footer>
      <div class="footer">フッター</div>
    </footer>
  </body>
</html>

その他 Tips など

触ってみて個人的にハマったポイントなどを少し書いておきます。

文法

Pug-php は Phug をベースにしていますので、
まず基本的な文法はこちらを読んでおきましょう。
https://www.phug-lang.com/

レイアウト使えない問題

上述したPugView クラスでは、レイアウトを使わないように設定していますが、
これは、Cakeのレイアウトの機能を使うと doctype が変わってしまったり、意図しないHTMLが出力されてしまったためです。
なので、そこは使わないようにして、 pug の extends を使う方針にしました。

おそらく、レイアウトとビューを2重にコンパイルしようとしているためだと思うのですが、
細かくは追えてません。。

同じ名前の変数を使うとぶつかる

当たり前な話ですが、コントローラーからセットした変数と同じ名前の変数がテンプレート内で宣言・代入されていると、そちらが優先されてしまいます。
そのため、元から pug で使われている変数名と、コントローラーからセットする変数名はぶつからないようにしないと行けません。
これはちょっと命名を工夫しないとマズイですね。。

変数の出力

Phug では$を付けることになっていますが、Pug-phpでは$がなくても出力されるようです。

// どっちでもOK
= $a
= a

※オブジェクトのメソッドを呼ぶ場合は、$が必要です。

!= $obj->method()

エスケープ処理

変数を出力する際にエスケープするかどうかは、=!= のどちらを使うかで切り替えられます。

= $a   // エスケープされる
!= $a  // エスケープされない

checked の切り替え

ラジオやチェックボックスの checked を付けるときは、下記のように書くと使えます。
$checked の値は、'checked' ではなく、true/false の bool を入れます。

:php
  $checked = (isset($input['checked']) && $input['checked'] == 1);
input(type="checkbox" checked=$checked)

Formヘルパーも使えます

上述したサンプルはベタで書いてましたが、こんな感じでFormヘルパー使って書くことも出来ます。
エスケープされないように != を使います。

  != $view->Form->create('contact')
  != $view->Form->control('name')
  != $view->Form->control('email')
  != $view->Form->control('content', ['type' => 'textarea'])

感想

手探りな状態なので、まだまだ気づいていない罠とかありそうな気もしてますが、
結構行けるんじゃないか?という感触でした。
なので、引き続き探ってみようかと思います。

Why do not you register as a user and use Qiita more conveniently?
  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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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