Фичи, применимые в Yii, которые использую я

от автора

За долгое время работы с Yii Framework накопилось некоторое количество полезного опыта. Хочу им поделиться с хабрасообществом. Всё что ниже написано — плоды рефакторинга и трезвого взгляда на код.

То, о чем я расскажу под катом:

  • Открытие одной и той же странички: через ajax-запрос (без layout) и обычное открытие странички вместе с layout
  • Кеширование моделей без кода в каждой модели
  • Как сделать логирование логики с минимальным кодом
  • Как обернуть всё в транзакции с минимальным кодом
  • Как сделать так, чтобы на каждом сервере (с экземпляром приложения) не менять файл основного конфига приложения. Упрощаем деплой

Открытие одной и той же странички: через ajax-запрос и обычное открытие странички с layout

Описание: Очень полезно бывает уметь открывать одну и ту же страницу (одна и таже view + action) и как фрагмент страницы запрошенный через ajax ( $.load() ) и как цельную страницу вместе с layout.
Пример применения: Табы на bootstrap с реализацией через ajax. Они сделаны в виде <li><a></a></li> — соответственно их можно «открыть в новом окне» — тут будет полезно уметь отдать страничку вместе с layout.
Код:

# protected/components/LController.php class LController extends CController {     public function init() {         parent::init();         if (Yii::app()->request->getIsAjaxRequest()) {                    $this->layout = '//layouts/clear';        }     } } # themes/classic/views/layouts/clear.php: <?= $content ?> # в контроллерах, где это нужно, наследуемся от LController: class DefaultController extends LController { …. }

Кеширование моделей без кода в каждой модели

Описание: Все мы (разработчики под Yii) в какой то степени используем findByPk и довольно сильно нагружаем базу запросами. Пусть они и по Primary Key, но сам факт запроса неприятен. Здесь я покажу как в Yii раз и навсегда засунуть модель в кеш без лишнего кода в самих моделях
Применение: Любое приложение, где требуется кеширование. При условии, что вся работа с базой делается только через ActiveRecord и изменения производятся только через методы save() и delete() экземпляра модели.
Код:

# protected/components/LActiveRecord.php class LActiveRecord extends CActiveRecord {          public static function getByPk($pk){                 $cache = Yii::app()->cache->get(‘activerecord.’.self::$clss.’.’.$pk);                 if ($cache) $cache = unserialize($cache);                 if ($cache) return $cache;                 $clss = self::$class                 $entity = $clss::model()->findByPk($pk);                 Yii::app()->cache->set(‘activerecord.’.self::$clss.’.’.$pk,serialize($entity));         }                  public function save($runValidation = true, $attributes = NULL){                 $result = parent::save($runValidation, $attributes);                 $self = get_class($this);                 Yii::app()->cache->set(‘activerecord.’.$self::$clss.’.’.$pk, serialize($this));                 return $result;         }                  public function delete() {                 $self = get_class($this);                 $ret = parent::delete();                 Yii::app()->cache->delete(‘activerecord.’.$self::$clss.’.’.$pk);                 return $ret;         } } #все модели наследуем от LActiveRecord: class User extends LActiveRecord {         public final static $clss = “User”; //прописываем имя класса для дальнейших обращений (были в классе-родителе)         ... }  #все выборки по Primary Key делаем так: $user = User::getByPk(1); 

Логирование логики

Описание: Иногда полезно писать логи действий с моделями — что, когда, с чем. Полезно и для отладки и для анализа популярности функционала и для анализа производительности.
Применение: почти везде где требуется вышесказанное
Код:
В указанный выше класс LActiveRecord добавляем следующее:

