thinkphp5 URL和路由的功能详解与实例


Posted in PHP onDecember 26, 2017

前面的话

本文将详细介绍thinkphp5URL和路由

URL访问

ThinkPHP采用单一入口模式访问应用,对应用的所有请求都定向到应用的入口文件,系统会从URL参数中解析当前请求的模块、控制器和操作,下面是一个标准的URL访问格式:

http://domainName/index.php/模块/控制器/操作

其中index.php就称之为应用的入口文件(注意入口文件可以被隐藏,后面会提到)

模块在ThinkPHP中的概念其实就是应用目录下面的子目录,而官方的规范是目录名小写,因此模块全部采用小写命名,无论URL是否开启大小写转换,模块名都会强制小写

应用的index模块的Index控制器定义如下:

<?php
namespace app\index\controller;
class Index
{
  public function index()
  {
    return 'index';
  }
  public function hello($name = 'World')
  {
    return 'Hello,' . $name . '!';
  }
}

如果直接访问入口文件的话,由于URL中没有模块、控制器和操作,因此系统会访问默认模块(index)下面的默认控制器(Index)的默认操作(index),因此下面的访问是等效的:

http://tp5.com/index.php
http://tp5.com/index.php/index/index/index

如果要访问控制器的hello方法,则需要使用完整的URL地址

http://tp5.com/index.php/index/index/hello/name/thinkphp

访问URL地址后页面输出结果为:

Hello,thinkphp!

由于name参数为可选参数,因此也可以使用

http://tp5.com/index.php/index/index/hello

访问URL地址后页面输出结果为:

Hello,World!

默认情况下,URL地址中的控制器和操作名是不区分大小写的,因此下面的访问其实是等效的:

http://tp5.com/index.php/index/Index/Index
http://tp5.com/index.php/index/INDEX/INDEX

如果控制器是驼峰的,例如定义一个HelloWorld控制器(application/index/controller/HelloWorld.php): 

<?php
namespace app\index\controller;
class HelloWorld
{
  public function index($name = 'World')
  {
    return 'Hello,' . $name . '!';
  }
}

正确的URL访问地址(该地址可以使用url方法生成)应该是

http://tp5.com/index.php/index/hello_world/index

系统会自动定位到HelloWorld控制器类去操作

如果使用

http://tp5.com/index.php/index/HelloWorld/index

将会报错,并提示Helloworld控制器类不存在

如果希望严格区分大小写访问(这样就可以支持驼峰法进行控制器访问),可以在应用配置文件中设置:

// 关闭URL自动转换(支持驼峰访问控制器)
'url_convert' => false,

关闭URL自动转换之后,必须使用下面的URL地址访问(控制器名称必须严格使用控制器类的名称,不包含控制器后缀):

http://tp5.com/index.php/index/Index/index
http://tp5.com/index.php/index/HelloWorld/index

如果服务器环境不支持pathinfo方式的URL访问,可以使用兼容方式,例如:

http://tp5.com/index.php?s=/index/Index/index

其中变量s的名称的可以配置的

5.0不再支持普通的URL访问方式,所以下面的访问是无效的,你会发现无论输入什么,访问的都是默认的控制器和操作

http://tp5.com/index.php?m=index&c=Index&a=hello

参数传入

通过操作方法的参数绑定功能,可以实现自动获取URL的参数,仍然以上面的控制器为例,控制器代码如下:

<?php
namespace app\index\controller;
class Index
{
  public function index()
  {
    return 'index';
  }
  public function hello($name = 'World')
  {
    return 'Hello,' . $name . '!';
  }
}

当我们访问

http://tp5.com/index.php/index/index/hello

就是访问app\index\controller\Index控制器类的hello方法,因为没有传入任何参数,name参数就使用默认值World。如果传入name参数,则使用:

http://tp5.com/index.php/index/index/hello/name/thinkphp

页面输出结果为:

Hello,thinkphp!

现在给hello方法增加第二个参数:

