Laravel 5:在从BaseController扩展的控制器中对FormRequest类进行类型提示

Tom*_*Tom 25 php dependency-injection ioc-container laravel laravel-5

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

BaseController.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);
}
Run Code Online (Sandbox Code Playgroud)

然后我BaseController在一个更具体的控制器中扩展它,例如UserController,像这样:

UserController.php

class UserController extends BaseController {

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

}
Run Code Online (Sandbox Code Playgroud)

这非常有效.但是,我现在想要扩展UserController以注入Laravel 5的新FormRequest类,它负责User资源的验证和身份验证之类的事情.我想这样做,通过覆盖store方法并使用Laravel的类型提示依赖注入为其Form Request类.

UserController.php

public function store(UserFormRequest $request)
{
    return parent::store($request);
}
Run Code Online (Sandbox Code Playgroud)

从哪里UserFormRequest延伸Request,其本身延伸自FormRequest:

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

}
Run Code Online (Sandbox Code Playgroud)

问题是BaseController需要一个Illuminate\Http\Request对象而我传递一个UserFormRequest对象.因此我收到此错误:

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
Run Code Online (Sandbox Code Playgroud)

那么,如何在仍然遵守BaseController的Request要求的同时键入提示注入UserFormRequest?我无法强制BaseController需要UserFormRequest,因为它应该适用于任何资源.

我可以用一个接口就像RepositoryFormRequest在两个BaseControllerUserController,但随后的问题是,Laravel不再注入了UserFormController通过其类型提示依赖注入.

Jer*_*ten 9

与许多"真正的"面向对象语言相比,这种在重写方法中的类型提示设计在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
}
Run Code Online (Sandbox Code Playgroud)

这会产生与代码相同的错误.我不会store()在你的方法中加入一种方法BaseController.如果您认为自己在重复代码,请考虑引入服务类或特征.

使用服务类

下面是一个使用额外服务类的解决方案.这对你的情况来说可能有点过头了.但是如果你为StoringServices store()方法添加更多功能(比如验证),它可能会很有用.您还可以添加更多的方法来对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);
    }

}
Run Code Online (Sandbox Code Playgroud)

使用特征

您也可以使用特征,但是您必须重命名特征的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);
    }

}
Run Code Online (Sandbox Code Playgroud)

此解决方案的优点是,如果您不必为该store()方法添加额外的功能,您可以只是use没有重命名的特性,您不必编写额外的store()方法.

使用继承

在我看来,继承不太适合你需要的代码重用,至少在PHP中不是这样.但是,如果您只想对此代码重用问题使用继承,请store()以您的BaseController另一个名称给出该方法,确保所有类都有自己的store()方法并调用该方法BaseController.像这样的东西:

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);
}
Run Code Online (Sandbox Code Playgroud)

UserController.php

public function store(UserFormRequest $request)
{
    return $this->createResource($request);
}
Run Code Online (Sandbox Code Playgroud)