🐘

Laravel considered harmful 〜SOLID原則にそぐわないLaravelのイマイチな習慣〜

2023/05/03に公開

はじめに

この記事は、「Laravel considered harmful」という英文記事に書かれている18 個の Laravel の有害な習慣のうち、個人的に関心を持った一部を抜粋・翻訳[1]し、私見を交えたものです。

https://www.reddit.com/r/PHP/comments/131t2k1/laravel_considered_harmful/

この記事の主題は原文記事からいただき、副題は日本語で翻訳し私見を踏まえた上での原文記事の要約的なものをつけさせていただきました。

注意点として、この記事の中では原文に沿った形で harmful="有害な"という表現で通していますが、記事タイトルとしては攻撃的すぎると思い、かなり意訳ですが"イマイチな"とさせていただいています。

原文記事は結構 Laravel に対して痛烈なことを書いているのですが、個人的には「まあせやな…」となる内容かなとは思います。

特に、オブジェクト指向について理解が浅い状態で Laravel を使っているな、という自覚がある方にとってはぜひ知っておいて欲しい内容です。

あと、Laravel を知らない人であっても、オブジェクト指向について一歩踏み込んで学習するにはもってこいな内容ではないかと思っています。

ちなみに原文記事を知ったのは、つよつよエンジニア=にゃんだーすわん(@tadsan)さんのこのツイートがきっかけです。

https://twitter.com/tadsan/status/1652387237152317441

原文記事の"はじめに"[2]

私は 10 年以上にわたり、趣味としても、職業としても PHP を使ってきました。コミュニティでの最善の慣行の使用に向けた進歩を多く見てきました。言語自体も大きく進化しました。数年前には、Composer や名前空間が存在しなかったのに、私たちは長い道のりを歩んできました。

フレームワークは常に存在していましたが、時間が経つにつれて、より包括的なツールとより良い慣行を提供するようになり、Zend Framework、CakePHP、CodeIgniter、Symfony などの人気のあるオプションがありました。十数年前、Laravel が登場し、バージョン 4 がリリースされるとすぐに、最も広く使われる PHP フレームワークとなりました。この投稿では、PHP コミュニティにとって Laravel が危険であるべき理由を説明したいと思います。私は 20 人の開発者が同じプロジェクトで Laravel を使用していたことがあり、このフレームワークがどのように危険であるかを学びました。

原文記事で列挙されている 18 の問題点

  1. Singleton usage
  2. Traits(★)
  3. No dependency inversion(★)
  4. Models is a mixture of 2 concepts
  5. Facade pattern(★)
  6. APIs are too flexible(★)
  7. Too many god classes(★)
  8. No single class responsibility(★)
  9. No type hints
  10. Magic methods(★)
  11. Components are tightly coupled
  12. Macroable mess
  13. Functions don’t have namespace
  14. It’s not SOLID(★)
  15. No strict typing
  16. No usage of final(★)
  17. Bad encapsulation(★)
  18. Over usage of strings(★)

★ が付いている項目が、この記事で触れるトピックスです。

また、この記事で紹介している内容もそうでない内容も、本気で考え始めるとそれぞれで一本記事が書けるようなテーマなので、この記事では結構ざっくり私見を述べる程度にとどめておりますので予めご了承ください。

ここで注意事項として、原文記事から一部を抜粋し翻訳していますが、説明の都合上、この記事では原文記事の列挙順とは入れ替えて説明を行っています。

Traits[3]

Trait は Laravel のいたるところで使われています。Trait は慎重に使用する必要があります。クラスを膨らませるのは簡単ですが、Trait はまだコードを追加するための垂直な方法であり、継承と似ています。コードを所有していない場合、Trait を削除することはできません。ほとんどの場合、特定のクラスにロジックを持たせるために、依存性の注入を使用することが適切です。

これは同意できる内容です。

簡単にイメージを掴むために SessionGuard.php を使って説明します。SessionGuard.php の現状はこのようになっています。

vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
//前略
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
    use GuardHelpers, Macroable;
    //中略
}

すごく雑ですが、このコードはほとんどこの状態と同じです。

vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
//前略
//実際は多重継承できないのでこんな文法は存在しないし、エラーになるがイメージとして
class SessionGuard extends GuardHelpers, Macroable implements StatefulGuard, SupportsBasicAuth
{
    //中略
}

