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

标签 php .htaccess url-routing

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

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

博客 -> Controller cosplay -> Controller cosplayer-expo-today 中的方法 -> 方法中的变量

如果我动态扩展博客 Controller 中的类别会怎样?我是否需要创建该方法,或者是否有一些技巧可以自动执行此操作?我的意思是...我现在有这些类别:角色扮演、游戏、电影、系列。所以我需要在 Controller 中创建这些方法,但它们都做同样的事情,即从数据库中选择其他类别。
  • 函数 cosplay() = example.com/blog/cosplay/
  • 函数游戏() = example.com/blog/game/
  • 函数电影()= example.com/blog/movie/
  • 函数系列() = example.com/blog/series/

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

    更新

    这是我的 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();
            }
        }
    
    
    
    
    }
    

    这是我的 .htaccess
    Options +FollowSymLinks
    RewriteEngine On
    
    RewriteCond %{REQUEST_FILENAME} !-d  
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-l
    
    RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]
    

    最佳答案

    FYI: There are several things that you are doing wrong. I will try to go through each of them and explain the problems, the misconceptions and the possible solution(s).



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

    从您发布的代码来看,很明显,您有一个类,负责以下任务:
  • 路由:它将 URL 分成对其余应用程序具有一定意义的部分
  • 自动加载:类采用分离的 URL 段并尝试包含相关代码
  • 工厂:初始化新实例并对其调用一些方法
  • 响应:在某些情况下,类以 HTTP header 的形式向用户发送响应

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

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

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

    我看到的部分问题是关于自动加载在 PHP 中的实际工作方式的混淆。 include的来电或 require不需要在创建实例的地方完成。相反,您注册了一个处理程序(使用 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 .虽然它有一些重大问题,但它应该为您提供良好的基础。

    如何解析漂亮的网址?

    这已经不是什么 secret 了,Apache 的 mod_rewrite 在处理漂亮的 URL 方面非常有限。而且,虽然它是一个广泛使用的服务器,但它并不是网络服务器的唯一选择。这就是 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 );
    

    活码:herehere

    Note:
    If you use '(?P<name> .... )' notation, the matches will return array with 'name' as a key. Useful trick for more then routing.



    您可能希望从一些更易读的符号中生成用于匹配的正则表达式。例如,在配置文件中,这个表达式:
    '#^/(?P<id>[0-9]+)(?:/(?P<resource>[^/\\\\.,;?\n]+)(?:/(?P<action>[^/\\\\.,;?\n]+))?)?$#'
    

    .. 应该看起来像
    '/:id[[/:resource]/:action]'
    

    :param将指示一个 URL 段和 [...]将表示 URL 的可选部分。

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

    调用 Controller 上的东西..

    将 Controller 的执行埋在路由类(或多个类)深处是很常见的错误。这会导致两个问题:
  • 困惑:在应用程序中更难找到“真正的工作”从哪里开始
  • 耦合:您的路由器最终链接到对类似 MVC 架构的特定解释

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

    (真正的)简化版本看起来像:
    $matches = $router->parse( $url );
    
    $controller = new {'\\Controller\\'.$matches['controller']};
    $controller->{$matches['action']( $matches );
    

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

    那些动态扩展的类别呢?

    你正在以错误的方式看待它。 无需向 Controller 动态添加方法。 在您的示例中,实际上有一个 Controller 方法……类似以下内容:
    public function getCategory( $request ) {
        $category = $request->getParameter('category');
    
        // ... rest of your controller method's code
    }
    

    哪里$category最终会包含 "cosplay" , "game" , "movie" , "series"或您添加的任何其他类别。这是您的 Controller 将传递给模型层以过滤文章的内容。

    人们真正专业地使用什么?

    这些天来,因为每个人(好吧..每个人都有一些线索)使用 composer ,对于自动加载,最好的选择是使用 Composer 附带的加载器。

    您只需添加 require __DIR__ . '/vendor/autoload.php'有了一些配置,它就可以工作了。

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

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

    一些进一步阅读..

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

    P.S.: since PHP 5.0 was released (some time in 2004th), class's variables should be defined using public, private or protected instead of var.

    关于php - 如何在类似 MVC 的页面中基于漂亮的 URL 加载类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18727186/

    相关文章:

    php - 使用 PHP 处理 GET 参数中的 unicode 值

    .htaccess 子域重定向不起作用

    .htaccess - htaccess 将 301 https www 重定向到非 www

    javascript - 从路由目录中的文件导出时,为什么我的 View 没有返回?

    asp.net-mvc - ASP.NET MVC 5 路由,在 URL 中隐藏数据

    javascript - DataTable 在编辑后不记得分页页面

    php - HY000 2006 MySQL 服务器已经消失

    javascript - Ember.js - 如何使用 Ember.Router 将集合正确绑定(bind)到 View ?

    php - laravel 5.5 如何连接多个数据库?

    .htaccess - 使用 .htaccess 仅在 1 个页面和 1 个目录上强制使用 HTTPS?