【问题标题】:Is $view in an MVC php program a proper use of global variables?MVC php 程序中的 $view 是否正确使用全局变量?
【发布时间】:2010-09-06 04:58:20
【问题描述】:

我了解一般应避免使用全局变量。作为初学者,我试图了解在遵循 MVC 原则构建的动态 Web 程序中将 $view 变量设为全局变量是否是全局变量是个好主意的情况之一。

在我的程序中,我在 index.php 中创建了 $view(作为一个包含空数组的对象),并在使用它的控制器中将其转换为全局。

谢谢!

JDelage

【问题讨论】:

    标签: php model-view-controller global-variables


    【解决方案1】:

    在我的程序中,我在 index.php 中将 $view 创建为一个空数组(),并在使用它的控制器中将其转换为全局变量。

    为什么?如果您的控制器需要$view,那么只需通过构造函数或设置器将其传入。始终可以使用Dependency Injection 解决对全局变量的依赖。

    // index.php
    $view = array();
    $controller = new Controller($view);
    $controller->doAction();
    

    另外,请重新考虑 View 是否应该只是一个数组。视图必须在某个时候呈现。渲染是我在视图上看到的职责。数组不能呈现自己,所以我假设你在其他地方做。那将违反Single Responsibility Principle。我宁愿按照 (simplified) 的方式做一些事情:

    // index.php
    $view = new View;
    $controller = new Controller($view);
    $controller->doAction();
    
    // controller.php
    ...
    public function doAction()
    {
        ...
        $this->view->setTemplate('/path/to/template');
        $this->view->setVar('foo', 'bar');
        $this->view->render();
    }
    ...
    

    【讨论】:

      【解决方案2】:

      应该避免使用全局变量,是的。然而,它们对于保持全局状态很有用——这不会导致相互耦合——特别是在 $config 数组/对象中。

      但在你的情况下,我不确定 $view 是一个好主意。首先,我相信你正在组装的是输出数据。 MVC 中的“视图”更像是一个输出模板,我相信你的 index.php 是什么?如果是这样,那么您实际上是在组装 $model。不确定。
      理论上,将这个变量显式地传递给控制器​​会更干净。但是,如果它真的只有 一个 全局变量,并且您保证不会过度使用它,那么将它作为全局变量传递是可以的。

      但您也可以遵循上面提到的单例模式。但是我推荐程序变体,因为它更干净:

      function view() {
          static $view;
          if (!isset($view)) { $view = new ArrayObject(); }
          return $view;
      }
      

      这样你可以使用$view=view();在你的控制器中本地化它,同时作为数组$view["xy"]=和对象$view->xy=使用。
      或者直接在模板中写print view()->title;

      【讨论】:

        【解决方案3】:

        在项目中使用 MVC 模式时,将变量设为全局不是一个好主意。还有其他解决方案可以更明智地利用您的 MVC。

        如果您应该只使用一次资源,请使用singleton pattern。类似的东西

        class My_View {
        
           private $_instance;
        
           static public function getInstance() {
             if (null === self::$_instance) {
                self::$_instance = new self();
             }
             return self::$_instance;
           }
        
           private function __construct() { }
        
           public function __clone() {
              trigger_error("Cannot make copy of this object", E_USER_ERROR);
           }
        
           // add public methods and/or properties here...
        }
        

        并在任何地方获取该视图对象

        $view = My_View::getInstance();
        

        这样,您就没有全局变量,并且您正在使用最佳 OO 实践。

        但是,正如其他人指出的那样,拥有一个单例视图不一定是一个好主意......一个可能更好的方法是让您的调度程序代码创建一个新视图并将其设置为被调用的控制器,所以控制器将有一个可用的视图并直接访问它。

        视图(不是单例)也可以是公共属性,或者可以通过应用单例的公共方法访问; My_Application::getInstance()->getView(); 也可以保存当前配置、路径,

        建议的名称My_view 只是一个示例。使用某种命名约定有助于组织代码并帮助摆脱脚本头中的所有那些 include/require 调用。这不在您的问题范围内,但是为了清楚起见,我将简要解释一下:

        在您的引导程序中,您声明您的自动加载功能(在PHP manual 中定义):

        // My_Application.php located in /path/to/lib/My/Application.php
        class My_View {
        
           private $_instance;
        
           static public function getInstance() {
             if (null === self::$_instance) {
                self::$_instance = new self();
             }
             return self::$_instance;
           }
        
           private $_basePath;
           public $view;
        
           private function __construct() { }
        
           public function __clone() {
              trigger_error("Cannot make copy of this object", E_USER_ERROR);
           }
        
           public function initAutoloader($base) {
              $this->_basePath = $base;
              spl_autoload_register(array($this, 'autoload'));
           }
        
           public function autoload($name) {
              require $this->_basePath . str_replace('_', '/', $name) . '.php';
           }
        
           // get the application global view
           public function getView() {
              if (null === $this->view) {
                 $this->view = new My_View();
              }
              return $this->view;
           }
        }
        // perhaps have the parameter being a constant. I.g. APP_BASE_PATH
        My_Application::getInstance()->initAutoload('/path/to/lib/');
        

        只需包含文件'/path/to/lib/My/Application.php',当您访问My_View 时,load 函数将使用$name = 'My_View' 调用,该函数将只需要文件'/path/to/lib/My/View.php'。这对于一个文件来说并不多,但如果所有类都是这样的命名空间,你只需要一个包含(Autoloader),其他所有内容都会自动加载。

        【讨论】:

        • 但是:使用单例通常是不好的做法,在一些特殊情况下是安全的。
        • 单例只是一种精心设计的语义变通方法,可以让全局变量使用另一个名称。
        • 确保类完整性的不是单例。这就是班级本身。单例不是最佳 OO 实践。它们是一种代码气味。 My_View 是硬编码的类名。因此它是对全局范围(或​​当前命名空间)的依赖。这并不比使用全局变量更好。事实上,情况更糟,因为 global $view 可以更容易地用 Mock 代替,而不是硬编码的类。这并不是说使用全局变量。两个都很烂。还有,为什么只有一个 View 实例?
        • 这合乎逻辑吗? MVC 与单例模式无关。它甚至不是一个网络展示模式。单例只确保一个特定的类只能被实例化一次。就这样。 OP没有要求这样做。 Singleton 无处不在的原因仅仅是因为它是通过getInstance() 方法静态 访问的。但是 - 如果您不使用命名空间 - 执行 MyClass::foo() 与使用常规函数没有什么不同,例如myclass_foo(),因为静态调用是对全局命名空间的调用,具有相同的冲突风险。
        • @Yanick Rochon,您说:“这个答案在命名空间中组织了不同的组件”。但实际上,您在类中组织不同的组件。一个类!=一个命名空间。尽管两者都提供了一种封装形式。
        【解决方案4】:

        我见过的解决此问题的方法是让控制器从基本控制器扩展。在该基本控制器中,$data 属性被实例化;然后将新的视图数据附加到此属性。例如:

        class PageController extends BaseController {
        
            function IndexAction() {
                $this->data['title'] = "Page Title";
                $this->data['content'] = "<p>Page content.</p>";
        
                $this->render();
            }
        }
        

        render 方法(可以在方法运行后自动加载,而不是显式加载)然后使用 $data 数组中保存的数据组装模板。

        希望这会有所帮助,并且您能够实施可行的解决方案。

        【讨论】:

        • 那应该没问题,但也没有使用“全局”变量。不知道是你不理解上面的例子,还是 PHP 中 global 的定义。
        猜你喜欢
        • 2014-12-08
        • 2011-11-13
        • 2015-05-26
        • 2013-12-19
        • 2011-12-29
        • 1970-01-01
        • 1970-01-01
        • 2012-03-11
        • 1970-01-01
        相关资源
        最近更新 更多