yii2のCRUDGenerator(scaffold)を改良した件

こんばんは。運動しなさすぎてフィットネスジムに入会しようか悩んでいるmorifujiです

## 概要
- PHPカンファレンスでとてもPHP強そうな人と喋ってた時に`さくっと作るならYii2が最速`と聞いたのでちょこっとかじって見た
- Yii2
 - https://www.yiiframework.com/
 - GUIベースのscaffoldが付いている
 - [ベンチマークはそこそこ](https://github.com/kenjis/php-framework-benchmark#results)


## 構築手順

めんどくさかったのでローカルでやっちゃいました :sweat_smile: 

### 前提環境

- Mac HighSierra
- php 7.1
- mysql5.7



```
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
cd basic
php yii serve
```

終了。はや。

`localhost:8080/`でアクセスできる

<img width="1440" alt="スクリーンショット 2018-08-30 23.59.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/c49b5d58-9e61-ca3b-fa7c-1925974f3904.png">

## 解説

こんな感じ1

![](https://www.yiiframework.com/doc/guide/2.0/ja/images/application-structure.png)

こんな感じ2

```
basic/                  アプリケーションのベース・パス
    composer.json       Composer によって使用される。パッケージ情報を記述
    config/             アプリケーションその他の構成情報を格納
        console.php     コンソール・アプリケーションの構成情報
        web.php         ウェブ・アプリケーションの構成情報
    commands/           コンソール・コマンドのクラスを格納
    controllers/        コントローラのクラスを格納
    models/             モデルのクラスを格納
    runtime/            実行時に Yii によって生成されるファイル (ログやキャッシュなど) を格納
    vendor/             インストールされた Composer パッケージ (Yii フレームワークそのものを含む) を格納
    views/              ビュー・ファイルを格納
    web/                アプリケーションのウェブ・ルート。ウェブ・アクセス可能なファイルを格納
        assets/         Yii によって発行されるアセット・ファイル (javascript と CSS) を格納
        index.php       アプリケーションのエントリ・スクリプト (ブートストラップ・スクリプト)
    yii                 Yii コンソール・コマンド実行スクリプト
```

こんな感じ3

![](https://www.yiiframework.com/doc/guide/2.0/ja/images/request-lifecycle.png)

## DB接続
`config/db.php`に以下の通り設定。

dsnってポートは`port=3111;`こう書くんですね

```<?php
return [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=127.0.0.1;port=3111;dbname=hogehoge',
    'username' => 'root',
    'password' => 'password',
    'charset' => 'utf8'
];
```

> 出典：https://www.yiiframework.com/doc/guide/2.0/ja/start-workflow

## gii

yii2のscaffoldツールは`gii`と呼ばれているらしい

https://www.yiiframework.com/doc/guide/2.0/ja/start-gii


実はすでにインストールされていて、`/index.php?r=gii`を叩くとメニュー出てきた、思ったより多機能 :astonished: 

<img width="1378" alt="スクリーンショット 2018-08-31 0.01.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/041a8123-809a-28b4-5c03-44adc667f7b1.png">

基本的にはこの２つで事足りる
- Model Generator
- CRUD Generator


### Model Generator
DBに定義しているテーブルからモデルクラスを自動出力してくれる

この２つだけ記載すればあとはよしなにやってくれる。

- Table Name
- Model Class Name

TableNameに入力すると予測変換が出てきて少し感動。続けてキャメルケースでModelClassNameを書く。

最後に`Preview`を押す。するとどんなファイルが出力されるか表示される。`Generate`で出力する

<img width="907" alt="スクリーンショット 2018-08-31 0.07.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/464d7476-6b22-31d1-4067-ced23127cb21.png">

### CRUD Generator

ModelGeneraterによって出力されたModelを元に自動でCRUDのページ・ルーティング、果てはフォームも自動生成する

この３つを設定

- ModelClass
- SearchClass
- ControllerClass

#### ModelClass
すでにあげたディレクトリ構成に従うと`app\models\{キャメルケース}`となる

#### SearchClass
ドキュメントでは、すでにあげたディレクトリ構成に従い、suffixを`Search`として`app\models\{キャメルケース}Search`としている

#### ControllerClass
すでにあげたディレクトリ構成に従うと`app\controllers\{キャメルケース}Controller`となる

<img width="807" alt="スクリーンショット 2018-08-31 0.13.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/85f53c3a-9257-3b9b-cc9f-eb27c147ec5c.png">


同じく`Preview`を押して`Generate`

<img width="807" alt="スクリーンショット 2018-08-31 0.13.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/6580f3aa-414e-d38b-1c85-35cfbe8e6ade.png">

できた！

### 実際の画面は？

`/index.php?r={モデル名}`で表示されます

#### 検索画面

<img width="1197" alt="スクリーンショット 2018-08-31 0.15.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/6797849c-4a7a-d728-75b5-c6494c46e51a.png">

ご丁寧に、画面に表示できないプロパティはコメントアウトしてくれている。こういうところ嬉しいですよね :cry: 

```php:views/users/_search.php
    <?php $form = ActiveForm::begin([
        'action' => ['index'],
        'method' => 'get',
    ]); ?>

    <?= $form->field($model, 'id') ?>

    <?= $form->field($model, 'email') ?>

    <?= $form->field($model, 'password') ?>

    <?= $form->field($model, 'nearest_station_id') ?>

    <?= $form->field($model, 'frequently_drinking_station_id') ?>

    <?php // echo $form->field($model, 'sex') ?>

    <?php // echo $form->field($model, 'payment_customer_token') ?>

    <?php // echo $form->field($model, 'payment_subscription_token') ?>

    <?php // echo $form->field($model, 'date_of_birth') ?>

    <?php // echo $form->field($model, 'name') ?>

    <?php // echo $form->field($model, 'role') ?>

    <?php // echo $form->field($model, 'franchise_id') ?>

    <?php // echo $form->field($model, 'corporation_id') ?>

    <?php // echo $form->field($model, 'encrypted_id') ?>

    <?php // echo $form->field($model, 'created_at') ?>

    <?php // echo $form->field($model, 'updated_at') ?>

    <?php // echo $form->field($model, 'deleted_at') ?>

    <div class="form-group">
```

#### 詳細画面
So simple

<img width="700" alt="スクリーンショット 2018-08-31 0.19.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/5fde733c-d72d-8a6e-c0f8-d8715b8e63b2.png">

#### 登録画面・更新画面
<img width="1071" alt="スクリーンショット 2018-08-31 0.23.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/6bdaa688-5f10-5e79-6b1c-da34e10c5f45.png">
<img width="1083" alt="スクリーンショット 2018-08-31 0.23.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/f32f3283-4348-5874-4aea-ac7dafac8cb6.png">

特徴としてはこんな感じ。

- パスワードがシークレットな表示 (画像でいう`Password`)
- enumで定義したカラムはドロップダウン表示 (画像でいう`Role`)
- date型・time型は文字列として表示 (画像でいう`Date Of Birth`)


## 本題

`date型・time型は文字列として表示`ここがおしい。ここもきちんとdatepicker/timepicker出してくれたらわりと活躍するかなあと思ってましたが残念すぎる、。。。

ということで、この部分だけサクッとカスタマイズしましょう

### テンプレートファイル編集
https://www.yiiframework.com/extension/yiisoft/yii2-gii/doc/guide/2.1/en/topics-creating-your-own-templates

これにしたがって、giiのscaffold出力ロジックをいじいじした。

結果、ここでいじいじしてるみたい。

```php:/vendor/yiisoft/yii2-gii/src/generators/crud/Generator.php


  /**
     * Generates code for active field
     * @param string $attribute
     * @return string
     */
    public function generateActiveField($attribute)
    {
        $tableSchema = $this->getTableSchema();
        if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) {
            if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) {
                return "\$form->field(\$model, '$attribute')->passwordInput()";
            }

            return "\$form->field(\$model, '$attribute')";
        }
        $column = $tableSchema->columns[$attribute];
        if ($column->phpType === 'boolean') {
            return "\$form->field(\$model, '$attribute')->checkbox()";
        }

        if ($column->type === 'text') {
            return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])";
        }

        if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) {
            $input = 'passwordInput';
        } else {
            $input = 'textInput';
        }

        if (is_array($column->enumValues) && count($column->enumValues) > 0) {
            $dropDownOptions = [];
            foreach ($column->enumValues as $enumValue) {
                $dropDownOptions[$enumValue] = Inflector::humanize($enumValue);
            }
            return "\$form->field(\$model, '$attribute')->dropDownList("
                . preg_replace("/\n\s*/", ' ', VarDumper::export($dropDownOptions)) . ", ['prompt' => ''])";
        }

        if ($column->phpType !== 'string' || $column->size === null) {
            return "\$form->field(\$model, '$attribute')->$input()";
        }

        return "\$form->field(\$model, '$attribute')->$input(['maxlength' => true])";
    }
```

なるほど。カラム名がpasswordとかpasswdとかならシークレットな表示にするのか笑

```
            return "\$form->field(\$model, '$attribute')->$input()";
```

日付型・時間型はここに引っかかっているらしい

となるとformを出力する必要があるので、Extensionから検索。だれか作ってるでしょ

### datetimepickerプラグイン

あった。総ダウンロード数19000ぐらい、普通の感覚では少し不安かもしれないけど、他のextensionは一桁台ダウンロード数だったのでこのextensionの有用さがうかがえる

https://www.yiiframework.com/extension/zhuravljov/yii2-datetime-widgets

composerに追加

```
composer require zhuravljov/yii2-datetime-widgets
```

先ほどの`Generator.php`を編集

```php

        if ($column->type === 'date') {
            return "\$form->field(\$model, '$attribute')->widget(\zhuravljov\yii\widgets\DatePicker::class, [
    'clientOptions' => [
        'format' => 'yyyy-mm-dd',
        'language' => 'ja',
        'autoclose' => true,
        'todayHighlight' => true,
    ],
    'clientEvents' => [],
])";
        }


        if ($column->type === 'time') {
            return "\$form->field(\$model, '$attribute')->widget(\zhuravljov\yii\widgets\DateTimePicker::class, [
    'clientOptions' => [
        'format' => 'hh:ii:00',
        'language' => 'ja',
        'autoclose' => true
    ]
])";
        }

        if (in_array($column->type, ["timestamp", "datetime"])) {
            return "\$form->field(\$model, '$attribute')->widget(\zhuravljov\yii\widgets\DateTimePicker::class, [
    'clientOptions' => [
        'format' => 'yyyy-mm-dd hh:ii:00',
        'language' => 'ja',
        'autoclose' => true
    ]
])";
        }
```


## 結果

もう一度CRUDGeneratorを使う

<img width="830" alt="スクリーンショット 2018-08-31 0.38.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/c0f81501-340c-a057-bb62-f1a087b8b940.png">

このとき、`override`にチェックを入れて`Generate`

そして登録画面・編集画面へ http://localhost:8080/index.php?r=users%2Fcreate

![aaaaaa.gif](https://qiita-image-store.s3.amazonaws.com/0/158393/c5480ba7-8aea-3ea0-458a-310bd394f2de.gif)

ｷﾀ━━━━━━━━m9( ﾟ∀ﾟ)━━━━━━━━!!


## 所感

- 普段使っていないfwは面白い
 - しかもscaffold使うとガシガシできて気持ちいい。
- コミュニティは活発なようだがextensionの荒廃している感が否めない
- 3コマンドで環境構築できるとか強すぎひん？
 - 地味にプロファイリングツールもついてたww

<img width="1440" alt="スクリーンショット 2018-08-31 0.50.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/6d7af762-9fe4-335e-8b46-3eefa54d6633.png">