Laravel 5:在从 BaseController 扩展的控制器内部键入 FormRequest 类

我有一个为我的API服务器的大多数HTTP方法提供基础,例如该方法:BaseControllerstore

基本控制器.php

/**
 * Store a newly created resource in storage.
 *
 * @return Response
 */
public function store(Request $request)
{
    $result = $this->repo->create($request);

    return response()->json($result, 200);
}

然后,我在一个更具体的控制器中对此进行扩展,例如 ,如下所示:BaseControllerUserController

用户控制器.php

class UserController extends BaseController {

    public function __construct(UserRepository $repo)
    {
        $this->repo = $repo;
    }

}

这效果很好。但是,我现在想扩展以注入Laravel 5的新类,该类负责资源的验证和身份验证等事务。我想这样做,通过覆盖存储方法并使用Laravel的类型提示依赖注入其Form Request类。UserControllerFormRequestUser

用户控制器.php

public function store(UserFormRequest $request)
{
    return parent::store($request);
}

其中 从 扩展,其本身从 :UserFormRequestRequestFormRequest

用户表单请求.php

class UserFormRequest extends Request {

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name'  => 'required',
            'email' => 'required'
        ];
    }

}

问题是 需要一个对象,而我传递一个对象。因此,我得到这个错误:BaseControllerIlluminate\Http\RequestUserFormRequest

in UserController.php line 6
at HandleExceptions->handleError('2048', 'Declaration of Bloomon\Bloomapi3\Repositories\User\UserController::store() should be compatible with Bloomon\Bloomapi3\Http\Controllers\BaseController::store(Illuminate\Http\Request $request)', '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php', '6', array('file' => '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php')) in UserController.php line 6

那么,如何在仍然遵守基本控制器请求要求的同时键入提示注入UserFormRequest?我不能强制 BaseController 要求 UserFormRequest,因为它应该适用于任何资源。

我可以使用像 the 和 the 这样的接口,但问题是 Laravel 不再通过其类型提示依赖注入注入。RepositoryFormRequestBaseControllerUserControllerUserFormController


答案 1

与许多“真正的”面向对象语言相比,这种在重写方法中的类型提示设计在PHP中是不可能的,请参阅:

class X {}
class Y extends X {}

class A {
    function a(X $x) {}
}

class B extends A {
    function a(Y $y) {} // error! Methods with the same name must be compatible with the parent method, this includes the typehints
}

这会产生与代码相同类型的错误。我只是不会在你的.如果你觉得你在重复代码,考虑引入一个服务类或一个特征。store()BaseController

使用服务类

下面是一个利用额外服务类的解决方案。对于您的情况,这可能有些过分。但是,如果向 s 方法添加更多功能(如验证),则可能会很有用。您还可以向类似 、 、 添加更多方法,但您可能希望以不同的方式命名服务。StoringServicestore()StoringServicedestroy()update()create()

class StoringService {

    private $repo;

    public function __construct(Repository $repo)
    {
        $this->repo = $repo;
    }

    /**
     * Store a newly created resource in storage.
     *
     * @return Response
     */
    public function store(Request $request)
    {
        $result = $this->repo->create($request);

        return response()->json($result, 200);
    }
}

class UserController {

    // ... other code (including member variable $repo)

    public function store(UserRequest $request)
    {
        $service = new StoringService($this->repo); // Or put this in your BaseController's constructor and make $service a member variable
        return $service->store($request);
    }

}

使用特征

您也可以使用 trait,但您必须重命名 trait 的方法,然后:store()

trait StoringTrait {

    /**
     * Store a newly created resource in storage.
     *
     * @return Response
     */
    public function store(Request $request)
    {
        $result = $this->repo->create($request);

        return response()->json($result, 200);
    }
}

class UserController {

    use {
        StoringTrait::store as baseStore;
    }

    // ... other code (including member variable $repo)

    public function store(UserRequest $request)
    {
        return $this->baseStore($request);
    }

}

此解决方案的优点是,如果不必向该方法添加额外的功能,则可以只使用 trait 而不重命名,而不必编写额外的方法。store()usestore()

使用继承

在我看来,继承并不适合你在这里需要的那种代码重用,至少在PHP中不是这样。但是,如果只想将此代码重用问题使用继承,请以另一个名称指定该方法,确保所有类都有自己的方法,并在 中调用该方法。像这样:store()BaseControllerstore()BaseController

基本控制器.php

/**
 * Store a newly created resource in storage.
 *
 * @return Response
 */
protected function createResource(Request $request)
{
    $result = $this->repo->create($request);

    return response()->json($result, 200);
}

用户控制器.php

public function store(UserFormRequest $request)
{
    return $this->createResource($request);
}

答案 2

您可以将逻辑从 BaseController 移动到 trait、service、façade。

你不能覆盖现有的函数并强制它使用不同类型的参数,它会破坏东西。例如,如果您稍后会这样写:

function foo(BaseController $baseController, Request $request) {
    $baseController->store($request);
}

它会与你的和打破,因为期望,而不是(从角度来看,这是延伸并且是有效的论据)。UserControllerOtherRequestUserControllerUserControllerOtherRequestRequestfoo()


推荐