ですが、原文記事においては依存性の注入を使用することが適切だと言っているので、コンストラクタインジェクションを使った委譲パターンであるべきだとしています。(実際修正する場合、こんなに単純ではないがざっくりイメージを掴むために)

vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
//前略
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
    //中略
    /**
     * Create a new authentication guard.
     *
     * @param  string  $name
     * @param  \Illuminate\Contracts\Auth\UserProvider  $provider
     * @param  \Illuminate\Contracts\Session\Session  $session
     * @param  \Symfony\Component\HttpFoundation\Request|null  $request
     * @param  \Illuminate\Support\Timebox|null  $timebox
     * @return void
     */
    public function __construct($name,
                                UserProvider $provider,
                                Session $session,
                                Request $request = null,
                                Timebox $timebox = null,
                                //ここが増えた
                                GuardHelpers $guardHelpers,
                                Macroable $micraoable)
    {
        $this->name = $name;
        $this->session = $session;
        $this->request = $request;
        $this->provider = $provider;
        $this->timebox = $timebox ?: new Timebox;
        //ここが増えた
        $this->guardHelpers = $guardHelpers;
        $this->micraoable = $micraoable;
    }
    //後略
}

では Trait を多用することで困ることとはなんでしょうか?

自分は大きく 2 点あると思っています。

依存関係がとても複雑になる

Trait 自体は実質的に、多重継承と同様の実装パターンを実現します。

すると Trait の特性上、Trait 同士の依存関係をも考慮して修正を行う必要があるため、その修正範囲は単に継承を行なった際に比べて水平方向にも広がっていくため非常にカオスです。

単体テストが難しい

先に述べた「依存関係がとても複雑になる」に起因していることですが、Trait が使われた場合 Trait が使われているクラス単体でテストを行うことが難しくなります。

これは Trait が使われているクラスが Trait の副作用を受ける可能性があるからです。

副作用をなくすためには、あらかじめ振る舞いが決められたオブジェクトをコンストラクタで受け取る必要があるということです。

ただし、普通にコンストラクタインジェクションで Trait を使わなかったとしても、完全に副作用をなくすことはできません。

その理由については、次の「No usage of final」を見ればわかります。

No usage of final(final の使用習慣がない)[4]

フレームワークのクラスで final キーワードを使用しているものはありません。開発者が何かを拡張することは想定されていなくても、内部的なクラスがいつでもものを壊す可能性があるため、フレームワークを使用する開発者のコードがより脆弱になります。フレームワーク内部でどの部分が内部的であるか明確でなく、Symfony のように後方互換性の約束がありません。final を使用すると、継承の誤用を防止し、継承よりもコンポジションを推進し、フレームワークと開発者間の契約をより明確にすることができます。クラスは抽象クラスまたは final のいずれかであるべきだと主張できます。

これも同意できる内容です。

先に述べたように、Trait を使わずにコンストラクタインジェクションをしたとしても final を使用しなければ、その後誤用によってインジェクションされたインスタンスが上書きされる可能性があります。

なので上書きを想定していないのであれば、基本的に final をつけて運用すべきです。

これは Laravel(PHP) に限った話ではなく、例えば Kotlin の val やモダン JavaScript の const にも同様に見受けられる考え方です。

ここで「コンポジションとは????」な読者は次の「コンポジション」の項を見てみてください。(理解している人は読み進めてもらって大丈夫です)

コンポジション

オブジェクト指向におけるコンポジション(Composition)とは、複数のオブジェクトを組み合わせて新しいオブジェクトを作る手法のことです。

例えば、車はエンジンやタイヤ、ブレーキなどのパーツから構成されています。

これらのパーツを個別に作成し、組み合わせることで車を作り出します。

コンポジションは、クラスの再利用性を高め、メンテナンス性を向上させるために使用されます。

class car {
    $wheel = new Wheel()
    $engine = new Engine()
}

また、継承よりも柔軟であり、クラス間の依存性を減らすことができます。

このコンポジションと合わせてよく利用される実装パターンとして委譲があります。

委譲とは何か?については別の Kotlin デザインパターンについて書いた記事で触れているのでそちらをご参照ください。

https://zenn.dev/yskn_sid25/articles/b8bb1b9a4e5209#委譲

