Шаблон проектирования „Registry“ на PHP

в 15:04, , рубрики: patterns, php, Registry, singleton, метки: , , ,

В этой статье хочу рассказать о своем методе реализации шаблона проектирования “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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js