public function hello($name = 'World', $city = '')
  {
    return 'Hello,' . $name . '! You come from ' . $city . '.';
  }

访问地址为http://tp5.com/index.php/index/index/hello/name/thinkphp/city/shanghai

页面输出结果为:

Hello,thinkphp! You come from shanghai.

可以看到,hello方法会自动获取URL地址中的同名参数值作为方法的参数值,而且这个参数的传入顺序不受URL参数顺序的影响,例如下面的URL地址输出的结果和上面是一样的:

http://tp5.com/index.php/index/index/hello/city/shanghai/name/thinkphp

或者使用http://tp5.com/index.php/index/index/hello?city=shanghai&name=thinkphp

还可以进一步对URL地址做简化,前提就是我们必须明确参数的顺序代表的变量,我们更改下URL参数的获取方式,把应用配置文件中的url_param_type参数的值修改如下:

// 按照参数顺序获取
'url_param_type' => 1,

现在,URL的参数传值方式就变成了严格按照操作方法的变量定义顺序来传值了,也就是说我们必须使用下面的URL地址访问才能正确传入name和city参数到hello方法:http://tp5.com/index.php/index/index/hello/thinkphp/shanghai

页面输出结果为:

Hello,thinkphp! You come from shanghai.

如果改变参数顺序为http://tp5.com/index.php/index/index/hello/shanghai/thinkphp

页面输出结果为:

Hello,shanghai! You come from thinkphp.

显然不是我们预期的结果。

同样,我们试图通过http://tp5.com/index.php/index/index/hello/name/thinkphp/city/shanghai

访问也不会得到正确的结果

[注意]按顺序绑定参数的话,操作方法的参数只能使用URL pathinfo变量,而不能使用get或者post变量

隐藏入口

可以去掉URL地址里面的入口文件index.php,但是需要额外配置WEB服务器的重写规则。

以Apache为例,需要在入口文件的同级添加.htaccess文件(官方默认自带了该文件),内容如下

<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>

如果用的phpstudy,规则如下:

<IfModule mod_rewrite.c> 
Options +FollowSymlinks -Multiviews 
RewriteEngine on 
RewriteCond %{REQUEST_FILENAME} !-d 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteRule ^(.*)$ index.php [L,E=PATH_INFO:$1] 
</IfModule>

接下来就可以使用下面的URL地址访问了

http://tp5.com/index/index/index
http://tp5.com/index/index/hello

如果使用的apache版本使用上面的方式无法正常隐藏index.php,可以尝试使用下面的方式配置.htaccess文件:

<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L]
</IfModule>

如果是Nginx环境的话,可以在Nginx.conf中添加:

location / { // …..省略部分代码
  if (!-e $request_filename) {
    rewrite ^(.*)$ /index.php?s=/$1 last;
    break;
  }
}

定义路由

URL地址里面的index模块怎么才能省略呢,默认的URL地址显得有点长,下面就来说说如何通过路由简化URL访问。

我们在路由定义文件(application/route.php)里面添加一些路由规则,如下:

return [
  // 添加路由规则 路由到 index控制器的hello操作方法
  'hello/:name' => 'index/index/hello',
];

该路由规则表示所有hello开头的并且带参数的访问都会路由到index控制器的hello操作方法。

路由之前的URL访问地址为:http://tp5.com/index/index/hello/name/thinkphp

定义路由后就只能访问下面的URL地址http://tp5.com/hello/thinkphp

[注意]定义路由规则后,原来的URL地址将会失效,变成非法请求。

但这里有一个小问题,如果我们只是访问http://tp5.com/hello

将发生错误

事实上这是由于路由没有正确匹配到,我们修改路由规则如下:

return [
  // 路由参数name为可选
  'hello/[:name]' => 'index/hello',
];

使用[]把路由规则中的变量包起来,就表示该变量为可选,接下来就可以正常访问了http://tp5.com/hello

当name参数没有传入值的时候,hello方法的name参数有默认值World,所以输出的内容为 Hello,World!

