В этой статье хочу рассказать о своем методе реализации шаблона проектирования “Registry“ на PHP. Если кратко о шаблоне — это алгоритм, с помощью которого можно хранить переменные в одном месте. Про этот шаблон можно почитать, например, здесь. От себя же хочу добавить, что этот шаблон очень хорошо идет в связке с шаблоном “Facade“.
Реализация
Реализация может быть различой, я же сделал ее удобной для себя.
<?php
/**
* Класс «Registry» хранит глобальные переменные и должен быть передан во все объекты.
* Класс защищен от повторного создания с помощью шаблона проектирования «Singleton».
*/
final class Registry {
private $vars = array(); // массив переменных
// Singleton ----------------------------------------------------
static private $instance = NULL; // инстанс класса
// создание и получение инстанса
static function instance() {
if (self::$instance == NULL) {
self::$instance = new Registry();
};
return self::$instance;
}
// скрытие конструктора и клонирования
private function __construct() {}
private function __clone() {}
// --------------------------------------------------------------
// публичные функции --------------------------------------------
// сохранение
function set($name, $value = NULL, $type = 'simple') {
if (!is_string($name)) {
$this->errors('arg', 1, __METHOD__, 'string', gettype($name));
return false;
};
// установка переменной
switch ($type) {
case 'protected':
if (!isset($this->vars[$name]) || (isset($this->vars[$name]) && $this->vars[$name]['type'] == 'simple')) {
$this->vars[$name] = array(
'value' => $value,
'type' => $type
);
return true;
} else {
$this->errors('var', 0, $name, __METHOD__);
return false;
};
case 'simple':
if (isset($this->vars[$name]) && $this->vars[$name]['type'] == 'protected') {
$this->errors('var', 0, $name, __METHOD__);
} else {
$this->vars[$name] = array(
'value' => $value,
'type' => $type
);
return true;
};
default:
$this->errors('var', 2, $name, __METHOD__);
return false;
};
}
// получение
function get($name) {
if (!is_string($name)) {
$this->errors('arg', 1, __METHOD__, 'string', gettype($name));
return false;
};
if (isset($this->vars[$name])) {
return $this->vars[$name]['value'];
} else {
$this->errors('var', 1, $name, __METHOD__);
return false;
}
}
// --------------------------------------------------------------
// ошибки (приватная функция) -----------------------------------
private function errors($type) {
switch ($type) {
case 'arg':
// инициализация аргументов
$number = func_get_arg(1);
$method = func_get_arg(2);
$must = func_get_arg(3);
$given = func_get_arg(4);
$call = debug_backtrace()[1];
trigger_error("argument <i>$number</i> passed to <i>$method()</i> must be an instance of <u>$must</u>, <u>$given</u> given, called in <b>" . $call['file'] . "</b> on line <b>" . $call['line'] . "</b> and defined", E_USER_WARNING);
break;
case 'var':
// инициализация аргументов
$error = func_get_arg(1);
$name = func_get_arg(2);
$method = func_get_arg(3);
// ошибки
$errors = array(
array(
'value' => "protected variable <i>$name</i> passed to <i>$method()</i> has already been registered",
'type' => E_USER_ERROR
),
array(
'value' => "variable <i>$name</i> passed to <i>$method()</i> has not been registered",
'type' => E_USER_WARNING
),
array(
'value' => "type of variable <i>$name</i> passed to <i>$method()</i> must be <u>simple</u> or <u>protected</u>",
'type' => E_USER_WARNING
)
);
$call = debug_backtrace()[1];
trigger_error($errors[$error]['value'] . ", called in <b>" . $call['file'] . "</b> on line <b>" . $call['line'] . "</b> and defined", $errors[$error]['type']);
break;
};
}
// --------------------------------------------------------------
}
?>
“Singleton“
Во избежание копирования или создания нового инстанса, нужно применить шаблон “Singleton“. Это поможет избежать проблем с доступом к переменным.
// Singleton ----------------------------------------------------
static private $instance = NULL; // инстанс класса
// создание и получение инстанса
static function instance() {
if (self::$instance == NULL) {
self::$instance = new Registry();
};
return self::$instance;
}
// скрытие конструктора и клонирования
private function __construct() {}
private function __clone() {}
// --------------------------------------------------------------
Для того, чтобы получить инстанс класса Registry, который защищен этим способом, нужно вызвать метод Registry::instance()
, который возвращает объект, если он существует, в противном случае предварительно его создав. Создать объект снаружи класса через оператор new или клонировать невозможно. Т.к. этот метод статичен, его можно вызвать в любом месте кода и, как следствие, получить объект класса Registry.
Хранение значений
Класс позволяет хранить два типа переменных — защищенные (protected) и обычные (simple). Защищенные переменные невозможно изменить после создания, а обычные — возможно.
Установка значений
bool Registry::set(string $name, mixed $value [, string $type])
Где $name
— имя переменной, $value
— значение переменной, $type
— необязательный тип переменной (по умолчанию 'simple'
). Вернет true, в случае успешного добавления, и false, в случае ошибки. Если Вы попробуете переписать защищенную переменную, то будет выведена ошибка.
Получение значений
mixed Registry::get(string $name)
Где $name
— имя переменной. Возвращает значение переменной $name
или false, в случае ошибки. При попытке получить несуществующую переменную будет выведена ошибка.
Пример
// получение инстанса
$registry = Registry::instance();
// установка защищенной переменной
$registry->set('protected_var', 'This variable is protected', 'protected');
// установка обычной переменной
$registry->set('simple_var', 'This variable is simple');
echo $registry->get('protected_var'); // => 'This variable is protected'
echo $registry->get('simple_var'); // => 'This variable is simple'
echo $registry->get('var'); // => false
// перезаписывание значения обычной переменной
$registry->set('simple_var', 'Rewrite simple variable');
// перезаписывание значения защищенной переменной обернется ошибкой
$registry->set('protected_var', 'Rewrite protected variable');
Ошибки
Класс имеет встроенный вывод ошибок, использующий функцию trigger_error(). Ниже приведены все возможные ошибки и их тип.
Тип | Ошибка | Значение |
E_USER_ERROR | protected variable test_var passed to Registry::set() has already been registered, called in /test.php on line 10 | Нельзя изменять значение защищенной переменной test_var. |
E_USER_WARNING | variable test_var2 passed to Registry::get() has not been registered, called in /test2.php on line 5 | Вы пытаетесь получить значение несуществующей переменной test_var2. |
E_USER_WARNING | type of variable test_var3 passed to Registry::set() must be simple or protected, called in /test3.php on line 7 | Вы указали неверный тип переменной test_var3, он может быть либо simple, либо protected. |
E_USER_WARNING | argument 1 passed to Registry::set() must be an instance of string, array given, called in /test4.php on line 6 | Первый аргумент метода Registry::set() должен быть строкой, Вы пытаетесь передать массив. |
Заключение
Благодаря этому шаблону проектирования, в конструкторах классов, которые вызываются фасадом в моей системе, я могу получить и сохранить необходимые для работы объекты, которые я создавал ранее (например, шаблонизатор Smarty мне нужен практически везде). Существование защищенных переменных гарантирует мне, что я нигде не перепишу их значение и не положу из-за этого работу всего сайта, а защита объекта шаблоном Singleton гарантирует, что сохраненные переменные не потеряются. Мне кажется это очень удобным и хорошим способом хранить важные глобальные данные и получать их в любом месте кода.
Автор: cheremushkin