Laravel - 添加其他信息以进行路由

时间:2018-05-13 13:38:48

标签: php laravel rest api routes

目前我正在开发一个我们正在尝试创建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()来确定执行/ on逻辑所需的模型类。这种方法不是很灵活,并且限制了应用程序的目录结构(非常讨厌)。

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

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

然而,看起来这是不可能的。

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

2 个答案:

答案 0 :(得分:1)

最好的方法是将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构造函数。

答案 1 :(得分:0)

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

进一步搜索引导我this post,其他人已设法扩展此注册商,添加一些自定义功能。

我的自定义注册商看起来像:

Serialized

不要忘记绑定<?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
]);

没有hacky url解析逻辑了!