我有一个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);
}
字符串
然后我在一个更具体的控制器中扩展这个BaseController
,比如UserController
,如下所示:
- UserController.php*
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
}
型
这工作得很好。然而,我现在想扩展UserController
来注入Laravel 5的新FormRequest
类,它负责User
资源的验证和身份验证。我想这样做,通过删除store方法并使用Laravel的类型提示依赖注入其Form Request类。
- UserController.php*
public function store(UserFormRequest $request)
{
return parent::store($request);
}
型
其中UserFormRequest
从Request
延伸,而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'
];
}
}
型
问题是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
型
那么,我如何在类型提示注入UserFormRequest的同时仍然遵守BaseController的请求要求呢?我不能强制BaseController要求UserFormRequest,因为它应该对任何资源都有效。
我可以在BaseController
和UserController
中使用像RepositoryFormRequest
这样的接口,但问题是Laravel不再通过其类型提示依赖注入来注入UserFormController
。
6条答案
按热度按时间sshcrbum1#
与许多“真实的”面向对象的语言相比,这种类型暗示的设计在PHP中是不可能的,请参阅:
字符串
这会产生与你的代码相同的错误。我只是不会在你的
BaseController
中放入store()
方法。如果你觉得你在重复代码,考虑引入例如服务类或trait。使用服务类
下面是一个使用额外服务类的解决方案。这可能对您的情况有点过分。但是如果您向
StoringService
的store()
方法添加更多功能(如验证),它可能会很有用。您也可以向StoringService
添加更多方法,如destroy()
,update()
,create()
,但是您可能希望以不同的方式命名服务。型
使用trait
你也可以使用trait,但是你必须重命名trait的
store()
方法,然后:型
这种解决方案的优点是,如果你不需要向
store()
方法添加额外的功能,你可以只use
trait而不重命名,你不必编写额外的store()
方法。使用继承
在我看来,继承不太适合你在这里需要的那种代码重用,至少在PHP中不适合。但是如果你只想使用继承来解决这种代码重用问题,给予x1m13 n1x中的x1m12 n1x方法另一个名字,确保所有类都有自己的x1m14 n1x方法,并在x1m15 n1x中调用该方法。类似这样:
型
型
inn6fuwd2#
您可以将逻辑从BaseController移动到trait、service、facade。
你不能覆盖现有的函数,并强制它使用不同类型的参数,这将破坏东西。例如,如果你以后会写这个:
字符串
它将与
UserController
和OtherRequest
中断,因为UserController
需要UserController
,而不是OtherRequest
(它扩展了Request
,从foo()
的Angular 来看是有效的参数)。2hh7jdfx3#
正如其他人所提到的,你不能做你想做的事情有很多原因。如前所述,你可以用特质或类似的方法解决这个问题。我提出了一种替代方法。
在猜测中,这听起来像是你试图遵循Laravel的
RESTful Resource Controllers
提出的命名约定,这迫使你在控制器上使用特定的方法,在本例中,store
。查看
ResourceRegistrar.php
的源代码,我们可以看到在getResourceMethods
方法中,Laravel会与传入的选项数组和默认值进行diff或intersect。然而,这些默认值是受保护的,包括store
。这意味着你不能向
Route::resource
传递任何东西来强制覆盖路由名,所以让我们排除这种情况。一个简单的方法是只为这条路由设置一个不同的方法。这可以通过执行以下操作来实现:
字符串
注意:根据文档,自定义路由必须在Route::资源调用之前。
vlju58qv4#
UserController::store()
的声明应该与BaseController::store()
兼容,这意味着BaseController
和UserController
的给定参数应该完全相同。你实际上可以强制BaseController要求一个UserFormRequest,这不是最好的解决方案,但它工作。
如果你不使用
UserFormRequest
来替换Request
,那么为什么不同时使用这两个方法呢?给这两个方法一个可选的参数来注入UserFormRequest
对象。这将导致:字符串
型
这样,您可以在使用
BaseController::store()
时忽略该参数,并在使用UserController::store()
时注入该参数。ev7lccsx5#
我发现规避这个问题的最简单、最干净的方法是在父方法前面加一个下划线。例如:
BaseController:
_update(Request $request) { ... }
个用户控制器:
store(UserFormRequest $request) { return parent::_store($request); }
个update(UserFormRequest $request) { return parent::_update($request); }
个我觉得创建服务提供者是一种矫枉过正的做法。我们在这里试图规避的不是Liskov替换原则,而仅仅是缺乏适当的PHP反射。类型提示方法本身毕竟是一种黑客。
这将迫使你在每个子控制器中手动实现一个
store
和update
。我不知道这对你的设计来说是否麻烦,但在我的设计中,我为每个控制器使用自定义请求,所以我不得不这样做。xe55xuns6#
如果我使用DI将UserFormRequest发送到UserController的__construct中,则BaseController的所有函数都将使用UserFormRequest进行验证。
字符串