重构两个逻辑相似Laravel控制器的方案及传参报错解决
问题根因
你遇到的报错:
App\Http\Controllers\FeedbackController::__invoke(): Argument #2 ($service) must be of type App\Http\Services, string given, called in C:\OpenServer\domains\TaskForce\app\Http\Controllers\FeedbackController.php on line 29
核心原因很明确:
- PHP中
类名::class语法返回的就是带完整命名空间的类名字符串,本身不会自动触发类实例化 - Laravel的自动依赖注入只有在框架路由层直接调度控制器方法的时候才会生效,你现在是在控制器内部手动调用
$this->__invoke()传参,整个过程完全绕开了服务容器的参数解析流程,传进去的就是原始字符串,自然触发类型不匹配错误。
当前写法的其他问题
除了依赖未正常解析的问题,你的代码还有几个明显缺陷:
__invoke是单动作控制器的专属入口方法,本身设计就是给路由直接调用的,不适合作为内部公共方法被其他类方法手动调用- 核心逻辑写错了传参:原逻辑是把查询到的
$feedback模型实例传给$service->execute(),你改成了传$feedbackId,就算依赖解析正常,这里也会出现逻辑错误 $service参数没有声明明确的类型约束,就算框架直接调度__invoke方法,容器也不知道要注入什么实例
重构方案
两种成熟方案可选,根据团队代码规范选择即可:
方案1:合并为单个控制器
把两个操作拆为控制器的独立公开方法,公共逻辑抽为私有方法,通过服务容器手动解析服务实例,不需要手动调用__invoke:
<?php namespace App\Http\Controllers; use App\Models\Feedback; use Illuminate\Database\Eloquent\ModelNotFoundException; use App\Http\Services\AcceptFeedback; use App\Http\Services\DeleteFeedback; use Illuminate\Support\Facades\App; class FeedbackController extends Controller { public function accept($feedbackId) { return $this->processFeedback($feedbackId, AcceptFeedback::class); } public function delete($feedbackId) { return $this->processFeedback($feedbackId, DeleteFeedback::class); } private function processFeedback(int $feedbackId, string $serviceClass) { try { $feedback = Feedback::findOrFail($feedbackId); // 从服务容器解析服务,自动注入服务自身的依赖 $service = App::make($serviceClass); if ($service->execute($feedback)) { return redirect()->route('task.page', ['id' => $feedback->task->id]); } return back(); } catch (ModelNotFoundException $e) { return back(); } } }
路由直接绑定两个方法即可:
Route::post('/feedback/{feedbackId}/accept', [FeedbackController::class, 'accept'])->name('feedback.accept'); Route::post('/feedback/{feedbackId}/delete', [FeedbackController::class, 'delete'])->name('feedback.delete');
方案2:保留单动作控制器,抽公共逻辑到Trait
如果你偏好单动作控制器职责单一的特性,不需要强行合并控制器,把重复逻辑抽成复用Trait即可,代码更清晰:
先创建公共Trait:
<?php namespace App\Http\Controllers\Traits; use App\Models\Feedback; use Illuminate\Database\Eloquent\ModelNotFoundException; trait FeedbackProcessTrait { /** * 要求引入Trait的控制器必须声明对应服务类的$serviceClass属性 */ // protected string $serviceClass; public function __invoke(int $feedbackId) { try { $feedback = Feedback::findOrFail($feedbackId); $service = app($this->serviceClass); if ($service->execute($feedback)) { return redirect()->route('task.page', ['id' => $feedback->task->id]); } return back(); } catch (ModelNotFoundException $e) { return back(); } } }
两个单动作控制器可以简化到几行代码:
// AcceptFeedbackController.php <?php namespace App\Http\Controllers; use App\Http\Services\AcceptFeedback; use App\Http\Controllers\Traits\FeedbackProcessTrait; class AcceptFeedbackController extends Controller { use FeedbackProcessTrait; protected string $serviceClass = AcceptFeedback::class; }
// DeleteFeedbackController.php <?php namespace App\Http\Controllers; use App\Http\Services\DeleteFeedback; use App\Http\Controllers\Traits\FeedbackProcessTrait; class DeleteFeedbackController extends Controller { use FeedbackProcessTrait; protected string $serviceClass = DeleteFeedback::class; }
路由保持单动作控制器的绑定方式即可:
Route::post('/feedback/{feedbackId}/accept', AcceptFeedbackController::class)->name('feedback.accept'); Route::post('/feedback/{feedbackId}/delete', DeleteFeedbackController::class)->name('feedback.delete');
内容的提问来源于stack exchange,提问作者poggersbasedcringe
相关产品推荐
相关产品推荐

