Один и тот же контроллер ресурсов Laravel для нескольких маршрутов

Я пытаюсь использовать эту черту как подсказку для моих контроллеров ресурсов Laravel.

Метод контроллера:

public function store(CreateCommentRequest $request, Commentable $commentable)

В котором Commentable это типичная подсказка, которую используют мои модели Eloquent.

Commentable черта выглядит так:

namespace App\Models\Morphs;

use App\Comment;

trait Commentable
{
/**
* Get the model's comments.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function Comments()
{
return $this->morphMany(Comment::class, 'commentable')->orderBy('created_at', 'DESC');
}
}

В моем маршруте у меня есть:

Route::resource('order.comment', 'CommentController')
Route::resource('fulfillments.comment', 'CommentController')

Оба заказа и выполнения могут иметь комментарии, и поэтому они используют один и тот же контроллер, поскольку код будет одинаковым.

Тем не менее, когда я публикую order/{order}/commentЯ получаю следующую ошибку:

Осветить \ Контракты \ Container \ BindingResolutionException
Цель [App \ Models \ Morphs \ Commentable] не может быть создана.

Это вообще возможно?

4

Решение

Таким образом, вы хотите избежать дублирования кода для контроллеров ресурсов заказов и выполнения и быть немного СУХИМ. Хорошо.

Черты не могут быть напечатаны

Как Мэтью заявил, вы не можете напечатать черты характера, и по этой причине вы получаете ошибку разрешения привязки. Кроме этого, даже если бы он был типизируемым, контейнер был бы запутан, какую модель он должен создать, так как есть два Commentable модели доступны. Но мы вернемся к этому позже.

Интерфейсы наряду с чертами

Часто хорошей практикой является наличие интерфейса, сопровождающего черту. Помимо того, что интерфейсы могут быть напечатаны, вы придерживаетесь Разделение интерфейса принцип, который «при необходимости» является хорошей практикой.

interface Commentable
{
public function comments();
}

class Order extends Model implements Commentable
{
use Commentable;

// ...
}

Теперь, когда он напечатан. Давайте вернемся к проблеме с контейнером.

Контекстная привязка

Контейнерные опоры Laravel контекстная привязка. Это способность четко сказать, когда и как преобразовать реферат в конкретный.

Единственный отличительный фактор, который вы получили для своих контроллеров, это маршрут. Мы должны опираться на это. Что-то вроде:

# AppServiceProvider::register()
$this->app
->when(CommentController::class)
->needs(Commentable::class)
->give(function ($container, $params) {
// Since you're probably utilizing Laravel's route model binding,
// we need to resolve the model associated with the passed ID using
// the `findOrFail`, instead of just newing up an empty instance.

// Assuming this route pattern: "order|fullfilment/{id}/comment/{id}"$id = (int) $this->app->request->segment(2);

return $this->app->request->segment(1) === 'order'
? Order::findOrFail($id)
: Fulfillment::findOrFail($id);
});

Вы в основном говорите контейнер, когда CommentController требует Commentable Например, сначала проверьте маршрут, а затем создайте экземпляр правильной комментируемой модели.

Неконтекстная привязка также будет полезна:

# AppServiceProvider::register()
$this->app->bind(Commentable::class, function ($container, $params) {
$id = (int) $this->app->request->segment(2);

return $this->app->request->segment(1) === 'order'
? Order::findOrFail($id)
: Fulfillment::findOrFail($id);
});

Неправильный инструмент

Мы только что устранили дублирующий код контроллера, добавив ненужную сложность, которая еще хуже. ��

                                   

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

наследование

Правильный инструмент для устранения подобных проблем — это просто наследование. Ввести абстрактный базовый класс контроллера комментариев и расширить его два.

# App\Http\Controllers\CommentController
abstract class CommentController extends Controller
{
public function store(CreateCommentRequest $request, Commentable $commentable) {
// ...
}

// All other common methods here...
}

# App\Http\Controllers\OrderCommentController
class OrderCommentController extends CommentController
{
public function store(CreateCommentRequest $request, Order $commentable) {
return parent::store($commentable);
}
}

# App\Http\Controllers\FulfillmentCommentController
class FulfillmentCommentController extends CommentController
{
public function store(CreateCommentRequest $request, Fulfillment $commentable) {
return parent::store($commentable);
}
}

# Routes
Route::resource('order.comment', 'OrderCommentController');
Route::resource('fulfillments.comment', 'FulfillCommentController');

Простой, гибкий и ремонтопригодный.

Arrrgh, неправильный язык

Не так быстро:

Декларация OrderCommentController :: store (CreateCommentRequest $ request, Order $ commentable) должен быть совместим с CommentController :: store (CreateCommentRequest $ request, Commentable $ commentable).

Несмотря на то, что переопределение параметров метода работает в конструкторах просто отлично, это не работает для других методов! Конструкторы Особые случаи.

Мы могли бы просто отбросить шрифты в родительских и дочерних классах и продолжить нашу жизнь с простыми идентификаторами. Но в этом случае, поскольку неявная привязка модели Laravel работает только с печатными текстами, для наших контроллеров не будет автоматической загрузки моделей.

Хорошо, может быть, в лучшем мире.

                           

Явная привязка модели маршрута

Так что мы будем делать?

Если мы явно скажем маршрутизатору, как загрузить наш Commentable модели, мы можем просто использовать одинокий CommentController учебный класс. Laravel-х явная привязка модели работает путем отображения заполнителей маршрута (например, {order}) для моделирования классов или пользовательских логик разрешения. Итак, пока мы используем наш сингл CommentController мы можем использовать отдельные модели или логику разрешения для заказов и выполнения в зависимости от их заполнителей маршрута. Итак, мы отбрасываем шрифт и полагаемся на заполнитель.

Для контроллеров ресурсов имя заполнителя зависит от первого параметра, который вы передаете Route::resource метод. Просто сделай artisan route:list выяснить.

Хорошо давай сделаем это:

# App\Providers\RouteServiceProvider::boot()
public function boot()
{
// Map `{order}` route placeholder to the \App\Order model
$this->app->router->model('order', \App\Order::class);

// Map `{fulfillment}` to the \App\Fulfilment model
$this->app->router->model('fulfillment', \App\Fulfilment::class);

parent::boot();
}

Ваш код контроллера будет:

# App\Http\Controllers\CommentController
class CommentController extends Controller
{
// Note that we have dropped the typehint here:
public function store(CreateCommentRequest $request, $commentable) {
// $commentable is either an \App\Order or a \App\Fulfillment
}

// Drop the typehint from other methods as well.
}

И определения маршрута остаются прежними.

Это лучше, чем первое решение, так как оно не опирается на сегменты URL, которые подвержены изменениям, в отличие от заполнителей маршрута, которые редко изменяются. Это также универсально, как и все {order}с будет решено до \App\Order модель и все {fulfillment}с App\Fulfillment,

Мы могли бы изменить первое решение, чтобы использовать параметры маршрута вместо сегментов URL. Но нет никакой причины делать это вручную, когда Laravel предоставил это нам.


Да, я знаю, я тоже не очень хорошо себя чувствую.

6

Другие решения

Вы не можете напечатать черты характера.

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

РЕДАКТИРОВАТЬ: Как любезно отметил @Stefan, по-прежнему будет трудно разрешить интерфейс для конкретного класса, потому что он должен будет разрешить для разных классов при разных обстоятельствах. Вы можете получить доступ к запросу в поставщике услуг и использовать путь, чтобы определить, как его разрешить, но я немного сомневаюсь в этом. Я думаю, лучше поместить их в отдельные контроллеры и использовать наследование / признаки для совместного использования общих функций, поскольку методы в каждом контроллере могут напечатать подсказку для требуемого объекта, а затем передать их эквивалентному родительскому методу.

1

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector