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

mwngjboj  于 2023-11-16  发布在  PHP
关注(0)|答案(6)|浏览(106)

我有一个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);
}


其中UserFormRequestRequest延伸,而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,因为它应该对任何资源都有效。
我可以在BaseControllerUserController中使用像RepositoryFormRequest这样的接口,但问题是Laravel不再通过其类型提示依赖注入来注入UserFormController

sshcrbum

sshcrbum1#

与许多“真实的”面向对象的语言相比,这种类型暗示的设计在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
}

字符串
这会产生与你的代码相同的错误。我只是不会在你的BaseController中放入store()方法。如果你觉得你在重复代码,考虑引入例如服务类或trait。

使用服务类

下面是一个使用额外服务类的解决方案。这可能对您的情况有点过分。但是如果您向StoringServicestore()方法添加更多功能(如验证),它可能会很有用。您也可以向StoringService添加更多方法,如destroy()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,但是你必须重命名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);
    }

}


这种解决方案的优点是,如果你不需要向store()方法添加额外的功能,你可以只use trait而不重命名,你不必编写额外的store()方法。

使用继承

在我看来,继承不太适合你在这里需要的那种代码重用,至少在PHP中不适合。但是如果你只想使用继承来解决这种代码重用问题,给予x1m13 n1x中的x1m12 n1x方法另一个名字,确保所有类都有自己的x1m14 n1x方法,并在x1m15 n1x中调用该方法。类似这样:

  • 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);
}

  • UserController.php*
public function store(UserFormRequest $request)
{
    return $this->createResource($request);
}

inn6fuwd

inn6fuwd2#

您可以将逻辑从BaseController移动到trait、service、facade。
你不能覆盖现有的函数,并强制它使用不同类型的参数,这将破坏东西。例如,如果你以后会写这个:

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

字符串
它将与UserControllerOtherRequest中断,因为UserController需要UserController,而不是OtherRequest(它扩展了Request,从foo()的Angular 来看是有效的参数)。

2hh7jdfx

2hh7jdfx3#

正如其他人所提到的,你不能做你想做的事情有很多原因。如前所述,你可以用特质或类似的方法解决这个问题。我提出了一种替代方法。
在猜测中,这听起来像是你试图遵循Laravel的RESTful Resource Controllers提出的命名约定,这迫使你在控制器上使用特定的方法,在本例中,store
查看ResourceRegistrar.php的源代码,我们可以看到在getResourceMethods方法中,Laravel会与传入的选项数组和默认值进行diff或intersect。然而,这些默认值是受保护的,包括store
这意味着你不能向Route::resource传递任何东西来强制覆盖路由名,所以让我们排除这种情况。
一个简单的方法是只为这条路由设置一个不同的方法。这可以通过执行以下操作来实现:

Route::post('user/save', 'UserController@save');
Route::resource('users', 'UserController');

字符串
注意:根据文档,自定义路由必须在Route::资源调用之前。

vlju58qv

vlju58qv4#

UserController::store()的声明应该与BaseController::store()兼容,这意味着BaseControllerUserController的给定参数应该完全相同。
你实际上可以强制BaseController要求一个UserFormRequest,这不是最好的解决方案,但它工作。
如果你不使用UserFormRequest来替换Request,那么为什么不同时使用这两个方法呢?给这两个方法一个可选的参数来注入UserFormRequest对象。这将导致:

  • BaseController.php*
class BaseController {

  public function store(Request $request, UserFormRequest $userFormRequest = null)
  {
      $result = $this->repo->create($request);
      return response()->json($result, 200);
  }

}

字符串

  • UserController.php*
class UserController extends BaseController {

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

    public function store(UserFormRequest $request, UserFormRequest $userFormRequest = null)
    {
        return parent::store($request);
    }

}


这样,您可以在使用BaseController::store()时忽略该参数,并在使用UserController::store()时注入该参数。

ev7lccsx

ev7lccsx5#

我发现规避这个问题的最简单、最干净的方法是在父方法前面加一个下划线。例如:
BaseController:

  • 第一个月
  • _update(Request $request) { ... }

用户控制器:

  • store(UserFormRequest $request) { return parent::_store($request); }
  • update(UserFormRequest $request) { return parent::_update($request); }

我觉得创建服务提供者是一种矫枉过正的做法。我们在这里试图规避的不是Liskov替换原则,而仅仅是缺乏适当的PHP反射。类型提示方法本身毕竟是一种黑客。
这将迫使你在每个子控制器中手动实现一个storeupdate。我不知道这对你的设计来说是否麻烦,但在我的设计中,我为每个控制器使用自定义请求,所以我不得不这样做。

xe55xuns

xe55xuns6#

如果我使用DI将UserFormRequest发送到UserController的__construct中,则BaseController的所有函数都将使用UserFormRequest进行验证。

class UserController extends BaseController
{
    protected $service;

    public function __construct(IUserService $service, UserFormRequest  $validation)
    {

        parent::__construct($service, $validation);
    }
}

字符串

相关问题