在自己的 php 框架中管理 URL 路由


Manage URL routes in own php framework

我正在创建一个PHP框架,我有一些疑问...

框架以这种方式获取 url: http:/web.com/site/index

它将第一个参数加载控制器(site(,然后加载特定动作(index(。

如果您已在基本 URL 中安装了框架,则可以正常工作,但是如果您将其安装在如下所示的子文件夹中: http://web.com/mysubfolder/controller/action

我的脚本将其解析为控制器 = mysubfolder 和操作 = controller

如果有更多的子文件夹,结果将是最糟糕的。

这是我的路线代码:

Class Route
{
    private $_htaccess = TRUE;
    private $_suffix = ".jsp";
    public function params()
    {
        $url='';
        //nombre del directorio actual del script ejecutandose.
        //basename(dirname($_SERVER['SCRIPT_FILENAME']));
        if($this->_htaccess !== FALSE):
            //no está funcionando bien si está en un subdirectorio web, por ej stynat.dyndns.org/subdir/
            // muestra el "subdir" como primer parámetro
            $url = $_SERVER['REQUEST_URI'];
            if(isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])):
                $url = str_replace("?" . $_SERVER['QUERY_STRING'], '',$url);
            endif;
        else:
            if(isset($_SERVER['PATH_INFO'])):
                $url = $_SERVER['PATH_INFO'];
            endif;
        endif;
        $url = explode('/',preg_replace('/^('/)/','',$url));
        var_dump($url);
        var_dump($_GET);
    }
}

感谢您提供的任何帮助。

您缺少基本路径。路由脚本现在必须在检测到模式或路由时从何处开始。

伪代码:

 //set the base URI
$base_uri = '/base';
//remove the base URI from the original uri. You can also REGEX or string match against it if you want
$route_uri = str_replace($base_uri,'',$uri);
//perform route matching $route_uri, in your code a simple explode
$url = explode('/',preg_replace('/^('/)/','',$route_uri));

您可以在有或没有RewriteBase的情况下使用它,只要它们使用相同的工具 - index.php。

此外,您可以使用正则表达式函数(如preg_match和preg_match_all(改进路由匹配过程。它们允许您定义要匹配的模式和匹配字符串数组的结果 - 请参阅 http://php.net/manual/en/function.preg-match.php。

即使您正在创建自己的框架,也没有理由不重用健壮、经过良好测试和记录的组件,例如此路由组件。

只需使用 Composer,它已成为 PHP 中依赖管理的标准,你就会没事的。向堆栈添加任意数量的组件。

在这里,您有一个关于如何制作自己的框架的必读指南。

是的,我想我知道如何解决这个问题。

(免责声明:我知道你知道大部分,但我要为那些可能不知道一些陷阱的人解释一切(

使用 PATH_INFO 和 .htaccess

php 中有一个技巧,如果你去这样的路径:

http://web.com/mysubfolder/index.php/controller/action

您将在$_SERVER['PATH_INFO']变量中获得"/控制器/操作">

现在你需要做的是获取一个.htaccess文件(或等效文件(,让它告诉你的php脚本当前的文件夹深度。

为此,请将 .htaccess 文件放入"mysubfolder"中

mysubfolder
    .htaccess
    index.php

.htaccess 应包含:

RewriteEngine on
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# otherwise forward it to index.php
RewriteRule (.*) index.php/$1

(我用了 yii 框架手册作为参考,我也建议使用 html5 样板(

基本上,您将它设置为将所有内容重定向到索引.php在URL中的特定点。

现在,如果您访问:http://web.com/mysubfolder/index.php/controller/action

现在您可以从$_SERVER['PATH_INFO']获得正确的路径"/控制器/操作"

除非您访问 http://web.com/mysubfolder/,它不会有任何价值,因为 .htaccess 文件将忽略重写,因为 http://web.com/mysubfolder/路径请求 mysubfolder/index.php该文件夹/索引实际上存在并被拒绝,谢谢你RewriteCond %{REQUEST_FILENAME} !-f

ifsetor 函数

为此,您可以使用这个名为ifsetor的超级方便的功能(我不记得我从哪里得到的(

function ifsetor(&$variable, $default = null) {
    return isset($variable)? $variable: $default;
}

它的作用是对可能存在也可能不存在的变量的引用,如果不存在,则提供默认值,而不会引发任何通知或错误

现在您可以使用它像这样安全地获取PATH_INFO变量

在您的索引中.php

function ifsetor(&$variable, $default = null) {
    return isset($variable)? $variable: $default;
}
$path = ifsetor($_SERVER['PATH_INFO'],'/');
var_dump($path);

php 5.4 也有这种新的较短的三元格式,如果你不关心通知,你可以使用它(我愿意(

$path = $_SERVER['PATH_INFO']?:'/';

处理QUERY_STRING

现在从技术上讲,您不会获得URL,它只是其路径部分,不会包含query_string,例如在访问时

http://web.com/mysubfolder/index.php/test?param=val

您只会在$path变量中获得"/test",要使用$_SERVER['QUERY_STRING']变量获取查询字符串

索引.php

function ifsetor(&$variable, $default = null) {
    return isset($variable)? $variable: $default;
}
$path = ifsetor($_SERVER['PATH_INFO'],'/');
$fullpath = ($_SERVER['QUERY_STRING'])? $path.'?'.$_SERVER['QUERY_STRING']:$path;
var_dump($fullpath);

但这可能取决于您的需求

$_SERVER["QUERY_STRING"] 与 $_GET

另请记住,$_SERVER['QUERY_STRING'] 变量与 $_GET$_REQUEST 变量不同,因为它保留了查询字符串中的重复参数,例如:

访问此页面

http://web.com/mysubfolder/controller/action?foo=1&foo=2&foo=3

如果要给你一个看起来像的$_SERVER['QUERY_STRING']

?foo=1&foo=2&foo=3

虽然$_GET变量将是这样的数组:

array(
    'foo'=>'3'
);

如果您没有 .htaccess

您可以尝试利用SCRIPT_NAME来发挥自己的优势

list($url) = explode('?',$_SERVER['REQUEST_URI']);
list($basepath) = explode('index.php',$_SERVER['SCRIPT_NAME']);
$url = substr($url,strlen($basepath));

如果你喜欢炸毁像我这样的东西:)

您的案例

Class Route
{
private $_htaccess = TRUE;
private $_suffix = ".jsp";
public function params()
{
    $url='';
    //nombre del directorio actual del script ejecutandose.
    //basename(dirname($_SERVER['SCRIPT_FILENAME']));
    if($this->_htaccess !== FALSE):
        //no está funcionando bien si está en un subdirectorio web, por ej stynat.dyndns.org/subdir/
        // muestra el "subdir" como primer parámetro
        list($url) = explode('?',$_SERVER['REQUEST_URI']);
        $basepath = dirname($_SERVER['SCRIPT_NAME']);
        $basepath = ($basepath==='/')? $basepath: $basepath.'/';
        $url = substr($url,strlen($basepath));
    else:
        if(isset($_SERVER['PATH_INFO'])):
            $url = $_SERVER['PATH_INFO'];
            $url = preg_replace('|^/|','',$url);
        endif;
    endif;
    $url = explode('/',$url);
    var_dump($url);
    var_dump($_GET);

}
}

我希望这有帮助

附言抱歉回复晚了:(

在某些时候,您必须检查 $_SERVER ['HTTP_HOST'] 和由程序员/用户定义的配置变量,该变量指示应用程序所在的子文件夹,并从 URL 的其余部分中删除您不感兴趣的部分。

您可以在代码点火器表单上查看此论坛帖子以获取一些提示。

CodeIgniter 使用另一种不同的方式在内部路由控制器/方法。按$_SERVER['PATH_INFO']值进行路由。您可以像这样使用网址:myapp.com/index.php/controller/method .

为了避免显示索引.php在 uri 上你必须依赖 Apache 重写规则,但即使这样,我认为 CI 是一个不错的解决方案,一旦你有了索引文件的位置,你可以避免解析 URL 的所有麻烦。

如果我正确理解你所追求的是什么,那么一种解决方案可能是继续做你正在做的事情,但也要获取主路由脚本的路径(例如使用 realpath(( (。

如果最后一个文件夹(或之前的文件夹等(与起始 URL 项(或其他部分(匹配,则忽略它并转到下一个。

只是我的2美分:-(。

在应用程序配置脚本中放置一个变量,该变量将设置为应用程序运行的路径。

另一种方法是动态设置该路径。

零件之前

$url = explode('/',preg_replace('/^('/)/','',$url));

使用预定义的应用程序路径从$url字符串中删除位置(子文件夹(路径。

这就是

我实现加载器的方式.php

   <?php
/*@author arun ak
auto load controller class and function from url*/
class loader
{
    private $request;
    private $className;
    private $funcName;
    function __construct($folder    = array())
    {   
        $parse_res  = parse_url($this->createUrl());
        if(!empty($folder) && trim($folder['path'],DIRECTORY_SEPARATOR)!='')
         {
            $temp_path  = explode(DIRECTORY_SEPARATOR,trim($parse_res['path'],DIRECTORY_SEPARATOR));
            $folder_path    = explode(DIRECTORY_SEPARATOR,trim($folder['path'],DIRECTORY_SEPARATOR));
            $temp_path      = array_diff($temp_path,$folder_path);
                if(empty($temp_path))
                {
                    $temp_path =    '';
                }
         }else
         {
            if(trim($parse_res['path'],DIRECTORY_SEPARATOR)!='')
            {
             $temp_path = explode(DIRECTORY_SEPARATOR,trim($parse_res['path'],DIRECTORY_SEPARATOR));
            }
                 else
             $temp_path ='';
         }
        if(is_array($temp_path))
        {   
            if(count($temp_path) ==1)
            {
                array_push($temp_path,'index');
            }
            foreach($temp_path as $pathname)
            {   
                $this->request .= $pathname.':';
            }
        }
        else $this->request = 'index'.':'.'index';
    }
 private function createUrl()
 {  
    $pageURL  = (@$_SERVER["HTTPS"] == "on") ? "https://" : "http://";
    $pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
    return $pageURL;
 }
 public function autolLoad()
 {
    if($this->request) 
    {   
        $parsedPath = explode(':',rtrim($this->request,':'));
        if(is_file(APPLICATION_PATH.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.$parsedPath[0].'_controller'.'.php'))
        {
            include_once(APPLICATION_PATH.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.$parsedPath[0].'_controller'.'.php');
            if(class_exists($parsedPath[0].'_controller'))
            {
                $class  = $parsedPath[0].'_controller';
                $obj    = new $class();
                //$config       = new config('localhost','Winkstore','nCdyQyEdqDbBFpay','mawinkcms');
                //$connect  =  connectdb::getinstance();
                //$connect->setConfig($config);
                //$connection_obj = $connect->connect();
                //$db               = $connect->getconnection();//mysql link object
                //$obj->setDb($db);
                $method = $parsedPath[1];
                if(method_exists ($obj ,$parsedPath[1] ))
                {
                    $obj->$method();
                }else die('class method '.$method.' not defined');
            }else die('class '.$parsedPath[0]. ' has not been defined' );
        } else die('controller not found plz define one b4 u proceed'.APPLICATION_PATH.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.$parsedPath[0].'.php');
     }else{ die('oops request not set,i know tis is not the correct way to error :)'); }
 }
}

现在在我的索引文件中

//include_once('config.php');
include_once('connectdb.php');
require_once('../../../includes/db_connect.php');
include_once('view.php');
include_once('abstractController.php');
include_once('controller.php');
include_once('loader.php');
$loader = new loader(array('path'=>DIRECTORY_SEPARATOR.'magsonwink'.DIRECTORY_SEPARATOR.'modules'.DIRECTORY_SEPARATOR.'admin'.DIRECTORY_SEPARATOR.'atom'.DIRECTORY_SEPARATOR));
$loader->autolLoad();

我没有使用模块的概念,只呈现控制器操作和视图。

你确定你的htaccess正确吗?

我想如果你把你的框架放在subfolder上,那么你必须在htaccess文件中将你的RewriteBase/更改为/subfolder/。 它会是这样的:

# on root
RewriteBase /
#on subfolder
RewriteBase /subfolder/

在你的情况下,这是我唯一能想知道的事情......

我不使用 OOP,但可以向您展示一些我如何做事以动态检测我是否在子目录中的片段。保持简短,我只会描述它的一部分,而不是发布所有代码。

因此,我从一个.htaccess开始,该将每个请求发送到重定向.php在其中拼接$_SERVER['REQUEST_URI']变量。我使用正则表达式来决定哪些部分应该是键,哪些应该是值(我通过分配以 0-9 开头的任何内容作为值,以及以 a-z 开头的任何内容作为键来做到这一点(并构建数组$GET[]

然后,我检查重定向路径.php并将其与我的$GET数组进行比较,以确定实际 URL 的开始位置 - 或者在您的情况下,哪个键是控制器。对你来说看起来像这样:

$controller = keyname($GET, count(explode('/', dirname($_SERVER['SCRIPT_NAME']))));

就是这样,我有URL的第一部分。keyname()函数如下所示:

/*************************************
 *  get key name from array position
 *************************************/
function keyname ($arr, $pos)
{
    $pos--;
    if ( ($pos < 0) || ( $pos >= count($arr) ) )
          return "";  // set this any way you like
    reset($arr);
    for($i = 0;$i < $pos; $i++) next($arr);
    return key($arr);
}

为了使链接指向正确,我使用一个名为fixpath()的函数,如下所示:

print '<a href="'.fixpath('/admin/logout').'">link</a>';

这就是该函数的外观:

/*************************************
 *   relative to absolute path
 *************************************/
function fixpath ($path)
{
    $root = dirname($_SERVER['SCRIPT_NAME']);
    if ($root == "''" || $root == ".") {
        $root = "";
    }
    $newpath = explode('/', $path);
    $newpath[0] .= $root;
    return implode('/', $newpath);    
}

我希望这有所帮助,并可以给你一些灵感。

忘记"重新发明轮子是错误的"的说法。他们不必使用我们的轮子。不久前我走在同一条路上,我非常感谢我得到的......我希望你也会

当涉及到您的特定问题的答案时 - 如果面对 - 有一个非常简单的解决方案。 这是根文件夹的 .htaccess 中的新行...

只需将下面的行添加到您的根 .htaccess 文件中;(如果您的子文件夹是"子文件夹"(

RewriteRule subfolder/ - [L]

这将使此文件夹与重写指令分开

通过使用这种方式,您可以根据需要安装任意数量的框架实例。但是,如果您希望这是框架驱动的,那么您必须在框架内创建/更改.htaccess。

创建/myBaseDirectory/public目录并将文件放在那里 - 就像index.php一样。
这是有效的,因为 Apache 看到这个目录就像基目录一样。

基本上在第一个斜杠之后抓取 url 字符串,然后将其分解成数组(我使用"/"作为分隔符(。

然后小心地array_shift掉元素并将它们存储为变量

item 0: controller
item 1: the action / method in that controller
item 2 thru n: the remaining array is your params