No dependency inversion(依存性の逆転が見受けられない)[5]

これは非常に基本的な概念であり、誰にでも理解されるべきです。依存関係の注入は、コードを切り離し、テストできるようにするために非常に重要です。フレームワークは app()を多くの場所で使用しているため、物事はブラックボックスのように動作します。テストするのは難しく、モックするのも難しいです。動作の仕組みを理解するためにフレームワークからファイルを開く必要があり、使用可能な契約(入力)を使用する必要があります。詳細については、https://phptherightway.com/#dependency_injection および https://en.wikipedia.org/wiki/Black_box を参照してください。

オブジェクト指向の SOLID 原則に関わるものであり、この議論の行き着く先はやはり単体テストとモック化を困難にしていることに行き着きます。

SOLID 原則について未履修の方はこの機会にぜひ履修してください。Laravel(PHP)に限らずオブジェクト指向を活用する言語においては必須知識です。

SOLID 原則

SOLID 原則は、オブジェクト指向プログラミングにおける 5 つの原則の総称です。

SOLID とは以下の原則を指します。

  1. 単一責任の原則(Single Responsibility Principle): 一つのクラスやメソッドは、ただ一つの責務を持つようにすること。
  2. オープン/クローズドの原則(Open/Closed Principle): 拡張に対して開かれ、修正に対して閉じた設計にすること。
  3. リスコフの置換原則(Liskov Substitution Principle): サブクラスは、そのスーパークラスの代わりに利用できるべきであること。
  4. インタフェース分離の原則(Interface Segregation Principle): インタフェースは、必要な機能だけを提供するように分離すること。
  5. 依存性逆転の原則(Dependency Inversion Principle): 上位モジュールは下位モジュールに依存すべきではなく、抽象に依存するべきであること。

これらの原則によって、プログラムの保守性や拡張性を高め、再利用可能なコードを実現することができます。

この章で登場した「依存性逆転の原則」についてはこの記事がわかりやすいので、詳しい説明をお譲りします。

https://zenn.dev/chida/articles/e46a66cd9d89d1

APIs are too flexible(API が柔軟すぎる)[6]

多くのオブジェクトの API はあまりにも柔軟です。多くの引数が string|array を受け付け、同じことをする多くの方法があるため、良いツールがないと規約を守ることが難しくなります。たとえば、リクエストがある場合、request->fooまたはrequest->input('foo')またはrequest->get('foo')またはrequest->toArray()[ 'foo']など、Symfony から他の方法があります。混乱しています。さらに、request->foo(またはrequest->input('foo'))を使用すると、リクエストクエリまたはリクエストボディで機能します。公開 API がある場合、クライアントが何を使用するかわからないため、ドキュメント化するのは大変です。リクエストボディにはrequest->requestを、クエリにはrequest->query を使用してください。これは Symfony API から来ます。

これはまあ…同意はできるけどっていうくらいでしょうか。

別に複数の API があるのは構わないですし、それを言い始めると 2+2=4 と 2*2=4 も同じ API じゃんっていう話になるわけで、好みの問題かなと思います。

ただ自分が「同意はできるけど」と言っている理由は、この次に紹介する"神クラス"の問題が絡んでくるからです。

Too many god classes(神クラスが多すぎる)[7]

初心者の向けて念のために言っておくと、神クラスは褒め言葉ではないです(日本語の語感的にそう聞こえるので)

神クラスとは、オブジェクト指向プログラミングにおいて、神クラスとはあまりにも多くの責任を持ち、あまりにも多くのフィールドやメソッドを持つ大きなクラスのことを指します。

これは、先に出てきた SOLID 原則の単一責任の原則を破り、コードの保守性を低下させ、テストの難易度を増加させる可能性があります。

また、このようなクラスはしばしば過剰な継承を引き起こし、コードの柔軟性を低下させます。

原文では以下のように問題提起しています。

再びリクエストの例を取ると、それは単純に多すぎます。Symfony リクエストを拡張し、5 つのトレイトを実装し、多くのメソッドを提供しています。代わりにコンポジションを使用する必要があります。なぜ、 $request->user() はユーザーを取得するのですか?戻り値の型は mixed ですが、リクエストから直接完全なユーザーを取得できますか?なぜリクエストに Mockable トレイトがあるのですか?それを使いたくないし、開発者に使わせたくありません。なぜ多くのユーティリティがあるのでしょうか?

