Contextual Bindingの仕様変更について(Lumen v10 -> v11)
はじめに
Lumen v10からv11にアップグレードする際、意図しない動作に戸惑った方もいるのではないでしょうか?
私は、Contextual Bindingの仕様変更により、以前は正常に動作していたコードが機能しなくなるケースに遭遇しました。
この記事では、Contextual Bindingに関する変更点を具体的なサンプルコードと一緒に解説します。v10での動作例、v11で発生する問題、そしてその解決方法を順を追って説明していきます。
参考: Contextual Bindingの基本について詳しく知りたい方は、Laravel公式ドキュメントをご覧ください。
仕様変更の背景
Lumen v10では、when
メソッドを使ったContextual Bindingが、DI(依存注入)のチェーンを飛び越えて適用される仕組みになっていました。
つまり、あるクラスの依存先に設定した具象クラスが、その先のDIにも反映されていたのです。
しかし、v11ではこの挙動が変更され、when
メソッドで指定したクラスに直接DIされる場合にのみ適用されるようになりました。
これにより、今まで動いていたコードが意図せず動作しなくなることがあります。
依存関係の図解
以下がこの記事で扱う依存関係の図です:
v10の動作:
CreditController ──依存→ MoneyService ──依存→ PaymentInterface
│ ↑
└────────────when().needs()──────────────┘
(CreditControllerからの指定がPaymentInterfaceに届く)
v11の動作:
CreditController ──依存→ MoneyService ──依存→ PaymentInterface
│ │ ↑
└─when().needs()─╳ └─when().needs()─┘
(直接の依存先にのみ適用される)
では、具体的にどのようなケースで問題が発生するのか、コード例を使って見ていきましょう。
v10のコード例
まずは、Lumen v10で正常に動作していたコードの例から見ていきます。
AppServiceProvider内の設定
$this->app->bind(PaymentInterface::class, function ($app) {
return new class implements PaymentInterface {
public function paymentMethod(): string
{
return 'default';
}
};
});
$this->app
->when(CreditController::class)
->needs(PaymentInterface::class)
->give(function () {
return new class implements PaymentInterface {
public function paymentMethod(): string
{
return 'credit card';
}
};
});
ここでは、CreditController
を基準に PaymentInterface
の具象クラスを切り替えるよう設定しています。
CreditController
class CreditController extends Controller
{
public function __construct(private MoneyService $moneyService)
{
}
public function index(): string
{
return $this->moneyService->paymentMethod();
}
}
CreditController
は MoneyService
をDIし、その中で PaymentInterface
の paymentMethod
を呼び出しています。
MoneyService
class MoneyService
{
public function __construct(private PaymentInterface $paymentInterface)
{
}
public function paymentMethod(): string
{
return $this->paymentInterface->paymentMethod();
}
}
MoneyService
は PaymentInterface
をDIするクラスです。
実行結果
このコードをLumen v10で実行すると、CreditController
の index
メソッドを呼び出した際に 'credit card'
が正しく返されます。
v11の問題例
では、このコードをそのままLumen v11で実行するとどうなるでしょうか?
問題の原因
CreditController
は MoneyService
をDIし、さらに MoneyService
は PaymentInterface
をDIしています。
しかし、v11では when
メソッドで指定したクラスに直接DIされる場合にしか適用されなくなりました。そのため、CreditController
を基準に設定しても、その中で使用される MoneyService
に渡される PaymentInterface
はデフォルトのまま、default
が返されてしまいます。
実行結果
CreditController
を呼び出しても、期待していた 'credit card'
ではなく、デフォルト設定の 'default'
が返ってしまいます。
発生するエラーの例
明示的なエラーメッセージは表示されませんが、戻り値が期待と異なる結果になります。例えば以下のようなテストを実施すると失敗します:
// テストコード
public function testCreditControllerReturnsCorrectPaymentMethod()
{
$response = $this->get('/credit');
$this->assertEquals('credit card', $response->getContent());
// v11で失敗する - 'default'が返される
}
実際の開発環境では、このような挙動の違いが原因で機能が正しく動作しなくなり、トラブルシューティングに時間がかかることがあります。
修正版コード例(v11対応)
では、Lumen v11で正しく動作させるにはどうすれば良いのでしょうか?
答えはシンプルで、when
メソッドで設定する基準を MoneyService
に変更するだけです。
AppServiceProvider内の設定
$this->app->bind(PaymentInterface::class, function ($app) {
return new class implements PaymentInterface {
public function paymentMethod(): string
{
return 'default';
}
};
});
$this->app
->when(CreditController::class)
->needs(MoneyService::class)
->give(function () {
$creditCardInterface = new class implements PaymentInterface {
public function paymentMethod(): string
{
return 'credit card';
}
};
return new MoneyService($creditCardInterface);
});
ここでのポイントは、needs
メソッドの基準を PaymentInterface
ではなく MoneyService
に変更している点です。
これにより、正しい PaymentInterface
をDIした MoneyService
が使用されるようになります。
CreditController
class CreditController extends Controller
{
public function __construct(private MoneyService $moneyService)
{
}
public function index(): string
{
return $this->moneyService->paymentMethod();
}
}
MoneyService
class MoneyService
{
public function __construct(private PaymentInterface $paymentInterface)
{
}
public function paymentMethod(): string
{
return $this->paymentInterface->paymentMethod();
}
}
実行結果
この修正版コードを実行すると、CreditController
の index
メソッドを呼び出した際に 'credit card'
が正しく返されるようになります。
まとめ
今回の変更点を振り返ると、Lumen v11ではContextual Bindingのスコープがより厳密になったようです。
その結果、when
メソッドを使用する際には、DIチェーンを考慮して基準となるクラスを正しく指定する必要があります。
この仕様変更については、v10->v11のアップグレードガイドにも記述がなかったような気がするんですが、「どこかに書いてたよ!」とか「こうすればv10のコードのまま動くよ!」などあればコメント欄にて教えてください🙇
Discussion