gpt4 book ai didi

php - 多个路由的相同 Laravel 资源 Controller

转载 作者:塔克拉玛干 更新时间:2023-11-03 06:04:39 25 4
gpt4 key购买 nike

我正在尝试使用 trait 作为我的 Laravel 资源 Controller 的类型提示。

Controller 方法:

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

订单和履行都可以有注释,因此它们使用相同的 Controller ,因为代码是相同的。

然而,当我发帖到 order/{order}/comment ,我收到以下错误:

Illuminate\Contracts\Container\BindingResolutionException
Target [App\Models\Morphs\Commentable] is not instantiable.



这可能吗?

最佳答案

所以你想避免订单和履行资源 Controller 的重复代码,并且有点枯燥。好的。

特征不能被打字提示

如马修 stated ,您不能输入提示特征,这就是您收到绑定(bind)解析错误的原因。除此之外,即使它是 typehintable,容器也会混淆它应该实例化哪个模型,因为有两个 Commentable可用型号。但是,我们稍后会谈到。

接口(interface)与特征

拥有一个伴随特征的界面通常是一个很好的做法。除了接口(interface)可以进行类型提示这一事实之外,您还遵守了 Interface Segregation原则,“如果需要”是一种很好的做法。

interface Commentable 
{
public function comments();
}

class Order extends Model implements Commentable
{
use Commentable;

// ...
}

现在它是类型提示的。让我们来看看容器混淆问题。

上下文绑定(bind)

Laravel 的容器支持 contextual binding .这就是明确告诉它何时以及如何将抽象解析为具体的能力。

您为 Controller 获得的唯一区别因素是路线。我们需要以此为基础。类似的东西:

# 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例如,首先检查路由,然后实例化正确的可评论模型。

非上下文绑定(bind)也可以:

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

错误的工具

我们刚刚通过引入不必要的复杂性来消除重复的 Controller 代码,这更糟。 👍



尽管它有效,但它很复杂,不可维护,非通用,最糟糕的是,依赖于 URL。它使用了错误的工具来完成这项工作,这是完全错误的。

遗产

消除这些问题的正确工具就是继承。引入一个抽象的基本评论 Controller 类并从中扩展两个浅层。

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

简单、灵活、可维护。

啊,错误的语言

没那么快:

Declaration of OrderCommentController::store(CreateCommentRequest $request, Order $commentable) should be compatible with CommentController::store(CreateCommentRequest $request, Commentable $commentable).



尽管覆盖方法参数在构造函数中工作得很好,但它根本不适用于其他方法!构造函数是 special cases .

我们可以在父类和子类中删除类型提示,并继续使用普通 ID 继续我们的生活。但在这种情况下,由于 Laravel 的隐式模型绑定(bind)仅适用于类型提示,因此我们的 Controller 不会自动加载模型。

好吧,也许在一个更好的世界。



🎉更新:见 PHP 7.4对类型变化的支持 🎉

显式路由模型绑定(bind)

那我们怎么办?

如果我们明确告诉路由器如何加载我们的 Commentable模型,我们可以只使用单独的 CommentController类(class)。 Laravel 的 explicit model binding通过将路由占位符(例如 {order})映射到模型类或自定义解析逻辑来工作。所以,虽然我们正在使用我们的单 CommentController我们可以根据他们的路线占位符为订单和履行使用单独的模型或解析逻辑。因此,我们放弃类型提示并依赖占位符。

对于资源 Controller ,占位符名称取决于您传递给 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();
}

您的 Controller 代码将是:

# 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} s 将解析为 \App\Order模型和所有 {fulfillment} s 到 App\Fulfillment .

我们可以改变第一个解决方案以使用路由参数而不是 URL 段。但是当 Laravel 提供给我们时,没有理由手动完成它。

是的,我知道,我也感觉不舒服。

关于php - 多个路由的相同 Laravel 资源 Controller ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46128476/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com