【问题标题】:Manage URL routes in own php framework在自己的 php 框架中管理 URL 路由
【发布时间】:2012-07-31 23:56:45
【问题描述】:

我正在创建一个 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);

    }
}

感谢您提供的任何帮助。

【问题讨论】:

  • 为什么要重新发明轮子?现有的 MVC 框架还不够好?
  • “不重新发明轮子”意味着互联网上只有 1 个 MVC 框架,我们无法获得更好的产品。有时是必要的。
  • @codingbiz 伙计,只是展示一些 Luv ! Luv 先生的精彩论证
  • @Stefan Luv 无论如何,我祝你好运......为你的勇气。 GitHub!!! :)
  • @codingbiz 当你想了解轮子时,重新发明轮子是件好事。

标签: php url frameworks routes


【解决方案1】:

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

伪代码:

 //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));

您可以在 .htaccess 中使用或不使用 RewriteBase,只要它们使用相同的工具 - index.php。

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

【讨论】:

    【解决方案2】:

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

    只要使用Composer,它已经成为PHP 中依赖管理的标准,就可以了。向堆栈中添加任意数量的组件。

    你有一个必读guide关于如何制作你自己的框架。

    【讨论】:

    • 这是正确答案。这个路由组件也支持 Drupal 8、Larval 和 Silex。它是最好的品种。我在 7 年前编写了自己的路由框架,很高兴我再也不需要这样做了。
    • 不,这只是一个可能的答案,因为 OP 已经建立了路由机制,并在特定用例中寻求帮助...
    【解决方案3】:

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

    (免责声明:我知道您知道其中的大部分内容,但我将为可能不知道其中一些问题的其他人解释一切)

    使用 PATH_INFO 和 .htaccess

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

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

    您将在$_SERVER['PATH_INFO'] 变量中获得“/controller/action”

    现在您需要做的是获取一个 .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 framework manual作为参考,我也推荐使用html5 boilerplate

    基本上,您将其设置为将所有内容重定向到 url 中特定点的 index.php。

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

    现在你可以从$_SERVER['PATH_INFO']得到正确的路径“/controller/action”

    除非您访问 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 变量

    在你的 index.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,它只是它的path 部分并且不会包含查询字符串,例如在访问时

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

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

    index.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);
    
    
    }
    }
    

    希望对你有帮助

    附:抱歉回复晚了:(

    【讨论】:

      【解决方案4】:

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

      您可以在 codeigniter 表单上查看 this forum post 以获得一些提示。

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

      为避免在 uri 上显示 index.php,您必须依赖 Apache 重写规则,但即便如此,我认为 CI 是一个不错的解决方案,一旦您有了索引文件位置,就可以避免所有麻烦解析网址。

      【讨论】:

        【解决方案5】:

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

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

        只要我的 2 美分 :-)。

        【讨论】:

          【解决方案6】:

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

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

          部分之前

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

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

          【讨论】:

            【解决方案7】:

            这就是我实现 loader.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();
            

            我没有使用模块的概念。只有控制器动作和视图被渲染。

            【讨论】:

              【解决方案8】:

              您确定您的htaccess 正确吗?

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

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

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

              【讨论】:

                【解决方案9】:

                我不使用 OOP,但可以向您展示一些关于我如何动态检测我是否在子目录中的 sn-ps。太简短了,我只会描述其中的一部分,而不是发布所有代码。

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

                然后我检查redirect.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);    
                }
                

                希望对你有所帮助,能给你一些启发。

                【讨论】:

                  【解决方案10】:

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

                  当涉及到您的具体问题的答案时——如果你也面临这个问题——有一个非常简单的解决方案。这是根文件夹的 .htaccess 中的新行...

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

                  RewriteRule subfolder/ - [L]
                  

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

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

                  【讨论】:

                    【解决方案11】:

                    创建/myBaseDirectory/public 目录并将您的文件放在那里 - 比如index.php
                    这是有效的,因为 Apache 将此目录视为基本目录。

                    【讨论】:

                      【解决方案12】:

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

                      然后小心地 array_shift 离开你的元素并将它们存储为变量

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

                      【讨论】:

                      • 我正在使用 $_SERVER['REQUEST_URI'] 获取控制器/动作,但问题是,如果我在子文件夹中有框架核心,我得到 /subfolder/controller/action/。我需要首先识别子文件夹结构,然后获得一个真正的控制器/动作调用或者我曾经得到一个假控制器动作(子文件夹/子文件夹)
                      • 是的,我知道,但我告诉你一个更好的方法是使用 get 简单地获取整个 url 字符串,然后从那里进行所有操作以提取数据。此外,使用 .htaccess 将您的请求定向到单个公共目录。然后根据您的控制器/视图等使用 php 生成内容
                      • 这将是最糟糕的做法@Kristian。您还确定了子文件夹。
                      猜你喜欢
                      • 2011-02-05
                      • 1970-01-01
                      • 2011-05-03
                      • 2011-04-20
                      • 2015-10-23
                      • 1970-01-01
                      • 1970-01-01
                      • 2021-02-22
                      • 1970-01-01
                      相关资源
                      最近更新 更多