12.2 注册表
注册表的作用是提供系统级别的对象访问功能。通过单例对象来实现,因为单例对象不能被覆盖,因此它不像普通全局变量那样被随意修改,打来不确定性,造成隐性的bug。
12.2.1 问题
企业系统被分为几个层,每个层只通过事先定义好的通道和相邻的层交流,这使得我们在一个层中获得不相邻的另一个层所需要的信息比较的麻烦。
下面我们假设有一个 ApplicationHelper 类用于读取用户信息:
// woo\controller\ApplicationHelper
function getOptions() {
if (!file_exists("data/woo_options.xml")) {
throw new woo_base_AppException("Could not find files.");
}
$option = simplexml_load_file("data/woo_options.xml");
$dsn = $option->dsn;
}
注:本章节例子围绕一个虚拟的事件列表系统(Woo,What’s On Outside)。系统由场所(venue,如剧院、电影院)、空间(space,如屏幕)和事件(event,如各种各样的电影)。本系统的操作包括创建场所、添加空间到场所和列出系统中的所有场所。
获取信息很简单,但是如何在数据层读取的信息让系统的各部分都能读取到这些配置信息呢?
一个办法是在系统中吧这些信息从一个对象传递到另一个对象,另一个对象再传递到下一个层,这样的一层一层的传递信息,类似于函数间的传参;另一个方法是创建一个中间对象 ApplicationHelper ,通过这个中间对象将信息传递到目标对象。
而注册表的解决方案是提供静态方法来让其他对象访问其他的数据。这样整个系统中的每个对象就都可以访问这些数据对象。
12.2.2 实现
首先我们实现一个注册表 Registry 对象,它的作用是存储和提供 Request 对象。
// Registry 注册表类
class Registry {
private static $instance;
private $request;
private function __construct(){}
static function instance() {
if (!isset(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
function getRequest() {
return $this->request;
}
}
// 空类 仅用于测试
class Request{}
然后就可以在系统的一个地方添加 Request 对象:
$reg = Registry::instance();
$reg -> setRequest(new Request());
然后在另一个位置获取到这些信息:
$reg = Registry::instance();
$reg -> getRequest();
Registry 是一个单例对象,通过 instance() 方法,在没有创建过 Registry 对象的情况下创建该对象并返回,在创建过该对象的情况下直接返回该对象;同时,通过静态方法能实现全局调用。这样我们就实现了在系统的任何地方都能调用该对象,进而在统的任何地方都能调用存储该对象中的数据。
12.2.2 扩展 为不同的作用域实现注册表类
作用域用于描述对象和值的可见程度。变量作用域分为3个级别:标准级别、会话级别和应用程序级别。
标准级别是指一个 HTTP 的请求时间,即从请求的开始到结束的周期。
会话级别是指在一次 HTTP 请求结束后,会话变量会被序列化并存储到系统或者数据库中,然后在下一个请求开始时取回。存放在 cookie 中的会话 ID 和通过查询字符串传递的会话 ID 被用于跟踪该回话的拥有者。利用这一点,可以在几次请求之间存放对象,保存用户数据到数据库。
应用程序级别。在 Java 和 Perl 中有一个「应用程序作用域」的概念,即内存中的所有变量可以被程序中的所有对象访问,这种作用域级别的变量可以通过注册表实现。
我们以一个 Registry 类演示三个级别的作用域:
首先是 Registry 抽象类:
namespace woo\base;
abstract class Registry {
abstract protected function get($key);
abstract protected function set($key, $val);
}
抽象类 Registry 定义了两个方法:get() 和 set()。
请求级别的注册表类隐藏 Registry 唯一的实例,并提供静态方法来设置和获取对象。
namespace woo\base;
class RequestRegistry extends Registry {
private $value = array();
private static $instance;
private function __construct(){}
static function instance(){
if (!isset(self::$instance)) {
self::$instance = new RequestRegistry();
}
return self::$instance;
}
protected function get($key) {
if (isset($this->value[$key])) {
return $this->value[$key];
}
return null;
}
protected function set($key, $val) {
$this->value[$key] = $val;
}
static function getRequest() {
return self::instance()->get('request');
}
static function setRequest(Request $request) {
return self::instance()->set('request', $request);
}
}
会话级别 注册表需要使用 PHP 内置的 Seesion 会话支持:
namespace woo\base;
class SessionRegistry extends Registry {
private static $instance;
private function __construct(){
session_start();
}
static function instance(){
if (!isset(self::$instance)) {
self::$instance = new RequestRegistry();
}
return self::$instance;
}
protected function get($key) {
if (isset($_SESSION[__CLASS__][$key])) {
return $_SESSION[__CLASS__][$key];
}
return null;
}
protected function set($key, $val) {
$_SESSION[__CLASS__][$key] = $val;
}
function getComplex() {
return self::instance()->get('complex');
}
function setComplex(Complex $complex) {
return self::instance()->set('complex', $complex);
}
}
这个类使用了 PHP 中预定义的变量 $_SESSION 来设置和取回值,并在构造器中使用session_start()
方法来开启会话。
应用程序级别的注册表相对比较复杂,这里只实现其大致过程:
namespace woo\base;
class ApplicationRegistry extends Registry {
private static $instance;
private $freezedir = "data";
private $value = array();
private $mtimes = array();
private function __construct(){}
static function instance(){
if (!isset(self::$instance)) {
self::$instance = new RequestRegistry();
}
return self::$instance;
}
protected function get($key) {
$path = $this->freezedir.DIRECTORY_SEPARATOR.$key;
if (file_exists($path)) {
clearstatcache();
$mtime = filemtime($path);
if (!isset($this->mtimes[$key]) {
$this->mtimes[$key]=0;
}
if ($mtime > $this->mtimes[$key]) {
$data = file_get_contents($path);
$this->mtimes[$key] = $mtime;
return ($this->values[$key]=unserialize($data));
}
}
if (isset($this->values[$key]) {
return $this->values[$key];
}
return null;
}
protected function set($key, $val) {
$this->value[$key] = $val;
$path = $this->freezedir.DIRECTORY_SEPARATOR.$key;
file_get_contents($path, serialize($val));
$this->mtimes[$key]=time();
}
static function getDsn() {
return self::instance()->get('dsn');
}
static function setDsn($dsn) {
return self::instance()->set('dsn', $dsn);
}
}
其基本思路是通过读写自定义文件的方式保存数据,并通过记录时间来判断文件的更新情况。只要这些文件不被清理,就相当于这个程序一直在执行,用户就可以一直读取到这些数据。