如何在类似MVC的页面中基于漂亮的URL加载类?

时间:2013-09-10 19:21:36

标签: php .htaccess url-routing

我想问一些关于如何解决这个问题的提示。我正在尝试建立自己的MVC网站。我学习了URL的基础知识。

http://example.com/blog/cosplay/cosplayer-expo-today

博客 - >控制器
cosplay - >控制器中的方法
cosplayer-expo-today - >方法中的变量

如果我在博客控制器中动态扩展类别怎么办?我是否需要创建该方法,还是有一些技巧可以自动完成?我的意思是......我现在有这些类别:角色扮演,游戏,电影,系列。所以我需要在控制器中创建这些方法,但它们都做同样的事情,即从数据库中选择其他类别。

  • function cosplay()= example.com/blog/cosplay /
  • function game()= example.com/blog/game /
  • function movie()= example.com/blog/movie /
  • function series()= example.com/blog/series /

关于如何编写控制器来自动执行此操作,是否有任何好的建议?我的意思是如果我在我的数据库中上传一个新类别,但我不想修改控制器。可能吗?谢谢你的帮助!

更新

这是我的网址爆炸类

class Autoload
{
    var $url;
    var $controller;
    function __construct()
    {
        $this->url = $_GET['url'];
        //HNEM ÜRES AZ URL
        if($this->url!='' && !empty($this->url))
        {
            require 'application/config/routes.php';
            //URL VIZSGÁLATA
            $this->rewrite_url($this->url);

            //URL SZÉTBONTÁSA
            $this->url = explode('/', $this->url);

            $file = 'application/controllers/'.$this->url[0].'.php';
            //LÉTEZIK A CONTROLLER?
            if(file_exists($file))
            {
                require $file;
                $this->controller = new $this->url[0];

                //KÉRELEM ALATT VAN AZ ALOLDAL?
                if(isset($this->url[1]))
                {
                    //LÉTEZIK A METÓDUS? ENGEDÉLYEZVE VAN?
                    if(method_exists($this->controller, $this->url[1]) && in_array($this->url[1], $route[$this->url[0]]))
                    {
                        if(isset($this->url[2]))
                        {
                            $this->controller->{$this->url[1]}($this->url[2]);
                        }
                        else
                        {
                            $this->controller->{$this->url[1]}();
                        }
                    }
                    else
                    {
                        header('location:'.SITE.$this->url[0]);
                        die();
                    }
                }
            }
            else
            {
                header('location:'.SITE);
                die();
            }
        }
        else
        {
            header('location:'.SITE.'blog');
            die();
        }
    }

    /**
     * Első lépésben megvizsgáljuk, hogy a kapott szöveg tartalmaz-e nagybetűt. Amennyiben igen átalakítjuk kisbetűsre.<br/>
     * Második lépésben megnézzük, hogy a kapott szöveg '/'-re végződik-e. Amennyiben igen levágjuk azt.<br/>
     * Harmadik lépésben újra töltjük az oldalt a formázott szöveggel.
     * 
     * @param string $url Korábban beolvasott URL.
     */
    private function rewrite_url($url)
    {
        //HA NAGYBETŰ VAN AZ URL-BEN VAGY '/'-RE VÉGZŐDIK
        if(preg_match('/[A-Z]/', $url) || substr($url, -1)=='/')
        {
            //NAGYBETŰS AZ URL KICSIRE ALAKÍTJUK
            if(preg_match('/[A-Z]/', $url))
            {
                $url = strtolower($url);
            }
            //HA '/'-RE VÉGZŐDIK LEVÁGJUK
            if(substr($url, -1)=='/')
            {
                $url = substr($url, 0, strlen($url)-1);
            }
            header('location:'.SITE.$url);
            die();
        }
    }




}

这是我的.htacces

Options +FollowSymLinks
RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d  
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l

RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]

2 个答案:

答案 0 :(得分:36)

  

FYI:有几件事情你做错了。我将尝试通过每一个并解释问题,误解和可能的解决方案。

自动加载和路由是分开的。

