如何在类似 MVC 的页面中基于漂亮的 URL 加载类? [英] How to load classes based on pretty URLs in MVC-like page?

查看:14
本文介绍了如何在类似 MVC 的页面中基于漂亮的 URL 加载类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I would like to ask for some tips, on how solve this problem. I'm trying to build my own MVC website. I learned the basics of the URL.

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

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

blog -> the controller
cosplay -> the method in controller
cosplayer-expo-today ->variable in method

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

What if i dynamically extend the category in my blog controller? Will I need to create the method, or is there some trick to do that automatically? I mean... i have these categories now: cosplay,game,movie,series. So I need to create these methods in controller, but they all do the same thing, namely select other category from database.

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

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

Is there any good advice on how can i write my controller to do that automatically? I mean if I upload a new category in my database, but i don't want to modify the controller. Is it possible? Thanks for the help!

更新

这是我的 URL 爆炸器类

Here is my URL exploder class

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

And here is my .htacces

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).

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

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

The autoloading and routing are separate things.

From the look of you posted code, it's obvious, that you have a single class, which is responsible the following tasks:

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

在 OOP 中有一个东西,叫做:单一职责原则 [短版].基本上这意味着一个类应该处理一个特定的事情.上面的列表为您的 Autoload 类构成了至少 4 种不同的职责.

In OOP there is this thing, called: Single Responsibility Principle [short version]. Basically it means that a class should be handling one specific are thing. The list above constitutes at least 4 different responsibilities for your Autoload class.

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

Instead of what you have now, each of these general tasks should be handled by a separate class. And in case of autoloader, you could get away with a single function.

我看到的部分问题是关于自动加载在 PHP 中的实际工作方式的混淆.includerequire 的调用不需要在创建实例的地方完成.相反,您注册一个处理程序(使用 spl_autoload_register() 函数),然后当您尝试使用以前未定义的类时 **自动* 调用该函数.

Part of the problem that I see is the confusion about how autoload actually works in PHP. The call of include or require doesn't need to be done where the instance will be created. Instead you register a handler (using spl_autoload_register() function), which then is **automatically* called, when you try to use previously-undefined class.

最简单的例子是:

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

这个特定的例子使用了匿名函数,这是特征之一这是在 PHP 5.3 中引入的,但是 spl_autoload_register() 的手册页也将向您展示如何使用对象或普通函数实现相同的示例.

This particular example uses anonymous function, which is one of features that was introduced in PHP 5.3, but the manual page for the spl_autoload_register() will also show you examples how to achieve the same with objects or ordinary functions.

另一个与自动加载密切相关的新功能是命名空间.在这种情况下,命名空间会给您带来两个直接的好处:能够拥有多个具有相同名称的类以及从多个目录加载类文件的选项.

Another new feature that is closely related to autoloading is namespaces. In this context the namespaces would give you two immediate benefits: ability to have multiple classes with same name and options to load class file from multiple directories.

例如,你可以有这样的代码:

For example, you can have code like this:

$controller = new ControllersOverview;
$view = new ViewsOverview;

$controller->doSomething( $request );

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

.. in this case you can have autoloader fetching classes from /project/controllers/overview.php and /project/views/overview.php files respectively. Because the spl_autoload_register() will pass "ControllersOverview" and "ViewsOverview" to the handler function.

还有一个关于如何实现自动加载器的FIG 建议.您可以在此处找到它.虽然它存在一些重大问题,但它应该为您提供了一个良好的基础.

There is also a FIG recommendation for how to implement autoloaders. You can find it here. While it has some significant problems, it should provide you with good base on which to build upon.

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

It is no secret, that Apache's mod_rewrite is quite limited in what it can do with pretty URLs. And, while it's a widespread server, it is not the only option for webservers. This is why for maximum flexibility PHP developers opt to handle URLs on the PHP end.

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

And the first thing any newbie will do is explode('/', ... ). It is a natural choice, but you will soon notice that it is also extremely limited in what it can really do. The routing mechanism will start to grow. At first based on count of segments, later - adding different conditional values in segments, that require different behavior.

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

Essentially, this will turn in huge, fragile and uncontrollable mess. Bad idea.

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

Instead what you should do is have a list of regular expressions, that you match against given pretty URL. For example:

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

上面定义的模式将匹配所有具有两个段的 URL,第一个段中有一些文本,第二个段中有一些文本 "foobar" ...如 "/testme/foobar".

The above defined pattern would match all the URL that have two segments, with some text in first segment and "foobar" in the second ... like "/testme/foobar".

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

Additionally you can link each pattern with corresponding default values for each match. When you put this all together, you might end up with configuration like this (uses 5.4+ array syntax, because that's how I like to write .. deal with it):

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

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

Which you could handle using following code:

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

实时代码:此处这里

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

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

You probably will want to generate the regular expressions for the matching from some more-readable notations. For example, in configuration file, this expression:

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

.. 应该看起来像

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

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

Where the :param would indication an URL segment and [...] would signify an optional part of URL.

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

Based on this you should be able to flesh out your own routing system. The code fragments above is just example of simplified core functionality. To get some perspective on how it might look when fully implemented, you could look at code in this answer. It should give you some ideas for your own API version.

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

It is quite common mistake to bury the execution of controllers somewhere deep in the routing class (or classes).This causes two problems:

  • 困惑:在应用程序中很难找到真正的工作"从哪里开始
  • 耦合:你的路由器最终被链接到类 MVC 架构的特定解释

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

Routing is a task which even in custom-written application will naturally gravitate toward the "framework-ish" part of codebase.

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

The (really) simplified versions would look like:

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

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

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

This way there is nothing that requires your routing results to be used in some MVC-like architecture. Maybe you just need a glorified fetching mechanism for serving static HTML files.

你看错了.无需向控制器动态添加方法.在您的示例中,实际上有一个控制器方法……大致如下:

You are looking at it the wrong way. There is no need for dynamically adding methods to controller. In your example there is actually one controller method ... something along the lines of:

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

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

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

Where $category would end up containing "cosplay", "game", "movie", "series" or any other category that you have added. It is something that your controller would pass to the model layer, to filter out articles.

现在,由于每个人(好吧..每个人都有一些线索)使用 composer,因此自动加载的最佳选择是使用 Composer 自带的加载器.

These days, since everyone (well .. everyone with some clue) uses composer, for autoloading the best option is to use the loader that is comes bundled with composer.

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

You simply add require __DIR__ . '/vendor/autoload.php' and with some configuration it will just work.

至于路由,有两种主要的独立"解决方案:FastRouteSymfony 的路由组件.这些可以包含在您的项目中,而不会带来额外的麻烦.

As for routing, there are two major "standalone" solutions: FastRoute or Symfony's Routing Component. These ones can be included in you project without additional headaches.

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

But since some of people will be using frameworks, each of those will also contain capability of routing the requests.

如果您想了解有关 MVC 架构模式的更多信息,我强烈建议您阅读 这篇文章.将其视为强制性阅读/观看列表.您可能还会发现我在 MVC 相关主题上的这些旧帖子有些有益:此处这里这里

If you want to learn more about MVC architectural pattern, I would strongly recommend for you to go though all the materials listed in this post. Think of it as mandatory reading/watching list. You also might find somewhat beneficial these old posts of mine on the MVC related subjects: here, here and here

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

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.

这篇关于如何在类似 MVC 的页面中基于漂亮的 URL 加载类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