Laravel 5.4 make:model コマンド改造入門

# 本記事でやること
php artisan make:model コマンドに以下の改造を加えます。

* __デフォルトの生成対象ディレクトリと名前空間を変更する__
* __stubを弄って生成される初期クラスの様相を変える__

## 対象環境
Laravel 5.4.11
CentOS 6.5

# make:model 改造の前準備
まずmake:modelを改造できるようにクラスを準備してコマンドとして登録します。
以下の手順を全て終わらせてください。

## 1. make:command を実行
app/Console/Commands/ModelMakeCommand.php
が生成されます。

```
php artisan make:command ModelMakeCommand
```

## 2. クラスをConsole/Kernelに登録
app/Console/Kernel.php を編集して、ModelMakeCommandを登録します。

```php:app/Console/Kernel.php
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        'App\Console\Commands\ModelMakeCommand', # add make:model
    ];

    ...
```

## 3. ModelMakeCommand のメソッドとプロパティを全て削除
クラスの中身を削除してこの形にします。

```php:app/Console/Commands/ModelMakeCommand.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class ModelMakeCommand extends Command
{
}
```

## 4. 継承先を変更
継承先をフレームワークコアのModelMakeCommandに変更します。
これで準備は完了です。

```php:app/Console/Commands/ModelMakeCommand.php
<?php

namespace App\Console\Commands;

class ModelMakeCommand extends \Illuminate\Foundation\Console\ModelMakeCommand
{
}
```

# デフォルトの生成対象ディレクトリと名前空間を変更する

## 前提
### make:modelの対象位置はコマンド指定できる
make:modelは以下のようにパスまで含めたモデル名を渡すことで、
クラスが生成されるディレクトリと名前空間を変更することが出来ます。

```php
php artisan make:model Tables/Test
```

それを、以下のように打つだけでクラスの生成ディレクトリと名前空間を変えてしまおうという試みとなります。

```php
php artisan make:model Test
```

### なぜデフォルトディレクトリを変更するのか
毎回ディレクトリの位置を指定するのが面倒だから。
打ち間違えで変なディレクトリ作るくらいなら最初から決め打ちしましょう。

## 実装手順
### 1. メソッドをオーバーライド

```getDefaultNamespace``` をオーバーライドして返却する名前空間に好きな階層を追加するだけです。
ここではTablesとしています。

```php:app/Console/Commands/ModelMakeCommand.php
<?php

namespace App\Console\Commands;

class ModelMakeCommand extends \Illuminate\Foundation\Console\ModelMakeCommand
{
    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return $rootNamespace . '\Tables';
    }
}
```

### 2. 実行
特にパスを指定せずにmake:modelを実行します。

```
$ php artisan make:model Test
Model created successfully.
```

Tablesディレクトリにモデルが生成されています。

```git
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   app/Tables/Test.php
#
```

名前空間も正しく変更されています。

```php:app/Tables/Test.php
<?php

namespace App\Tables;

use Illuminate\Database\Eloquent\Model;

class Test extends Model
{
    //
}
```

# stubを弄って生成される初期クラスの様相を変える
## 前提
### 生成されるモデルの継承元は常に同じ
常にmake:modelされるモデルは継承元として、```Illuminate\Database\Eloquent\Model```を指します。
AppModelを作って共通メソッドを作るといったCakePHP的なやり方に対応するためには、
毎回生成後のモデルのextends以降を書き換えなければいけません。

それを解決するために今回は __「stub」__ を書き換えます。

### stubとは
artisanのジェネレート系コマンドで利用される、クラスのひな型です。
フレームワークコアのこちらのディレクトリに格納されています。

```bash:vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs
$ ls
console.stub               event.stub       listener-queued.stub  markdown-mail.stub          model.stub         policy.stub    routes.stub
event-handler-queued.stub  job-queued.stub  listener.stub         markdown-notification.stub  notification.stub  provider.stub  test.stub
event-handler.stub         job.stub         mail.stub             markdown.stub               policy.plain.stub  request.stub   unit-test.stub
```

```php:model.stub
<?php

namespace DummyNamespace;

use Illuminate\Database\Eloquent\Model;

class DummyClass extends Model
{
    //
}
```

## 実装
### 1. model.stub をアプリケーションへコピー
```vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs``` からmodel.stubをコピーします。
コピー先は ```app/Console/Commands/stubs``` です。

例はアプリケーションルートで叩いている想定です。
パスは適宜読み替えてください。

```bash
$ mkdir app/Console/Commands/stubs
$ cp vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/model.stub app/Console/Commands/stubs
```

### 2. model.stub を ModelMakeCommand に登録
```getStub``` メソッドをオーバーライドしてstubの位置を返します。
今回は ```ModelMakeCommand.php``` と同じ階層にある ```stubs``` ディレクトリに ```model.stub``` を配置したので、
このような指定にしてください。

```php:app/Console/Commands/ModelMakeCommand.php
<?php

namespace App\Console\Commands;

class ModelMakeCommand extends \Illuminate\Foundation\Console\ModelMakeCommand
{
    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return __DIR__ . '/stubs/model.stub';
    }
}
```

### 3. stubを改造
今回は共通処理をAppModelに纏めて全てのモデルで継承することにします。
model.stubの継承元クラスを、 ```AppModel``` に変更します。

__「DummyNamespace」__ と __「DummyClass」__ 以外はすべて書き換え可能です。
これらについては名前空間とクラス名がstr_replaceで置き換えられているので、変更しないでください。

```php：app/Console/Commands/stubs/model.stub
<?php

namespace DummyNamespace;

class DummyClass extends AppModel
{
    //
}
```

### 4. 実行
make:modelを実行します。

```
$ php artisan make:model Test
Model created successfully.
```

生成されたクラスにAppModelがextendsされています。
あとはこのクラスを実装して共通処理を記述するだけです。

```php:app/Test.php
<?php

namespace App;

class Test extends AppModel
{
    //
}
```

### 備考

更にいろいろ動的に弄りたい場合は、
こちらのメソッドをオーバーライドしてstubをひたすらstr_replaceすると良いでしょう。

```php
protected function buildClass($name) {}
```

詳しくはこのあたりを参照してください。
[ModelMakeCommand.php](https://github.com/laravel/framework/blob/999e3bfd8745d91682ca0fad578ea469b5a19e91/src/Illuminate/Foundation/Console/ModelMakeCommand.php)
[GeneratorCommand.php](https://github.com/laravel/framework/blob/999e3bfd8745d91682ca0fad578ea469b5a19e91/src/Illuminate/Console/GeneratorCommand.php)

# まとめ
今回はModelMakeCommandを編集することでmake:modelを改造しました。
その他のmake系コマンドもすべて改造可能なので、そちらも記事にしていく予定です。

またmodel.stubをbladeテンプレート化してみようと試みたこともあったのですが、
エスケープ記述が煩雑になってファイル全体の見通しが地獄になったので、あまりオススメできません。