资讯专栏INFORMATION COLUMN

Laravel 路由设置

张红新 / 2817人阅读

摘要:本质是将为的请求转化为追加的组内请求,对应的匿名函数依然是为的请求假如为,则返回优先从设置里面取值,没有则生成单数形式的字符串,并将字符替换为小结资源类型的构造,实际上会被转化为构造多个默认资源的路由,本质依然是基本构造

Laravel 路由 路由构造总览

构造方法有:
Route::get、Route::post、Route::put、Route::patch、Route::delete、Route::options、Route::any、Route::match、Route::resource、Route::resources、Route::group

Route::get("foo", function () {
    // 基本方式
});
Route::match(["get", "post"], "/", function () {
    // 基本方式
});
Route::any("foo", function () {
    // 基本方式
});

Route::get("posts/{post}/comments/{comment}", function ($postId, $commentId) {
    // 必选路由参数
});

Route::get("user/{name?}", function ($name = "John") {
    // 可选路由参数
});

Route::get("user/{id}/{name}", function ($id, $name) {
    // 正则表达式约束
})->where(["id" => "[0-9]+", "name" => "[a-z]+"]);

// 全局约束 RouteServiceProvider 的 boot 方法
public function boot()
{
    Route::pattern("id", "[0-9]+"); 
    parent::boot();
}
Route::get("user/{id}", function ($id) {
    // 仅在 {id} 为数字时执行...
});

Route::get("user/profile", function () {
    // 命名路由
})->name("profile");
Route::get("user/profile", "UserController@showProfile")->name("profile");
为命名路由生成:
// 生成 URL...
$url = route("profile");
// 生成重定向...
return redirect()->route("profile");

// 路由组
Route::group(["middleware" => "auth"], function () {
    Route::get("/", function ()    {
        // 使用 `Auth` 中间件
    });

    Route::get("user/profile", function () {
        // 使用 `Auth` 中间件
    });
});

命名空间|子域名路由|路由前缀
Route::group(["namespace" => "Admin","domain" => "{account}.myapp.com","prefix" => "admin"], function () {
    // 在 "AppHttpControllersAdmin" 命名空间下,子域名为{account}.myapp.com,路由前缀匹配 "/admin" 的控制器
});

