Laravel - 为路由添加额外信息

Posted

技术标签:

【中文标题】Laravel - 为路由添加额外信息【英文标题】:Laravel - Add additional information to route 【发布时间】:2018-10-23 07:28:41 【问题描述】:

目前我正在做一个项目,我们正在尝试创建一个 RESTful API。此 API 使用一些默认类,例如 ResourceController,用于在需要时可以覆盖的基本行为。

假设我们有一个 API 资源路由:

Route::apiResource('posts', 'ResourceController');

这条路线将使用ResourceController:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Repositories\ResourceRepository;

class ResourceController extends Controller

    /**
     * The resource class.
     *
     * @var string
     */
    private $resourceClass = '\\App\\Http\\Resources\\ResourceResource';

    /**
     * The resource model class.
     *
     * @var string
     */
    private $resourceModelClass;

    /**
     * The repository.
     *
     * @var \App\Repositories\ResourceRepository
     */
    private $repository;

    /**
     * ResourceController constructor.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    public function __construct(Request $request)
    
        $this->resourceModelClass = $this->getResourceModelClass($request);

        $this->repository = new ResourceRepository($this->resourceModelClass);

        $exploded = explode('\\', $this->resourceModelClass);
        $resourceModelClassName = array_last($exploded);

        if (!empty($resourceModelClassName)) 
            $resourceClass = '\\App\\Http\\Resources\\' . $resourceModelClassName . 'Resource';

            if (class_exists($resourceClass)) 
                $this->resourceClass = $resourceClass;
            
        
    

    ...

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    
        $this->validate($request, $this->getResourceModelRules());

        $resource = $this->repository->create($request->all());

        $resource = new $this->resourceClass($resource);

        return response()->json($resource);
    

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    
        $resource = $this->repository->show($id);

        $resource = new $this->resourceClass($resource);

        return response()->json($resource);
    

    ...

    /**
     * Get the model class of the specified resource.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    private function getResourceModelClass(Request $request)
    
        if (is_null($request->route())) return '';

        $uri = $request->route()->uri;

        $exploded = explode('/', $uri);

        $class = str_singular($exploded[1]);

        return '\\App\\Models\\' . ucfirst($class);
    

    /**
     * Get the model rules of the specified resource.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    private function getResourceModelRules()
    
        $rules = [];

        if (method_exists($this->resourceModelClass, 'rules')) 
            $rules = $this->resourceModelClass::rules();
        

        return $rules;
    

您可能会说我们没有使用模型路由绑定,而是使用存储库来执行我们的逻辑。

您还可以看到,我们使用一些脏逻辑getResourceModelClass() 来确定执行逻辑所需的模型类。这种方法不是很灵活,并且限制了应用程序的目录结构(非常讨厌)。

解决方案可能是在注册路由时添加有关模型类的一些信息。这可能看起来像:

Route::apiResource('posts', 'ResourceController', [
    'modelClass' => Post::class
]);

但是看起来这是不可能的。

是否有人对如何使这项工作或如何使我们的逻辑更加简洁和灵活有任何建议。灵活性和易用性是重要因素。

【问题讨论】:

【参考方案1】:

在 Laravel 的源代码中大量搜索和潜水后,我发现ResourceRegistrar 中的getResourceAction 方法处理传递给路由的选项。

进一步搜索将我带到this post,其他人已经设法扩展此注册器并添加一些自定义功能。

我的自定义注册器如下所示:

<?php

namespace App\Http\Routing;

use Illuminate\Routing\ResourceRegistrar as IlluResourceRegistrar;

class ResourceRegistrar extends IlluResourceRegistrar

    /**
     * Get the action array for a resource route.
     *
     * @param  string  $resource
     * @param  string  $controller
     * @param  string  $method
     * @param  array   $options
     * @return array
     */
    protected function getResourceAction($resource, $controller, $method, $options)
    
        $action = parent::getResourceAction($resource, $controller, $method, $options);

        if (isset($options['model'])) 
            $action['model'] = $options['model'];
        

        return $action;
    

别忘了绑定在AppServiceProvider:

$registrar = new ResourceRegistrar($this->app['router']);

$this->app->bind('Illuminate\Routing\ResourceRegistrar', function () use ($registrar) 
    return $registrar;
);

此自定义注册器允许以下操作:

Route::apiResource('posts', 'ResourceController', [
    'model' => Post::class
]);

最后我们可以得到我们的模型类:

$resourceModelClass = $request->route()->getAction('model');

不再有 hacky url 解析逻辑!

【讨论】:

【参考方案2】:

最好的方法是将ResourceController 重构为一个抽象类,并为每个资源使用一个单独的控制器来扩展它。

我很确定没有办法在路由文件中传递一些上下文信息。

但是您可以 bind different instances 将存储库的存储库添加到您的控制器。这通常是一个很好的做法,但是依靠 URL 来解析它是非常 hacky。

您必须将所有依赖项放在构造函数中:

public function __construct(string $modelPath, ResourceRepository $repo // ...)

    $this->resourceModelClass = $this->modelPath;
    $this->repository = $repo;
    // ...

并在服务提供商中执行此操作:

use App\Repositories\ResourceRepository;
use App\Http\Controllers\ResourceController;
// ... model imports

// ...

public function boot()

    if (request()->path() === 'posts') 
        $this->app->bind(ResourceRepository::class, function ($app) 
            return new ResourceRepository(new Post);
        );
        $this->app->when(ResourceController::class)
          ->needs('$modelPath')
          ->give(Post::class);
     else if (request()->path() === 'somethingelse') 
        // ...
    

这将为您提供更大的灵活性,但同样,依赖纯 URL 路径是不明智的。

我只是展示了一个绑定模型路径和绑定 Repo 实例的示例,但是如果你沿着这条路走,你会想要将所有实例化移出 Controller 构造函数。

【讨论】:

感谢您的回答。扩展资源控制器确实是一种选择。但是,我们是懒惰的开发人员,不喜欢在添加新内容时重复操作。我们希望能够只添加一条新路线,就是这样。我们可能不得不考虑扩展路由类或其他东西。

以上是关于Laravel - 为路由添加额外信息的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 路由相同的基本路径

在 Laravel 中为资源控制器添加新方法

Laravel基础教程

php Laravel为请求添加额外参数

Larave中CSRF攻击

如何在 Laravel 8 中为完整的日历 JS 插件添加额外的字段值