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

Posted

技术标签:

【中文标题】如何在类似 MVC 的页面中加载基于漂亮 URL 的类?【英文标题】:How to load classes based on pretty URLs in MVC-like page? 【发布时间】:2013-09-14 15:37:36 【问题描述】:

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

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

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

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

函数 cosplay() = example.com/blog/cosplay/ function game() = example.com/blog/game/ 函数电影() = example.com/blog/movie/ 函数 series() = example.com/blog/series/

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

更新

这是我的 URL 爆炸器类

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]

【问题讨论】:

你需要做的是设置一个routing系统。 This 的帖子可能会给你一些建议。 你在使用 codeigniter 吗?? 不,我没有使用 codeigniter。 如果您的博客控制器只处理类别,您可以添加重写规则RewriteRule ^myblog/(.+)$ blog/$1 [QSA,L] 如果我不想在控制器中使用其他一些方法,它会起作用吗? (impresszum,conactme) 【参考方案1】:

仅供参考:您做错了几件事。我将尝试逐一解释并解释问题、误解和可能的解决方案。

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

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

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

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

这些常规任务中的每一个都应该由一个单独的类来处理,而不是您现在所拥有的。如果是自动加载器,您可以使用一个函数。

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

我看到的部分问题是关于自动加载在 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。虽然它有一些重大问题,但它应该为您提供良好的基础。

如何解析漂亮的网址?

众所周知,Apache 的 mod_rewritepretty URLs 的作用非常有限。而且,虽然它是一个广泛使用的服务器,但它并不是网络服务器的唯一选择。这就是 PHP 开发人员选择在 PHP 端处理 URL 的最大灵活性的原因。

任何新手都会做的第一件事是explode('/', ... )。这是一个自然的选择,但您很快就会注意到,它真正能做的事情也极其有限。路由机制将开始增长。起初基于段数,后来 - 在段中添加不同的条件值,这需要不同的行为。

本质上,这将变成巨大的、脆弱的和无法控制的混乱。坏主意。

您应该做的是拥有一个正则表达式列表,您可以将其与给定的漂亮 URL 进行匹配。例如:

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

上面定义的模式将匹配所有有两个段的 URL,第一个段中有一些文本,第二个段中有一些文本 "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 );

直播代码:here 或 here

注意:如果您使用 '(?P&lt;name&gt; .... )' 表示法,则匹配将返回带有 'name' 的数组关键。比路由更有用的技巧。

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

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

.. 应该看起来像

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

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

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

调用控制器上的东西..

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

困惑:在应用程序中找到“真正的工作”从哪里开始变得更加困难 耦合:您的路由器最终会被链接到对类似 MVC 架构的特定解释

路由是一项任务,即使在自定义编写的应用程序中,也会自然而然地倾向于代码库的“框架式”部分。

(真正的)简化版本如下所示:

$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,因此自动加载的最佳选择是使用与 composer 捆绑在一起的加载程序。

您只需添加require __DIR__ . '/vendor/autoload.php' 并进行一些配置即可工作。

至于路由,有两种主要的“独立”解决方案:FastRoute 或Symfony's Routing Component。这些可以包含在您的项目中,而不会带来额外的麻烦。

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

一些进一步的阅读..

如果您想了解更多有关 MVC 架构模式的信息,我强烈建议您阅读 this post 中列出的所有资料。将其视为强制性阅读/观看列表。您还可能会发现我在 MVC 相关主题上的这些旧帖子有些有益:here、here 和 here

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

【讨论】:

【参考方案2】:

我觉得你想多了这个......

所以控制器/资源是一个博客......所有它们都应该运行的方法是“读取”(使用 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;
    

【讨论】:

有什么关系?这只是意味着您有一个称为数据的类别。 谢谢!有了你的动力,我解决了这个问题。我将工作交给控制器构造函数。

以上是关于如何在类似 MVC 的页面中加载基于漂亮 URL 的类?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Next.js 访问当前在导航中加载的 url 或页面

如何使用 php-url 路径在 Magento CMS 页面中加载图像背景?

IE9 在 iframe 中加载脚本时抛出异常。为啥?

在 mvc 中加载 2 个下拉列表的更好方法

如何在 Joomla Protostar 模板中加载外部 JS 和 URL

在 UIWebview 中加载带有帖子的 URL 的问题