スポンサーリンク

CakePHP3のbakeによる自動生成をカスタマイズする2

前回のbakeのカスタマイズの際、add()をedit()と統合した結果add.ctpの生成自体が不要となりました。
特定のテンプレートファイルの生成をやめるにはBakeShellとそこから呼び出される各タスクをカスタマイズが必要そうだったので今回は以下のような作業を実施しました。

BakeShellとタスククラスをコピーする

  1. cake_app/vendor/cakephp/bake/src/Shell配下のプログラムをコピー
    1. コピーした先で名前空間を一括でBake\Shell\Task;からApp\Shell\Task;に置換

コピー元(赤枠部分)

コピー先(赤枠部分)

add.ctpの生成をやめる

  1.  cake_app/src/Shell/Task/TemplateTask.phpを編集
    1. ロギング($this->log())を活用するなどしてプログラムを調査
    2. bakeによって生成するテンプレートファイルの名称の配列を返す関数があるので、addを除外するようにプログラム修正
/**
     * Get a list of actions that can / should have view templates baked for them.
     *
     * @return array Array of action names that should be baked
     */
    protected function _methodsToBake()
    {
        $base = Configure::read('App.namespace');

        $methods = [];
        if (class_exists($this->controllerClass)) {
            $methods = array_diff(
                array_map(
                    'Cake\Utility\Inflector::underscore',
                    get_class_methods($this->controllerClass)
                ),
                array_map(
                    'Cake\Utility\Inflector::underscore',
                    get_class_methods($base . '\Controller\AppController')
                )
            );
        }
        if (empty($methods)) {
            $methods = $this->scaffoldActions;
        }
        foreach ($methods as $i => $method) {
-            if ($method[0] === '_') {
+            // add用のテンプレートファイルはeditと共通化されたため不要
+            if ($method[0] === '_' || $method === 'add') {
                unset($methods[$i]);
            }
        }

        return $methods;
    }

MySQLのテーブルからCOMMENTを取得して項目名として使用する

  1. samplesテーブルにCOMMENTを設定
  2. ConnectionManagerでCOMMENTを取得
    1. information_schemaのTABLES、COLUMNSテーブルをSELECTすることで取得
    2. coalesce関数を使用しCOMMENTが設定されてないときは物理名を設定
    3. カラムのCOMMENTはCakeのHash関数でデータを持ち替えてKeyValueペアに変換
  3. BakeTemplateTaskにデータをセット&twigテンプレート内で表示する

samplesテーブルとそのカラムについて以下のようなCOMMENTを設定してみました。

DROP TABLE IF EXISTS `samples`;
CREATE TABLE `samples` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `value1` varchar(20) NOT NULL COMMENT '値1',
  `value2` varchar(255) NOT NULL COMMENT '値2\r\nほげほげ',
  `created` datetime DEFAULT NULL COMMENT '作成日時',
  `modified` datetime DEFAULT NULL COMMENT '更新日時',
  `delete_flag` char(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='サンプル';

INSERT INTO `samples` VALUES ('1', 'aaa1', 'bbb1', '2019-07-12 00:16:11', '2019-07-12 07:09:22', '0');
INSERT INTO `samples` VALUES ('2', 'test1', 'test2', '2019-07-12 00:17:07', '2019-07-12 00:19:03', '1');
INSERT INTO `samples` VALUES ('3', '111', '2222', '2019-07-12 00:21:36', '2019-07-12 00:21:36', '0');
INSERT INTO `samples` VALUES ('4', '123', '456', '2019-07-12 07:04:07', '2019-07-12 07:04:07', '0');
INSERT INTO `samples` VALUES ('5', '222', '333', '2019-07-12 07:09:12', '2019-07-12 07:09:12', '0');

COMMENTを取得する処理は後々他のタスククラスでも使うかもしれないので継承元のBakeTaskクラス内に作成しました。
edit.twigとindex.twigの修正内容について差分表示しています。

/**
     * ConnectionManagerでテーブルとテーブル内のカラムのCOMMENT句の情報を取得する
     * コメント句の改行コードは削除する
     *
     * コメント句が見つからない場合は以下の値をセットする
     * テーブル:テーブル名
     * カラム:カラム名
     *
     * @param unknown $table_name
     */
    public function getColumnCommentData($table_name) {
        $return_arr = [];
        $connection = ConnectionManager::get('default');

        // テーブルのコメント句を取得
        $query = <<<EOD
select
    coalesce(nullif(replace(replace(replace(TABLE_COMMENT, '\r\n', ''), '\r', ''), '\n', ''), ''), '{$table_name}') as table_comment
from
    information_schema.TABLES
where
    TABLE_SCHEMA = database()
and TABLE_NAME = '{$table_name}'
EOD;
        $result = $connection->execute($query)->fetch('assoc');
        $return_arr['table_comment'] = $result['table_comment'];

        // テーブルのカラムのコメント句を取得
        $query = <<<EOD
select
    COLUMN_NAME as c_id
    , coalesce(nullif(replace(replace(replace(COLUMN_COMMENT, '\r\n', ''), '\r', ''), '\n', ''), ''), COLUMN_NAME) as c_val
from
    information_schema.COLUMNS
where
    TABLE_SCHEMA = database()
and TABLE_NAME = '{$table_name}'
order by
    ORDINAL_POSITION;
EOD;
        $result = $connection->execute($query)->fetchAll('assoc');
        $return_arr['column_comments'] = Hash::combine($result, '{n}.c_id', '{n}.c_val');
        return $return_arr;
    }
/**
 * Builds content from template and variables
 *
 * @param string $action name to generate content to
 * @param array|null $vars passed for use in templates
 * @return string|false Content from template
 */
public function getContent($action, $vars = null)
{
    if (!$vars) {
        $vars = $this->_loadController();
    }

    if (empty($vars['primaryKey'])) {
        $this->abort('Cannot generate views for models with no primary key');

        return false;
    }

    if ($action === "index" && !empty($this->params['index-columns'])) {
        $this->BakeTemplate->set('indexColumns', $this->params['index-columns']);
    }

    $this->BakeTemplate->set('action', $action);
    $this->BakeTemplate->set('plugin', $this->plugin);
    $this->BakeTemplate->set($vars);

+    // コメント情報をセット
+    $table_name = ltrim(strtolower(preg_replace('/[A-Z]/', '_\0', $this->modelName)), '_');
+    $this->BakeTemplate->set('comment_data', $this->getColumnCommentData($table_name));

    return $this->BakeTemplate->generate("Template/$action");
}
<?php
/**
 * @var \{{ namespace }}\View\AppView $this
 * @var \{{ entityClass }} ${{ singularVar }}
 */
$button_name = (!empty(${{ singularVar }}) && !${{ singularVar }}->isNew()) ? "更新" : "登録";
+$this->assign('title', "{{ comment_data.table_comment }}{$button_name}");
?>
{% set fields = Bake.filterFields(fields, schema, modelObject) %}
 <div class="col-md-12 mb-12">
  <div class="card">
   <div class="card-body">
    <?= $this->Form->create(${{ singularVar }}) ?>
    <div class="row">
{% for field in fields if field not in primaryKey %}
+{% set jp_value = comment_data.column_comments[field] %}
    {%- if keyFields[field] %}
        {%- set fieldData = Bake.columnData(field, schema) %}
        {%- if fieldData.null %}
            <div class="col-md-6 col-sm-12">
                <div class="form-group">
-                    <?= $this->Form->control('{{ field }}', ['options' => ${{ keyFields[field] }}, 'empty' => true]); ?>
+                    <?= $this->Form->control('{{ field }}', ['options' => ${{ keyFields[field] }}, 'empty' => true, 'label' => '{{ jp_value }}']); ?>
                </div>
            </div>
            {{- "\n" }}
        {%- else %}
<?php
/**
 * @var \{{ namespace }}\View\AppView $this
 * @var \{{ entityClass }}[]|\Cake\Collection\CollectionInterface ${{ pluralVar }}
 */
+$this->assign('title', "{{ comment_data.table_comment }}");
?>
{% set fields = Bake.filterFields(fields, schema, modelObject, indexColumns, ['binary', 'text']) %}
<div class="col-md-12 mb-12">
 <div class="card">
  <div class="card-header">
   <button type="button" class="btn btn-flat btn-outline-secondary" onclick="location.href='/{{ pluralVar }}/add/'">新規登録</button>
  </div>
  <div class="card-body">
   <table class="table table-bordered">
    <thead>
            <tr>
{% for field in fields %}
+{% set jp_value = comment_data.column_comments[field] %}
{# delete_flagは出力対象外 #}
{% if field not in ['delete_flag'] %}
-               <th scope="col"><?= $this->Paginator->sort('{{ field }}') ?></th>
+               <th scope="col"><?= $this->Paginator->sort('{{ field }}', '{{ jp_value }}') ?></th>
{% endif %}
{% endfor %}
                <th scope="col" class="actions">操作</th>
            </tr>
    </thead>

bakeを再実行して動作確認する

出力されたログの中にadd.ctpについて実ファイルが生成されていないこととログが出ていないことを確認しました。
COMMENTで設定した値が項目名として設定されていることを確認しました。

cake_app\bin\cake bake all samples --force

ビルトインサーバーを起動して項目名にCOMMENTの値が表示されることを確認しました。

メモ

目的としていたタスククラス自体の拡張とCOMMENTを活用した項目名の設定が行えました。
今回の修正はどちらも参考とした情報がないので、もしかしたら正しいやり方が別にあるのかもしれません。

タイトルとURLをコピーしました