除了路由配置文件中定义之外,还可以采用动态定义路由规则的方式定义,例如在路由配置文件(application/route.php)的开头直接添加下面的方法:

use think\Route;
Route::rule('hello/:name', 'index/hello');

完成的效果和使用配置方式定义是一样的。

无论是配置方式还是通过Route类的方法定义路由,都统一放到路由配置文件application/route.php文件中

[注意]路由配置不支持在模块配置文件中设置

【完整匹配】

前面定义的路由是只要以hello开头就能进行匹配,如果需要完整匹配,可以使用下面的定义:

return [
  // 路由参数name为可选
  'hello/[:name]$' => 'index/hello',
];

当路由规则以$结尾的时候就表示当前路由规则需要完整匹配。

当我们访问下面的URL地址的时候:

http://tp5.com/hello // 正确匹配
http://tp5.com/hello/thinkphp // 正确匹配
http://tp5.com/hello/thinkphp/val/value // 不会匹配

【闭包定义】

还支持通过定义闭包为某些特殊的场景定义路由规则,例如:

return [
  // 定义闭包
  'hello/[:name]' => function ($name) {
    return 'Hello,' . $name . '!';
  },
];

或者

use think\Route;
Route::rule('hello/:name', function ($name) {
  return 'Hello,' . $name . '!';
});

[注意]闭包函数的参数就是路由规则中定义的变量

因此,当访问下面的URL地址:http://tp5.com/hello/thinkphp

会输出

Hello,thinkphp!

【设置URL分隔符】

如果需要改变URL地址中的pathinfo参数分隔符,只需要在应用配置文件(application/config.php)中设置:

// 设置pathinfo分隔符
'pathinfo_depr'     => '-',

路由规则定义无需做任何改变,我们就可以访问下面的地址:http://tp5.com/hello-thinkphp

【路由参数】

还可以约束路由规则的请求类型或者URL后缀之类的条件,例如:

return [
  // 定义路由的请求类型和后缀
  'hello/[:name]' => ['index/hello', ['method' => 'get', 'ext' => 'html']],
];

上面定义的路由规则限制了必须是get请求,而且后缀必须是html的,所以下面的访问地址:

http://tp5.com/hello // 无效
http://tp5.com/hello.html // 有效
http://tp5.com/hello/thinkphp // 无效
http://tp5.com/hello/thinkphp.html // 有效

【变量规则】

接下来,尝试一些复杂的路由规则定义满足不同的路由变量。在此之前,首先增加一个控制器类如下:

<?php
namespace app\index\controller;
class Blog
{
  public function get($id)
  {
    return '查看id=' . $id . '的内容';
  }
  public function read($name)
  {
    return '查看name=' . $name . '的内容';
  }
  public function archive($year, $month)
  {
    return '查看' . $year . '/' . $month . '的归档内容';
  }
}

添加如下路由规则:

return [
  'blog/:year/:month' => ['blog/archive', ['method' => 'get'], ['year' => '\d{4}', 'month' => '\d{2}']],
  'blog/:id'     => ['blog/get', ['method' => 'get'], ['id' => '\d+']],
  'blog/:name'    => ['blog/read', ['method' => 'get'], ['name' => '\w+']],
];

在上面的路由规则中,我们对变量进行的规则约束,变量规则使用正则表达式进行定义。

我们看下几种URL访问的情况

// 访问id为5的内容
http://tp5.com/blog/5
// 访问name为thinkphp的内容
http://tp5.com/blog/thinkphp
// 访问2015年5月的归档内容
http://tp5.com/blog/2015/05

 【路由分组】

上面的三个路由规则由于都是blog打头,所以我们可以做如下的简化:

return [
  '[blog]' => [
    ':year/:month' => ['blog/archive', ['method' => 'get'], ['year' => '\d{4}', 'month' => '\d{2}']],  
    ':id'     => ['blog/get', ['method' => 'get'], ['id' => '\d+']],
    ':name'    => ['blog/read', ['method' => 'get'], ['name' => '\w+']],
  ],
];

