PHP MVC框架开发之简单路由(更)
3年前
更新说明
修复多处路由判断不严谨可能带来的错误
修复自定义路由文件命名规则和正常路由规则保持一致
何为自定义路由
自定义路由就是可以在任意位置创建一个路由文件进行路由映射(普通路由必须在规定的位置创建才能起效),当然自定义路由也有使用规则,不按照使用规则创建和使用也是无法起效的。
自定义路由规则
路由代码
<?php
/*
* Path_Info模式路由功能
* 作者:寻梦xunm
* 什么是PHP - Path_Info模式路由?请看以下URL地址:curl
* http://www.xxxx.com/index.php/index-showlist.html测试
* 从如上地址中咱们没法再像普通路由那样“直观”的看见C和M的存在,只能在index.php后面看到两个用-符号分割的“伪参数”,控制器(controllers)和方法(method)this
* Path_Info模式路由的关键就在于,咱们若是从URL中提炼出C和M的存在,而后返回给GET让路由器去进行解析url
* 就像上面的URL,当前访问的是Index控制器下的Showlist方法,并进行了伪后缀假装spa
* 话很少说,咱们马上来看下代码吧
* 测试URL以下:
* http://127.0.0.1/ 访问默认控制器和方法
* http://127.0.0.1/index.php/Ceshi-Showlist.html 访问Ceshi控制器下的Showlist方法
*/
//控制器基类
class url {
private $mokuai = 'app'; //默认模块(通俗就是文件夹)
private $classa = false; //默认引入模式(通俗就是控制器是类或者不是类的引入判断)
private $func = true; //函数控制器(不推荐使用该模式,有很大的安全风险)
private $controller = 'index'; //默认控制器(通俗就是文件)
private $method = 'home'; //默认方法(通俗就是要执行的方法,注意这里如果不是类引入方式可以不使用)
private $url_split = '-'; //路由分隔符
private $url_suffix = '.html'; //URL后缀名
private $url_xianzhi = array("index","Ceshi","she"); //控制器路由安全强制限制器,注意不使用请就一个空数组
private $url_yingshe = array("ap/she" => "ap/cc/cc"); //自定义路由
private $url_can = ""; //参数获取
private $url_canshu = "1"; //路由参数获取方式
private $config = ""; //配置文件信息
//默认读取配置文件中的参数
public function __construct(){
global $config;
$this->config = $config;
$this->mokuai = $config["url"]["mokuai"];
$this->classa = $config["url"]["classa"];
$this->func = $config["url"]["func"];
$this->controller = $config["url"]["controller"];
$this->method = $config["url"]["method"];
$this->url_split = $config["url"]["url_split"];
$this->url_suffix = $config["url"]["url_suffix"];
$this->url_xianzhi = $config["url"]["url_xianzhi"];
$this->url_canshu = $config["url"]["url_canshu"];
$this->url_yingshe = $config["url"]["url_yingshe"];
}
//路由实现
public function Curl() {
try {
//判断路由参数是否为空
if(trim(htmlspecialchars($_SERVER['PATH_INFO']),'/') != "" || preg_replace_callback("#(.*?).php#is",function ($matches){return '';},$urla) !=""){
//$_SERVER['PATH_INFO']URL地址中文件名后的路径信息, 很差理解, 来看看例子
//好比你如今的URL是 http://www.xxxx.com/index.php 那么你的$_SERVER['PATH_INFO']就是空的
//可是若是URL是 http://www.xxxx.com/index.php/abc/123
//如今的$_SERVER['PATH_INFO']的值将会是 index.php文件名称后的内容 /abc/123/
//判断路由参数获取方式
if ($this->url_canshu == "1"){
$path = trim(htmlspecialchars($_SERVER['PATH_INFO']),$this->url_split); //删除最末尾的分隔符
$path = str_replace($this->url_suffix,'',$path); //将右侧的伪后缀删除
$path = str_replace('?','',$path); //将左侧的/符号删除
$path = trim($path, '/');
}else{
//获取路由参数
$urla = htmlspecialchars($_SERVER['REQUEST_URI']);
// 通过正则表达替换路由参数中的相关字符串
$urla = preg_replace_callback("#(.*?).php#is",function ($matches){return '';},$urla);
//移除字符串两侧的字符
$urla = trim($urla, '/');
$urla = str_replace('?','',$urla); //将左侧的/符号删除
$path = str_replace($this->url_suffix,'',$urla); //将右侧的伪后缀删除
}
$paths = explode($this->url_split, $path); //将URL分割成数组
//赋值给一个变量用于其它方法使用
$this->url_can = $paths;
//判断变量是否存在
if (!empty($paths)){
//统计路由参数个数
$tongji = count($paths);
//通过对存储路由信息的变量进行统计,在进行判断存在多少数组避免使用时候数组不存在等问题
if ($tongji > 0){
$mokuai = $paths[0]; //得到模块名
}
if ($tongji > 1){
$Controller = $paths[1]; //得到C参数
}
if ($tongji > 2){
$Method = $paths[2]; //得到M参数
}
}
//检查变量是否为空
if (!empty($mokuai)){
//检查字符串是否为字母和数字,避免中文等导致编码乱码等问题
if (!ctype_alnum($mokuai)) {
echo exit("模块名称不符合规则");
}
}
//检查变量是否为空
if (!empty($Controller)){
//检查字符串是否为字母和数字,避免中文等导致编码乱码等问题
if (!ctype_alnum($Controller)) {
echo exit("控制器文件名不符合规则");
}
}
//检查变量是否为空
if (!empty($Method)){
//检查字符串是否为字母和数字,避免中文等导致编码乱码等问题
if (!ctype_alnum($Method)) {
echo exit("方法名不符合规则");
}
}
//检查数组是否为空
if (!empty($this->url_xianzhi)){
if (!in_array($Controller, $this->url_xianzhi)) {
echo exit("控制器不在路由安全数组中");
}
}
}
//若是参数模块存在则得到模块名,不存在则加载默认模块
$mokuai = !empty($mokuai) ? $mokuai : $this->mokuai; //得到当前模块名称
$Controller = !empty($Controller) ? $Controller : $this->controller; //得到当前控制器名称
//同上
$Method = !empty($Method) ? $Method : $this->method; //得到当前方法名称
//判断模块是否存在
if(!file_exists(APP_PATH.$mokuai)){
echo exit($mokuai."模块不存在");
}
if ($this->classa == true){
//判断变量是否为空
if (!empty($this->url_yingshe)){
//拼装名称
$yingshe =$mokuai."/".$Controller;
//判断数组中是否存在该键
if (array_key_exists($yingshe, $this->url_yingshe)) {
//拼装控制器文件路径
$lujing = APP_PATH.$this->url_yingshe[$yingshe].'.class.php';
}else{
//拼装控制器文件路径
$lujing = APP_PATH.$mokuai.'/controller/'.$Controller.'.class.php';
}
}else{
//拼装控制器文件路径
$lujing = APP_PATH.$mokuai.'/controller/'.$Controller.'.class.php';
}
//判断控制器文件是否存在
if(!file_exists($lujing)){
echo exit($Controller."控制器文件不存在");
}
var_dump($lujing);
include_once $lujing;
//自动加载控制器
$instance = new $Controller();
//判断类中是否定义该方法
if (!method_exists($instance,$Method)){
echo exit($Method."方法不存在");
}
//访问对应的操做方法
$instance->$Method();
}else{
//判断变量是否为空
if (!empty($this->url_yingshe)){
//拼装名称
$yingshe =$mokuai."/".$Controller;
//判断数组中是否存在该键
if (array_key_exists($yingshe, $this->url_yingshe)) {
//拼装控制器文件路径
$lujing = APP_PATH.$this->url_yingshe[$yingshe].'.php';
}else{
//拼装控制器文件路径
$lujing = APP_PATH.$mokuai.'/controller/'.$Controller.'.php';
}
}else{
//拼装控制器文件路径
$lujing = APP_PATH.$mokuai.'/controller/'.$Controller.'.php';
}
//判断控制器文件是否存在
if(!file_exists($lujing)){
echo exit($Controller."控制器文件不存在");
}
include_once $lujing;
if ($this->func == true){
echo $Method();
}
}
} catch (Exception $e) {
throw $e;
}
}
//路由参数获取
//$c 通过int 参数被声明为一个整数
public function C(int $c) {
//判断值是否符合条件(是否为数值)
if (!is_numeric($c)){
echo exit("传入的参数不符合规则,请使用数字");
}
//判断路由参数是否为空
if ($this->url_can == ""){
//写入默认路由参数数据
$url = array(
$this->config["url"]["mokuai"],
$this->config["url"]["controller"],
$this->config["url"]["method"],
);
}else{
$url = $this->url_can;
}
//返回值
return $url[$c];
}
}
配置路由文件
<?php
//全局核心配置文件
return $config = array(
//路由默认配置
"url" => array(
"mokuai" => 'app', //默认模块(通俗就是文件夹)
"classa" => true, //默认引入模式(通俗就是控制器是类或者不是类的引入判断)
"func" => true, //函数控制器(不推荐使用该模式,有很大的安全风险)
"controller" => 'index', //默认控制器(通俗就是文件)
"method" => 'index', //默认方法(通俗就是要执行的方法,注意这里如果不是类引入方式可以不使用)
"url_split" => '-', //路由分隔符
"url_suffix" => '.html', //URL后缀名
"url_xianzhi" => array("index","Ceshi","she"), //控制器路由安全强制限制器,注意不使用请就一个空数组
"url_yingshe" => array(), //自定义路由
"url_canshu" => "1", //路由参数获取方式
),
);
路由使用方法
<?php
//核心配置信息文件
$config = include APP_PATH.'xm/config.php';
//核心路由文件
include APP_PATH.'xm/url.php';
//实例化控制器
$u = new url();
//加载路由监控
$u ->curl();
//路由参数调用函数,通过此函数可以获取浏览器地址栏url参数信息和GET方式,传入的参数为整数。(http://www.xxx.cc/index.php/dd-123-444.html,例如我想获取123这个参数就可以这是使用函数 U('2')即可)
function U($c){
//引入外部变量
global $u;
//执行一个类方法并返回值
return $u ->c($c);
}
自行创建文件目录结构:app/controller,创建好后在controller目录下面创建控制器文件。
注意控制器文件创建规则
如果路由规则使用的类模式就需要按照指定方式创建控制器文件反之不是类模式创建控制器文件也需要按照指定方式创建
类:例如:index.class.php 这就是类控制器文件命名规则,其中index是可以自行随缘写,其它的不能改。
普:例如:index.php 这就是普通控制器文件命名规则,其中index是可以自行随缘写,其它的不能改。
改版路由类(测试代码,请勿使用)
<?php
//为了防止类冲突这里可以给类加一个命名空间(个人理解为别名)解决
namespace xm;
/*
* Path_Info模式路由功能
* 作者:寻梦xunm
*/
class url {
// 路由规则
private $rules = [
'get' => [],
'post' => [],
'put' => [],
'delete' => [],
'*' => []
];
private $mokuai = 'app'; //默认模块(通俗就是文件夹)
private $classa = false; //默认引入模式(通俗就是控制器是类或者不是类的引入判断)
private $func = false; //函数控制器(不推荐使用该模式,有很大的安全风险)
private $controller = 'index'; //默认控制器(通俗就是文件)
private $method = 'home'; //默认方法(通俗就是要执行的方法,注意这里如果不是类引入方式可以不使用)
private $url_split = '-'; //路由分隔符
private $url_suffix = '.html'; //URL后缀名
private $url_xianzhi = array(); //控制器路由安全强制限制器,注意不使用请就一个空数组
private $url_yingshe = array(); //自定义路由
private $url_can = null; //参数获取
private $url_canshu = "1"; //路由参数获取方式
private $config = ""; //配置文件信息
//默认读取配置文件中的参数
public function __construct(){
global $config;
$this->config = $config;
//这里可以通过入口文件定义一个常量进行默认模块
if (!empty(APP_APP)){
$this->mokuai = APP_APP;
}else{
$this->mokuai = $config["url"]["mokuai"];
}
$this->classa = $config["url"]["classa"];
$this->func = $config["url"]["func"];
$this->controller = $config["url"]["controller"];
$this->method = $config["url"]["method"];
$this->url_split = $config["url"]["url_split"];
$this->url_suffix = $config["url"]["url_suffix"];
$this->url_xianzhi = $config["url"]["url_xianzhi"];
$this->url_canshu = $config["url"]["url_canshu"];
$this->url_yingshe = $config["url"]["url_yingshe"];
}
/**
* 绑定路由
* @param string $rule 路由规则
* @param string $route 路由地址
* @param string $type 请求类型 (默认通用)
*/
public function rule($rule, $route = '', $type = '*'){
//把路由规则转换为小写
$rule = strtolower($rule);
//函数返回字符串的一部分并进行判断
$rule = '/'==substr($rule,0,1) ? $rule : '/'.$rule;
$route = strtolower($route);
$type = strtolower($type);
if ('*' == $type) {
$this->rules['*'][$rule] = $route;
$this->rules[$type][$rule] = $route;
}else{
$this->rules[$type][$rule] = $route;
/*$this->rules['*'][$rule] = $route;*/
}
}
/**
* 解析当前请求URL到自定义路由数组进行匹配
* @return array 路由信息数组
*/
public function ruleurl($URL_PATH){
//判断变量是否存在
if (!empty($URL_PATH)){
//替换相关分隔符使其符合路由匹配规则
$URL_PATH = str_replace($this->url_split,'/',$URL_PATH);
$URL_PATH = str_replace($this->url_suffix,'',$URL_PATH); //将右侧的伪后缀删除
//把请求类型转换成小写
$METHOD = strtolower($_SERVER['REQUEST_METHOD']);
//获取URL地址参数
//$URL_PATH = $this->path(true);
//匹配路由数组中是否有相关规则
if (isset($this->rules[$METHOD][$URL_PATH])) {
$analysis['rule'] = $URL_PATH;
$analysis['route'] = $this->rules[$METHOD][$URL_PATH];
}else{
// 自定义路由规则相关类型处理
foreach ($this->rules[$METHOD] as $rule => $route) {
if (substr($rule,-1)=='$' && substr_count($rule,'/')!=substr_count($URL_PATH,'/')) {
continue ;
}
$reg = '/^'.str_replace('/','\/',preg_replace('/:[a-z]+(?=\/|\$|$)/','\S+',$rule)).'/';
if (!preg_match($reg,$URL_PATH)) {
continue ;
}
$analysis['rule'] = preg_replace('/:[a-z]+(?=\/|\$|$)/','',$rule);
//$analysis['rule'] = str_replace('$','',str_replace(':','',$rule));
$analysis['route'] = $route;
break;
}
}
$analysis = $analysis ?? false;
//var_dump($analysis);
return $analysis;
}
return false;
}
//解析当前请求url自定义路由中不存在的未注册路由
public function puurl($path){
//获取URL地址参数
//$path = $this->path();
//判断变量是否存在
if (!empty($path)){
$path = str_replace($this->url_suffix,'',$path); //将右侧的伪后缀删除
$path = str_replace('?','',$path); //将左侧的/符号删除
$path = trim($path, '/');
$paths = explode($this->url_split, $path); //将URL分割成数组
//赋值给一个变量用于其它方法使用
$this->url_can = $paths;
$mokuai = $paths[0] ?? $this->mokuai;
//var_dump($paths);
$Controller = $paths[1] ?? $this->controller;
$Method = $paths[2] ?? $this->method;
$analysis['rule'] = '/'.$mokuai.'/'.$Controller.'/'.$Method;
$analysis['route'] = '/'.$mokuai.'/controller/'.$Controller;
}else{
$analysis['rule'] = '/'.$this->mokuai.'/'.$this->controller.'/'.$this->method;
$analysis['route'] = '/'.$this->mokuai.'/controller/'.$this->controller;
}
return $analysis;
}
//请求的url参数
public function path($a=false){
//判断路由参数获取方式
if ($this->url_canshu == "1"){
$path = trim(htmlspecialchars($_SERVER['PATH_INFO']),$this->url_split); //删除最末尾的分隔符
}else{
$path = trim(htmlspecialchars($_SERVER['REQUEST_URI']),$this->url_split); //删除最末尾的分隔符
//移除两侧的字符串“/”
$path = trim($path, '/');
}
// 替换掉参数中的*.php相关文件信息
$path = preg_replace_callback("#(.*?).php#is",function ($matches){return;},$path);
if ($a == true){
$path = $path ?? "/";
}
return $path;
}
//路由映射模式分为两种,第一种为类文件,第二种为普通文件
public function geturl($url){
var_dump($url);
//移除两侧的字符串“/”
$urla = trim($url['rule'], '/');
$urla = explode('/', $urla);
//将URL分割成数组
//进行参数过滤
$mokuai= A($urla[0] ?? $this->mokuai,'xssreplace');
$Controller= A($urla[1] ?? $this->controller,'xssreplace');
$Method= A($urla[2] ?? $this->method,'xssreplace');
//检查变量是否为空
if (!empty($mokuai)){
//检查字符串是否为字母和数字,避免中文等导致编码乱码等问题
if (!ctype_alnum($mokuai)) {
//写入日志
XLOG($mokuai."模块名称不符合规则");
echo exit($mokuai."模块名称不符合规则");
}
}
//检查变量是否为空
if (!empty($Controller)){
//检查字符串是否为字母和数字,避免中文等导致编码乱码等问题
if (!ctype_alnum($Controller)) {
//写入日志
XLOG($Controller."控制器文件名不符合规则");
echo exit($Controller."控制器文件名不符合规则");
}
}
//检查变量是否为空
if (!empty($Method)){
//检查字符串是否为字母和数字,避免中文等导致编码乱码等问题
if (!ctype_alnum($Method)) {
//写入日志
XLOG($Method."方法名不符合规则");
echo exit($Method."方法名不符合规则");
}
}
//检查数组是否为空
if (!empty($this->url_xianzhi)){
if (!in_array($Controller, $this->url_xianzhi)) {
echo exit($Controller."控制器不在路由安全数组中");
}
}
//判断模块目录是否存在
//注意这里如果使用了自定义路由(其中的模块参数如果在随意)可能会存在报错
//对自定义路由进行判断,如果存在自定义路由就关闭模块是否存在检查
//不太建议使用该模式,如果想要使用该模式请先注释掉大概259—263行的代码,并删除掉下大概245—255行相关注释
/*
if (empty($this->url_yingshe)){
if(!is_dir(APP_PATH.$mokuai)){
echo exit($mokuai."模块不存在");
}
}
*/
//推荐都进行模块检查,这样有利于发现问题
if(!is_dir(APP_PATH.$mokuai)){
//写入日志
XLOG($mokuai."模块不存在");
echo exit($mokuai."模块不存在");
}
if ($this->classa == true){
//拼装控制器文件路径
$lujing = APP_PATH.$url['route'].'.class.php';
//判断控制器文件是否存在
if(!file_exists($lujing)){
//写入日志
XLOG($Controller."控制器文件不存在");
echo exit($Controller."控制器文件不存在");
}
include_once $lujing;
//自动加载控制器
$instance = new $Controller();
//判断类中是否定义该方法
if (!method_exists($instance,$Method)){
//写入日志
XLOG($Method."方法不存在");
echo exit($Method."方法不存在");
}
//访问对应的操做方法
$instance->$Method();
}else{
//拼装控制器文件路径
$lujing = APP_PATH.$url['route'].'.php';
//判断控制器文件是否存在
if(!file_exists($lujing)){
//写入日志
XLOG($Controller."控制器文件不存在");
echo exit($Controller."控制器文件不存在");
}
include_once $lujing;
if ($this->func == true){
echo $Method();
}
}
}
//启动路由解析等相关服务
public function run (){
//获取URL地址参数
$PATH = $this->path();
//先进行自定义路由匹配操作,如果没有在进行未注册路由匹配
$url = $this->ruleurl($PATH);
if ($url == false){
//进行未注册路由匹配操作
$url = $this->puurl($PATH);
if ($url == false){
echo exit("无法匹配到路由");
}
}
//路由映射
echo $this->getURL($url);
}
//路由参数获取
//$c 通过int 参数被声明为一个整数
public function C(int $c) {
//判断值是否符合条件(是否为数值)
if (!is_numeric($c)){
echo exit("传入的参数不符合规则,请使用数字");
}
//这段代码大概和下面注释代码一样
//使用的NULL 合并运算符
$url = $this->url_can ?? array(
$this->mokuai,
$this->controller,
$this->method,
);
if (array_key_exists($c, $url)){
$url = $url[$c];
}else{
$url = '';
}
//返回值
return $url;
}
}