代码之家  ›  专栏  ›  技术社区  ›  andershagbard Anoop

用于多个路由的同一Laravel资源控制器

  •  6
  • andershagbard Anoop  · 技术社区  · 8 年前

    控制器方法:

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

    其中 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 ,我得到以下错误:


    目标[App\Models\Morphs\Commentable]不可实例化。

    这有可能吗?

    3 回复  |  直到 8 年前
        1
  •  11
  •   sepehr GR7    6 年前

    因此,您希望避免订单和履行资源控制器的代码重复,并且有点枯燥。好的

    无法输入特征

    作为马修 stated ,您无法键入提示特征,这就是您出现绑定解析错误的原因。除此之外,即使它是可类型化的,容器也会混淆它应该实例化哪个模型,因为有两个模型 Commentable 型号可用。但是,我们稍后再谈。

    通常,有一个界面来伴随一个特征是一种很好的做法。除了界面可以类型化之外,您还坚持 Interface Segregation “如果需要”的原则是一种良好的做法。

    interface Commentable 
    {
        public function comments();
    }
    
    class Order extends Model implements Commentable
    {
        use Commentable;
    
        // ...
    }
    

    现在它是可打印的。让我们来谈谈容器混淆问题。

    语境结合

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

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

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

    错误的工具

    我们刚刚通过引入不必要的复杂性消除了重复的控制器代码,这种复杂性甚至更糟糕。

    遗产

    消除这些问题的正确工具就是继承。引入一个抽象的基注释控制器类,并从中扩展了两个浅层注释控制器类。

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

    啊,错误的语言

    没那么快:

    声明 .

    尽管重写方法参数在构造函数中工作得很好,但它对其他方法根本不起作用!构造函数是 special cases .

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

    更新:请参阅 PHP 7.4 的类型差异支持

    那我们该怎么办?

    如果我们明确告诉路由器如何加载 可评论的 注释控制器 explicit model binding 通过映射路线占位符(例如。 {order} )建模类或自定义解析逻辑。所以,当我们使用我们的单曲 我们可以根据订单和履行的路径占位符使用单独的模型或解析逻辑。因此,我们放弃typehint并依赖占位符。

    对于资源控制器,占位符名称取决于传递给 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段,而依赖于很少更改的路由占位符。它也是通用的 s将被解决为 \App\Order 模型和所有 {fulfillment} App\Fulfillment .

    我们可以改变第一种解决方案,利用路由参数而不是URL段。但是,当拉雷维尔提供给我们时,没有理由手动执行。


    是的,我知道,我也感觉不太好。

        2
  •  1
  •   Matthew Daly    8 年前

    You can't typehint traits .

    编辑:正如@Stefan善意地指出的那样,很可能仍然难以将接口解析到具体类,因为它需要在不同的情况下解析到不同的类。您可以访问服务提供商中的请求,并使用路径来确定如何解决它,但我对此有点怀疑。我认为将它们放在单独的控制器中,并使用继承/特征来共享公共功能可能是更好的选择,因为每个控制器中的方法可以键入所需的对象,然后将它们传递给等效的父方法。

        3
  •  0
  •   Mark Khor    5 年前

    就我的情况而言,我有以下资源:

            Route::resource('books/storybooks', 'BookController');
            Route::resource('books/magazines', 'BookController');
    

            Route::model('magazine', \App\Book::class);
    

    注意单数和复数。

    推荐文章