对于这种定义方式,我们称之为路由分组,路由分组一定程度上可以提高路由检测的效率

【复杂路由】

有时候,还需要对URL做一些特殊的定制,例如如果要同时支持下面的访问地址

http://tp5.com/blog/thinkphp
http://tp5.com/blog-2015-05

我们只要稍微改变路由定义规则即可:

return [
  'blog/:id'      => ['blog/get', ['method' => 'get'], ['id' => '\d+']],
  'blog/:name'     => ['blog/read', ['method' => 'get'], ['name' => '\w+']],
  'blog-<year>-<month>' => ['blog/archive', ['method' => 'get'], ['year' => '\d{4}', 'month' => '\d{2}']],
];

对 blog-<year>-<month> 这样的非正常规范,我们需要使用<变量名>这样的变量定义方式,而不是 :变量名方式。

简单起见,我们还可以把变量规则统一定义,例如:

return [
  // 全局变量规则定义
  '__pattern__'     => [
    'name' => '\w+',
    'id'  => '\d+',
    'year' => '\d{4}',
    'month' => '\d{2}',
  ],
  // 路由规则定义
  'blog/:id'      => 'blog/get',
  'blog/:name'     => 'blog/read',
  'blog-<year>-<month>' => 'blog/archive',
];

在__pattern__中定义的变量规则我们称之为全局变量规则,在路由规则里面定义的变量规则我们称之为局部变量规则,如果一个变量同时定义了全局规则和局部规则的话,当前的局部规则会覆盖全局规则的,例如:

return [
  // 全局变量规则
  '__pattern__'     => [
    'name' => '\w+',
    'id'  => '\d+',
    'year' => '\d{4}',
    'month' => '\d{2}',
  ],

  'blog/:id'      => 'blog/get',
  // 定义了局部变量规则
  'blog/:name'     => ['blog/read', ['method' => 'get'], ['name' => '\w{5,}']],
  'blog-<year>-<month>' => 'blog/archive',
];

URL生成

定义路由规则之后,可以通过Url类来方便的生成实际的URL地址(路由地址),针对上面的路由规则,我们可以用下面的方式生成URL地址。

// 输出 blog/thinkphp
Url::build('blog/read', 'name=thinkphp');
Url::build('blog/read', ['name' => 'thinkphp']);
// 输出 blog/5
Url::build('blog/get', 'id=5');
Url::build('blog/get', ['id' => 5]);
// 输出 blog/2015/05
Url::build('blog/archive', 'year=2015&month=05');
Url::build('blog/archive', ['year' => '2015', 'month' => '05']);

[注意]build方法的第一个参数使用路由定义中的完整路由地址

还可以使用系统提供的助手函数url来简化

url('blog/read', 'name=thinkphp');
// 等效于
Url::build('blog/read', 'name=thinkphp');

通常在模板文件中输出的话,可以使用助手函数,例如:

{:url('blog/read', 'name=thinkphp')}

如果我们的路由规则发生调整,生成的URL地址会自动变化

如果你配置了url_html_suffix参数的话,生成的URL地址会带上后缀,例如:

'url_html_suffix'  => 'html',

那么生成的URL地址 类似

blog/thinkphp.html 
blog/2015/05.html

如果你的URL地址全部采用路由方式定义,也可以直接使用路由规则来定义URL生成,例如:

url('/blog/thinkphp');
Url::build('/blog/8');
Url::build('/blog/archive/2015/05');

生成方法的第一个参数一定要和路由定义的路由地址保持一致,如果你的路由地址比较特殊,例如使用闭包定义的话,则需要手动给路由指定标识,例如: 