これについては二つの点で同意できるところがあります。

  1. 単一責任の原則を破っている
  2. トレイトを実装するのではなく、コンポジションを使用する

この二つの点はまた以降でも問題提起されているので、このまま続きを見ていきます。

No single class responsibility(単一責任クラスがない)[8]

これは以前に引用された多くのポイントに関係していますが、ユーザーをどのように取得するのでしょうか? $request->user() または auth()->user() または Auth::user()? はい、これらすべてが隠れた依存関係です。答えは:インジェクションする必要があります! Auth をインジェクトするか、ユーザーを引数にバインドします。

これは半分同意できるけど、半分違うでしょと思っている内容です。

確かに"request が user()を持つのではなく、auth が user()を持つべきでしょ"という点については単一責任の原則から「せやな」となります。

が、auth()->user()と Auth::user()が存在することに対して単一責任クラスの観点から反対意見を述べるのは微妙に違うと思っています。

この点は、"単一の責任をもつクラスが複数存在している"というまた別視点からの切り口の問題で、DRY 原則に違反するものだと思っています。

先に触れた「API が柔軟すぎる」の問題についても、同様に考えています。

DRY 原則

この記事に関心を持って見にくるような人で知らない人はいなさそうですが、念のため触れておきます。

DRY 原則とは、Don't Repeat Yourself の略で、重複を避けるための原則です。

同じコードやロジックが複数の場所に出現することを避け、変更が必要になった場合に複数の箇所を変更する必要がなくなるようにします。

これにより、コードの保守性が向上し、バグやエラーを防ぐことができます。

また、コードの重複が減るため、コード量も少なくなります。

Bad encapsulation(悪いカプセル化)[9]

多くのクラスには、プロテクトされたフィールドがあります。なぜでしょうか?もちろん、継承可能にするためです。代わりに、プライベートにしてコンポジションを継承よりも優先すべきです。しかし、アーキテクチャが多くの場所でうまく設計されていないため、それが簡単な解決策でした。それにより、プライベートを使用しない開発者もいます。「外部クラスでは見えないので、柔軟性がある方が良い」という理由で、プライベートを使用しない開発者もいました。

これは完全同意な内容です。

また、public, protected, private のアクセス修飾子を適切に使い分けることができないプログラマが書くコードはだいたい後にバグを生む、もしくは保守困難なコードを生みます。これは自身の矮小な経験上ではありますが、ほぼ 100%そうだと断言できることです。

余談ですが、「アクセス修飾子の範囲は?」に即答できないプログラマはオブジェクト指向をきちんと理解できていない傾向があるので、メンターはメンティーのオブジェクト指向理解度を測定するための一つの質問としてこれを持っておくことをお勧めします(笑)

Facade pattern[10]

モデル、サービス、ツールなど、ほとんどのものが「ファサード」パターンを使用しています。ファサードパターンは、Laravel で実装されているものとは全く関係がないばかりか、非常に悪い習慣です。ファサードを簡単にモックすることができないため、ブラックボックスを作成し、依存性注入を使用しないように促します。ファサードをモックすることは可能ですが、ハッキーで契約に基づいていないため、変更するとすべてを壊すことができます。ファサードには唯一の利点があり、シングルトンのように使用できることですが、それが望ましいものではありません。依存性注入は非常に重要な概念であり、ファサードは存在してはならなかったと思われます。

これも完全に同意できる内容で、自分も Facade が存在する意味はほぼないと思っています。

原文でも触れられているように、DI があるのならそれを使うべきです。

原文記事の著者は、「Facade の唯一の利点はシングルトンのように使用できること」と言っていますが、だったら app->singleton すればいいだけの話なので、この点でも Facade の存在意義はほぼありません。

では、Facade のデメリットについて考えるために以下のコードを見てみます。

//AとBの処理に違いはない

//A
DB::select('select 1');

//B
$app = app()
$app->make('db')->select('select 1');

ここで A と B の処理を比べたときに、処理として違いはありません。

しかし、メソッド内部で DB クラスそのものを利用すると考えたとき、大きな違いがあります。

それは、B の方は$app->make('db')としてコンストラクタもしくは引数として処理から分離できるのに対して、DB::select('select 1')をメソッド内部で書いてしまったら最後、DB 本体を分離することができません。

