Registry

22 Февраль 2007
Это теоритическая часть, практическую можно найти на странице CORE

Ну я думаю стоит начать из далека... когда еще деревья были большими а колбаса вкусной...
Нет это слишком рано ;) Начнем с того что паттерны в программировании были придуманы никак не для PHP, а для Smalltalk. Ну а к PHPони были притянуты "за уши", с появлением 5-ой версии.

Идея паттерна Registry состоит в том что проект (читай : сайт) содержит много разнообразных классов, например класс для работы с БД и шаблонизатор. А теперь появился класс User который должен работать с БД и передавать данные шаблонизатору. Значит он должен знать о функциях SELECT и ASSIGN двух других классов. Классы можно было бы наследовать и получить этот доступ но наследовать в PHP можно только один класс, да и если проект большой то стоит ли наследовать 150 разных классов?! Значит надо передавать экземпляры классов в функции которые их используют, или еще лучше в конструктор а там присвоить их свойствам. А зачем передавать(?!) можно сразу создать новый экземпляр прямо в конструкторе User'a, допустим, а если при создании конструкторам нужно передавать параметры? Или можно использовать паттерн Singleton для того чтобы не пересоздавать объект каждый раз но это не всегда удобно. Ну, а что если, например вы решите сменить шаблонизатор с XTemplate на Smarty и придется искать и реплейсить все названия класса.

И придумали умные программисты создавать один класс который бы знал о всех остальных, и раздавал кому нужно ссылки на них! При всем этом желательно чтоб еще никто не создал случайно второй экземпляр этого чудо класса поэтому в нем используют паттерн Singleton Еще неплохо было бы там использовать фабричные методы но мы это опустим... покрайней мере пока

Итак создадим класс A и скажем что напрямую его создать нельзя будет, но можно создать через функцию instance(), а также создадим статичный метод для регистрации некого объекта B о котором известно только что он создается напрямую через конструктор.


class A{

	public $registry;
	private static $instance;
	
	private function __construct(){
		echo "A::__construct()\n";
	}
	
    static function & instance(){
        if (!isset(self::$instance)){
            self::$instance = new self;
        }
        return self::$instance;
    }
    
    public static function register(&$obj){
    	$instance =& A::instance();
    	$instance->registry = $obj;
    }
    
	public function __destruct(){ 
		echo "A::__destruct()\n";
	}
}

class B {

	public function __construct(){
		echo "B::__construct()\n";
	}
	
	public function __destruct(){
		echo "B::__destruct()\n";
	}
}

A::instance();
A::register(new B);

//RESULT :
//A::__construct()
//B::__construct()
//A::__destruct()
//B::__destruct()

Создали, зарегистрировали, прикольно. Теперь обратите внимание на порядок следования деструкторов. Логично было ло бы, чтоб деструктор класса A вызывался ПОСЛЕ деструктора B для этого изменим несколько строк


	public function __destruct(){
		$instance =& A::instance();
    	$instance->registry = null;
		echo "A::__destruct()\n";
	}

//RESULT :
//A::__construct()
//B::__construct()
//B::__destruct()
//A::__destruct()

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


class A{

	public $registry;
	private static $instance;
	
	private function __construct(){
		echo "A::__construct()\n";
	}
	
    static function & instance(){
        if (!isset(self::$instance)){
            self::$instance = new self;
        }
        return self::$instance;
    }
    
    public static function register(&$obj,$name){
    	$instance =& A::instance();
    	$instance->registry[$name] =& $obj;
    }
    
    public static function & extract($name){
    	$instance =& A::instance();
    	return $instance->registry[$name];
    }
    
    public static function unregister($name) {
    	unset($instance->registry[$name]);
    }
    
	public function __destruct(){
		echo "Вызван деструктор A\n";
		$instance =& A::instance();
		foreach ($instance->registry as $name => &$obj){
			echo "Вызван деструктор $name\n";
    		$obj = null;
		}
		echo "Объект разрушен   A\n";
	}
}

class B{

	public function __construct(){
		echo "B::__construct()\n";
	}
	
	public function __destruct(){
		echo "Объект разрушен   B\n";
	}
}

class C{

	public function __construct(){
		echo "C::__construct()\n";
	}
	
	public function __destruct(){
		unset($this->B);
		echo "Объект разрушен   C\n";
	}
}

A::instance();
A::register(new B,'B');
A::register(new C,'C');

//RESULT
//A::__construct()
//B::__construct()
//C::__construct()
//Вызван деструктор A
//Вызван деструктор B
//Объект разрушен   B
//Вызван деструктор C
//Объект разрушен   C
//Объект разрушен   A

Однако на этом проблемы не заканчиваются. Случилось собственно то ради чего все это затевалось В объекте класса C понадобилась информация об объекте класса B


class C{

	public $B;
	
	public function __construct(){
		echo "C::__construct()\n";
		$this->B =& A::extract('B');
	}
	
	public function setB(&$B){
		$this->B =& $B;
	}
	
	public function __destruct(){
		echo "Объект разрушен   C\n";
	}
}

A::instance();
A::register(new B,'B');
A::register(new C,'C');

// или переприсваисаем значение через seter
A::extract('C')->setB(A::extract('B'));

//RESULT :
//A::__construct()
//B::__construct()
//C::__construct()
//Вызван деструктор A
//Вызван деструктор B
//Объект разрушен   B
//Вызван деструктор C
//Объект разрушен   C
//Объект разрушен   A

Примерно так выглядело ядро, этого сайта, пока я не перешел на WordPress, только немного сложнее - с добавлением фабричных методов.

  1. 16 Ноябрь 2008 в 22:14 | #1
    Спс огромное, давно искал подходящий паттерн для создания собственной CMS. Побольше бы подобных статей - блогу бы цены не было ))
  2. 5 Февраль 2009 в 21:34 | #2
    Извращенное у вас понятие о Registry. Пример registry:
    
    class DB {
      private static $instances = array();
      private function __construct($db_name) {
        $instances[$db_name] = new MySQLConnection($db_name);
      }
      public static getInstance($db_name) {
        if (!array_key_exists($db_name, self::$instances)) {
           self::$instances[$db_name] = new DB($db_name);
        }
        return self::$instances[$db_name];
      }
    }
    
  3. 6 Февраль 2009 в 11:14 | #3
    @solenko Жалко что вы до конца не дочитали, а сделали вывод по первой статье из серии.
  4. Антон
    16 Апрель 2009 в 04:36 | #4
    В PHP5 все объекты передаются по ссылке, не вижу смысла в некоторых местах ставить знак амперсанда.
  5. 16 Апрель 2009 в 05:31 | #5
    Убери их и посмотри насколько различаются результаты
Комментирование отключено.