複雑度の上がったLaravelをミドルウェアから眺める方法


この記事は、[PHP Advent Calendar 2021](https://qiita.com/advent-calendar/2021/php)の２０日目です。

https://qiita.com/advent-calendar/2021/php

## 複雑度の上がったLaravelとは

[採用情報](https://corp.kaonavi.jp/recruit/list/#engineer)にも記載のある通り、私の所属するカオナビ社では PHP(Laravel) でWebアプリケーションを作成しています。

カオナビがビジネスとして成長していく中で、提案・要望による機能追加・仕様追加が行われてきました。これに伴って、理解しなければならない条件・情報も増え、プログラムのファイル数、ディレクトリ数も増えていき、アプリケーションは複雑になっていきます。このような状況を、**複雑度が上がる**と表現します。

サービスが続く限り、どうしても複雑度は上昇していきます。しかし、人間の認知能力には限界があります。一度に脳内に詰め込んで考えることが出来る量には限りがあり、かつ劇的に増えることはありません。

このようなアプリケーションは、全部のソースコードを読んで、全体を一気に理解するのは非常に難しいです。

複雑度の上がったアプリケーションを、少しずつ紐解いて理解していく方法はたくさんあるのですが、今回はLaravelのミドルウェアを外形的に調査して、理解する手法を紹介します。

## Laravelのミドルウェアとは

Laravel には ミドルウェア という機能があって、ルート（URLとほぼ同義）で指定された処理の前後に、任意の処理を行わせることが可能です。

主処理とは独立した形で実行できるため、Cookieの暗号化、セッション認証など、分離しやすい機能単位で便利に使うことができます。
一般的なウェブアプリケーションを利用したことのある人であれば、アクション前後に実行する「共通処理」を、主処理への依存性を排除した上で追加できる機能という説明が分かりやすいかもしれません。

ミドルウェア は、アプリケーションのルート定義に対して、任意に設定することが可能です。例えば、以下のような使い方をします。

- `/foo` に `Authentication` ミドルウェアを設定することで、ログインした人だけが見れるURLにする。
- `/bar` は、ログインしてない人にも見せたいから、ミドルウェアを設定しない。 

そのため、どのミドルウェアが、どのURLに対して設定されているのかを調べることで、複雑度が高くなったアプリケーションにおいても、外形的に仕様や、全体的なアーキテクチャの様子を理解することが出来ます。

## 具体的な調査方法

Laravelには、ルート定義とミドルウェアの一覧を出力してくれる`route:list`という便利なコマンドがあります。 ただ、デフォルトだとテキスト形式で出力されるので解析に不向きです。下記のように、`json`形式で出力するオプションをつけます。

`php artisan route:list --json`

出力されたJSONをただ眺めるだけだと、よく分からないので、jqコマンドを使って、JSONファイルの出力結果を絞り込みます。jqコマンドは便利なのですが、絞り込み構文をよく失念するので、下記にLaravelのルートを絞り込むときの代表的なパターンを上げておきます。

#### xxxx という名前のミドルウェアを含むルートに絞り込む

```bash
$ php artisan route:list --json | jq '.[] | select(.middleware | contains("xxxx"))'
```

#### xxxx, yyyy という２つのミドルウェア名で絞り込む

```bash
$ php artisan route:list --json | jq '.[] | select((.middleware | contains("xxxx")) and (.middleware | contains("yyyy")))'
```

※ `and` の箇所を `or` に変えることでどちらか一方のミドルウェア名を含む場合も検索出来ます。

#### tttt という名前のミドルウェアを、結果から除外する

```bash
$ php artisan route:list --json | jq '.[] | select(.middleware | contains("tttt") | not)'
```

## より発展的な調査方法

ワンライナーで、簡単な集計を行うことも出来ます。

#### 各ミドルウェアの組み合わせを使っているルートの数

```bash
$ php artisan route:list --json | jq '.[].middleware' | uniq -c | sort -n
1 "web,fuga"
2 "web,fuga,hoge"
10 "web,hoge"
```

#### それぞれのミドルウェアが利用されている回数の集計

```bash
php artisan route:list --json | jq '.[].middleware' | sed -e "s/\"//g" | sed -e s/,/\\$'\n'/g | awk '{cn[$0]++}END{for(i in cn)print cn[i], i}' | sort -n
1 fuga
15 hoge
20 web
```

## 調査の副産物として、分かるかもしれない情報

本来は、複雑度を調べるための外形的な調査ですが、不具合・脆弱性につながりかねない情報も見つかる可能性があります。

1. 本来は認証ミドルウェアが入ってなければならないルートに認証が入ってない。
1. 本来はミドルウェアが入っていてはいけないルートに、ミドルウェアが一切設定されていない。
1. 間違ったミドルウェアが設定されている。
1. 同様の機能群とは一貫性のない組み合わせのミドルウェアが設定されている。

## まとめ

今回紹介した方法は、ソースコードをなるべく読まずにアプリケーションを理解する方法の一つです。これ以外にも、静的解析、循環複雑度の解析など、色んな方法が世の中には存在します。
もちろん、適切な抽象化や、適切なアーキテクチャが選定されていて、ひと目で仕様が分かるようなソースコードは目指すべき一つの理想です。しかし、「全然、複雑で分からない！」という状況に陥ったときは、気分を変えて多面的にアプリケーションを眺めてみるのも良い手段だと思います。

## おまけ

記事のおまけとして、複雑度の解析に役立つツールや、書籍を載せておきます。

### ツール類
Laravelアプリケーションをお手軽に解析してくれるツール

https://github.com/stefanzweifel/laravel-stats

循環的複雑度を簡単に測定出来るツール

https://github.com/sebastianbergmann/phploc

### アプリケーションの複雑性や、その対処について解説した書籍

名著APoSD

https://www.amazon.co.jp/dp/173210221X

レガシーコード改善ガイドも、複雑なソースコードと対決するときは読んでおきたいです。

https://www.amazon.co.jp/dp/4798116831

もはや古典になってしまいそうですが、アプリケーションを作るとはどういうことなのか、よくまとまっています。

https://www.amazon.co.jp/dp/489100455X