前端控制器将 link 参数传递给应用程序逻辑的最佳方式?

Front controller best way to link parameters to application logic?

我正在构建一个使用前端控制器设计模式的应用程序,只有一个页面 index.php 所有用户请求都通过该页面作为参数传递(与常规设计中的不同 pages/controllers 不同)。

如何将这些参数连接到应用程序逻辑?

例如我有两个不同的动作:

index.php?action=userLogin&username=admin&password=qwerty //process user login

index.php?action=displayUsersTable //show registered users

目前我有一个 array,其中包含系统接受的所有操作(以及预期的参数),我将 URL 中的 action 参数与它的 key 进行比较数组,然后检查此操作所需的参数。

//1 = optional, 2=required
$systemActions = [
      "userLogin" => [
            "login" => 2,
            "password" => 2
                     ],
      "displayUsersTable" => []
                 ];

显然,随着系统的增长,这将成为一个怪物阵列。

是否有更好的方法将发送到前端控制器的参数绑定到系统操作?

由于代码是 "fixed"(即不是从数据库驱动),因此无需泵入数组(以及它所需的所有 processing/memory 开销。所以是的,它可以改进。

但是根据项目的增长程度,有很多选择。

最简单

最简单的是 "if" 语句或开关。我将从那里开始以保持简单。

更复杂

你说其他项目有不同的页面/控制器 - 但这是有原因的。当你要求改进时,特别是如果你期望项目增长到你正在寻找优化的程度,那么你真的应该考虑这些原因(并拆分成文件)。

在天平的另一端,您可以将所有调用拆分为 files/classes 和 auto-load files/classes。

这样你只执行你需要的代码(较小的文件大小),非常模块化并且易于协作。如果您添加一个新动作,您不需要修改索引或数组——您只需修改您正在处理的动作文件。

示例(从我目前正在使用这种方法进行的项目中大大简化):

1) 创建一个 "baseAction" 基础 class 所有动作都将从中扩展。您可以添加常用功能,例如 cleaning/pre-processing 参数、日志记录、验证 headers 等

abstract class baseAction {
    protected $aExpectedParams = [];
    protected $aParams = [];
    protected $validParams = true;

    function __construct() {
        foreach (self::$aExpectedParams as $name=>$aParam) {
            if (isset($_GET[$name]))
                if ($aParam['type'] == 'string') {
                    self::$aParams[$name] = $_GET[$name];
                } elseif ($aParam['type'] == 'int') {
                    self::$aParams[$name] = (int)$_GET[$name];
                }
            } elseif ($aParam['required']) {
                self::$validParams = false;                
            }
        }
    }

    // This is the called function
    abstract function execute();

}

2) 通过扩展基本 Action 创建 "action" classes。将它们保存在单独的文件中(以便其他人可以在不干扰的情况下协作完成该项目)。

// put in 'actions/userLogin.php
class userLogin extends baseAction {
    protected $aExpectedParams = [
            'login' => ['type' => 'string', 'required' => true]
            'password' => ['type' => 'string', 'required' => true]  // NOTE: you should never actually pass password unencrypted through "get" as they'll get stuck in user logs!
                    ];

    public function execute() {
        // Do Whatever
    }
}

.

// put in 'actions/displayUsersTable.php
class displayUsersTable extends baseAction {
    public function execute() {
        // Do Whatever
    }
}

3) 创建一个自动加载器来拉入那些单独的文件。

function myAutoloader($className) {
    if (file_exists(__DIR__ . '/actions/' . $className . '.php')) {
        require_once(__DIR__ . '/actions/' . $className . '.php');
    }
}
spl_autoload_register ('myAutoloader');

4) 那么你的index.php就和

一样干净
$action = $_GET['action'] ?? '';
if (strlen($action) > 0 && class_exists($action) && method_exists($action, 'execute')) {
    $oAction = new $action();
    $oAction->execute();
} else {
    // Oopsie
}

(关于最后一个片段的注释:"class_exists" 触发 auto-loader。"method_exists" 是为了检查是否有人没有请求一个普通的 php class 例如 "object";如果你更安全,你应该命名空间或添加额外的验证。这只是一个例子!)