laravel newは何をやっているか？(あるいは何をやっていないか？)

# laravel newは何をやっているか？(あるいは何をやっていないか？)

この記事は [Laravel Advent Calendar 2019](https://qiita.com/advent-calendar/2019/laravel) の20日目の記事です
 
## そもそもlaravel newとは何か？

laraval newは新規にLaravelのプロジェクトを立ち上げる際にディレクトリ構成等を作ってくれるコマンド

### 使い方

composerはインストール済みであることを前提として進めます

```
$ composer global require laravel/installer
$ laravel new SOME_PROJECT
```
結果、下記の構成のディレクトリが出来上がります。ここからLaravelのプロジェクトを作っていくことになります
この時点で動かすだけなら`php artisan serve`すればビルトインサーバーが立ち上がる状態となっています

```
$ tree -a -L 1
.
├── .editorconfig
├── .env
├── .env.example
├── .gitattributes
├── .gitignore
├── .styleci.yml
├── README.md
├── app
├── artisan
├── bootstrap
├── composer.json
├── composer.lock
├── config
├── database
├── package-lock.json
├── package.json
├── phpunit.xml
├── public
├── resources
├── routes
├── server.php
├── storage
├── tests
├── vendor
└── webpack.mix.js
```

## 本題のlaravel newは何をやっているか？

[こちらのファイル](https://github.com/laravel/installer/blob/master/bin/laravel)がコマンドの実体となります
上記のコマンドは[SymphonyのCLI](https://symfony.com/doc/2.6/cookbook/console/console_command.html)を経由して[src配下のファイル](https://github.com/laravel/installer/blob/master/src/NewCommand.php)をキックします

----

### NewCommand.phpは何をしているか見ていく

Laravelのコマンドを自作したことがあれば元になっているSymphonyのコマンドは読めると思います
まずは大雑把に見ていくと
1. [オプションを初期化](https://github.com/laravel/installer/blob/master/src/NewCommand.php#L24)
2. [ZIP拡張モジュールがロードされているかチェック](https://github.com/laravel/installer/blob/master/src/NewCommand.php#L44)
3. [作成するプロジェクト名を退避して既存のプロジェクトがないかチェック](https://github.com/laravel/installer/blob/master/src/NewCommand.php#L48)
4. [アプリケーションの雛形をダウンロードしてカレントディレクトリに展開](https://github.com/laravel/installer/blob/master/src/NewCommand.php#L58)
5. [composerコマンドを実行準備](https://github.com/laravel/installer/blob/master/src/NewCommand.php#L63)
6. [5で準備していたcomposerコマンドを実行](https://github.com/laravel/installer/blob/master/src/NewCommand.php#L90)
と、この様な流れになっています。見ておくべき箇所は4と6の箇所、オプションという意味では次点で1も重要といったところでしょうか

### オプションを確認する

まずはコマンドラインのヘルプを参照します

```
$ laravel new --help
Description:
  Create a new Laravel application

Usage:
  new [options] [--] [<name>]

Arguments:
  name

Options:
      --dev             Installs the latest "development" release
      --auth            Installs the Laravel authentication scaffolding
  -f, --force           Forces install even if the directory already exists
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
```

続けてコマンドで定義しているオプションを確認します

```php
    protected function configure()
    {
        $this
            ->setName('new')
            ->setDescription('Create a new Laravel application')
            ->addArgument('name', InputArgument::OPTIONAL)
            ->addOption('dev', null, InputOption::VALUE_NONE, 'Installs the latest "development" release')
            ->addOption('auth', null, InputOption::VALUE_NONE, 'Installs the Laravel authentication scaffolding')
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces install even if the directory already exists');
    }
```

コマンド側で定義しているのは引数nameと下記のオプションだけということがわかります

- dev
- auth
- force

じゃ、他のオプションはどこで定義されているのかっていうと、[symphony/consoleのデフォルトオプション](https://github.com/symfony/console/blob/master/Application.php#L981)で定義されています
各オプションの意味は下記です

| オプション | 意味 |
|----------------|------|
| --dev | masterでなく、developブランチの最新からプロジェクトを作成する |
| --auth | 認証機能を備えたプロジェクトを作成する |
| --force | 既存の同名ディレクトリがあってもプロジェクトを作成する |

### --authオプションで作成した場合の差異

Laravelで用意された認証機能が使える状態でプロジェクトが作成されます
`--auth`を使わないプロジェクトのroutesがこちら

```
+--------+----------+----------+------+---------+--------------+
| Domain | Method   | URI      | Name | Action  | Middleware   |
+--------+----------+----------+------+---------+--------------+
|        | GET|HEAD | /        |      | Closure | web          |
|        | GET|HEAD | api/user |      | Closure | api,auth:api |
+--------+----------+----------+------+---------+--------------+
```

一方、`--auth`オプションを使ったプロジェクトのroutesはこちらとなります

```
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method   | URI                    | Name             | Action                                                                 | Middleware   |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | GET|HEAD | /                      |                  | Closure                                                                | web          |
|        | GET|HEAD | api/user               |                  | Closure                                                                | api,auth:api |
|        | GET|HEAD | home                   | home             | App\Http\Controllers\HomeController@index                              | web,auth     |
|        | GET|HEAD | login                  | login            | App\Http\Controllers\Auth\LoginController@showLoginForm                | web,guest    |
|        | POST     | login                  |                  | App\Http\Controllers\Auth\LoginController@login                        | web,guest    |
|        | POST     | logout                 | logout           | App\Http\Controllers\Auth\LoginController@logout                       | web          |
|        | GET|HEAD | password/confirm       | password.confirm | App\Http\Controllers\Auth\ConfirmPasswordController@showConfirmForm    | web,auth     |
|        | POST     | password/confirm       |                  | App\Http\Controllers\Auth\ConfirmPasswordController@confirm            | web,auth     |
|        | POST     | password/email         | password.email   | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail  | web          |
|        | GET|HEAD | password/reset         | password.request | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web          |
|        | POST     | password/reset         | password.update  | App\Http\Controllers\Auth\ResetPasswordController@reset                | web          |
|        | GET|HEAD | password/reset/{token} | password.reset   | App\Http\Controllers\Auth\ResetPasswordController@showResetForm        | web          |
|        | GET|HEAD | register               | register         | App\Http\Controllers\Auth\RegisterController@showRegistrationForm      | web,guest    |
|        | POST     | register               |                  | App\Http\Controllers\Auth\RegisterController@register                  | web,guest    |
+--------+----------+------------------------+------------------+------------------------------------------------------------------------+--------------+
```

### forceオプションは前のプロジェクトの内容を消してしまうのか？

消しません。あくまで上書きとなります
`--force`オプションで行うことはあくまで既存の同名ディレクトリが存在するかのチェックを行わないことのみです
使い方としては既存のプロジェクトに最新のLaravelをインストールすることでしょうか？

## アプリケーションの雛形をダウンロード

下記のいずれかのファイルをダウンロードする

- [デフォルト](http://cabinet.laravel.com/latest.zip)
- [--devオプションあり](latest-develop.zip)
- [--authオプションあり](http://cabinet.laravel.com/latest-auth.zip)

devオプションとauthオプションは排他の様ですね

## composerコマンドを実行

PROJECT_ROOT/.composer.jsonのscriptディレクティブのコマンドが実行されます
実行されるコマンドは下記の4つ

```
$ composer install --no-scripts
$ composer run-script post-root-package-install
$ composer run-script post-create-project-cmd
$ composer run-script post-autoload-dump
```

本記事執筆時点では上記のスクリプトの内容は下記となっています

```
"scripts": {
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi"
        ],
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate --ansi"
        ]
    }
```

### 実行されるcomposerコマンドを見ていく

- `composer install`
	- script実行(後続の処理)を行わずに必要なパッケージをインストール
- `composer run-script post-root-package-install`
	- .envファイルがなければ.envをコピーして作成
- `composer run-script post-create-project-cmd`
	- APP_KEYを作成
- `composer run-script post-autoload-dump`
	- 自動的に`bootstrap/cache/packages.php`にProvidor登録されたパッケージを表示
	- 詳しく’知りたい方は[こちら]を参照してください(https://readouble.com/laravel/6.x/ja/packages.html)を参照してください

----

## laravel newコマンドがやっていることまとめ
1. プロジェクトの雛形となるZIPファイルをダウンロード
	1. ZIPモジュールがロードされていることが前提条件
	2. 最新のdevelop、認証機能を追加でインストールしたい場合はオプションを指定する必要がある
2. composerに定義されたスクリプトを実行する
	1. `composer install`の実行
	2. .envファイルの作成
	3. APP_KEYの作成

----

## laravel newがやっていないこと

- DBの準備、具体的にはuserテーブルの作成
	- 必要な場合は`database/migration`を用いて作成する
- LaravelのVersionを指定したプロジェクトの作成
	- 指定をする必要がある経験者はcomposer.jsonを持っているだろうということでしょう
	- 逆に新規は極力、最新のLaravelを使った方がよいという判断かと思います
- フロントエンドのディレクトリ構成(React || Vue)の作成
	- 5.7の頃はVueのサンプルコードが入っていた記憶があるんですが、Reactとの選択式になったということかな？
	- Vueを使う場合は下記を実行すれば、Vueのサンプルコンポーネントが作成されます(npmを使う場合)

```
$ npm install
$ npm run dev
```

Reactを使う場合は下記を実行します

```
$ php artisan preset react
$ npm install
$ npm run dev
```

`artisan preset`は[none, bootstrap, vue, react]の4種に対応しますが、ここではnoneとbootstrapは読み飛ばします

## artisan presetがやっていること

ArtisanServiceProvider.phpに定義があります

```php
    protected function registerPresetCommand()
    {
        $this->app->singleton('command.preset', function () {
            return new PresetCommand;
        });
    }
```
	- [STUBディレクトリ](https://github.com/laravel/framework/tree/6.x/src/Illuminate/Foundation/Console/Presets)配下からテンプレートを読み込み、package.json等のファイルを指定されたライブラリ向けに変更する
		- PresetCommandの実体は[こちら](https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Console/PresetCommand.php)
	- 上記の書き換えにより必要なファイルのインストール準備が整い、`npm run dev`でコンパイルされることでフロントエンドの開発準備が整います
	- 後は`npm run watch`をバックエンドで走らせるなりで開発していくことになるでしょう

----

## まとめ
1. プロジェクトの最新の雛形からbuilt-in-serverが動く最低限の準備を行う
	1. LaravelのVersion指定はできない
	2. 認証機能が欲しい場合は`--auth`を指定する
	3. masterでなくdevelopを使いたい場合は`--dev`を指定する
2. DBの作成は行わない、authオプションを指定した場合はmigrationを用いる
3. フロントエンドのファイル構成は準備しない、ディレクトリ構成を準備したい場合は`npm run dev`を行う
	1. デフォルトではVue.js
	2. Reactを使いたい場合は`php artisan preset react`を用いてから

## 最後に
この記事を書こうと思ったのは11月からReactのプロジェクトに参加していて`create-react-app`を勉強がてら読んだことがきっかけでした
ReactもVueもアドベントカレンダーが空いてなかったのでlaravel newについて書きましたが、知らないこともいっぱいあってよい学びとなりました
PresetCommandなんかは今回色々と調べていて初めて存在を知ったくらい

ここしばらくコマンドの内容を理解せずに使うことが増えてしまっていましたが、よくない傾向なので改めて内容を理解して使う意識を持ちたいと思います
自分の知らないことを知るよい機会にもなるのでよく使うコマンドのリーディングは貴重な経験となりますね