Laravel - Добавить дополнительную информацию к маршруту - PullRequest
0 голосов
/ 13 мая 2018

В настоящее время я работаю над проектом, в котором мы пытаемся создать 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
]);

Однако, похоже, что это невозможно.

Есть ли у кого-нибудь какие-либо предложения о том, как сделать эту работу или как сделать нашу логику более чистой и гибкой?,Важными факторами являются гибкость и простота использования.

Ответы [ 2 ]

0 голосов
/ 14 мая 2018

После долгих поисков и погружений в исходном коде Laravel я обнаружил, что метод getResourceAction в ResourceRegistrar обрабатывает опцию, переданную в маршрут.

Дальнейший поиск привел меня к этой записи , где кому-то еще удалось расширить этот регистратор и добавить некоторые пользовательские функции.

Мой пользовательский регистратор выглядит так:

<?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');

Больше нет логики хакерского разбора url!

0 голосов
/ 13 мая 2018

Наилучшим способом было бы преобразовать ResourceController в абстрактный класс и иметь отдельный контроллер, расширяющий его - для каждого ресурса.

Я почти уверен, что невозможно передать некоторую контекстную информацию в файл маршрутов.

Но вы могли бы связывать различные экземпляры репозиториев с вашим контроллером.Как правило, это хорошая практика, но полагаться на URL-адрес для решения проблемы очень просто.

Вы должны поместить все зависимости в конструктор:

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 - это глупо.

Я только что показал пример для привязки пути модели и привязкиРепозиторий, но если вы пойдете по этому пути, вы захотите убрать все экземпляры из конструктора Controller.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...