Pattern: Registry


Подробное описание работы класса смотрите в предыдущей статье CORE.

Возможно вам будет интересна статья ServiceLocator, она имеет непосредственное отношение к этой статье.

Решил обновить класс, но очень не хотел переписывать прошлую статью, она мне дорога как память :). Поэтому решил еще раз опубликовать свежий класс. Напомню или расскажу для тех, кто не заметил сверху ссылки на предыдущую версию, что класс реализует паттерн Registry. То есть это Singleton класс, имеющий в себе ссылки на все основные ресурсы сайта. Например, удобно получать ссылку на объект для работы с базой данных с помощью выражения Registry::extract('db') не заботясь о том где, как и когда он был создан.





class Registry {


	private $tools = array(); // array
	private static $instance; // object


	/**
	 * Private constructor does nothing
	 *
	 */
	final private function __construct(){
		/* ... */
	}


	/**
	 * Return the single instance of object
	 *
	 * @return object
	 */
	public static function & __instance(){
		if (!isset(self :: $instance))
			self :: $instance = new self;
		return self :: $instance;
	}


	/**
	* Register tools
	*
	* @param object|string $tool
	* @param string $name
	* @param array $p
	* @return object
	*/
	public static function & register($tool, $name='', $p=null, $f='__instance'){
		if (is_string($tool) && !$name)
			$name = $tool;
		$instance = self :: __instance();
		$instance->tools[$name] = $instance->factory($tool, $p, $f);
		return $instance->tools[$name];
	}


	/**
	 * Unregister tools
	 *
	 * @param string $name
	 * @param bool $force
	 */
	public static function unregister($name, $force=false){
		$instance = self :: __instance();
		unset($instance->tools[$name]);
		if ($force && is_callable(array($name,"__kill")))
	   		call_user_func(array($name,"__kill"));
	}


	/**
	 * Search for tool
	 *
	 * @param srting $name
	 * @return bool
	 */
	public static function has($name){
		$instance = self :: __instance();
		if (isset($instance->tools[$name]))
			return true;
		return false;
	}


	/**
	 * Factory method
	 *
	 * @param string|object $name
	 * @return object
	 */
	public static function & factory($name,$params=null,$func="__instance"){


		if (is_object($name))
			return $name;


		if (!class_exists($name) && is_callable("__autoload"))
			__autoload($name);


		if (class_exists($name)){
			if(is_callable(array($name,$func))){
				return call_user_func_array(array($name,$func),$params); // метод $name::$func вызван статично
			}else if(!$params){ // пытаемся сэкономить время
				return new $name();
			}else{
				$reflection = new ReflectionClass($name);
				return $reflection->newInstanceArgs($params);
			}
		}
		throw new RegistryException("Class '$name' doesn't declared and can't be loaded!");


	}


	/**
	 * Access method for tools
	 *
	 * @param string $name
	 * @return object
	 */
	public static function & extract($name){
		if (!self :: has($name))
			self :: factory($name);
		$instance = self :: __instance();
		return $instance->tools[$name];
	}


	/**
	 * Overload
	 *
	 * @param string $name
	 * @return object
	 */
	public function __get($name){
		return self :: extract($name);
	}


	/**
	 * Overload
	 *
	 * @param string $name
	 * @param object|string $tool
	 * @return object
	 */
	public function __set($name,$tool){
		return self :: register($tool,$name);
	}


	/**
	 * Cloning is deprecated
	 *
	 */
	public function __clone(){
		throw new RegistryException('Clone is not allowed!');
	}


	/**
	 * Destroy all tools
	 *
	 */
	public function __destruct(){
		$instance = self :: __instance();
		$tools = array_reverse(array_keys($instance->tools),true);
		foreach ($tools as $name){
			echo "Вызыв. деструктор $namen";
			self :: unregister($name,true);
		}
	}
}


Основные изменения коснулись фабричного метода, были добавлены новые параметры и реализовано создание класса с параметрами через конструктор, что до сих пор было не возможно. Причем старый метод создания класса без параметров остался в силу того, что работает он быстрее, чем новый основанный на рефлекции. Так была исправлена ошибка, которая регистрировала свежесозданные объекты прямо в фабрике.

Из-за неудобства в использовании были убраны исключения порождаемые при попытке повторной регистрации под тем же именем и разрегистрации несуществующего объекта, теперь не придется проверять наличие/отсутствие объектов перед их заменой.

Еще одно нововведение - теперь класс имеет перегруженные методы __set и __get, благодаря которым практически отпадает надобность в статических методах.

