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

в 16:21, , рубрики: ajax, php, 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 . "rn";
        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. Если Вы будете использовать этот код и возникнут проблемы, то можно смело обращаться за поддержкой прямо ко мне — всегда рад помочь и сделать код лучше.

Автор: piromanlynx

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js