Laravel通用CommonController实现及FormRequest实例化问题求助
Laravel通用控制器优化问题
问题场景
我的Laravel项目中多数控制器结构高度相似,示例控制器代码如下:
class CourseController extends Controller { public function index(Request $request) { $courses = Course::query(); $courses = $this->commonIndex($courses, $request); return CourseResource::collection($courses); } public function store(StoreCourseRequest $request) { $course = Course::create($request->validated); return CourseResource::make($course); } public function show(Request $request, Course $course) { return CourseResource::make($course); } public function update(UpdateCourseRequest $request, Course $course) { $course->update($request->validated()); return CourseResource::make($course); } public function destroy(Course $course) { $course->delete(); return response()->noContent(); } }
为了复用逻辑,我编写了CommonController让简单控制器继承,通过公共变量传递Model、FormRequest和Resource,代码如下:
class CommonController extends BaseController { use AuthorizesRequests, ValidatesRequests; public $model; public $resource; public $storeFormRequest; public $updateFormRequest; public function index(Request $request) { $model = new $this->model; $resource = new $this->resource($request); $data = $model->query(); return $resource->collection($data); } public function show(Request $request, string $id) { $model = new $this->model; $resource = new $this->resource($request); $data = $model->find($id); return $resource->make($data->load($loadWith)); } public function store(Request $request) { $model = new $this->model; $resource = new $this->resource($request); $request = new $this->storeFormRequest($request->toArray()); $validated = $request->validated(); $data = $model->create($validated); return $resource->make($data); } }
子类控制器使用方式:
class CourseController extends CommonController { public $model = Course::class; public $resource = CourseResource::class; public $storeFormRequest = StoreCourseRequest::class; public $updateFormRequest = StoreCourseRequest::class; }
其中StoreCourseRequest是标准Laravel表单请求:
class StoreCourseRequest extends FormRequest { public function authorize(): bool { return true; } public function rules(): array { return [ 'name' => 'required|string', 'description' => 'required|string', 'language_id' => 'required|exists:languages,id', 'image' => 'array', ]; } }
现在遇到两个问题:
- 如何通过构造函数传递并处理这些公共变量,确保子类正确配置?
- 直接实例化
$this->storeFormRequest时返回null,无法正常获取验证后的参数。
解决方案
1. 构造函数处理公共变量的最佳方案
推荐在CommonController的构造函数中添加参数校验,确保子类必须设置必要的属性,避免运行时错误。同时将属性改为受保护,避免外部随意修改:
class CommonController extends BaseController { use AuthorizesRequests, ValidatesRequests; protected $model; protected $resource; protected $storeFormRequest; protected $updateFormRequest; public function __construct() { // 校验必要属性是否设置 $requiredProperties = ['model', 'resource', 'storeFormRequest', 'updateFormRequest']; foreach ($requiredProperties as $property) { if (empty($this->$property)) { throw new \RuntimeException(get_class($this) . " 必须设置 {$property} 属性"); } // 校验类是否存在 if (!class_exists($this->$property)) { throw new \RuntimeException("类 {$this->$property} 不存在"); } } } // ... 其他方法 }
子类依然可以保持原有的赋值方式,构造函数会自动校验配置是否正确,提前暴露错误。
如果需要更严格的约束,可将CommonController改为抽象类,定义抽象属性强制子类实现:
abstract class CommonController extends BaseController { use AuthorizesRequests, ValidatesRequests; abstract protected string $model; abstract protected string $resource; abstract protected string $storeFormRequest; abstract protected string $updateFormRequest; // ... 其他方法 }
子类必须实现这些抽象属性,否则会触发编译错误,这是最严格的约束方式。
2. 解决FormRequest实例化返回null的问题
Laravel的FormRequest不能直接通过new关键字实例化,因为它依赖服务容器处理请求解析、授权验证、规则校验等生命周期流程。直接实例化会跳过这些步骤,导致对象无法正常工作。
正确做法是使用Laravel服务容器解析FormRequest,用app()辅助函数或resolve()方法:
修改CommonController的store和update方法:
public function store(Request $request) { // 通过容器解析FormRequest,自动处理验证和授权 $formRequest = app($this->storeFormRequest); $validated = $formRequest->validated(); $model = $this->model::create($validated); return $this->resource::make($model); } public function update(Request $request, string $id) { $formRequest = app($this->updateFormRequest); $validated = $formRequest->validated(); $model = $this->model::findOrFail($id); $model->update($validated); return $this->resource::make($model); }
同时优化index和show方法,简化模型和资源的调用:
public function index(Request $request) { $data = $this->model::query(); // 保留自定义的commonIndex逻辑 $data = $this->commonIndex($data, $request); return $this->resource::collection($data); } public function show(Request $request, string $id) { // 用findOrFail避免返回null $data = $this->model::findOrFail($id); // 支持子类定义预加载关联 if (property_exists($this, 'loadWith')) { $data->load($this->loadWith); } return $this->resource::make($data); }
子类如果需要预加载关联,只需添加protected $loadWith = ['关联名称'];即可。
内容的提问来源于stack exchange,提问作者K i
相关产品推荐
相关产品推荐

