6

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

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

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

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

  • 功能 cosplay() = example.com/blog/cosplay/
  • 功能游戏()= example.com/blog/game/
  • 函数电影() = example.com/blog/movie/
  • 函数系列() = 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]
4

2 回答 2

35

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

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

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

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

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

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

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

我看到的部分问题是关于自动加载在 PHP 中的实际工作方式的混淆。includeor的调用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;
});

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

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

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

$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建议。你可以在这里找到它。虽然它有一些重大问题,但它应该为您提供良好的基础。

如何解析漂亮的 URL?

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

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

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

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

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

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

Live code: here or here

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

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

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

..应该看起来像

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

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

基于此,您应该能够充实自己的路由系统。上面的代码片段只是简化核心功能的示例。要了解完全实施后的外观,您可以查看此答案中的代码。它应该为您自己的 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'并进行一些配置,它就可以工作。

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

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

一些进一步的阅读..

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

PS:自 PHP 5.0 发布以来(2004 年的某个时间),类的变量应该使用定义publicprivate或者protected代替var.

于 2013-10-11T03:42:53.530 回答
-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;
    }
}
于 2013-09-10T19:33:35.460 回答