【问题标题】:Checking Model Relationships From Nested Resources从嵌套资源检查模型关系
【发布时间】:2016-11-27 10:59:01
【问题描述】:

Laravel 路由中的一个常见设置是使用嵌套资源和路由模型绑定。这允许很好的逻辑 url 代表模型在数据库中彼此之间的实际关系。 /library/section/book/ 就是一个例子。书归部门所有,部门归图书馆所有。但是在使用路由模型绑定的时候,这些资源的id就变成了模型,彼此都不知道。 /1/7/234 将返回这些资源的模型,但不能保证它们正确相关。书 234 可能不属于第 7 节,第 7 节可能不属于图书馆 1。我经常在每个控制器的顶部有一个方法来处理我所谓的关系测试。这个函数可以在 Book 控制器中找到。

private function relationshipCheck($library, $section, $book)
{
    if(library->id == $section->library_id) {
        if($book != false) {
            if($section->id == $book->section_id) {
                return true;
            } else {
                return response()->json(["code" => 401], 401);
            }
        } else {
            return true;
        }
    } else {
        return response()->json(["code" => 401, 401);
    }
}

使用这些代表关系的路由的正确方法是什么?有没有更自动化的方法来做到这一点?当关系都是一对多时,是否有充分的理由忽略除最后一个资源之外的所有内容?

【问题讨论】:

标签: laravel laravel-5


【解决方案1】:

这是一个古老的问题,但在今天仍然很重要。有一个很好的答案here,它建议明确绑定有问题的模型。它与此处的另一个答案类似,但抽象程度较低。

Route::bind('section', function ($section, $route) {
    return Section::where('library_id', $route->parameter('library'))->findOrFail($section);
});

Route::bind('book', function ($book, $route) {
    return Book::where('Section_id', $route->parameter('section'))->findOrFail($book);
});

这将自动在任何地方工作。如果需要,您可以测试要找到的上游参数,并仅在这些情况下执行测试(例如,满足仅指定一本书的路线)。

Route::bind('book', function ($book, $route) {
    $section = $route->parameter('section');
    return $section ? Book::where('Section_id', $route->parameter('section'))->findOrFail($book) : $book;
});

【讨论】:

    【解决方案2】:

    ...当使用路由模型绑定时,这些资源的 id 会在彼此不知情的情况下转化为模型。

    我刚刚开始处理这个问题,这就是我决定采用这种方法的方式。

    • 更容易检查模型的关系
      • Laravel 5.3 有一种方法可以判断两个模型是否具有相同的 ID 并属于同一个表。 is()
      • 我提交了pull request that would add relationship tools。您可以看到我在项目中使用的对 Illuminate\Database\Eloquent\Model 的更改。
    • 使用模型绑定为嵌套路由创建中间件。

    中间件

    <?php
    
    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Http\Exception\HttpResponseException;
    
    /**
     * Class EntityChain
     *
     * Determine if bound models for the route are related to
     * each other in the order they are nested.
     *
     * @package App\Http\Middleware
     */
    class EntityChain
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request $request
         * @param  \Closure $next
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
            // Array of the bound models for the route.
            $parameters = array_filter($request->route()->parameters(),
                function ($v) {
                    if ($v instanceof Model) return true;
                    return false;
                });
    
            // When there are two or more bound models.
            if (count($parameters) > 1) {
    
                // The first model is the parent.
                $parent = array_shift($parameters);
    
                while (count($parameters) > 0) {
    
                    // Assume the models are not related.
                    $pass = false;
    
                    // Set the child model.
                    $child = array_shift($parameters);
    
                    // Check if the parent model is related to the child.
                    if ($parent->is_related($child)) {
                        $pass = true;
                    }
    
                    $parent = $child;
    
    
                    // Fail on no relation.
                    if (!$pass) {
                        throw new HttpResponseException(response()->json('Invalid resource relation chain given.', 406));
                    }
                }
            }
    
            return $next($request);
        }
    }
    

    【讨论】:

      【解决方案3】:

      我以前遇到过这样做的需要。这就是我的做法:

      在我的 RouteServiceProvider.php 文件中,我有以下方法:

      private function addSlugBindingWithDependency(Router $router, $binding, $className, $dependency, $dependencyClassName, $dependencyField)
      {
          $router->bind($binding, function($slug, $route) use($className, $dependency, $dependencyClassName, $dependencyField) {
              if (!is_string($slug)) {
                  throw new NotFoundHttpException;
              }
      
              $params = $route->parameters();
              if (!$params || !isset($params[$dependency]) || get_class($params[$dependency]) != $dependencyClassName) {
                  throw new NotFoundHttpException;
              }
      
              $dependencyInstance = $params[$dependency];
      
              $item = $className::where('slug', $slug)->where($dependencyField, $dependencyInstance->id)->first();
              if (!$item) {
                  throw new NotFoundHttpException;
              }
      
              return $item;
          });
      }
      

      这是一个帮助我为 slug 设置路由/模型绑定的函数,该 slug 取决于 URL/路径的另一部分。它的工作原理是查看路线中已经绑定的部分,并抓取它之前绑定的模型实例,并使用它来检查两者是否链接在一起。

      我还有另一个更基本的辅助函数 addSlugBinding,我也用它来将 slug 绑定到对象。

      您可以在RouteServiceProvider 类的引导方法中使用它,如下所示:

      public function boot(Router $router)
      {
          parent::boot($router);
      
          $this->addSlugBinding($router, 'librarySlug', 'App\Library');
          $this->addSlugBindingWithDependency($router, 'sectionSlug', 'App\Section', 'librarySlug', 'App\Library', 'library_id');
          $this->addSlugBindingWithDependency($router, 'bookSlug', 'App\Book', 'sectionSlug', 'App\Section', 'section_id');
      }
      

      然后在我的路线文件中,我可能有以下内容:

      Route::get('{librarySlug}/{sectionSlug}/{bookSlug}', function($librarySlug, $sectionSlug, $bookSlug) {
      
      });
      

      注意:当我想要通过 slug 而不是 ID 嵌套 URL 时,我已经这样做了,但它可以很容易地适应使用 ID。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-23
        • 1970-01-01
        • 1970-01-01
        • 2023-02-20
        • 1970-01-01
        • 1970-01-01
        • 2016-12-30
        • 1970-01-01
        相关资源
        最近更新 更多