这是在PHP for MVC中将URI与类/方法匹配的好方法

时间:2011-08-11 21:49:00

标签: php regex performance model-view-controller uri

我是MVC的新手,所以这是我的第一次尝试,我相信你们可以给我改进,感谢任何提示或帮助!

以下是我为我的个人框架设计的路由器/调度程序系统,这是我第一次尝试使用MVC模式。

第一个代码块只是我的.htaccess文件,它通过我的index.php文件路由所有请求。

第二个代码块是我的“路由”数组,它将告诉Router对象,调用哪个类和方法以及任何ID或分页号(如果存在)。

第三个代码块是路由器类。

第四个块正在运行类

所以路由器类必须使用正则表达式来匹配URI和路由映射中的路由,理论上,当有正则表达式必须运行的50多条路由的列表时,这听起来就像是糟糕的性能,应该我这样做会有所不同吗?我使用正则表达式的主要原因是匹配路由中存在的页码和ID号。

另外请不要只是告诉我使用框架,我这样做是为了更好地学习它,我这样学习得更好,只是不喜欢现在使用现有的框架,我已经研究了所有主要的框架和一些不常见的想法已经存在。

1)所以主要问题是,做些什么看起来不正确吗?

2)有没有更好的方法来检测URI中的内容,而不是像我一样在数组上使用正则表达式,在高流量站点上考虑它?

3)由于所有内容都通过index.php文件进行路由,我将如何处理AJAX请求?

抱歉,如果这令人困惑,我自己有点困惑!


.htaccess文件

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$   index.php?uri=$1    [NC,L,QSA]

Map array()

/**
 * Map URI to class/method and ID and Page numbers
 * Must be an array
 */
$uri_route_map = array( 
    //forums
    'forums/' => array(
        'controller' => 'forums',
        'method' => 'index',
        'id_number' => '',
        'page_number' => ''),

    'forums/viewforum/(?<id_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewforum',
        'id_number' => isset($id_number),
        'page_number' => ''),  

    'forums/viewthread/(?<id_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewthread',
        'id_number' => isset($id_number),
        'page_number' => ''),

    'forums/viewthread/(?<id_number>\d+)/page-(?<page_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewthread',
        'id_number' => isset($id_number),
        'page_number' => isset($page_number)),

    // user routes
    // account routes
    // blog routes 
    // mail routes
    // various other routes
);

读取并匹配上面的Map数组的路由器类

/**
 * Run URI against our Map array to get class/method/id-page numbers
 */
 class Router
{
    private $_controller = '';
    private $_method = '';
    public $page_number = '';
    public $id_number = '';

    public function __construct($uri, array $uri_route_map)
    {
        foreach ($uri_route_map as $rUri => $rRoute)
        {
            if (preg_match("#^{$rUri}$#Ui", $uri, $uri_digits))
            {
                //if page number and ID number in uri then set it locally
                $this->page_number = (isset($uri_digits['page_number']) ? $uri_digits['page_number'] : null);
                $this->id_number = (isset($uri_digits['id_number']) ? $uri_digits['id_number'] : null);
                $this->_controller = $rRoute['controller'];
                $this->_method = $rRoute['method'];

                // just for debug and testing while working on it / will be removed from final code
                echo '<hr> $page_number = ' . $this->page_number . '<br><br>';
                echo '<hr> $id_number = ' . $this->id_number . '<br><br>';
                echo '<hr> $controller = ' . $this->_controller . '<br><br>';
                echo '<hr> $method = ' . $this->_method . '<br><br>';
                break;
            }else{
                $this->page_number = '';
                $this->id_number = '';
                $this->_controller = '404';
                $this->_method = '404';
            }
        }
    }

    public function getController()
    {
        return $this->_controller;
    }

    public function getMethod()
    {
        return $this->_method;
    }

    public function getPageNumber()
    {
        return $this->page_number;
    }

    public function getIDNumber()
    {
        return $this->id_number;
    }

    /**
     * Call our class and method from values in the URI
     */
    public function dispatch()
    {
        if (file_exists('controller' . $this->_controller . '.php'))
        {
            include ('controller' . $this->_controller . '.php');
            $controllerName = 'Controller' . $this->_controller;
            $controller = new $controllerName($this->getIDNumber(),$this->getPageNumber());
            $method = $this->_method;
            if (method_exists($this->_controller, $this->_method))
            {
                return $controller->$method();
            } else {
                // method does not exist
            }
        } else {
            // Controller does not exist
        }
    }

}

运行它

/**
 * Testing the class
 */
$uri = isset($_GET['uri']) ? $_GET['uri'] : null;
$router = new Router($uri, $uri_route_map);
$router->dispatch();

?>

6 个答案:

答案 0 :(得分:4)

1)对我好一点。但代码看起来有点乱。

2)是的,有更好的方法。你正在做正则表达式,因为你想匹配你不知道的URL部分。为什么不$parts = explode("/", $uri)然后看看你是否能找到你正在寻找的页面?您需要定义每个页面预期的参数数量,或者您不知道是选择forums参数array("viewform", 123)还是forums/viewforum参数array(123)

