前提条件
公式ページのインストールガイドを参考に Sail を利用した Laravel アプリケーションのインストールが完了していること。
$ sail php -v
PHP 8.1.13 (cli) (built: Nov 26 2022 14:07:55) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.13, Copyright (c) Zend Technologies
with Zend OPcache v8.1.13, Copyright (c), by Zend Technologies
with Xdebug v3.1.5, Copyright (c) 2002-2022, by Derick Rethans
$ sail artisan --version
Laravel Framework 9.42.2
本記事におけるプロジェクトのディレクトリ構造は以下の通りとします。
git-repository/
└── my-laravel/
├── app/
├── bootstrap/
├── config/
├── database/
├── composer.json
├── composer.lock
└── ...
Larastan を導入
ソースコードの文法上の問題点を事前に検出するための方法として、ソースコードの静的解析が挙げられます。
PHP には PHPStan という静的解析ツールがあり、Larastan はこれを Laravel プロジェクト用に拡張したラッパーとなっています。
Larastan を導入することで、先に挙げたソースコードの文法上の問題点を事前に検出できるだけでなく、PSR に基づいたコーディングを強制することによって、チームで開発している場合は個人の力量に依存しない一定の品質を保ったコードを書くことに一役買ってくれます。
Larastan は Composer でインストール可能です。
今回は事前に Laravel Sail がインストールされた環境のため、 sail
コマンドで composer を実行します。
$ sail composer require --dev nunomaduro/larastan
インストールが完了していれば ./vendor/bin/phpstan --version
で PHPStan のバージョン情報が確認できるはずです。
# コンテナの bash を起動する。
$ sail shell
# インストール確認のためにバージョン情報を表示する。
$ ./vendor/bin/phpstan --version
続いて公式の README に従い phpstan.neon
ファイルを作成していきます。
下記ファイル内容は一例のため、解析レベルや解析対象パス、各種ルールなどは PHPStan と Larastan の各種情報を確認し、要件に合う適切な設定を施してください。
includes:
- ./vendor/nunomaduro/larastan/extension.neon
parameters:
# 解析レベル
# https://phpstan.org/user-guide/rule-levels
level: 0
# 解析対象に含めるパス
paths:
- app
- bootstrap
- config
- resources
- routes
- tests
# 解析対象に含めないパス
excludePaths:
- routes/console.php
後は PHPStan のバージョンを確認した時と同じように、コンテナの bash を起動して ./vendor/bin/phpstan analyze -- app/
といった具合にコマンドを実行すれば解析できます。
$ ./vendor/bin/phpstan analyze -- app/
Note: Using configuration file /var/www/html/phpstan.neon.
24/24 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
[OK] No errors
余談 : Larastan を artisan コマンド化する
Larastan の実行はできるようになりましたが、上述した方法ですと毎回コンテナの bash を起動する一手間があり面倒だったので sail artisan larastan
といった形で artisan コマンド化しました。
まずは Command
クラスのスケルトンを作成します。
$ sail artisan make:command LarastanCommand
Larastan を実行できるよう実装していきます。
こちらを参考にさせていただきました。
Larastan に定義できる設定内容は phpstan.neon
に記述することとし、 LarastanCommand
クラスがパースできるオプションは非常に簡素なものとしています。
この辺りは使用感に合わせて追々拡充させていけば良いと考えています。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Isolatable;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Process;
class LarastanCommand extends Command implements Isolatable
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'larastan
{--without-tty : Disable output to TTY}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the appliation static analyze by Larastan';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->ignoreValidationErrors();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$command = ['vendor/bin/phpstan', 'analyze', '--', 'app/'];
$process = new Process($command);
try {
$process->setTty(!$this->option('without-tty'));
} catch (RuntimeException $e) {
$this->output->writeln('Warning: ' . $e->getMessage());
}
$exitCode = Command::FAILURE;
try {
$exitCode = $process->run(fn ($type, $line) => $this->output->write($line));
} catch (ProcessSignaledException $e) {
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
throw $e;
}
}
$this->newLine();
return $exitCode;
}
}
artisan コマンドとして実行してみて、先ほどと同様の解析結果になることを確認します。
$ sail artisan larastan
Note: Using configuration file /var/www/html/phpstan.neon.
24/24 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
[OK] No errors
GitHub Actions への組み込み
Larastan を実行することへの面倒臭さは解消されました。
ただ現時点ではまだ手動実行のため、そもそも実行することを忘れる可能性があります。
Git フックを利用して pre-commit や pre-push のタイミングで解析を行うのも一案ですが、開発体験はむしろ悪化する恐れがある上に、チームで開発している場合などは、ローカルに設定する方法では信用に足らないと思います。
上記の課題を解決すべく GitHub Actions で CI/CD ワークフローを構築し「GitHub の master ブランチにプッシュされたら Larastan を実行する。」といった状態を目指していきます。
GitHub Actions で実行するワークフローは、リポジトリ内に配置した .github/workflows
ディレクトリ配下の yml ファイルにて定義できます。
本記事における yml ファイル作成後のディレクトリ構成、yml ファイルの内容については以下の通りです。
git-repository/
├── .github/
| └── workflows/
| └── my-laravel-ci.yml
└── my-laravel/
├── app/
├── bootstrap/
├── config/
├── database/
├── composer.json
├── composer.lock
└── ...
# GitHub Actions 上での当該ワークフロー名です。
# 識別しやすい名称を付けてください。
name: my-laravel-ci
# 当該ワークフローの実行タイミングに関する定義です。
# ここでは main ブランチの my-laravel ディレクトリ配下にある PHP ファイルに対して push が行われたタイミングとしています。
# トリガーや対象ブランチ、パス定義などは変更可能なため、プロジェクトの性格に合わせて変更すべきポイントです。
on:
push:
branches:
- "main"
paths:
- "my-laravel/**.php"
jobs:
# ubuntu-latest 仮想マシン上で以下の流れでワークフローを実行していきます。
# 1. 対象プロジェクトをチェックアウト。
# 2. sail コマンドでアプリケーションコンテナ(+α)を起動。
# 3. Larastan を実行。
# 4. PHPUnit を実行。
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./my-laravel
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-install=dist
- name: Start application
run: vendor/bin/sail up -d
- name: Generate key
run: vendor/bin/sail artisan key:generate
# 本記事で実装した LarastanCommand クラスを `sail artisan` で呼び出すことで Larastan を実行しています。
- name: Execute lint via Larastan
run: vendor/bin/sail artisan larastan --without-tty
- name: Execute test via PHPUnit
run: vendor/bin/sail artisan test
yml ファイルを commit 後 my-laravel プロジェクトの PHP ファイルに変更を加えて commit & push をしてみるとワークフローが実行されます。
GitHub 上で test ジョブの実行履歴を開くと、無事 Larastan が実行されていることがわかります。(スクショのワークフロー名が異なっているのはご容赦ください。)
一度 GitHub Actions への登録が済んでしまえば、以降は Larastan の実行を意識することなく開発に専念できるため非常に便利だと感じました。
他にも CI/CD ツールはたくさんありますが、 GitHub でコード管理をしていれば別ツールやサービスを利用せずとも CI/CD を始められるため、取っ掛かりとしての心理的ハードルも低いと思います。
GitHub Actions は他にも様々な機能が提供されているので、使いこなせるよう勉強していきたいです🖋