そうなると、簡単にモックすることができず、単体テストの際に副作業が起こる可能性もあり、IDE によっては定義元へ一撃で飛ぶことができないため、いろんな面でデメリットが多いです。

ついでに言うと、静的解析にも支障がでます。詳しくはこちらの記事をご参照ください。

https://zenn.dev/bs_kansai/articles/4a476c4b28f1d6#ルールレベル

また、Facade はマジックメソッドを利用してインスタンス化とメソッドの実行を行っているため、非常にトリッキーです。

なぜ Facade とマジックメソッドが関係しているのかピンと来ていない人は、Facade について解説されているこの記事を読んでみてください。

https://zenn.dev/bs_kansai/articles/71a17cf67d6461

Magic methods[11]

Laravel のコードには、非常に多くのマジックメソッドがあります。私はそれが好きではありませんが、一部の使用方法は「問題ない」です。問題は、それが過度に使用されており、コードの一部がほとんど読み取れなくなっていることです(たとえば、https://github.com/laravel/framework/blob/5f304455e0eec1709301cec41cf2c36ced43a28d/src/Illuminate/Routing/RouteRegistrar.php#L267-L285)。

先の「Facade pattern」の章でマジックメソッドが出てきたので、SOLID 原則とはあまり関係がありませんが、合わせて紹介することにしました。

これも完全に同意できる内容です。同意点については原文と同じ内容なので割愛します。

Over usage of strings(過剰な文字列の使用)[12]

文字列は、設定するために多くの場所で使用されています。一部の使用法は問題ありませんが、しばしば文字列の使用には制限があり、意図した解決策よりも問題を引き起こします。たとえば、バリデーション、ミドルウェアなどでの使用です。さらに、IDE が理解することができないため、問題があります。このポイントは、やや主観的なものです。

「このポイントは、やや主観的なものです」とあるとおり、この点については好みが分かれるところではあるかと思います。

例えば悪名高い Pipeline.php の carry というメソッドについて見てみます。

$carry = method_exists($pipe, $this->method)
                                ? $pipe->{$this->method}(...$parameters)
                                : $pipe(...$parameters);

このように"$pipe->{$this->method}(...$parameters)"とメソッドを{$this->method}として呼び出しています。

ちなみにこの場合の$this->method は基本的に handle であり、これが原文でも触れられているようにミドルウェアのメインメソッドです。

ここで、$pipe の実行メソッドが何か?はソースリーディングをするもしくはデバッガで停止させない限り知ることはできませんので、DX が低いと言えるでしょう。

ちなみに、なぜ handle がミドルウェアのメインメソッドか知りたい人はこちらの記事を読んでみてください。

https://zenn.dev/yskn_sid25/articles/6bb62cbc02445f

おわりに

今回は原文で挙げられた 18 個の有害な慣習とされているもののうち、SOLID 原則が守れていないことに関するものを中心に紹介してみました。

ほとんどの内容が「まあそうだよね」と同意できるものなのですが、巨大なプロジェクトになっていくにつれて SOLID 原則が守れなくなっていくのはある種自然なものなわけで。

(全てのプロジェクトが SOLID 原則を守り切れる方法があるなら、こんな議論が起こることなんてないわけで)

この問題提起から一周回って、「理想像としてこうあるべきだよね」というのを知るきっかけとしてはいい記事なんじゃないかと思いました。

メンバー募集中!

サーバーサイド Kotlin コミュニティを作りました!

Kotlin ユーザーはぜひご参加ください!!

https://serverside-kt.connpass.com/

また関西在住のソフトウェア開発者を中心に、関西エンジニアコミュニティを一緒に盛り上げてくださる方を募集しています。

よろしければ Conpass からメンバー登録よろしくお願いいたします。

https://blessingsoftware.connpass.com/

脚注
  1. 一次翻訳は ChatGPT にしてもらい、その後著者が校閲をしています。 ↩︎

  2. Having worked with PHP both as a hobby and professionally for more than 10 years I saw a lot of progress in community to use best practices. The language itself has evolved a lot. Some years ago we didn’t have composer or namespaces, we have come a long way.Frameworks have always been available, but as time passed, they began to offer more comprehensive tooling and better practices, with popular options like Zend Framework, CakePHP, CodeIgniter, and Symfony. Over ten years ago, Laravel emerged, and with the release of version 4, it quickly became the most widely used PHP framework. I this post I want to explain why Laravel should be considered harmful for the PHP community. I did use Laravel with 20 devs working on the same project and that’s how I learned how harmful and toxic this framework can be. ↩︎

  3. Traits are used everywhere in Laravel. Trait should be use with caution. It’s easy to bloat classes because it’s still a vertical way to add code, similar to inheritance. You cannot remove a trait if you don’t own the code. In the majority of the cases, using dependency injection would be the right way, to have logic in a specific class. ↩︎

  4. No classes are using the final keyword in the framework, even if devs are not supposed to extends something. This makes the code of devs using the framework more fragile because “internal” classes can potentially break things at any time. It’s not clear what it internal to the framework or not and there is no backward compatibility promise (unlike Symfony for example https://symfony.com/doc/current/contributing/code/bc.html). Using final would prevent inheritance misusage, push for composition over inheritance and make the contract between the framework and the devs more clear. I would argue that classes should either be abstract or final. ↩︎

  5. This is a pretty basic concept that should be understood by everybody. Injecting dependencies is extremely important to be able to decouple the code, to be able to test things, to make it easier to compose. Unfortunately the framework uses app() in many places which makes things act like a black box. It’s hard to test, it’s hard to mock. You need to open files from the framework to understand how it works, instead of using the contracts (inputs available). For more info https://phptherightway.com/#dependency_injection and https://en.wikipedia.org/wiki/Black_box. ↩︎

  6. APIs are too flexible: the API of many objects is just too flexible. Many arguments accept string|array, there is many methods to do similar things which makes it hard to keep conventions without good tooling. For example when you have a request you can do $request->foo or $request->input(‘foo’) or $request->get(‘foo’) or $request->toArray()[‘foo’] and other ways from Symfony. What a mess. On top of that using $request->foo (or $request->input(‘foo’)) will work with request query OR request body. Like that when you have a public API you don’t know what clients will use, enjoy documenting it, enjoy edge-cases. Please use $request->request for body and $request->query for query, from the Symfony API. ↩︎

  7. Too many god classes: If we take the request example again, it simply does way too much. It extends Symfony request, it implements 5 traits (!) and provides a lot of methods. We should use composition instead. Why does $request->user() gives you the user? The return type is mixed yet you can get a full user directly from the request? Why the hell there is the Mockable trait in the request, I don’t want to use it, I don’t want devs to use it? Why so many utils? ↩︎

  8. No single class responsibility: it’s related to many points cited before but, how do you get a user? $request->user() or auth()->user() or Auth::user()? Yes all of those are hidden dependencies, the answer is: you should inject it! Inject Auth or bind the user to an argument somehow. ↩︎

  9. Many classes have protected fields. Why? To be able to extends of course. Instead we should have things private and use composition over inheritance. But because the architecture is not well designed in many places it was easier to have it that way. I saw some devs never using private because of that. “We don’t see it outside the class anyway, better to be flexible”. ↩︎

  10. Models/Service/Tools, almost everything uses the “facade” pattern. Not only the facade pattern has nothing to do with what is implemented in Laravel (see https://en.wikipedia.org/wiki/Facade_pattern, thanks Taylor for the confusion) but it’s also a terrible practice. It’s yet another part of something that we cannot mock with east, it creates a blackbox and pushes to not use dependency injection. Yes it’s possible to mock facade but it’s hacky and it’s not based on a contract. We can change the service and break everything, there is nothing that enforce us to follow anything. The only advantage facade have is to be able to use them like it was a singleton, but that’s exactly what we don’t want. It should never have been a thing, dependency injection is such an important concept. ↩︎

  11. There is a LOT of magic methods everywhere. While I dislike that, some usage are “ok”. The problem is that it’s over-used and it makes some part of the code barely readable (for example https://github.com/laravel/framework/blob/5f304455e0eec1709301cec41cf2c36ced43a28d/src/Illuminate/Routing/RouteRegistrar.php#L267-L285). ↩︎

  12. Strings are used in many placed to configure things. While some usage are fine, there is often a limitation about using strings and it creates more issues than it was intended to solve. For example in the validation, the middleware etc. On top of that it’s not possible for the IDE to understand. This point is a bit more subjective. ↩︎

Discussion