Обновить

использование псевдостатических методов на примере Zend_Db_ActiveRecord

Чулан
Случилось так, что осваивая Zend Framework я решил написать компонент Zend_ActiveRecord по функциональности как можно более похожий на Rails. Подобное предложение возникало на комьюнити зенда, но давно не обновлялось, да при этом требовало php 5.3 из-за его __callStatic(). Меня этот факт не устроил, но необходимость вызова динамических методов класса модели как статичных по прежнему остается очень актуальной темой, я же попробовал разобраться с этим как раз на примере моей собственной ActiveRecord для зенда

Допустим у нас есть модель Post, которая представляет таблицу (либо сервис) записей в блоге.

class Post extends Zend_ActiveRecord_Abstract {}


Логичным и удобным было бы производить поиск как

Post::find('all');


Тем самым мы избавляемся от необходимости инстанцировать модель каждый раз как нам понадобится доступ к поисковым методам. На первый взгляд кажется что достаточно описать этот метод как статический, но! При вызове статического метода не вызывается конструктор, а именно в нем по логике должны быть реализованы основные функции для приведения модели в рабочее состояние. Например, одним из основных преимуществ паттерна ActiveRecord является целостность и гибкое использование зависимых моделей, i.e. ассоциаций, поэтому попробуем добавить, например, комментарии к постам и произведем поиск всех постов И связанных с ними комментариев. В Rails это бы выглядело так:

class Post < ActiveRecord::Base
has_many :comments
end

class Comment < ActiveRecord::Base
belongs_to :post
end

Post.find :all, :include=>:comments


has_many и belongs_to в Rails представляют собой методы регистрации ассоциаций, по аналогии на php это должно выглядеть так:

class Post extends Zend_ActiveRecord_Abstract {
// выделяем отдельную функцию для инициализации, т.к. в php
// нет дефолтного конструктора при статических вызовах
protected static function initialize() {
// comments — в множественном числе, как в Rails,
// этот параметр обрабатывается инфлектором, так что не смущаемся
self::has_many('comments');
}
}

class Comment extends Zend_ActiveRecord_Abstract {
protected static function initialize() {
self::belongs_to('post');
}
}

// производим поиск
Post::find(array('all','include'=>'comments'));

Первое что бросается в глаза, так это то что мы должны использовать статические методы для объявления ассоциаций. Это необходимо делать потому, что на этапе вызова статического find у нас нет экземпляра класса, и вызов инициалайзера должен производится проводится вручную из find() а не из конструктора по умолчанию, что согласитесь неудобно, особенно если в теле инициализации использовать другие функции, которые по умолчанию подразумевают наличие экземпляра класса. Например

class Post extends Zend_ActiveRecord_Abstract {
function initialize{
self::has_many('comments');

// для примера поставим дату инициализации объекта
$this->set_attribute('initialized_at',timestamp());

}
}


При статическом вызове это вызовет fatal error.

Существуют также ситуации когда нужно вызывать find из уже созданного экземпляра класса

// подгузка поста с id=1
$post = new Post(1);

// вызываем поиск чего-нибудь
$something = $post->find(array('all','conditions'=>array(......));

// либо подгрузка комментариев к посту, вызывается метод find модели Comment, уже созданной по умолчанию
$otherthing = $post->comments->find(array('all'));

Все это приводит к тому что в разные моменты жизненного цикла модели необходимо знать как был осуществлен вызов метода, как статический либо как то иначе. Выход был найден: подразумевать метод find как НЕ СТАТИЧЕСКИЙ по умолчанию, что не помешает нам вызывать его статически, а при статическом вызове создавать временный экземпляр класса. PHP сам подсказал удобный способ в одну строчку.

class Zend_ActiveRecord_Abstract {

public function find($options = array()) {
// определим тип вызова по наличию $this. В статическом контексте переменная не определена
if (!isset($this)) $This = new self(); else $This = $this; // $this и $This — это разные переменные
// далее работаем с объектом не думая о последствиях, но используя $This вместо $this
$This->mehod();

}
}


либо можно использовать обвязку

class Zend_ActiveRecord_Abstract {
public static funtion get_instance() {
$obj = new self();
return $obj;
}

public funtion find($options = array()) {
if (!isset($this)) return self::get_instance()->find($options);
// далее работаем как обычно
}

}


При таком вызове мы просто эмулируем в самом псевдостатическом методе действия, которые было бы неудобно использовать в остальном коде. Т.е. в данном случае вызов

$post_instance = new Post();
$post_instance->find();

и
Post::find();


абсолютно идентичны, но в последнем случае нам не требуется каждый раз производить создание экземпляра класса
Теги:staticпсевдостатическийActiveRecordRailsphp
Хабы: Чулан
Рейтинг +3
Количество просмотров 237 Добавить в закладки 5
Комментарии
Комментарии 13

Похожие публикации

Лучшие публикации за сутки