スポンサーリンク

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

今回の更新内容

  • 新規入力項目の追加
    • GoogleMapの緯度経度項目
    • WYSIWYGエディタ項目
    • タグ入力項目
    • 日付+時間(日時)/時間だけの入力項目
    • 外部キー項目
  • 対応済の入力項目についてまとめ
  • Bake用の設定ファイルとMySQLのCREATE TABLEのSQLを生成するプログラムを作成

GoogleMapで緯度経度を取得する項目を追加

事前にGoogleMapのAPIキーを取得しておく必要あり。

  1. options.google_map_api_keyに取得しておいたGoogleMapのAPIキーを設定(必須)
  2. input_typeでgmap_latlonと指定
  3. gmap_height画面に表示するGoogleMapの高さを設定(任意)、デフォルト400px

地図内のクリックした位置にマーカーを立て、そのときの緯度経度を保管しています。
前回のファイルアップロードの項目と同様にJSON型のカラムで保管しています。
緯度経度の他にズームレベルも一緒に保管しています。
登録したデータは一覧画面ではGoogleMapへの外部リンク、詳細画面ではiframeによる地図表示を行っています。

WYSIWYGエディタの入力項目を追加

WISIWYGエディタを実現するのに使用するプラグインはTinyMCECKEditorsummernoteの3つで迷いました。今回はbootstrap用のプラグインで導入が簡単っぽいsummernoteにしてみました。
特に設定せずに画像ファイルをアップロードすることができました。アップロードした画像ファイルはDataURIスキームとして文字列で保管されているのでMySQLのカラム型はlongtextにしました。

タグ入力の入力項目を追加

Tagsinput.jsというプラグインを使用したタグ入力項目を追加してみました。
フォーム送信時にはあらかじめ指定したデリミタで連結した文字列として送られるようになっている模様。更新画面に入ったときの初期表示の際もphp側で配列としてセットとかは不要でそのままの文字列としてセットしたものをプラグイン側でデリミタで分割して表示してくれる模様。便利。

日付+時間(日時)/時間だけの入力項目を追加

プラグインは以前追加した日付項目と同様にBootstrap Material DatePickerを使用しています。
呼び出し時のオプションでそれぞれ以下のような感じで設定しています。

$('#testdatetime-datepicker').bootstrapMaterialDatePicker({
  lang: 'ja',
  nowButton: true,
  clearButton: true,
  format: 'YYYY-MM-DD HH:mm:00',
});
$('#testtime-datepicker').bootstrapMaterialDatePicker({
  lang: 'ja',
  nowButton: true,
  clearButton: true,
  format: 'HH:mm:00',
  date: false,
});

外部キー項目を追加

難しいと思いきや意外とすんなりいった模様。自分で書いていてもよくわからなくなりますが以下のような作業を行いました。

  1. Bake用の設定ファイルで外部キー項目の設定追加
    1. カラム名は外部のテーブル名の単数形+_id
    2. foreign_tableに外部のテーブル名を設定
    3. foreign_disp_columnに選択肢に表示するカラム名を設定
  2. AppTable.phpに外部キー項目で表示する選択肢情報を取得する処理を追加
  3. コントローラーのbake時にinitializeメソッドを追加(indexメソッドの上に追加したいのでindex.twig内に追記)
    1. リクエストのアクションがdelete以外=index,add,edit,viewのとき外部キー項目で表示する選択肢情報を取得してセット

注意点として外部テーブルの方を先にbakeしておく必要あり。サンプルの物件管理機能の場合estatesのbake前にusersのbakeを実行しておく必要があるということ。
estatesのbake時に外部キー項目の処理としてusers側を再度bakeするようにしてhasManyのアソシエーション設定が出来上がるようになっています(下記ModelTask.phpの差分の辺り参照)。

'columns' => [
    'user_id' => [
        'search' => true,
        'listview' => true,
        'label' => '担当者',
        'col_md_size' => 3,
        'col_sm_size' => 12,
        'input_type' => 'foreign_key',
        'foreign_table' => 'users',
        'foreign_disp_column' => 'name',
    ],
],
/**
 * 選択肢情報を取得する
 * @param string $table_class_name
 * @param string $display_column
 * @param boolean $add_empty_selection 空の選択肢を加えるか
 * @throws \Exception
 * @return NULL
 */