Для демонстрации возможностей создадим два демонстрационных класса. Первый будет реализовывать паттерн Singleton а второй будет обычным.



/**
 * Класс реализовывающий паттерн Singleton должен иметь два метода
 * __instance - для создания инстанса
 * __kill - для разрушения инстанса
 */
class B
{
	private static $instance;


	final private function __construct(){
		echo "B :: __construct()n";
	}


	static function & __instance(){
		if (!isset(self :: $instance))
			self :: $instance = new self;
		return self :: $instance;
	}


	public function __kill(){
		echo "Разрушаем объект  Bn";
		self :: $instance = null;
	}


	public function __destruct(){
		echo "Объект разрушен   Bn";
	}
}


Singleton класс помимо __construct и __destruct реализовывает еще два магических метода: __instance для создания/получения сущности и __kill для уничтожения этой сущьности при разрегистрации. Напомню, что если разрегистировать класс не вызвав __kill, то ссылка на класс останется в нем самом, и его можно будет продолжать использовать, реальное же разрушение класса произойдет только по окончанию скрипта.



class C
{
	public $B;


	public function __construct($B){
		echo "C :: __construct()n";
		$this->setB($B);
	}


	public function setB($B){
		$this->B = $B;
	}


	public function __destruct(){
		echo "Объект разрушен   Cn";
	}
}


Обычный класс, создаваемый через конструктор, не должен содержать никаких дополнительных методов, и будет разрушаться при разрегистрации сразу же если на него не осталось ссылок.

Демонстрация фабричного метода, без регистрации



// Создание singleton объекта
$b = Registry::factory("B", null, "__instance");


// Или короткий способ
$b = Registry::factory("B");


// Создание обычного объекта c параметрами
$c = Registry::factory("C",array($b));


Демонстрация регистрации и удаления объекта



// Создаем и регистрируем singleton объект
$b = Registry::register("B");


// Создаем и регистрируем обычный объект с параметрами
Registry::register("C","C",array($b));


// Экстрагируем объект
$c = Registry::extract("C");


// Регистрируем уже готовый объект
Registry::register($c,"D");


// Разрушаем обычный объект
Registry::unregister("D");


// Разрушаем singleton объект
Registry::unregister("B",true);


// Разрушаем ссылку на B в С
Registry::extract("C")->setB(null);


// разрушаем ссылки на объекты в глобальной области видимости
$b = $c = null;


// теперь остался один зарегистрированный экземпляр С
echo Registry::has("C");




А теперь внимание новинка, забудьте о двойных двоеточиях! Теперь не обязательно использовать статические методы класса Registry, можно с тем же успехом работать с ним как с обычным классом, присваивая и читая его свойства с помощью перегруженных __set и __get.



// Создаем и регистрируем обычный объект
Registry::register("C");


// присваиваем инстанс класса переменной
$registry = Registry::__instance();


// читаем и пишем в свойства объекта
var_dump($registry->C);


$Registry->B = "B";
var_dump($registry->B);


$registry->D = new C(new B());


  1. Дима
    31 Январь 2009 в 23:06 | #1
    после этой статьи, начал знакомиться с паттернами. Вы не могли бы посоветовать пару хороших книг по паттернам.
  2. 4 Февраль 2009 в 23:32 | #2
    Для PHP? к сожалению нет :(
  3. Сирожа
    19 Апрель 2009 в 22:59 | #3
    Ну зачем же сразу же использовать singleton, можно прекрасно обойтись статическими методами. К тому же ты упустил важный момент управления уровнями данных. В твоём примере используется только глобальный уровень данных, в то время как "A common kind of Registry data is thread scoped", то есть к глобальному уровню можно добавить уровень сессии и уровень текущего потока. Хотя, хз, как это будет выглядеть в php.
    Ещё ты решил делегировать Registry функционал инстанциирования новых объектов, это плохо. Registry не должен заниматься конструированием объектов.
    Последнее замечание, лишающее смысла второе замечание. Открываем Patterns of Enterprise Application Architecture By Martin Fowler & Co. Читаем страничку про registry "Registry is a well-known object that other objects can use to find common objects and services". Ключевое слово "to find COMMON objects", COMMON блин, а не чтопапало, переданное через метод register.
  4. 19 Апрель 2009 в 23:49 | #4
    Пришел Сирожа и разнес все в гавно! Начнем с того что в пехепе нету потоков. И да я засунул туда фабрику потому что с ней удобнее. От того что он еще и фабрика он меньше регистром не стал.
  1. Пока что нет уведомлений.