Magento的全局变量设计模式

PHP中,变量有全局的概念,比如$_GET $_POST等数组中保存的值都是全局的,如果直接使用一个变量,那么它也是全局可用的,如果在函数中使用global关键字声明一个变量,那这个函数就可以使用来自外部的这个变量值,函数中的这个变量值还可以从函数带出到外部,所有直接声明的变量或用global关键字声明的变量,实际上全部注册到$GLOBALS数组中。比如声明了$var,当要引用这个变量时可以直接用$var或$GLOBALS[‘var’]。这种搞法其实已经非常怪异了,不过还有更加怪异的,比如$_GET[‘name’],你可能可以使用$name直接引用或用$GLOBALS[‘name’]来引用这个变量(具体决定于PHP的配置,不过一般都是关闭的了)。 这些诡异的用法是PHP的历史原因导致的。不过作为一个PHP程序员,仍然需要知道这个。

这里的全局变量的使用有很大风险,比如我声明了一个$var,然后在其它地方再次声明一个同名的$var,那么后面的将悄悄覆盖前面的。更加糟糕的是,如果这个变量引用一个对象,但是由于一些第三方代码的引入,也声明了一个同名的变量,那么这个变量在其它地方调用方法时将出现错误,这个问题很令人烦恼。所以我们得到的教训是:慎用全局变量。

以上说得情况在面向过程的代码中中,尤为明显。由于这个问题,全局变量设计模式就产生了。以下主要探讨Magento中的全局设计模式和单态设计模式。

Magento中使用如下方法实现了全局设计模式:

Mage::register
Mage::unregister
Mage::registry
分别对应注册 和 注销 和 获取全局变量。首先看看注册一个全局变量时发生了什么事情:

1
2
3
4
5
6
7
8
9
10
11

public static function register($key, $value, $graceful = false)
{
if (isset(self::$_registry[$key])) {
if ($graceful) {
return;
}
self::throwException('Mage registry key "'.$key.'" already exists');
}
self::$_registry[$key] = $value;
}

首先检查$key是否存在$_registry数组中,如果存在就根据$graceful的设置,返回空或者抛出异常。如果不存在就直接赋值。可见,一个变量注册了,就不能再注册一个同名的变量了,这个很好解决了变量被覆盖的威胁。但是还是有不足,因为程序员可以先调用unregister来注销变量,然后设置自己的变量:

1
2
3
4
5
6
7
8
9
10

public static function unregister($key)
{
if (isset(self::$_registry[$key])) {
if (is_object(self::$_registry[$key]) && (method_exists(self::$_registry[$key], '__destruct'))) {
self::$_registry[$key]->__destruct();
}
unset(self::$_registry[$key]);
}
}

注销全局变量时如果全局变量是对象并且设置了__destruct析构函数,那么直接调用它的析构函数释放资源。否则就是简单unset,这个就依赖PHP的垃圾回收器来回收了。

当想要获取这个全局变量时,直接调用registry,可以把它看做是注册表:

1
2
3
4
5
6
7
8

public static function registry($key)
{
if (isset(self::$_registry[$key])) {
return self::$_registry[$key];
}
return null;
}

Magento内核高度使用这个模式,我们知道Magento中的控制器并不会直接和视图交互,真正和视图交互的是Block,控制器中只要使用renderLayout就可以获取输出:

1
2
3
4

$this->loadLayout();
$this->getLayout()->getBlock('content')->setSomeVar($var);
$this->renderLayout();

我们可以在控制器中添加:

1
2

Mage::register('product', $product);

一个Block中可能包含如下方法:

1
2
3
4
5

public function getProduct()
{
return Mage::registry('product');
}

这样就可以在视图中使用:

1
2

echo $this->getProduct()->getName();

Magento中的单态设计模式(Singleton Pattern)
获取一个对象的一份单态实例:

1
2

Mage::getSingleton('group/class');

过多解释在面对代码时非常苍白,所以我们这里直接看看这个方法的实现:

1
2
3
4
5
6
7
8
9
10

#File: app/Mage.php
public static function getSingleton($modelClass='', array $arguments=array())
{
$registryKey = '_singleton/'.$modelClass;
if (!self::registry($registryKey)) {
self::register($registryKey, self::getModel($modelClass, $arguments));
}
return self::registry($registryKey);
}

它使用’_singleton/’.$modelClass组建名字,然后调用register把getModel()返回的实例记录到注册表中,你可以看到,如果这个名称对应的值存在了,直接就放回这个实例,就是这样保证了全局唯一(看起来不是严格的单态模式,因为可以unregister)。这个封装另一个模式来实现的别的设计模式,也算是一个亮点吧。不过一般单态设计模式是控制类的构造函数来保证的,常见的使用方法是:

1
2

$bar = Object_Type::getInstance();

可是Magento中并没有采用这种方式。尽管可以unregister,但是它的灵活性也体现出来了。

坚持原创技术分享,您的支持将鼓励我继续创作!