public function findForeignSelectionData($table_class_name = null, $display_column = 'id', $add_empty_selection = false) {
  if (is_null($table_class_name)) {
    throw new \Exception();
  }
  $table = TableRegistry::getTableLocator()->get($table_class_name);
  $list = $table->find()->select(['id', $display_column])->enableHydration(false)->toArray();
  if (!empty($list)) {
    $list = Hash::combine($list, '{n}.id', "{n}.{$display_column}");
    if ($add_empty_selection) {
      $list = array_merge(["" => " "], $list);
    }
    return $list;
  }
  return null;
}
{% set need_initialize_method = false %}
{% for field, column_config in detail_config.columns if column_config.input_type == 'foreign_key' %}{% set need_initialize_method = true %}{% endfor %}
{% if need_initialize_method == true %}
    /**
     * Initialize Method.
     */
    public function initialize()
    {
        parent::initialize();
        if ($this->request->action != 'delete') {
{% for field, column_config in detail_config.columns if column_config.input_type == 'foreign_key' %}
            // {{ column_config.label }}の選択肢
            $this->set('{{ column_config.foreign_table|lower }}', $this->{{ currentModelName }}->findForeignSelectionData('{{ column_config.foreign_table }}', '{{ column_config.foreign_disp_column }}'));
{% endfor %}
        }
    }

{% endif %}
public function bake($name)
{
    // bakeの詳細設定を取得
    $detail_config = $this->getBakeDetailConfig($name);
    foreach ($detail_config['columns'] as $column_name => $config) {
        // input_typeがcheckboxの項目があったら子テーブルを生成する
        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);
        }
+        // input_typeがforeign_keyの項目があったら子テーブルを先にbakeしておく
+        else if ($config['input_type'] == 'foreign_key') {
+            $belong_table_class_name = Inflector::pluralize(Inflector::camelize($config['foreign_entity']));
+            $belong_table = $this->getTable($belong_table_class_name);
+            $belong_table_object = $this->getTableObject($belong_table_class_name, $belong_table);
+            $belong_data = $this->getTableContext($belong_table_object, $belong_table, $belong_table_class_name);
+            $this->bakeTable($belong_table_object, $belong_data);
+            $this->bakeEntity($belong_table_object, $belong_data);
+        }
        // input_typeがfileの項目があったらアップロード先のディレクトリを生成しておく
        else if ($config['input_type'] == 'file') {
            $dir_name = Inflector::underscore($name);
            if (!file_exists(UPLOAD_FILE_BASE_DIR . DS . $dir_name)) {
                @mkdir(UPLOAD_FILE_BASE_DIR . DS . $dir_name);
            }
        }
    }

    $table = $this->getTable($name);
    $tableObject = $this->getTableObject($name, $table);
    $data = $this->getTableContext($tableObject, $table, $name);
    // 設定配列を追加
    $data['detail_config'] = $detail_config;
    $this->bakeTable($tableObject, $data);
    $this->bakeEntity($tableObject, $data);
}

対応している入力項目について

現在対応しているinput_typeについて整理してみました。全14種。

[column].input_typeMySQL型概要、メモ
textvarchar、textなど標準的なテキスト項目
textareavarchar、textなど標準的なテキストエリア項目
[column].row_numで表示するテキストエリアの行数を設定可能
numberint,floatなど標準的な数値入力項目
[column].min_valueで最小値
、[column].max_valueで最大値、[column].step_valueでステップ値を設定可能
datedateBootstrap Material DatePickerを使用した日付、日時選択項目
dateのとき日付を選択するウィンドウのみを表示する
timeのとき時間を選択するウィンドウのみを表示する
datetimeのとき日付、日時を選択するウィンドウを表示する
timetime
datetimedatetime
selectcharselect2を使用したプルダウン項目
multipleは使用不可
radiochar標準的なラジオボタン項目
checkbox子テーブルを自動生成、不要標準的なチェックボックス項目
子テーブルの命名規則=(親テーブル名)_(子カラム名の複数形)
filejsonBootstrap File Inputを使用したファイルアップロード項目
[column].max_file_numでアップロード可能な最大ファイル数を設定可能
gmap_latlonjsonGoogleMapAPIを使用した緯度経度項目
options.google_map_api_keyでGoogleMapのAPIキーを設定する必要あり
wysiwyg_textareamediumtext、longtextなどsummernoteを使用したWYSIWYGフォーム項目
tagvarchar、textなどTagsinput.jsを使用したタグ入力項目
[column].tag_delimiterで任意のデリミタを設定可能
foreign_keyint外部キー項目
カラム名はCakePHP3の規則に則った形で指定
[column].foreign_tableで外部のテーブル名を設定
[column].foreign_disp_columnで選択肢として表示するカラム名を設定
登録/更新画面ではselect2による選択肢で表示される

Bake用の設定ファイルとMySQLのCREATE TABLEのSQLを生成するプログラムを作成

プログラムはCakePHPとは分離させたかったのでネイティブなPHPで書いてます。
項目タイプを変更することで項目ごとの固有の設定項目が表示されるようになっています。
このプログラムでざっくりとした設定ファイルを作成して細かいところは手作業で直していくイメージ。

メモ

データを管理する画面で必要な入力項目は大体網羅できたような気がする。
細かいバグなんかを修正しつつ今後どうするかを考え中。

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