类似 Laravel 框架这样显式路由的写法真的好吗?

问答 JokerRoc ⋅ 于 2017-01-11 15:41:52 ⋅ 最后回复由 edwin404 2017-05-08 22:19:32 ⋅ 5248 阅读

只有显式路由,这是 Laravel 中我几乎唯一感到神烦的一点。每一个请求都得单独写路由。这样有什么好处呢?像 CI 那样自动匹配 [模块 /控制器 /方法] 的隐式路由方式有啥坏处?别跟我说什么显式路由可以清晰地知道自己暴露了多少接口出去,当你的路由有数百行甚至上千行的时候看着一样晕好吗?!

而且很关键的一点是,我一直用sublime,显式路由让我失去了直接复制链接中的URI部分,然后sublime里ctrl+p就可以定位到控制器文件,ctrl+R就可以定位到action的便利了。影响开发的心情啊!

即使是CI和YII这样的框架,也是提供你显式路由+隐式路由两个选项啊。为什么Laravel如此反对隐式路由呢?求大神分析!

本帖已被设为精华帖!
本帖由 Summer 于 6个月前 加精
回复数量: 32
  • coodeer PHPer
    2017-01-11 17:58:25

    Laravel给了你更多的自由,但是你不想要自由,我还有什么好说的😂

    1. Route和Controller是两个东东,为什么一定要绑定在一起?
    2. 有些场景根本不需要Controller啊,比方说一个静态页面,这时候你去定义一个Controller是不是多此一举?
    3. 如果同一个Controller的方法需要被多个路由共用呢?
    4. 如果你是在开发RESTFUL API,你描述的隐式路由怎么体现HTTP动词?
    5. Route多了你可以分文件甚至分文件夹管理啊,Laravel并有没有强制要求你只使用一个route.php。
    6. 你需要快速定位你的controller,你可以把uri注释在方法头部,然后全局搜索不就好了?
    7. Route和Controller更应该发挥好自己的职责,而不要抢了他人的风头,不然就混乱了。
    8. Laravel的Route有分组、中间件、别名、前缀等等诸多优秀的特性,你应该多关注这些。
    9. ...

    所以,你只是习惯了以前的Route,现在有一种更优秀Route的摆在你面前,你是否要去接纳呢?好了,好话不多说,剩下的自己去慢慢体会。https://laravel-china.org/docs/5.3/routing

  • leo MOD 不会写前端的后端不是好运维
    2017-01-11 20:40:05

    可以自己支持呀,在路由文件的最后面加上一个全局捕获的路由

    Route::any(
        '{url}',
        function ($url) {
            echo $url;
        }
    )->where('url', '.+');

    然后拆解$url变量就可以了

  • JokerRoc
    2017-01-12 10:13:17

    @coodeer 。。。我一条一条反驳你。

    首先,Laravel 这不是给我更多的自由!只给你一种选择能叫自由?应该显式路由+隐式路由都支持,你想用那个用哪个才叫自由吧?

    1. Route和Controller是两个东东,我没说要绑在一起。
    2. 不需要Controller的,你可以自定义路由啊。一般知名的框架都支持同时支持两者啊。这并不矛盾!
    3. 一个控制器方法被多个URL访问,这种是极少数情况吧?而且这么做很不规范,对SEO非常不利,相当于是重复页面。这种情况用显式路由。
    4. RESTFUL API,同上。这种情况用显式路由。
    5. Route多了分文件,可以,但是这还是没有解决配置麻烦,无法直接通过URL定位Controller,性能差的问题啊!
    6. 快速定位Controller,我说的是直接用文件名匹配,你说的是搜索文件内容,这效率差多了吧?搜索文件内容操作步骤多不说,项目大的时候速度还超慢!
    7. Route和Controller更应该发挥好自己的职责,这一句给我感觉莫名其妙。。隐式路由也是路由啊,怎么就被Controller抢了风头了 = =
    8. 你说的这些优秀特性可以有,跟我说的并不矛盾。我只要求加上隐式路由的功能。。而且,分组、中间件,这个我隐式路由,控制器分成多个模块,不同控制器继承不同的父类,不是也实现了一样的效果?这样性能还更快。
      9...

    最后我补充一点,显式路由不仅仅有开发上麻烦、可读性差、无法快速定位Controller文件这几个问题,还有一个重大的问题是性能!! 路由文件里配置越多,越耗费性能!相当于每次进入页面都要把所有的路由配置解析一边,及时是用了路由缓存,当路由非常多的时候,也是很慢的!
    而隐式路由省略了解析大量路由配置的过程,直接通过URL匹配到Controller,效率快N多!

  • coodeer PHPer
    2017-01-12 10:39:25

    @JokerRoc 受教了😁

    我还是再说说我的观点吧。我说的自由,就是指Laravel可以实现你任何想要的,包括你的提到的“隐式路由”,就像2楼提供的方法。

    然而,我只是不推荐这种模式而已,毕竟明确定义路由才能更好地管理路由。管理代码也是一门艺术啊。

    如果你说定义很多路由会导致“开发上麻烦、可读性差、无法快速定位Controller文件这几个问题”,我只能理解为你没有管理好你的代码。

    最后,你提到的效率问题,那就是仁者见仁的事情了,我认为牺牲一定运行效率来提高开发效率是很有必要的,何况运行效率更应该从架构层面入手,多几个路由能影响多少毫秒的效率?

    哈哈,欢迎继续拍砖。

  • JokerRoc
    2017-01-12 11:10:49

    @coodeer 。。。 还是再补充一下。。

    1. 我说的显式路由在开发上的麻烦是指:每当我新建一个action的时候,都需要在Route文件里面增加配置;每当我需要通过URL定位Controller的时候,需要去一行一行的翻Route文件。这个麻烦是显式路由引起的,跟没好好管理代码无关吧?
    2. 可读性差是指:如果我用上你说的那些【优秀的特性】:分组、中间件、别名、前缀,去配置路由,不可避免的会导致路由看起来很复杂 = 不好读 = 可读性差。
    3. 如果用隐式路由,以上问题均不存在。
    4. 至于你说的管理代码的问题,用显式路由是否增加了我管理代码的成本?更何况即使好好的管理代码,上面我说的问题是不可避免的啊!
    5. 耗费如此多的代价(性能、开发成本、维护成本)使用显式路由,真的值得吗?
  • riverrun46
    2017-01-12 11:38:53

    https://philsturgeon.uk/php/2013/07/23/beware-the-route-to-evil/
    https://laracasts.com/lessons/say-no-to-implicit-routing
    https://laracasts.com/discuss/channels/general-discussion/implicit-controllers

    laravel社区的主流意见是不认可隐式路由的,这也是5.3将Route::controller()彻底移除的原因
    如果你更倾向于使用隐式路由,可以到github找类似的组件或者自己实现

  • coodeer PHPer
    2017-01-12 12:49:22

    @JokerRoc 脱离框架来说吧,我觉着,一个系统就好比一本书,路由就是这本书的目录索引,一个好的目录对于书的重要性可想而知。而这种维护成本不应该是我们想要做减法的那一部分吧?所以值不值得,就在于愿不愿意去著一本好书。

    另外,在做了很多Web之外的项目后,逐渐会发现隐式路由局限性太大了,用一个Github的接口为例子,/repos/:owner/:repo/issues/:issue_number/timeline,类似这样的URL怎么很好地用隐式路由实现?退一步来讲,这类URL咱们就直接使用显示路由好了,那么问题又来了,两种路由并存才是维护的灾难吧。当然你也可以认为两种方式并存是OK,存在即合理嘛。

    其实我并没有冒犯你的意思。当年在没有接触Laravel之前,我也使用Yii、CI,当时也觉得这种路由方式没毛病,完全OK啊。所以,好坏在于自己的主观意识。大家共勉😊

  • 远客 收住心,全力拼
    2017-01-12 14:34:53
    //测试路由一
    Route::any('/test/{action}', function ($action) {
        $class = App::make(\App\Http\Controllers\TestController::class);
        return $class->$action();
    });
    
    //测试路由二
    Route::post('/{dir}/{module}/{action}', function ($dir, $module, $action) {
        $class = App::make('App\\Http\\Controllers\\' . $dir . '\\' . $module . 'Controller');
        return $class->$action();
    });

    采用这种方式不就完全达到你想要的效果了吗?何至于加一个方法就写一条路由。。。

  • MrJing MOD
    2017-01-12 15:07:03

    @远客 :+1: 哈哈,ThinkPHP/CI 即视感,从其他框架转过来该感觉多么亲切啊。这样子,应该就用不了路由中间件了。不过,他们以前就没有过,也不会在乎吧。哈哈哈

  • maxincai
    2017-01-12 15:26:15

    以前也有过这样的困惑,后来才发现确实是显示路由好。

  • ertret
    2017-01-12 16:47:03

    PDOException in Connector.php line 55:
    SQLSTATE[HY000] [1045] Access denied for user 'homestead'@'localhost' (using password: YES)

  • 2017-01-12 17:23:51

    支持一下显示路由, 中间件, 路径前缀啥的都一目了然.
    隐式路由总感觉心里不踏实.

  • Scholer
    2017-01-12 22:45:49

    我也觉得有些繁琐,后来查了下,就是上面贴出来的那几个链接。

    如果你觉得烦,可以试着在Controller中加入这样的代码(我这是Lumen为例子,Laravel一样的):

        /**
         * Call controller as function.
         *
         * @param  Illuminate\Http\Request $request
         * @param  string $action
         *
         * @return string response body
         */
        public function __invoke(Request $request, $action = 'index')
        {
            // get action then check the method.
            $action = array_map('ucfirst', explode('-', $action));
            $method = strtolower($request->method()) . implode($action);
            // if method not existed, return 404.
            if (!method_exists($this, $method)) {
                abort(404);
            }
            // Call the method and return response.
            return app()->call([$this, $method], array_slice($request->route(), 1));
        }

    然后在路由中这样写:

    $app->addRoute(['GET', 'POST'], '/example[/{action}[/{args:.+}]]', 'ExampleController');

    这样也有问题,问题在于middleware先于404的判断。不过这也是解决问题的思路了。

    关于路由的效率,可以看看这:
    http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html

  • wh469012917
    2017-01-13 15:25:49

    1.Laravel也支持你所谓的隐式路由,只是文档没说,高版本的也不建议使用:

    Route::controller('admin',AdminController::class);

    这样定义即可实现

    2.隐式路由没办法实现Restful风格的API,试想我要获取一个用户:

    https://localhost/user/5

    这种路由通过隐式风格,你怎么实现?

    3.对于快速跳转到所对应的文件,完全可以在路由上注释,配合IDE即可搞定,效率绝对比[模块\控制器\方法]这种模式来的快

    4.当路由数量多了,比如有1000+以上的路由时,这时你就该开始考虑项目的架构了。比如拆分路由文件,大项目拆分小项目,远程RPC,而不是一直纠结在为何不支持隐式路由

    5.路由数量多影响效率,我一直都认为,牺牲一点性能来换来开发效率的提升,是一件很划算的事情。假如一直纠结在性能问题,为何不去使用原生PHP,或者Java、C++这一类静态语言呢?况且,因为路由数量多带来的性能损耗完全可以通过缓存和架构来解决。而且,那十几毫秒能给你带来什么?

    6.最后,并不是Laravel这样的开发方式有怎样的好处,只是你习惯了TP框架的模式,现在有更好的选择,只是你不愿意去接受罢了。

  • superwen
    2017-01-16 09:50:07

    https://laravelcollective.com/docs/5.3/annotations
    支持注释的路由服务。也不错。主要看你需要那种。

    显示路由更自由一些。而且根源上将来自于JAVA的ssh框架。

  • zhuzhichao Lalala Demacian !
    2017-01-17 17:14:30

    @MrJing 中间件写到 controller 的 __construct 里面,通过 request()->getfullurl() 的返回值判断使用什么中间件。这样,就可以按照自己的方式随意写的。只不过,和 laravel 提倡的方式越来越远而已了。:swimmer:

  • terranc
    2017-01-19 03:04:30

    @coodeer 看到书目这个例子,让我想起了写markdown,如果没有TOC,让我人工去维护一个目录,真是蛋疼的事情,分析内容自动给我建议的书目,加之可以手工补充,对于方便、快速开发来说肯定只有好处没有坏处。

    至于laravel没有,也就没有咯,这是作者的意图。我是折中意见的那一派,喜欢的话找第三方支持或者自己撸。

  • terranc
    2017-01-19 03:12:57

    @wh469012917
    1、还是要一个个controller去显性注明,且不见得controller要和router挂钩
    2、要隐形不代表不支持显性,也不是所有应用都对url有要求,做个后台管理系统与seo没半毛线关系,用querystring没什么不好的
    5、要说开发效率,肯定不多写代码,识别controller来得快吧,都要多写代码了,又谈什么开发效率,只不过站在方便长期管理的角度还差不多。
    6、『更好』都是相对的,并不是『最好』,一个框架无法知道应用于什么项目的开发,对于绝大多数中小应用(只有1-3个开发者),且对seo没太高要求的系统,隐形路由并没什么明显的痛点,感觉只不过laravel那帮人的不接受两种方式共存的洁癖导致的结果吧了。

  • Aolinver
    2017-01-20 17:24:52 ⋅ via iOS 客户端

    关于SEO的那个说法我想起几年前刚开始做SEO时候面试官问我SEO三大关键要素我没答上来(其实那会儿根本不懂SEO),然后面试官很简单的告诉我是title,keywords,description。以前搜索引擎是不接受带?的动态链接的,现在也已经接受了,隐式路由刚好是?开头,框架普遍的替代方法是pathinfo(就是tp那种长的看着头疼的链接形式),如果说链接影响SEO的话,显式路由更利于SEO(三大要素?那不是框架的事儿),刚开始接触会有点难以理解,就看接不接受了

  • mcxzyang a php developer
    2017-01-22 11:19:41

    觉得还是自己的理解和习惯问题吧,毕竟每个人的开发习惯和理解都不一样,我本人还是喜欢用显式路由,觉得清晰点。
    不过有人喜欢用隐式路由,那也是他们自己的事情,爱好不一样。
    最后说一句

    存在即合理

  • Will
    2017-01-24 15:45:37

    @wh469012917 我喜欢你的回答。。

  • wh469012917
    2017-01-25 12:06:12

    @terranc

    1. 高版本已经去掉了该功能,所以不做评价
    2. 花十分钟即可自己制定一个基于[模块\控制器\方法]的路由处理器出来,完全可以满足在后台开发的需求。(或许你会觉得,这本就该是框架应该做的,实际上并不是,因为隐式路由不符合Laravel的理念,所以不提供但是你可以自己实现。)
    3. 通过在路由上写注释,即可通过IDE快速跳转。(注意:是写注释,而不是代码。我一直都这样觉得,在代码中多写注释可能会多花时间,但长久来看是利大于弊的)
    4. Laravel专注于全栈开发框架,所以对于小项目我是建议你可以使用CI、ThinkPHP等这些轻量级的框架,毕竟杀鸡焉用牛刀。(注:没有贬低这些框架的意思,只是觉得在这种场合下,这些轻量级框架更加适合你们的业务)

    最后,存在即合理。不管你怎么嫌弃吐槽Laravel的路由模式,都无法影响他是最受欢迎的框架之一。

  • 279838089
    2017-02-06 18:01:25

    5.1文档还是支持隐性路由的
    关于速度问题,有路由缓存

  • 蘧伯玉
    2017-02-06 22:07:21

    回楼主:真的好。

  • jobsssss
    2017-02-11 12:34:31

    举例说明显示路由的好处:
    比如如下的一个导航页面:

    file
    比如我现在要改版页面,把加入我们这个导航下面的几个子导航,全部合并到关于美吉这个导航里面去。隐式的路由,在你把链接合并以后,你就需要把对应的controller里的方法也给合并到关于美吉的controller里,这么一点改动就要去改控制器。如果你不改控制器代码的仅仅把链接做一下迁移的话,为了保持导航定位,你可能就要在a标签里面去写一长串的if判断,用来确定导航的高亮定位。
    在显示的路由下,这都不是问题了,和并页面结构,就仅仅搞一下路由就可以了,控制器代码完全不用动

  • Boomdawn
    2017-02-11 15:50:53

    我支持up主的看法。
    刚刚接触laravel的时候我也疑惑这个问题。为什么php这么爽快的语言还要这么搞?路由直接对应类文件名和方法,文件做好分类什么的就很舒服了,整个项目一目了然,执行效率也很快。不过看到大家都说laravel这么做比较好,就没有深究就一直用下去了。
    做了几个小项目后,这种路由确实觉得很繁琐,而且性能不好。路由是有缓存,但是带参数的路由比如{id}/{name}什么的,应该是缓存不了的,都是需要正则去匹配的。当然就算全部缓存了,也没有直接根据路由载入文件快吧。中间件确实感觉很好用,但是隐式路由也未尝不能达到中间件的效果,cover的点不一样而已。
    最后感觉laravel这种类型的路由,就在必须遵守restful的时候最有用。当然我接触的项目,没有要求那么严格的。所以写api的时候,隐式路由对应文件也是很爽快的。而且开发api的话,还是选择lumen吧,laravel的响应相率太低了(现在很多时候会选择golang)。
    所以我也很期待laravel能有直接通透的隐式路由模式。

  • 海风蓝
    2017-02-17 10:37:33

    程序写了几万行以后特么根本不知道自己的程序对外暴露了多少接口出去。

  • 海风蓝
    2017-02-17 10:39:20

    显性路由就一目了然了,大项目维护的时候,就更方便,要不然找起来更麻烦,翻控制器,翻方法

  • aiddroid
    2017-02-17 14:02:03

    @wh469012917 嗯,存在即合理,其他框架普遍支持的隐式路由, laravel为什么不支持?

  • member
    2017-05-04 13:27:15

    在使用Laravel 5.1的时候 我写了一个Undefined的路由 也就是你说的隐式路由,

    Laravel 5.4时,路由支持了Cache,但是对于Closure的路由无法写入Cache

    毕竟Laravel中,最大的瓶颈在于路由的添加和匹配,这个我通过xDebug做过性能分析,如果路由多,会占用40%左右的执行时间很可怕。设置路由Cache之后,路由的执行效率提升很明显,在xDebug中看不到明显的延时。

    所以我选择删除了所有Closure路由,包括Undefined路由

    不过代码可以给您参照:

    use Illuminate\Support\Str;
    $hmvc_router = function ($ctrl = 'home', $action = 'index')
    {
        $route = Route::getCurrentRoute();
        $namespace = $route->getAction()['namespace'];
        $className = $namespace.'\\'.Str::studly($ctrl).'Controller';
        $action = Str::camel($action);
        (!class_exists($className) || !method_exists($className, $action)) && abort(404);
    
        $class = new ReflectionClass($className);
        $function = $class->getMethod($action); //ReflectionMethod
        $route_parameters = $route->resolveMethodDependencies(
            $route->parametersWithoutNulls(), $function
        );
        $parameters = $function->getParameters(); //ReflectionParameter 
    
        $_data = array();
        $request = app('request');
        for ($i = 0; $i < count($parameters); $i++)
        { 
            $key = $parameters[$i]->getName();
            if ( array_key_exists($i, $route_parameters) )
                $_data[] = $route_parameters[$i];
            else if ( array_key_exists($key, $route_parameters) )
                $_data[] = $route_parameters[$key];
            else if ($parameters[$i]->getClass()) //just in $route_parameters;
                $_data[] = app($parameters[$i]->getClass()->name);
            else //from $_GET/POST
            {
                $default = $parameters[$i]->isDefaultValueAvailable() ? $parameters[$i]->getDefaultValue() : NULL;
                $_data[] = $request->offsetExists($key) ? $request->input($key) : $default;
            }
        }
        $obj = app()->make($className);
        // Execute the action itself
        return $obj->callAction($action, $_data);
    };
    
    Route::group(['namespace' => 'Admin','prefix' => 'admin', 'middleware' => 'auth'], function($router) use($hmvc_router) {
    
        //admin目录下的其它路由需放置在本条前
        Route::any('{ctrl?}/{action?}', $hmvc_router);
    });
    
    //根目录的其它路由需放置在本条前
    Route::any('{ctrl?}/{action?}', $hmvc_router);

    最终 $_GET/$_POST可以附加到参数
    比如;http://..../member/some-function?id=123&name=567

    class MemberController {
      function someFunction(Request $r, $id, $name = '123') {
      }
    }
  • storefee
    2017-05-07 21:18:58

    @coodeer 确实如此,最近在看一些开源项目,里面基本是使用隐式路由Route::controller(),如果自己添加命名的显示路由,极易产生冲突,出现下面方法无法找到的情况:

    NotFoundHttpException in compiled.php line 8935:
    Controller method not found.
  • edwin404 http://tecmz.com
    2017-05-08 22:19:32

    比较有体会的一点,就是当你的项目重构、调整、拆分的时候,你会发现你会迅速知道所有正在使用路由,你会觉得可以hold住全部,不仅仅局限于回归测试。

暂无评论~~
  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
Ctrl+Enter