通过服务容器向控制器注入配置:实例化时注册支付方式失效及测试注入模拟对象问题咨询
问题分析与解决方案
首先得明确你遇到的核心问题:Laravel 默认不会从服务容器中解析控制器实例,而是直接 new 一个新的控制器对象。这就导致你在容器里绑定的那个已经调用了 registerPaymentMethod 的单例根本没被用到,所以你的 $methods 数组始终是空的。
接下来针对你的需求(生产环境注入真实支付方法、测试环境注入模拟对象),给你两种可行的解决方案:
方案一:让 Laravel 使用容器中的控制器实例
如果你不想改动现有控制器的结构,只需要确保路由中使用的是容器里的单例实例即可:
1. 正确绑定单例
保持你原来的绑定代码,但建议使用类名作为绑定标识(比字符串更严谨):
$this->app->singleton(CheckoutController::class, function($app) { $controller = new CheckoutController(); $controller->registerPaymentMethod('stripe', StripeCheckoutMethod::class); return $controller; });
2. 修改路由,从容器获取实例
不要用 Laravel 默认的控制器路由语法,而是手动从容器中取出实例并调用方法:
Route::get('/checkout/methods', function() { // 从容器中获取我们绑定的单例 $controller = app(CheckoutController::class); return $controller->getCheckoutMethods(); });
这样一来,路由中使用的就是你预先注册了支付方法的控制器实例,$methods 数组就不会是空的了。
方案二:改用接口绑定(更适合测试场景)
既然你的核心需求是在测试时替换依赖,依赖注入 + 接口抽象是更规范、可维护的做法:
1. 定义支付方法接口
先抽象出支付方法的通用行为:
interface PaymentMethodInterface { // 示例:定义支付方法必须实现的核心方法 public function processPayment(array $data); }
2. 让真实/模拟类实现接口
// 生产环境的 Stripe 实现 class StripeCheckoutMethod implements PaymentMethodInterface { public function processPayment(array $data) { // Stripe 支付逻辑 } } // 测试用的模拟实现 class MyDummyCheckoutMethod implements PaymentMethodInterface { public function processPayment(array $data) { // 模拟支付逻辑,比如直接返回成功 return true; } }
3. 修改控制器,通过构造函数注入依赖
把支付方法的注册逻辑从容器移到控制器的构造函数中,通过注入实现解耦:
class CheckoutController { protected $paymentMethods; // 构造函数注入支付方法数组 public function __construct(array $paymentMethods) { $this->paymentMethods = $paymentMethods; } public function getCheckoutMethods() { dd($this->paymentMethods); } }
4. 在容器中绑定控制器(生产+测试环境)
生产环境绑定
$this->app->bind(CheckoutController::class, function($app) { return new CheckoutController([ 'stripe' => $app->make(StripeCheckoutMethod::class) ]); });
测试环境覆盖绑定
在测试方法中直接覆盖容器绑定,注入模拟对象:
public function test_something() { $this->app->bind(CheckoutController::class, function($app) { return new CheckoutController([ 'dummy_method' => $app->make(MyDummyCheckoutMethod::class) ]); }); // 发起请求或者直接调用控制器方法进行测试 $response = $this->get('/checkout/methods'); // 这里可以断言控制器使用了 dummy_method }
这种方式的优势在于:完全遵循依赖倒置原则,测试时不需要修改控制器代码,只需要替换注入的实现即可,扩展性和可测试性都更强。
内容的提问来源于stack exchange,提问作者Zakalwe
相关产品推荐
相关产品推荐