// 添加hello路由标识
Route::rule(['hello','hello/:name'], function($name){
  return 'Hello,'.$name;
});
// 根据路由标识快速生成URL
Url::build('hello', 'name=thinkphp');
// 或者使用
Url::build('hello', ['name' => 'thinkphp']);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
PHP Session_Regenerate_ID函数双释放内存破坏漏洞
Jan 27 PHP
七款最流行的PHP本地服务器分享
Feb 19 PHP
php多文件上传下载示例分享
Feb 20 PHP
php获取文件大小的方法
Feb 26 PHP
php json转换成数组形式代码分享
Nov 10 PHP
浅析php工厂模式
Nov 25 PHP
Laravel 5框架学习之Blade 简介
Apr 08 PHP
基于PHP实现简单的随机抽奖小程序
Jan 05 PHP
PHP中的数组处理函数实例总结
Jan 09 PHP
PHP pear安装配置教程
May 14 PHP
PHP实现将MySQL重复ID二维数组重组为三维数组的方法
Aug 01 PHP
php遍历替换目录下文件指定内容的方法
Nov 10 PHP
php-msf源码详解
Dec 25 #PHP
关于 Laravel Redis 多个进程同时取队列问题详解
Dec 25 #PHP
源码分析 Laravel 重复执行同一个队列任务的原因
Dec 25 #PHP
浅析PHP中的闭包和匿名函数
Dec 25 #PHP
thinkphp5 加载静态资源路径与常量的方法
Dec 24 #PHP
PHP读取并输出XML文件数据的简单实现方法
Dec 22 #PHP
ajax+php实现无刷新验证手机号的实例
Dec 22 #PHP
You might like
phpmyadmin的#1251问题
2006/11/25 PHP
用PHP实现图象锐化代码
2007/06/14 PHP
PHP上传图片进行等比缩放可增加水印功能
2014/01/13 PHP
网站防止被刷票的一些思路与方法
2015/01/08 PHP
thinkphp中U方法按路由规则生成url的方法
2018/03/12 PHP
Laravel框架之解决前端显示图片问题
2019/10/24 PHP
Prototype 1.5.0_rc1 及 Prototype 1.5.0 Pre0小抄本
2006/09/22 Javascript
一些mootools的学习资源
2010/02/07 Javascript
预加载css或javascript的js代码
2010/04/23 Javascript
jquery 插件学习(三)
2012/08/06 Javascript
Nodejs进程管理模块forever详解
2014/06/01 NodeJs
简单实现jQuery上传图片显示预览功能
2020/06/29 jQuery
详解vue的diff算法原理
2018/05/20 Javascript
在Web关闭页面时发送Ajax请求的实现方法
2019/03/07 Javascript
Vue基础配置讲解
2019/11/29 Javascript
vue-router之解决addRoutes使用遇到的坑
2020/07/19 Javascript
Python中字符编码简介、方法及使用建议
2015/01/08 Python
Python之父谈Python的未来形式
2016/07/01 Python
Python中跳台阶、变态跳台阶与矩形覆盖问题的解决方法
2018/05/19 Python
Python基于pandas实现json格式转换成dataframe的方法
2018/06/22 Python
使用python画社交网络图实例代码
2019/07/10 Python
python爬虫 爬取58同城上所有城市的租房信息详解
2019/07/30 Python
基于python的docx模块处理word和WPS的docx格式文件方式
2020/02/13 Python
k-means 聚类算法与Python实现代码
2020/06/01 Python
记一次django内存异常排查及解决方法
2020/08/07 Python
 Alo Yoga官网:购买瑜伽服装
2018/06/17 全球购物
台湾全方位线上课程与职能学习平台:TibaMe
2019/12/04 全球购物
sort命令的作用和用法
2013/08/25 面试题
造价工程师个人求职信
2013/09/21 职场文书
网上卖盒饭创业计划书
2014/01/26 职场文书
培训协议书范本
2014/04/22 职场文书
2015年学校安全工作总结
2015/04/22 职场文书
青年联谊会致辞
2015/07/31 职场文书
2015年小学语文教师工作总结
2015/10/23 职场文书
安全学习心得体会范文
2016/01/18 职场文书
Go语言入门exec的基本使用
2022/05/20 Golang