从您发布的代码看起来很明显,您只有一个类,它负责以下任务:

  • 路由:它将URL拆分为对其余应用程序具有一定意义的部分
  • 自动加载:类获取已分隔的网址,并尝试包含相关代码
  • factory:初始化新实例并在其上调用某些方法
  • 响应:在某些情况下,该类以HTTP标头
  • 的形式向用户发送响应

在OOP中有这样的东西,名为:Single Responsibility Principle [short version] 。基本上它意味着一个类应该处理一个特定的东西。上面的列表对您的Autoload课程至少构成 4 不同的职责。

这些常规任务应该由一个单独的类处理,而不是现在拥有的内容。在自动加载器的情况下,您可以使用单个功能。

如何编写自己的自动加载代码?

我看到的部分问题是关于autoload在PHP中如何实际工作的混淆。在创建实例的地方,无需调用includerequire。相反,当你尝试使用以前未定义的类时,你会注册一个处理程序(使用spl_autoload_register()函数),然后自动*调用它。

最简单的例子是:

spl_autoload_register( function( $name ) use ( $path ) {
    $filename = $path . '/' . $name . '.php';
    if ( file_exists( $filename ) === true ) {
        require $filename;
        return true;
    }
    return false;
});

此特定示例使用anonymous function,这是PHP 5.3中引入的功能之一,但spl_autoload_register()的手册页还将向您展示如何实现对象或普通对象的示例功能

与自动加载密切相关的另一项新功能是namespaces。在此上下文中,命名空间将为您提供两个直接的好处:具有多个具有相同名称的类的能力以及从多个目录加载类文件的选项。

例如,您可以使用以下代码:

$controller = new \Controllers\Overview;
$view = new \Views\Overview;

$controller->doSomething( $request );

..在这种情况下,您可以分别从/project/controllers/overview.php/project/views/overview.php文件中获取自动加载器类。因为spl_autoload_register()会将"\Controllers\Overview""\Views\Overview"传递给处理函数。

还有关于如何实施自动加载器的FIG建议。你可以找到它here。虽然它有一些重大问题,但应该为你提供良好的基础。

如何解析漂亮的URL?

Apache的 mod_rewrite 漂亮的网址中的功能非常有限,这已经不是什么秘密了。而且,虽然它是一个广泛使用的服务器,但它不是网络服务器的唯一选择。这就是为什么PHP开发人员可以选择在PHP端处理URL的最大灵活性。

任何新手都要做的第一件事就是explode('/', ... )。这是一个很自然的选择,但您很快就会注意到它的确非常强大<非常有限。路由机制将开始增长。首先基于段的计数,稍后 - 在段中添加不同的条件值,这需要不同的行为。

基本上,这将变成巨大的,脆弱的和无法控制的混乱。不好的主意。

相反,你应该做的是有一个正则表达式列表,你可以匹配给定漂亮的URL。例如:

'#/(?P<resource>[^/\\\\.,;?\n]+)/foobar#'

上面定义的模式会匹配所有包含两个细分的网址,其中一些文字位于第一个细分中,"foobar"位于第二个细分中......如"/testme/foobar"

此外,您可以将每个模式与每个匹配的相应默认值相关联。当你把它们放在一起时,你可能会得到这样的配置(使用5.4+数组语法,因为我喜欢写 ...处理它):

$routes = [
    'primary' => [
        'pattern'   => '#/(?P<resource>[^/\\\\.,;?\n]+)/foobar#',
        'default'   => [
            'action'    => 'standard',
        ],
    ],
    'secundary' => [
        'pattern'   => '#^/(?P<id>[0-9]+)(?:/(?P<resource>[^/\\\\.,;?\n]+)(?:/(?P<action>[^/\\\\.,;?\n]+))?)?$#',
        'default'   => [
            'resource'  => 'catalog',
            'action'    => 'view',
        ]
    ],
    'fallback'  => [
        'pattern'   => '#^.*$#',
        'default'   => [
            'resource'  => 'main',
            'action'    => 'landing',
        ],
    ],
]; 

您可以使用以下代码处理:

// CHANGE THIS
$url = '/12345/product';

$current = null;

