スポンサーリンク

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

前回の時点でMySQLのテーブルやカラムのコメントが60文字までしか使えなかったりでテーブル定義だけでは自分がやりたい自動生成はできないことがわかりました。前回課題に挙げたbake実行時に設定ファイルを読み込み、それを元にbake実行時にやりたい放題する処理の流れを作ってみました。

設定ファイルを読み込んだbakeを実装する

以下のような物件に関する設定ファイルを作成しました。configディレクトリ以下にbake_configというディレクトリを作成してその中に配置しています。最初はBakeDetailConfigという名前のクラスを作成していたのですが思い付きベースで機能追加してるうちにめんどくさくなったので配列にしました(^q^;
bake実行時にbake_configディレクトリ内に[テーブル名]_config.phpという命名規則に則ったファイルが存在する場合に読み込むようになっています。

<?php
return [
    'function_title' => '物件',
    'columns' => [
        'id' => [
            'search' => true,
            'listview' => true,
            'col_md_size' => 6,
            'col_sm_size' => 12,
        ],
        'name' => [
            'search' => true,
            'listview' => true,
            'col_md_size' => 12,
            'col_sm_size' => 12,
            'search_type' => 'LIKE',
        ],
        'zip1' => [
            'search' => true,
            'listview' => false,
            'col_md_size' => 1,
            'col_sm_size' => 4,
            'append_class' => 'p-postal-code',
        ],
        'zip2' => [
            'search' => true,
            'listview' => false,
            'col_md_size' => 1,
            'col_sm_size' => 4,
            'append_class' => 'p-postal-code',
        ],
        'pref' => [
            'search' => true,
            'listview' => true,
            'col_md_size' => 1,
            'col_sm_size' => 4,
            'append_class' => 'p-region',
            'search_type' => 'LIKE',
        ],
        'city' => [
            'search' => false,
            'listview' => true,
            'col_md_size' => 9,
            'col_sm_size' => 12,
            'append_class' => 'p-locality',
        ],
        'address1' => [
            'search' => false,
            'listview' => false,
            'col_md_size' => 12,
            'col_sm_size' => 12,
            'append_class' => 'p-street-address',
        ],
        'address2' => [
            'search' => false,
            'listview' => false,
            'col_md_size' => 12,
            'col_sm_size' => 12,
            'append_class' => 'p-extended-address',
        ],
        'tikunen' => [
            'search' => true,
            'listview' => true,
            'col_md_size' => 6,
            'col_sm_size' => 6,
            'input_type' => 'select',
            'selections' => [
                '01' => '新築',
                '02' => '1年以内',
                '03' => '3年以内',
                '04' => '5年以内',
                '05' => '7年以内',
                '06' => '10年以内',
                '07' => '20年以内',
                '08' => '30年以内',
                '09' => '指定しない',
            ],
        ],
        // mensekiカラムの設定
        'menseki' => [
            'search' => false,
            'listview' => true,
            'col_md_size' => 6,
            'col_sm_size' => 6,
            'input_type' => 'number',
            'min_value' => 10,
            'max_value' => 1000,
            'step_value' => 5,
            'unit_text' => '㎡',
        ],
        // mensekiカラムをFromTo検索する検索専用項目の設定
        'menseki_min' => [
            'search' => true,
            'label' => '専有面積From',
            'input_type' => 'number',
            'min_value' => 10,
            'max_value' => 1000,
            'step_value' => 5,
            'unit_text' => '㎡以上',
            'search_type' => '>=',
            'search_column' => 'menseki',
        ],
        'menseki_max' => [
            'search' => true,
            'label' => '専有面積To',
            'input_type' => 'number',
            'min_value' => 10,
            'max_value' => 1000,
            'step_value' => 5,
            'unit_text' => '㎡以下',
            'search_type' => '<=',
            'search_column' => 'menseki',
        ],
        'kodawari' => [
            'editable' => true,
            'search' => true,
            'listview' => true,
            'label' => 'こだわり条件',
            'col_md_size' => 12,
            'col_sm_size' => 12,
            'input_type' => 'checkbox',
            'selections' => [
                '01' => '風呂・トイレ別',
                '02' => '2階以上',
                '03' => '駐車場あり',
                '04' => '室内洗濯機置場',
                '05' => 'エアコン付き',
                '06' => 'ペット相談可',
                '07' => 'オートロック',
                '08' => '洗面所独立',
            ],
        ],
        'limit_date' => [
            'search' => true,
            'listview' => true,
            'col_md_size' => 12,
            'col_sm_size' => 12,
            'input_type' => 'date',
            'search_type' => '>=',
        ],
        'memo' => [
            'search' => false,
            'listview' => false,
            'col_md_size' => 12,
            'col_sm_size' => 12,
            'input_type' => 'textarea',
            'row_num' => 8,
            'maxlength' => 1000,
        ],
        'created' => [
            'search' => false,
            'listview' => false,
            'col_md_size' => 6,
            'col_sm_size' => 12,
        ],
        'modified' => [
            'search' => false,
            'listview' => true,
            'col_md_size' => 6,
            'col_sm_size' => 12,
        ],
    ],
    'options' => [
        'is_search_form' => true,
        'yubinbango' => true,
    ],
];
/**
 * Path to the config directory.
 */
define('CONFIG', ROOT . DS . 'config' . DS);

+/**
+ * Bake detail config directory.
+ */
+define('BAKE_DETAIL_CONFIG', CONFIG . DS . 'bake_config' . DS);
/**
  * bakeの詳細設定を取得する
  * @param string $table_name
  */
 public function getBakeDetailConfig($table_name = null) {

 	$connection = ConnectionManager::get('default');
 	$collection = $connection->getSchemaCollection();

 	$config = [];

 	// カラムの情報を取得し設定配列に追加
 	$table_schema = $collection->describe($table_name);
 	$columns = $table_schema->columns();
 	foreach ($columns as $column) {
 		$field = $table_schema->getColumn($column);
 		$config['columns'][$column] = [
 				'label' => str_replace(["\r", "\n"], '', $field['comment']),
 				'input_type' => $field['type'],
 				'editable' => true,
 				'search' => true,
 				'listview' => true,
 				'col_md_size' => 6,
 				'col_sm_size' => 12,
 		];
 	}

 	// 独自設定ファイルが存在する場合は設定をオーバーライド
 	if (file_exists(BAKE_DETAIL_CONFIG . $table_name . "_config.php")) {
 		$unique_config =  include (BAKE_DETAIL_CONFIG . $table_name . "_config.php");
 		$config = $this->_merge_column_configs($config, $unique_config);
 	}

 	return $config;
 }

検索項目、一覧表示項目について

モーダルウィンドウに表示する検索項目や、一覧画面に表示する表示項目を制御できるようにしました。また、部分一致検索や大小検索を行えるようにするためのパラメータも作成してみました。

columns.[column].searchtrueのとき検索フォーム表示、検索条件の追加などを行う
columns.[column].listviewtrueのとき一覧表示項目として出力
columns.[column].search_typeLIKE、>=、<=などを指定すると検索条件に追加される
'pref' => [
    'search' => true,
    'listview' => true,
    'col_md_size' => 1,
    'col_sm_size' => 4,
    'append_class' => 'p-region',
    'search_type' => 'LIKE',
],
'menseki_min' => [
    'search' => true,
    'label' => '専有面積From',
    'input_type' => 'number',
    'min_value' => 10,
    'max_value' => 1000,
    'step_value' => 5,
    'unit_text' => '㎡以上',
    'search_type' => '>=',
    'search_column' => 'menseki',
],
'menseki_max' => [
    'search' => true,
    'label' => '専有面積To',
    'input_type' => 'number',
    'min_value' => 10,
    'max_value' => 1000,
    'step_value' => 5,
    'unit_text' => '㎡以下',
    'search_type' => '<=',
    'search_column' => 'menseki',
],

郵便番号を元に住所を自動入力する設定について

管理画面とか作成するときによく使うアレも設定できるようにしてみました。
以前はAjaxZip3を使用していましたが、今回はAjaxZip3の作者の方が推奨されているYubinBangoライブラリを使用してみました。
参考:GitHub – ajaxzip3/ajaxzip3.github.io
参考:GitHub – yubinbango/yubinbango

options.yubinbangotrueのときYubinBangoライブラリを読み込む
columns.[column].append_class入力値をinput要素のclassに追加する
<?php
return [
    'columns' => [
        'zip1' => [
            'search' => true,
            'listview' => false,
            'col_md_size' => 1,
            'col_sm_size' => 4,
            'append_class' => 'p-postal-code',
        ],
        'zip2' => [
            'search' => true,
            'listview' => false,
            'col_md_size' => 1,
            'col_sm_size' => 4,
            'append_class' => 'p-postal-code',
        ],
        'pref' => [
            'search' => true,
            'listview' => true,
            'col_md_size' => 1,
            'col_sm_size' => 4,
            'append_class' => 'p-region',
            'search_type' => 'LIKE',
        ],
        'city' => [
            'search' => false,
            'listview' => true,
            'col_md_size' => 9,
            'col_sm_size' => 12,
            'append_class' => 'p-locality',
        ],
        'address1' => [
            'search' => false,
            'listview' => false,
            'col_md_size' => 12,
            'col_sm_size' => 12,
            'append_class' => 'p-street-address',
        ],
        'address2' => [
            'search' => false,
            'listview' => false,
            'col_md_size' => 12,
            'col_sm_size' => 12,
            'append_class' => 'p-extended-address',
        ],
    ],
    'options' => [
        'yubinbango' => true,
    ],
];

選択肢設定について

選択肢の項目は以下のような形で定義しています。プルダウン、ラジオボタン、チェックボックスのいずれかで生成することが可能です。チェックボックスのときはテーブルを1:nで持つのが理想なので子テーブルを自動生成し自動でアソシエーション先のテーブルクラスを生成しています。今回はこれが結構大変でした。

columns.[column].selection_typeselect、radio、checkboxのいずれかを入力
selectはmultipleの設定は不可
checkboxのときは子テーブルを自動生成
columns.[column].selectionsコード定義の配列

テーブルクラスの名称の変換にInflectorを多用しています。Inflectorについては以下
参考:Inflector – 3.8

'tikunen' => [
    'search' => true,
    'listview' => true,
    'col_md_size' => 6,
    'col_sm_size' => 6,
    'input_type' => 'select',
    'selections' => [
        '01' => '新築',
        '02' => '1年以内',
        '03' => '3年以内',
        '04' => '5年以内',
        '05' => '7年以内',
        '06' => '10年以内',
        '07' => '20年以内',
        '08' => '30年以内',
        '09' => '指定しない',
    ],
],
'kodawari' => [
    'editable' => true,
    'search' => true,
    'listview' => true,
    'label' => 'こだわり条件',
    'col_md_size' => 12,
    'col_sm_size' => 12,
    'input_type' => 'checkbox',
    'selections' => [
        '01' => '風呂・トイレ別',
        '02' => '2階以上',
        '03' => '駐車場あり',
        '04' => '室内洗濯機置場',
        '05' => 'エアコン付き',
        '06' => 'ペット相談可',
        '07' => 'オートロック',
        '08' => '洗面所独立',
    ],
],
  /**
   * チェックボックスデータ用の子テーブルを生成する
   * @param unknown $parent_name
   * @param unknown $child_name
   * @param unknown $function_title
   * @param unknown $column_config
   * @return \Cake\Database\StatementInterface
   */
  public function createChildTable($parent_name, $child_name, $function_title, $column_config) {

    $connection = ConnectionManager::get('default');
    $collection = $connection->getSchemaCollection();

    $parent_singular_name = Inflector::singularize(Inflector::underscore($parent_name));
    $child_table_name = $parent_singular_name . "_" . Inflector::tableize($child_name);
    $query = <<<EOD
CREATE TABLE IF NOT EXISTS`{$child_table_name}` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `{$parent_singular_name}_id` varchar(20) NOT NULL COMMENT '{$function_title}ID',
  `{$child_name}` varchar(255) NOT NULL COMMENT '{$column_config['label']}',
  `created` datetime DEFAULT NULL COMMENT '作成日時',
  `modified` datetime DEFAULT NULL COMMENT '更新日時',
  `delete_flag` char(1) NOT NULL DEFAULT '0' COMMENT '削除フラグ',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='{$function_title} - {$column_config['label']}';
EOD;
    $result = $connection->execute($query);
    return $result;
  }
    /**
     * Generate code for the given model name.
     * ※FixtureとTestクラスの生成をやめる
     * ※bakeの詳細設定から条件に応じて子テーブルの生成を行う
     *
     * @param string $name The model name to generate.
     * @return void
     */
    public function bake($name)
    {
+        // bakeの詳細設定を取得、input_typeがcheckboxの項目があったら子テーブルを生成する
+        $detail_config = $this->getBakeDetailConfig($name);
+        foreach ($detail_config['columns'] as $column_name => $config) {
+            if ($config['input_type'] == 'checkbox') {
+                $this->createChildTable($name, $column_name, $detail_config['function_title'], $config);
+                $child_table_class_name = Inflector::singularize($name) . Inflector::pluralize(Inflector::camelize($column_name));
+                $child_table = $this->getTable($child_table_class_name);
+                $child_table_object = $this->getTableObject($child_table_class_name, $child_table);
+                $child_data = $this->getTableContext($child_table_object, $child_table, $child_table_class_name);
+                $this->bakeTable($child_table_object, $child_data);
+                $this->bakeEntity($child_table_object, $child_data);
+            }
+        }

        $table = $this->getTable($name);
        $tableObject = $this->getTableObject($name, $table);
        $data = $this->getTableContext($tableObject, $table, $name);
        $this->bakeTable($tableObject, $data);
        $this->bakeEntity($tableObject, $data);
    }

bakeの様子を動画にしてみた

ここまでプログラムのソースを抜粋してぺたぺたしてるだけだとよくわからないので現時点のbake実行時の流れについてアニメGIFを作成しました。ScreenToGifというソフトウェアを使ってキャプチャ→そのままモザイク、キャプション追加→変なものが映ってないかPhotoshopでフレームごとに確認→オンラインで編集可能なWebサイトでリサイズ、圧縮という流れで作成しました。
参考:ScreenToGif の評価・使い方 – フリーソフト100
参考:Animated GIF editor and GIF maker

その他課題

  • 各テンプレートの先頭にべた書きで配置してるコード定義の配列についてconfig配下に設定ファイルを生成し、そちらをConfigure::readで読み込むよう変更する
  • 左のナビゲーションバーにbakeした機能のリンクを生成する
  • フォームの見栄えをもう少しどうにかする

その他

アニメGIFをWordPressの管理画面でアップするときに413番エラーになってしまいました。
参考サイトにあるようにnginxに以下の設定を追加することで解決できました。
参考:Nginx での 413 Request Entity Too Large エラーの対処法 – Qiita

server {
    listen 443 ssl http2;
    ssl_certificate /etc/letsencrypt/live/imo-tikuwa.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/imo-tikuwa.com/privkey.pem;
    server_name blog.imo-tikuwa.com;
    root /var/www/wp;
    index index.php;

+    client_max_body_size 20M;

~~~省略~~~
}
タイトルとURLをコピーしました