从嵌套资源检查模型关系

时间:2016-07-23 18:19:32

标签: laravel laravel-5

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

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

3 个答案:

答案 0 :(得分:0)

我之前遇到过这样做的必要性。这就是我做到的:

在我的 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来嵌套网址时,我已经这样做了,但它很容易适应使用ID。

答案 1 :(得分:0)

  

...当使用路径模型绑定时,这些资源的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);
    }
}

答案 2 :(得分:0)

这是一个古老的问题,但今天仍然有意义。有一个很好的答案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;
});