// matching the route
foreach ($routes as $name => $route) {
    $matches = [];
    if ( preg_match( $route['pattern'], $url, $matches ) ) {
        $current = $name;
        $matches = $matches + $route['default'];
        break;
    }
}


// cleaning up results
foreach ( array_keys($matches) as $key ) {
    if ( is_numeric($key) ) {
        unset( $matches[$key] );
    }
}


// view results
var_dump( $current, $matches );

实时代码:herehere

  

注意:
如果您使用 '(?P<name> .... )' 表示法,则匹配将返回 'name'的数组作为关键。更多然后路由的有用技巧。

您可能希望从一些更易读的符号生成匹配的正则表达式。例如,在配置文件中,此表达式为:

'#^/(?P<id>[0-9]+)(?:/(?P<resource>[^/\\\\.,;?\n]+)(?:/(?P<action>[^/\\\\.,;?\n]+))?)?$#'

..应该看起来像

'/:id[[/:resource]/:action]'

:param表示网址段,[...]表示网址的可选部分。

基于此,您应该能够充实自己的路由系统。上面的代码片段只是简化核心功能的示例。要了解完全实现后的外观,您可以查看this answer中的代码。它应该为您自己的API版本提供一些想法。

调用控制器上的内容..

将控制器的执行埋在路由类(或类)的深处是很常见的错误。这会导致两个问题:

  • 混乱:更难找到&#34;实际工作&#34;从应用程序开始
  • 耦合:你的路由器最终被链接到类似MVC的架构的特定解释

路由是一项任务,即使在自定义编写的应用程序中,它也会自然地倾向于#34; framework-ish&#34;代码库的一部分。

(真正的)简化版本看起来像:

$matches = $router->parse( $url );

$controller = new {'\\Controller\\'.$matches['controller']};
$controller->{$matches['action']( $matches );

这样就没有什么需要在某些类似MVC的架构中使用路由结果。也许您只需要一个美化的提取机制来提供静态HTML文件。

那些动态扩展类别呢?

你正在以错误的方式看待它。 无需动态地向控制器添加方法。在您的示例中,实际上有一个控制器方法......类似于以下内容:

public function getCategory( $request ) {
    $category = $request->getParameter('category');

    // ... rest of your controller method's code
}

$category最终会包含"cosplay""game""movie""series"或您添加的任何其他类别。这是控制器将传递给模型层以过滤文章的东西。

人们实际使用什么专业?

现在,由于每个人(好吧......每个人都有一些线索)使用composer,对于自动加载,最好的选择是使用与作曲家捆绑在一起的加载器。

您只需添加require __DIR__ . '/vendor/autoload.php',只需添加一些配置即可。

至于路由,有两个主要的&#34;独立&#34;解决方案:FastRouteSymfony's Routing Component。这些可以包含在您的项目中,而无需额外的麻烦。

但由于有些人将使用框架,因此每个框架都将包含路由请求的功能。

进一步阅读..

如果您想了解有关MVC架构模式的更多信息,我强烈建议您浏览this post中列出的所有材料。将其视为强制性阅读/观看列表。你也可能会发现我对MVC相关主题的这些旧帖子有所帮助:hereherehere

  

PS:自PHP 5.0发布以来(2004年的某个时候),应使用 public 定义类的变量, private protected ,而不是 var

答案 1 :(得分:-3)

我认为你在想这个......

所以控制器/资源是一个博客....所有这些应该运行的方法是“读取”(使用crud ...我通常称它为数据但基本上是你的选择)。现在让你的方法接受一个类别值,该值根据url ....自动获取映射。

这里有一个示例......完全未经测试但只是为了向您展示这个想法(使用pdo)

public function data($id = 0, $category = 0){
    if (isset($id) AND $id != 0){
         $bind = array(":id", $id);
         $results = $db->query("SELECT * FROM blog WHERE blog_id = :id", $bind);
         return $results[0];
    } else if (isset($category) AND $id != 0){ 
         $approved_categories = array("cosplay","game","movie","series");
         if (in_array($category, $approved_categories)){
              $bind = array(":cat", $category);
              $results = $db->query("SELECT * FROM blog WHERE blog_cat = :cat", $bind);
         } 
         return $results;
    }
}