CakePHP3新機能 View Cellの使いどころ

  • 7
    いいね
  • 0
    コメント

View Cellとは

http://book.cakephp.org/3.0/ja/views/cells.html

RubyのCellに影響を受けて追加された、Micro MVCを作る機能です。
データとテンプレートをひとまとめにしたエレメントを実現できます。
本稿では以降、Cellと呼称します。

MVCとの対応

レイヤ MVC Cell 説明
controller Controller View\Cell View\Cell名前空間以下にCellクラスを配置する
model Table, Entity Table, Entity 通常のMVCでもCellでも共通
view Template Template\Cell Template\Cellディレクトリ以下にCell専用のテンプレートを配置する

利用手順

CakePHP3新機能 View Cell の利用手順

Cellの使いどころについて

メリット

CellはTemplateから呼び出せる都合上、
Controllerのアクションへ記述を追加することなく画面にデータと結びついた表示を追加できます。
これは既に複雑化しているControllerをに対する変更のリスクを避ける、非常に有効な方法です。

利用範囲はReadに留める

しかしアクションのコードを汚さなくて済むというメリットの一方で、デメリットも勿論存在します。
それは責任範囲をCRUDのうちReadのみに留めておかないと、Controllerがより一掃汚れてしまうという点です。

Cellでフォームを追加した場合、Cellの存在を知らなくて良いはずのControllerのアクションがその保存を担います。
ReadとCreate/Updateでアクションを分けていたとしても、Create/Update側のコードはどのみち汚されます。
これではCellを利用している意味がありません。

よってCellの利用範囲はReadを担当するエレメントに限定するべきです。

何でもCellにすれば良いってものじゃない

当たり前のことですがCellは出来る限り、何度も使いまわすデータと表示のセットでの活用に留めるべきです。
Cellは実装のレイヤが深くなるというデメリットもあるので、多用するとコードを追うのが困難になってしまいます。

一度しか使わない、かつコードの書き換えが容易なControllerアクションへ表示を追加する場合は、
今まで通りControllerとTemplateに書くべきです。

Cellの使いどころ

まとめると、以下のポイントがCellの使い所の判断基準になってきます。

  • 表示とデータをセットにして可搬性を向上したい
  • 複雑化したコントローラに触りたくない
  • Readの機能のみを提供するエレメントである

JSのコンポーネントシステムではダメなのか

勿論アリです。
Custom ElementやReact.js、Vue.jsでも勿論同様の動きは達成できると思います。
REST API用のコントローラを切って、そこからajaxでデータを取得できるコンポーネントを作成すれば良いでしょう。

しかしJSに弱いプロジェクトチームの場合、それらの学習コストをかけられない状況も存在します。
また非同期的にデータを何度も取得し直して表示するような仕組みを作る場合(ページ最下部までスクロールしたら次のデータが読み込まれる/地図をスクロールしたら非表示エリアの地図が読み込まれる etc...)でもない限り、
データの取得は1回のみ です。

データの取得が1回で良いのであれば、
サーバサイドのプロセス内で完結してしまったほうがHTTPのコストもかからないので幸せです。

想定される活用場面

私は複数画面で利用する統計データ表示エレメントを実装するためによく利用しています。
1ヶ月ごとにグラフを表示し、次の月へ進む場合はgetパラメータから値を取得します。
以下に実装例を載せておくので参考にしてみて下さい。

実例: CellとChart.jsを利用したグラフエレメントの実装

ECサイトに対する商品の登録数を日毎に表示するグラフを出力します。
グラフの表示用ライブラリはChart.jsを利用しています。

1. レイアウトテンプレートにchart.jsの読み込み処理を追加

以下をレイアウトテンプレートの<body>最下部に追記します。

Template\Layout\default.ctp
<?= $this->Html->script(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js']) ?>

2. View\Cell 側

テーブルからデータを収集してchart.jsに渡すデータ型に変換しています。
今回は1ヶ月毎のページネート機能は省略しましたが、Cellからも$this->requestが利用出来るので実装は簡単です。

View\Cell\ProductCountCell
<?php
namespace Admin\View\Cell;

use Cake\View\Cell;

/**
 * ProductCount cell
 */
class ProductCountCell extends Cell
{
    /**
     * Default display method.
     *
     * @return void
     */
    public function display()
    {
        // 日毎に登録された商品数を取得する
        $products= $this->loadModel('Products');
        $dataset = $products->find()
            ->select([
                'count' => 'COUNT(DATE_FORMAT(`created`, "%Y-%m-%d"))',
                'date'  => 'DATE_FORMAT(`created`, "%Y-%m-%d")',
            ])
            ->group(['DATE_FORMAT(`created`, "%Y-%m-%d")'])
            ->order(['date'])
            ->toArray();

        // countカラム、dateカラムだけを収集
        $labels = array_column($dataset, 'date');
        $data   = array_column($dataset, 'count');

        // chart.jsのdata型を作成
        $chart_data = [
            'labels'   => $labels,
            'datasets' => [
                [
                    'data'                      =>  $data,
                    'label'                     => '商品登録数',
                    'lineTension'               => 0.1,
                    'hoverBackgroundColor'      => 'rgba(255, 99, 132, 0.3)',
                    'backgroundColor'           => 'rgba(38, 185, 154, 0.31)',
                    'borderColor'               => 'rgba(38, 185, 154, 0.7)',
                    'pointBorderColor'          => 'rgba(38, 185, 154, 0.7)',
                    'pointBackgroundColor'      => 'rgba(38, 185, 154, 0.7)',
                    'pointHoverBackgroundColor' => '#fff',
                    'pointHoverBorderColor'     => 'rgba(220, 220, 220, 1)',
                    'pointBorderWidth'          => 1,
                ],
            ]
        ];

        // Templateにjsonを送る
        $this->set('chart_data', json_encode($chart_data));
    }
}

3. Template\Cell側

画面ロードの完了と同時にChart.jsを実行しています。

Template\Cell\ProductCount\display.ctp
<div class="canvas-container">
    <canvas id="product-count-chart"></canvas>
</div>

<script>
window.addEventListener('load', function() {
    var chart = new Chart(document.getElementById('product-count-chart'), {
        type: 'line',
        data: JSON.parse('<?= $chart_data ?>'),
    });
});
</script>

4. Cellを呼び出し

あとは好きなところに貼り付ければいつでも商品数の統計グラフを呼び出せます。

Template\Pages\home.ctp
<?= $this->cell('ProductCount')->render() ?>

表示結果

chart.png

まとめ

Cellは非常に便利な機能である一方で、使い方を誤るとコードの複雑化を招く両刃の剣です。
メリットとデメリットを正しく認識して、使うべきところに美しく差し込みましょう。

あと、せっかくなのでサンプルコードでPackを使いたかったのですが、
ComponentはControllerインターフェイスを要求するのでCellでは利用できませんでした。残念です。
参考: Controllerから簡単にJSに変数を渡したい in CakePHP3

投稿が遅れまして申し訳ありませんでした。
明日の担当者は今のところ空白です。
どなたか宜しくお願いします、何でもしますから!

この投稿は CakePHP3 Advent Calendar 201619日目の記事です。