You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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',
        ];
    }
}

现在遇到两个问题:

  1. 如何通过构造函数传递并处理这些公共变量,确保子类正确配置?
  2. 直接实例化$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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:32:32