Route::resource("photo", "PhotoController", ["except" => ["create", "store", "update", "destroy"], "names" => ["create" => "photo.build"],"middleware" => []);

路由模型绑定
隐式绑定#
Laravel 会自动解析定义在路由或控制器方法(方法包含和路由片段匹配的已声明类型变量)中的 Eloquent 模型
Route::get("api/users/{user}", function (AppUser $user) {
    return $user->email;
});
显式绑定
RouteServiceProvider 类中的 boot 方法
public function boot()
{
    parent::boot();    
    Route::model("user", AppUser::class);
}
Route::get("profile/{user}", function (AppUser $user) {
    //
});

自定义解析逻辑
public function boot()
{
    parent::boot();    
    Route::bind("user", function ($value) {
        return AppUser::where("name", $value)->first();
    });
}

基本有以下几种形式:uri 分为是否带有参数, action 分为匿名函数或者 Controller@Method 形式,可能还会带一些其他的前置操作

基本构造

Route::get、Route::post、Route::put、Route::patch、Route::delete、Route::options、Route::any、Route::match

以上的构造方法本质是一样的,区别在于第一个参数

public function get($uri, $action = null)
{
    return $this->addRoute(["GET", "HEAD"], $uri, $action);
}
protected function addRoute($methods, $uri, $action)
{
    // 创建 $route(IlluminateRoutingRoute) 对象并加入到集合(IlluminateRoutingRouteCollection 路由集合辅助类)里,再返回 $route
    return $this->routes->add($this->createRoute($methods, $uri, $action));
}
protected function createRoute($methods, $uri, $action)
{
    // $action 若为 Controller@Method|["uses"=>Controller@Method] 形式
    if ($this->actionReferencesController($action)) {
        $action = $this->convertToControllerAction($action);
    }

    $route = $this->newRoute(
        $methods, $this->prefix($uri), $action
    );
    // 如果前缀条件栈不为空,则对 $route 进行相应的设置
    if ($this->hasGroupStack()) {
        $this->mergeGroupAttributesIntoRoute($route);
    }
    // 将 where 前置条件注入到 $route 对象
    $this->addWhereClausesToRoute($route);

    return $route;
}
protected function actionReferencesController($action)
{
    if (! $action instanceof Closure) {
        return is_string($action) || (isset($action["uses"]) && is_string($action["uses"]));
    }

    return false;
}
protected function convertToControllerAction($action)
{
    if (is_string($action)) {
        $action = ["uses" => $action];
    }
    // 尝试加入前置条件 namespace
    if (! empty($this->groupStack)) {
        $action["uses"] = $this->prependGroupNamespace($action["uses"]);
    }
    // 通过控制器来获取 action
    $action["controller"] = $action["uses"];
    // 类似:["controller"=>"namespaceController@Method", "uses"=>"namespaceController@Method"]
    return $action;
}
// $uri 尝试增加前置条件 prefix(group 组中的 prefix,对应给下面的所有路由增加)
protected function prefix($uri)
{
    return trim(trim($this->getLastGroupPrefix(), "/")."/".trim($uri, "/"), "/") ?: "/";
}
public function getLastGroupPrefix()
{
    if (! empty($this->groupStack)) {
        $last = end($this->groupStack);

        return isset($last["prefix"]) ? $last["prefix"] : "";
    }

    return "";
}
protected function newRoute($methods, $uri, $action)
{
    return (new Route($methods, $uri, $action))
                ->setRouter($this)
                ->setContainer($this->container);
}
// new Route
public function __construct($methods, $uri, $action)
{
    $this->uri = $uri;
    $this->methods = (array) $methods;
    $this->action = $this->parseAction($action);

    if (in_array("GET", $this->methods) && ! in_array("HEAD", $this->methods)) {
        $this->methods[] = "HEAD";
    }
    // 再尝试给 uri 多带带的加入 prefix 前缀 
    if (isset($this->action["prefix"])) {
        $this->prefix($this->action["prefix"]);
    }
}
protected function parseAction($action)
{
    // 委托 RouteAction action 辅助类进行解析
    return RouteAction::parse($this->uri, $action);
}
public static function parse($uri, $action)
{
    if (is_null($action)) {
        return static::missingAction($uri); // 抛异常
    }
    // 匿名函数
    if (is_callable($action)) {
        return ["uses" => $action];
    }
    elseif (! isset($action["uses"])) {
        $action["uses"] = static::findCallable($action);
    }
    // 如果 $action["uses"] 类似 Controller 形式,则尝试构造为 Controller@__invoke 形式,即没有指定方法时调用 __invoke 方法
    if (is_string($action["uses"]) && ! Str::contains($action["uses"], "@")) {
        $action["uses"] = static::makeInvokable($action["uses"]);
    }

    return $action;
}
 protected static function findCallable(array $action)
{
    // 尝试从 $action 数组找到第一个满足可调用且为数字键的值作为 $action 返回
    return Arr::first($action, function ($value, $key) {
        return is_callable($value) && is_numeric($key);
    });
}
public function hasGroupStack()
{
    return ! empty($this->groupStack);
}
protected function mergeGroupAttributesIntoRoute($route)
{
    $route->setAction($this->mergeWithLastGroup($route->getAction()));
}
public function mergeWithLastGroup($new)
{
    // 使用上一层的 groupStack 设置
    return RouteGroup::merge($new, end($this->groupStack));
}
protected function addWhereClausesToRoute($route)
{
    $route->where(array_merge(
        $this->patterns, isset($route->getAction()["where"]) ? $route->getAction()["where"] : []
    ));

    return $route;
}
// 返回 IlluminateRoutingRoute 对象
public function add(Route $route)
{
    // 设置路由以何种方式放入路由集合,待后续按此种方式来获取
    $this->addToCollections($route);
    $this->addLookups($route);
    return $route;
}
protected function addToCollections($route)
{
    $domainAndUri = $route->domain().$route->uri();
    // 表面可以通过 method 和 uri 来获取路由
    foreach ($route->methods() as $method) {
        $this->routes[$method][$domainAndUri] = $route;
    }    
    $this->allRoutes[$method.$domainAndUri] = $route;
}
protected function addLookups($route)
{
    $action = $route->getAction();
    // 如果前置条件栈设置了 as ,则将 $route 注入到 $this->nameList,即可以通过名字来获取路由
    if (isset($action["as"])) {
        $this->nameList[$action["as"]] = $route;
    }        
    if (isset($action["controller"])) {
        $this->addToActionList($action, $route);
    }
}
protected function addToActionList($action, $route)
{
    // 表示可以通过控制器获取路由
    $this->actionList[trim($action["controller"], "")] = $route;
}

流程小结(创建 route ,并将加入到路由集合里进行统一的管理)

根据 action 的形式和前置条件,或转为数组(["use"=> Clause|namespaceController@Method]),或为匿名函数

根据前置条件,或将组 uri 加前缀

创建 route 对象,并将 action 统一为数组,再进行一些其他设置

若存在前置条件,则加入到 route 对象的 action 数组

route 对象加入 where 条件

其他构造

Route::group

public function group(array $attributes, $routes)
{
    $this->updateGroupStack($attributes);

    $this->loadRoutes($routes);

    array_pop($this->groupStack);
}
protected function updateGroupStack(array $attributes)
{
    if (! empty($this->groupStack)) {
        $attributes = RouteGroup::merge($attributes, end($this->groupStack));
    }

    $this->groupStack[] = $attributes;
}
protected function loadRoutes($routes)
{
    if ($routes instanceof Closure) {
        $routes($this);     // 注意:每个匿名函数都会有 router 对象
    } else {
        $router = $this;

        require $routes;
    }
}

小结

主要通过设置前置条件栈($groupStack),然后运用到组内的所有成员,本质还是基本构造

Route::resource、Route::resources

public function resource($name, $controller, array $options = [])
{
    if ($this->container && $this->container->bound(ResourceRegistrar::class)) {
        $registrar = $this->container->make(ResourceRegistrar::class);
    } else {
        $registrar = new ResourceRegistrar($this);
    }

    $registrar->register($name, $controller, $options);
}
public function __construct(Router $router)
{
    $this->router = $router;
}
public function register($name, $controller, array $options = [])
{
    if (isset($options["parameters"]) && ! isset($this->parameters)) {
        $this->parameters = $options["parameters"];
    }

    if (Str::contains($name, "/")) {
        $this->prefixedResource($name, $controller, $options);

        return;
    }

    $base = $this->getResourceWildcard(last(explode(".", $name)));
    // ["index", "create", "store", "show", "edit", "update", "destroy"]
    $defaults = $this->resourceDefaults;
    // 生成相应条件下的路由
    foreach ($this->getResourceMethods($defaults, $options) as $m) {
        $this->{"addResource".ucfirst($m)}($name, $base, $controller, $options);
    }
}
protected function prefixedResource($name, $controller, array $options)
{
    list($name, $prefix) = $this->getResourcePrefix($name);
    // $me 为 router 对象。本质是将 $name 为 "xx/yy/zz" 的 resource 请求转化为 groupStack 追加 ["prefix"=>"xx/yy"] 的 group 组内请求,对应的匿名函数依然是 $name 为 "zz" 的 resource 请求
    $callback = function ($me) use ($name, $controller, $options) {
        $me->resource($name, $controller, $options);
    };

    return $this->router->group(compact("prefix"), $callback);
}
protected function getResourcePrefix($name)
{
    $segments = explode("/", $name);

    $prefix = implode("/", array_slice($segments, 0, -1));
    // 假如 $name 为 "xx/yy/zz", 则返回 ["zz", "xx/yy"]
    return [end($segments), $prefix];
}
// 优先从设置里面取值,没有则生成单数形式的字符串,并将字符 "-" 替换为 "_"
public function getResourceWildcard($value)
{
    if (isset($this->parameters[$value])) {
        $value = $this->parameters[$value];
    } elseif (isset(static::$parameterMap[$value])) {
        $value = static::$parameterMap[$value];
    } elseif ($this->parameters === "singular" || static::$singularParameters) {
        $value = Str::singular($value);
    }

    return str_replace("-", "_", $value);
}
protected function getResourceMethods($defaults, $options)
{
    if (isset($options["only"])) {
        return array_intersect($defaults, (array) $options["only"]);
    } elseif (isset($options["except"])) {
        return array_diff($defaults, (array) $options["except"]);
    }

    return $defaults;
}
protected function addResourceIndex($name, $base, $controller, $options)
{
    $uri = $this->getResourceUri($name);

    $action = $this->getResourceAction($name, $controller, "index", $options);

    return $this->router->get($uri, $action);
}
public function getResourceUri($resource)
{
    if (! Str::contains($resource, ".")) {
        return $resource;
    }

    $segments = explode(".", $resource);

    $uri = $this->getNestedResourceUri($segments);
    // "xx/{xx}/yy/{yy}/zz"
    return str_replace("/{".$this->getResourceWildcard(end($segments))."}", "", $uri);
}
protected function getNestedResourceUri(array $segments)
{
    // ["xx","yy","zz"] => "xx/{xx}/yy/{yy}/zz/{zz}"
    return implode("/", array_map(function ($s) {
        return $s."/{".$this->getResourceWildcard($s)."}";
    }, $segments));
}
protected function getResourceAction($resource, $controller, $method, $options)
{
    $name = $this->getResourceRouteName($resource, $method, $options);

    $action = ["as" => $name, "uses" => $controller."@".$method];

    if (isset($options["middleware"])) {
        $action["middleware"] = $options["middleware"];
    }

    return $action;
}
protected function getResourceRouteName($resource, $method, $options)
{
    $name = $resource;
    
    if (isset($options["names"])) {
        if (is_string($options["names"])) {
            $name = $options["names"];
        } elseif (isset($options["names"][$method])) {
            return $options["names"][$method];
        }
    }

    $prefix = isset($options["as"]) ? $options["as"]."." : "";

    return trim(sprintf("%s%s.%s", $prefix, $name, $method), ".");
}

小结

资源类型的构造,实际上会被转化为构造多个默认资源的路由,本质依然是基本构造

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/22598.html

相关文章

  • 「新轮子」PHP CORS (Cross-origin resource sharing),解决 P

    摘要:而我的新轮子也并不是专门解决它的问题的,而是顺便解决而已。概述这个包,支持在所有的项目中使用。一旦出现成员,代表允许全部。列出允许跨域请求的方法列表,默认是代表所有方法。信息地址嗯,新轮子,求一波。 showImg(https://segmentfault.com/img/bV5VxN?w=844&h=656); 是的,可能了解 Laravel 的都知道,在 Laravel 中简单的设...

    lbool 评论0 收藏0
  • Laravel 请求周期

    摘要:请求周期加载自动加载器获取应用对象实例化应用解析此对象贯穿全文主要过程设置基础路径基础绑定注册全局基础服务核心容器别名设置注册三个单例获取对象实例化此对象为应用的枢纽,将会协调各部分之间的工作,完成请求主要过程注入应用对象注入事件对象注入 Laravel 请求周期 加载 composer 自动加载器 require __DIR__./../bootstrap/autoload.php;...

    Cristalven 评论0 收藏0
  • Laravel 路由研究之domain 解决多域名问题

    摘要:关于路由中的在多域名下的说明首先,我们需要知道决定了路由会绑定到哪个控制器,还有一点需要注意,路由中的属性,决定了辅助函数生成的。 材料准备 一份干净的laravel 两份Nginx配置文件,主要配置如下: server_name *.amor_laravel_test_1.amor; root /var/www/amor_laravel_test/public; index in...

    bladefury 评论0 收藏0
  • Laravel 路由研究之domain 解决多域名问题

    摘要:关于路由中的在多域名下的说明首先,我们需要知道决定了路由会绑定到哪个控制器,还有一点需要注意,路由中的属性,决定了辅助函数生成的。 材料准备 一份干净的laravel 两份Nginx配置文件,主要配置如下: server_name *.amor_laravel_test_1.amor; root /var/www/amor_laravel_test/public; index in...

    baishancloud 评论0 收藏0
  • 从PHP Laravel 到 Go Iris--路由

    摘要:可以通过来直接设置路由前缀给添加前缀通过,还是通过就可以了匹配包含的匹配包含的好了,这两个框架的路由基本比较和应用就这些了,还有一些比如控制器路由和如何自定义中间件等在后续再写吧,或者请自行查阅文档,以上内容如有错误请指出。 Laravel是我最喜欢的PHP Web开发框架,所以也希望可以在Go的Web框架中选择一个类似Laravel这样的好用又全栈的框架,刷了一下Beego, Ech...

    lingdududu 评论0 收藏0
  • LaravelLaravel 框架关键技术解析·读书笔记(二)

    摘要:框架关键技术解析读书笔记二第五章框架应用程序根目录版本默认的框架应用程序是符合规范的,所以相应的目录结构也是基本固定的,不同的目录加载了功能文件,如果添加了新的目录,需要在文件中添加规范的自动加载部分并执行命令。 Laravel 框架关键技术解析·读书笔记(二) 第五章 框架应用程序根目录(5.1版本) 默认的Laravel框架应用程序是符合PSR规范的,所以相应的目录结构也是基本...

    TIGERB 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<