# в метод save (на место старого parent::save): $ts = microtime(true); if ($this->isNewRecord) {         $action = 'create/' . $this->id; } else {         $action = 'update/' . $this->id; } $result = parent::save($runValidation, $attributes); $ts = microtime(true)-$ts; $this->logMe($action.’   time: ’.ts);  # в метод delete (на место старого parent::delete): $ts = microtime(true); $ret = parent::delete(); $ts = microtime(true)-$ts; $this->logMe('delete/' . $this->id.’   time:’.$ts);  #и добавляем метод logMe: private function logMe($action){         $model_log = Yii::app()->params['model_log_file'];         $table = $this->tableName();         $str = date('[Y-m-d H:i:s]') . ' [' . $table . '] ' . ' ' . $action . "\r\n";         error_log($str, 3, $model_log); }  #в наши params добавляем параметр с именем файла для лога: ‘'model_log_file’ => ‘/var/www/application/logs/model.log’; 

И снова все наши модели должны быть унаследованы от LActiveRecord.

Как обернуть всё в транзакции с минимальным кодом

Описание: Мне довольно часто нужно сделать так, чтобы все действия с базой были транзакционными, чтобы не писать код начала и конца транзакции везде, я вынес его в один метод, который yii вызывает автоматически.
Применение: везде где страшно получить кашу в базе
Код:
В указанный выше LController добавляем метод:

public function run($actionID) {         //начинается всё с копипасты из ядра yii:         if(($action=$this->createAction($actionID))!==null) {                 if(($parent=$this->getModule())===null)                         $parent=Yii::app();                 if($parent->beforeControllerAction($this,$action)) {                         //здесь начинается код отличный от кода yii, в нем мы запускаем транзакцию:                         try {                                 $transaction=Yii::app()->db->beginTransaction();                                 $this->runActionWithFilters($action,$this->filters()); //запускаем экшин                                 $transaction->commit(); //всё ок                         } catch (Exception $e){                                 $transaction->rollback(); //всё упало                                 throw $e;                         }                         //продолжается всё стандарным кодом yii:                         $parent->afterControllerAction($this,$action);                 }         } else                 $this->missingAction($actionID); } #соответственно все контроллеры, где должны быть транзакции надо унаследовать от LController 

Метод run в контроллере yii вызывает каждый раз, когда запускает контроллер, он реализован в классе CController. Нам повезло с тем, что он публичный и мы можем его переопределить. Из-за переопределения приходится частично возвращать в него код Yii, но в этом переопределении ничего плохого нет — этот код стабилен и уже работает.

Как сделать так, что бы не менять на каждом сервере с экземпляром приложения файл основного конфига. Упрощаем деплой

Описание: У нас есть задача — деплой кода на N серверов с помощью git pull. При этом у нас могут добавлятся строки в конфиг (с появлением новых модулей и прочего). Явно не хочется каждый раз руками выправлять адрес базы, memcached, других локальных настроек.
Применение: Везде где приложение развернуто в нескольких местах с разными настройками, хоть у 2х девелоперов и на продакшине.
Код:

#/protected/config/main.php #в самом начале файла добавляем строку: $local_config = require(dirname(__FILE__).’/local.php’);  #/protected/config/local.php: return array(         ‘db’ => array(                                                                     //копируем сюда строки с настройкой нашей БД, 'connectionString', 'username', ‘password’         ),         ‘memcache_servers’ => array(                 array('host'=>'127.0.0.1', 'port'=>11211, 'weight'=>60), //ваши настройки memcached         ) )  # в /protected/config/main.php делаем изменения: # 1. находим ключ ‘db’ и делаем его таким: 'db'=>$local_config[‘db’];  # 2. находим ключ ‘servers’ внутри массива ‘cache’ и делаем его таким: 'servers'=>$local_config[‘memcache_servers’] 

потом добавляем в .gitignore файл local.php. Перед деплоем не забываем на каждом сервере создать этот файл и внести локальные настройки.

Это всё работает и используется?
Да все эти методы уже используются в проекте note-space.com уже больше года. Работает это на Yii 1.1.8, возможно еще более новых версиях, возможно нет — но это не мешает Вам адаптировать код под Вашу версию фреймворка.

P.S. Если Вы будете использовать этот код и возникнут проблемы, то можно смело обращаться за поддержкой прямо ко мне — всегда рад помочь и сделать код лучше.

ссылка на оригинал статьи http://habrahabr.ru/post/157769/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *