Pull to refresh

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

Reading time 4 min
Views 565
Случилось так, что осваивая 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();


абсолютно идентичны, но в последнем случае нам не требуется каждый раз производить создание экземпляра класса
Tags:
Hubs:
+3
Comments 13
Comments Comments 13

Articles