explode感觉比正则表达式更好。它还增加了改进错误处理的好处。如果传递给viewforum的参数不是数字怎么办?当然,你可以做得比"404";)

更好

3)制作一个单独的ajax处理程序。无论如何,Ajax都是隐藏在视图中的,因此您无需费心提供语义URL。

示例:

function find_route($parts) {
    foreach ($uri_route_map as $route => $route_data) {
        $route_check = implode("/", array_slice($parts, 0, count($parts) - $route_data['num_arguments']));
        if ($route_check === $route) {
            return $route_data;
        }
    }
    throw new Exception("404?");
}

$uri = "forum/viewforum/522";

$parts = explode("/", $uri);
$route = find_route($parts);
$arguments = array_slice($parts, count($parts) - $route['num_arguments']);

$controller = $rRoute['controller'];
$method = $rRoute['method'];
$controller_instance = new $controller();
call_user_func_array(array($controller_instance, $method), $arguments);

(未测试的)

<强>插件

由于$ uri_route_map,您无​​法“动态”注册更多插件或页面或“路由”。我添加了一个函数来动态地向Router添加更多路由。

此外,您可以考虑一些自动发现方案,例如,将检查文件夹plugins/以查找名为“manifest.php”的文件夹,该文件在调用时可选择向路由器添加更多路由。

答案 1 :(得分:4)

1),2)我认为将id_number和page_number放在Router中是个好主意,因为将来你可能会遇到url的许多其他参数。最好只使用控制器和方法,并在控制器中定义如何处理其他参数或创建处理请求信息的其他类请求。

3)对于ajax,请使用url,如ajax / module / action。并创建一个执行基本ajax安全性的ajax控制器,比如检查XSRF,然后决定运行哪些控制器和要调用的操作。

答案 2 :(得分:2)

1)&amp; 2)我不会说,这不是正确但为什么不使用默认路由?大部分时间都是像

这样的路线
  

controller / action / param1 / param2

对于你的大多数页面都是好的。

你可能会做类似的事情来定义默认路线:

$this->controller = 'index';
$this->action = 'index';

private function getDefaultRoutes()
{
    $url = $_SERVER['REQUEST_URI'];
    $tabUrl = explode('/',$url);


    if(!empty($tabUrl))
    {
        $this->controller = array_shift($tabUrl);
        $this->action = array_shift($tabUrl);
        $this->params = $tabUrl;
    }
}

然后,如果您需要更多特定路线,您可以在数组或任何您想要的地方定义它们。在您的routeryou中,只需要检查当前URI是否与特定路由或默认路由匹配。 通过这样做,您将减少匹配的路由数量并提高路由器的速度。

3)您的路由器可能是由您的索引实现的,没有索引没有root,所以很遗憾您可能无法避免使用它。 这就是为什么在索引中避免昂贵的操作非常重要。通常,如果所有您的页面不需要,请不要在索引中初始化数据库连接。

  

另外请不要告诉我使用框架

不要忘记下载一些着名的框架并查看他们的代码。这是更好的学习方式。通过这样做,你可能会找到很多好的做法和答案。

答案 3 :(得分:2)

答案 4 :(得分:1)

1)它有效吗?如果是,那么是的。由于上面的代码只包含数组,正则表达式和验证,我不认为你的代码存在问题。只要它有效。但是,如果你问,'那个代码是可扩展的吗?'然后答案将是各种各样的,它完全取决于您的MVC框架目的(例如,是用于一般用途的框架,例如:博客,或其REST API提供程序的特殊内容。等等......)

2)是的。 Kohana,Zend,CI和其他流行的(并且经过高度优化的)PHP框架使用(路由器上的数组+正则表达式)。

3)我认为你可以在路由块/节中给它一个标志,并使该标志作为全局变量可用。因此,在您的控制器中,您可以通过检查该标志来决定为不同的请求类型(ajax / non-ajax)发送哪个响应(例如,您可以提供$this->is_ajax作为Controller范围内可用的全局方法)。

答案 5 :(得分:1)

如果我可以添加几点:

  • 从路由器中删除id_numberpage_number - 只需传递与控制器匹配的所有内容,毕竟,这是一个处理该数据的控制器作业,而不是路由器的

  • 不要将$uri传递给构造函数,而是将其传递给dispatch()

  • 为什么那些isset() - 在$uri_route_map中?显然它们是false,因为在$uri_route_map对象被实例化之前定义了Router()

  • 建议在匹配例程中添加更多逻辑 - 在当前情况下sitename/forums将不会与导致404的任何内容相匹配(没有尾随斜杠)

  • 您还可以在$uri_route_map中定义默认参数,然后在参数匹配时array_merge定义默认参数。因此,例如,当未指定页码时,page_number将等于1

  • 如果您担心高流量网站的性能,可以缓存路由。毕竟,forums/viewforum/100将始终指向相同的控制器/方法。

为什么你担心向你的index.php文件发送AJAX请求?有什么问题?