Использование событий Events в Yii

Календарь

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

Например, у кнопки Button есть свойства width, height, label; методы move(), setFocus(), события click, mouseMove, keyPress и подобные. События могут быть и у невизуальных объектов (например, события срабатывания таймера или прихода ответа в сокет).

Для знакомства нам нужен язык, который имеет уже готовую встроенную поддержку событий. Начнём с более привычного для веб-разработчика визуально-интерфейсного языка JavaScript.

Событийное программирование в JavaScript

Предположим, что нам нужно создать кнопку и повесить на неё обработчик щелчка мышью. В простейшем случае мы можем присвоить нужный нам обработчик свойству onclick нашего элемента:

<button onclick="alert('Click!')">Button_1</button>

Программным путём это же можно реализовать так:

<script>
    var body = document.getElementsByTagName('body').item(0);
    var button = document.createElement('button');
    button.innerText = 'Button_1';
    button.onclick = "alert('Click!')";
    body.appendChild(button);  
</script>

Здесь мы создаём HTML-элемент и передаём ему функцию-обработчик обратного вызова (callback), которую кнопка вызовет при наступлении события click.

За этой записью скрывается не что иное, как встроенная возможность реализации шаблона проектирования «Наблюдатель».

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

Чтобы добавить обработчик, нам нужно передать его и имя события в метод addEventListener объекта:

function clickListener1(event){
    alert('Click 1!');
};
 
var clickListener2 = function(event){
    alert('Click 2!');
};
 
button.addEventListener('click', clickListener1);
button.addEventListener('click', clickListener2);
button.addEventListener('click', function(event){
    alert('Click 3!');
});

Здесь мы добавили три обработчика, и при клике по кнопке сработают все.

В начальном примере onclick есть ни что иное, как сокращённый вариант передачи только одного обработчика для события 'click'. С помощью него нельзя добавить несколько обработчиков по очереди.

Перепишем наш пример так:

<html>
<body>
</body>
<script>
 
    var body = document.getElementsByTagName('body').item(0);
 
    function clickListener(event){
        alert('Click on ' + event.target.innerText);
    }
 
    var button = document.createElement('button');
    button.innerText = 'Button_1';
    button.addEventListener('click', clickListener);
    body.appendChild(button);    
 
</script>
</html>

В паре с addEventListener имеется метод removeEventListener. Он с помощью аналогичной сигнатуры позволяет удалять обработчики. Про него мы скажем далее.

Заметим, что в функцию передаётся объект event. В нём содержится информация о самом событии (например, код нажатой клавиши мыши) и свойство event.target, которое будет ссылаться на тот элемент, в котором сработало событие.

Это позволяет нам, например, создать в массиве десяток кнопок и навесить на все один и тот же обработчик:

function clickListener(event){
    alert('Click on ' + event.target.innerText);
}
 
for (i=1; i<=10; i++) {
    var button = document.createElement('button');
    button.innerText = 'Button_' + i;
    button.addEventListener('click', clickListener);
    body.appendChild(button);  
}

При этом не будет никаких конфликтов, так как event.target будет указывать каждый раз на тот элемент, по которому кликнули.

Вместо этого мы можем даже использовать анонимные функции, но с ними нужно быть осторожными (если нам потребуется удалять обработчики). Например, добавим и удалим clickListener:

function clickListener(event){
    alert('Click!');
}
 
button.addEventListener('click', clickListener);
button.removeEventListener('click', clickListener);

После этого он уже не будет реагировать на щелчки по кнопке, так как clickListener ссылается в памяти на одну и ту же функцию. А если мы напишем так:

button.addEventListener('click', function(event){
    alert('Click!');
});
button.removeEventListener('click', function(event){
    alert('Click!');
});

то первый обработчик не удалится, так как при такой записи в памяти создаются две функции с совершенно разными идентификаторами.

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

В принципе, знать что у DOM элементов в JavaScript есть пара этих методов уже достаточно для реализации интерактивного пользовательского интерфейса. Но для более оптимального использования событий при разработке пользовательских интерфейсов лучше изучить их подробнее и ознакомиться с такими нюансами, как направления распространения событий и их прерывания.

Рассмотрим также использование событий в другом языке.

События в ActionScript3

Этот объектно-ориентированный язык обладает развитой системой работы с системными и пользовательскими событиями. Рассмотрим реализацию подписки на события кнопки:

package {
    import flash.display.*;    
    import flash.events.*;
 
    class Application extends Sprite {    
        public function Application():void {
            var button:Button = new Button();   
            button.label = 'Button_1';
 
            // метод этого же класса (this. указывать не обязательно)
            button.addEventListener(MouseEvent.CLICK, clickListener);
 
            // метод другого объекта
            var listener:EventListener = new EventListener();
            button.addEventListener(MouseEvent.CLICK, listener.clickListener);
 
            // статический метод класса StaticEventListener
            button.addEventListener(MouseEvent.CLICK, StaticEventListener.clickListener);
 
            addChild(button);
        }
 
        private function clickListener(event:MouseEvent):void {
            trace('Click on ' + event.target.label);
        }
    }
 
    class EventListener {
        public function clickListener(event:MouseEvent):void {
            trace('Click on ' + event.target.label);
        }
    }
 
    class StaticEventListener {
        public static function clickListener(event:MouseEvent):void {
            trace('Click on ' + event.target.label);
        }
    }
}

Здесь мы определили три обработчика (в виде приватного метода текущего объекта this, метода другого объекта listener и статического метода класса StaticEventListener). Можно использовать любой вариант.

Как видно, во многих языках работа с событиями происходит практически одинаково.

Событийный подход упрощает написание программ и игр с графическим интерфейсом. Достаточно написать класс мячика Ball, который «умеет» случайным образом изменять свои координаты по отсчёту таймера:

class Ball extends Sprite 
{
    protected var timer:Timer = new Timer(20);
 
    public function startMovie():void {
        timer.addEventListener(TimerEvent.TIMER, timerListener);
        timer.start();
    }
 
    public function stopMovie():void {
        timer.removeEventListener(TimerEvent.TIMER, timerListener);
        timer.stop();
    }
 
    protected function timerListener(event:TimerEvent):void {
        this.x += ...;
        this.y += ...;
    }
}

и вбросить десяток шариков на экран, навесив на каждый удаление по щелчку и включив перемещение:

class Game extends Sprite 
{
    public function Game():void {
        for (i=0; i<10; i++){
            // создаём шарик
            var ball:Ball = new Ball();
            // запускаем движение
            ball.startMovie();     
            // следить за щелчками по нему будем мы сами          
            ball.addEventListener(MouseEvent.CLICK, clickListener)
            // добавляем на сцену
            addChild(ball);
        }
    }
 
    private function clickListener(event:MouseEvent):void {
        if (event.target is Ball){
            // приведение типа
            var ball:Ball = Ball(event.target);
            // останавливаем таймеры
            ball.stopMovie(); 
            // перестаём следить за щелчками
            ball.removeEventListener(MouseEvent.CLICK, clickListener)
            // удаляем со сцены
            removeChild(ball);
        }
    }
}

и игра заработает. Каждый шарик будет «жить своей жизнью», то есть перемещаться по экрану независимо от других. А при щелчке по шарикам мы будем их удалять.

Этот пример служит хорошей иллюстрацией важности использования метода removeEventListener. Здесь при удалении шарика с экрана мы вручную отключили для него слежение за мышью и таймером. Это можно делать и автоматически в событиях Event.ADDED_TO_STAGE и Event.REMOVED_FROM_STAGE. Но если забыть это сделать, то таймер каждого удалённого шарика останется работать и загружать процессор в фоне (а сборщик мусора может вообще не придти, если потребление памяти не растёт).

Пользовательские события

Методы addEventListener и removeEventListener позволяют работать с уже готовым списком предопределённых событий в системе. Но что делать, если нам нужно реализовать свои события?

Представим другую игру, которая содержит игровую карту и табло с отсчётом времени. Научим компонент нашей карты принимать слушателей и оповещать их о наступлении какого-либо события.

Сначала создадим класс события:

package {    
 
    import flash.events.*;    
 
    public class MapEvent extends Event {
 
        public static const START:String = 'start'; 
        public static const SUCCESS:String = 'success';
        public static const ERROR:String = 'error';
        public static const COMPLETE:String = 'complete';
 
        public function MapEvent (type:String, bubbles:Boolean = false, cancelable:Boolean = false) { 
            super(type, bubbles, cancelable); 
        } 
 
        public override function clone():Event { 
            return new MapEvent(type, bubbles, cancelable); 
        } 
 
        public override function toString():String { 
            return formatToString('ToggleEvent', 'type', 'bubbles',    'cancelable', 'eventPhase'); 
        } 
    }
}

Методы в этом классе нам не интересны (это специфика определения события в ActionScript). Обратим внимание только на имена событий, заданных константами.

Свежеобъявленные события вида MapEvent.START мы теперь можем использовать так же, как и использовали MouseEvent.CLICK:

package {    
 
    import flash.display.*;
 
    public class Game extends Sprite {
 
        private var map:Map;
        private var timerDisplay:TimerDisplay = new TimerDisplay();
        private var soundSystem:SoundSystem = new SoundSystem();
        private var messageBox:MessageBox = new MessageBox();
 
        public function Game():void {
            map = new Map();
            map.addEventListener(MapEvent.START, mapStartListener);
            map.addEventListener(MapEvent.SUCCESS, mapSuccessListener);
            map.addEventListener(MapEvent.ERROR, mapErrorListener);
            map.addEventListener(MapEvent.COMPLETE, mapCompleteListener);
            map.init();
            addChild(map);
        } 
 
        private function mapStartListener(event:MapEvent):void {
            addChild(timerDisplay);
            timerDisplay.startTimer();
        }
 
        private function mapSuccessListener(event:MapEvent):void {
            soundSystem.playSuccess();
        }
 
        private function mapErrorListener(event:MapEvent):void {
            soundSystem.playError();
        }
 
        private function mapCompleteListener(event:MapEvent):void {
            timerDisplay.stopTimer();
            addChild(messageBox);
            messageBox.showSuccess();
        }
    }
}

При запуске игры мы создаём экземпляр игровой локации, передаём ему наши обработчики для её событий, запускаем и добавляем карту на экран. Карта должна загрузить все ресурсы, расставить шарики и послать сообщение MapEvent.START. Также она должна вызывать событие MapEvent.SUCCESS при каждом попадании по шарику и MapEvent.ERROR при щелчке по фону:

package {    
 
    import flash.display.*;
    import flash.events.*; 
 
    public class Map extends MovieClip {
 
        private var balls:Array = new Array();
        private var background:Background = new Background();
 
        public function Map:void {}
 
        public function init():void {
            // загружаем ресурсы
            ...
 
            // добавляем фон     
            background.addEventListener(MouseEvent.CLICK, backgroundClickListener);
            addChild(ball);
 
            // добавляем шарики на карту поверх фона и запускаем их перемещение
            for (i=0; i<10; i++){
                var ball:Ball = new Ball();
                ball.startMovie();        
                ball.addEventListener(MouseEvent.CLICK, ballClickListener);
                addChild(ball);
            }    
 
            // ...и сообщаем о готовности
            sendSrartNotify();
        }
 
        private function backgroundClickListener(event:MouseEvent):void {
            if (event.target is Background){     
                // попали в фон, значит промахнулись мимо шарика
                sendErrorNotify();
            }
        }
 
        private function ballClickListener(event:MouseEvent):void {
            if (event.target is Ball){     
                var ball:Ball = Ball(event.target);           
                // удаляем шарик с экрана и из массива
                // не забыв всё отключить
                ball.stopMovie();   
                ball.removeEventListener(MouseEvent.CLICK, ballClickListener);
                removeChild(ball);
                balls.splice(balls.indexOf(ball), 1);
 
                // если шарики закончились       
                if (balls.length == 0) {
                    // сообщаем о завершении игры         
                    sendCompleteNotify();
                }
            }
        }
 
        private function sendSrartNotify():void {
            dispatchEvent(new MapEvent(MapEvent.START, true, false)); 
        }
 
        private function sendErrorNotify():void {
            dispatchEvent(new MapEvent(MapEvent.ERROR, true, false)); 
        }
 
        private function sendCompleteNotify():void {
            dispatchEvent(new MapEvent(MapEvent.COMPLETE, true, false)); 
        }
    }    
}

Строка

dispatchEvent(new MapEvent(MapEvent.START, true, false));

представляет собой создание экземпляра event и передача его на генерацию методом dispatchEvent() родительского класса:

var event:MapEvent = new MapEvent(MapEvent.START, true, false); 
this.dispatchEvent(event);

В ActionScript не обязательно указывать this. перед методами и полями текущего класса, поэтому мы пишем просто dispatchEvent(event).

То есть мы создали свои события, «научили» наш объект их запускать, и теперь можем подписаться на них с помощью метода addEventListener этого объекта.

В данном случае класс Game представляет из себя полноценный контроллер, так как загружает представления (карту и другие виджеты), подписывается на их события и командует их действиями. При этом ни карта, ни виджет таймера, ни шарики не догадываются о том, кто их использует и кто находится рядом. Они выполняют только свои встроенные обязанности и оповещают о своих событиях всех, кто на них подписался.

Теперь не надо в цикле или таймером проверять, готова карта или нет. Просто передаём функцию обратного вызова, и карта сама вызовом этого метода сообщит о своей готовности.

Использование событий в Yii

Язык PHP не имеет графического интерфейса, не работает с окнами, не способен отлавливать нажатия клавиш, события таймера и не способен что-то переспросить посреди программы с помощью STDIN (вне консольного режима). В отличие от других программ он запускается на один пролёт и должен вернуть текстовый результат на основе входных параметров $_REQUEST, $_SERVER и подобных. Он не может запуститься и ждать нажатия клавиши. Именно поэтому в нём неуместны примеры асинхронных событий вроде щелчков мыши и нет встроенных событий и методов для работы с ними.

Некоторые фреймворки и ORM, как более высокоуровневые программные системы, для универсализации разработки так или иначе предоставляют (эмулируют) этот удобный дизайн-паттерн «Наблюдатель» в дополнение к «Шаблонному методу».

При знакомстве с ActiveRecord разработчик узнаёт, что в модель можно добавить некоторые «специальные» методы, например beforeSave() и afterSave(), которые будут вызываться автоматически до и после сохранения записи:

class User extends CActiveRecord 
{
    protected function beforeSave() {
        if (parent::beforeSave()) {
            echo 'Ещё не сохранили';
            return true;
        } else {
            return false;
        }
    } 
 
    protected function afterSave() {
        echo 'Уже сохранили';
        parent::afterSave()
    }
}
 
class TestController extends Controller
{
    public function actionEvents() {
        $user = new User();
        $user->name = 'Вася';
        $user->save();
    }
}

Изучим эти два метода.

Перейдёим в класс CActiveRecord, а конкретнее в его метод save():

class CActiveRecord extends CModel 
{
    public function save($runValidation=true, $attributes=null) {
        if(!$runValidation || $this->validate($attributes))
            return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
        else
            return false;
    }
}

Здесь в зависимости от того, новая это запись или нет, происходит вызов метода вставки либо обновления строки в базе данных. Рассмотрим теперь их:

class CActiveRecord extends CModel 
{
    public function insert($attributes=null) {
        ...
        if($this->beforeSave()) {
            ...
            if($command->execute()) {
                ...
                $this->afterSave();
                ...
                return true;
            }
        }
        return false;
    }
 
    public function update($attributes=null) {
        ...
        if($this->beforeSave()) {
            ...
            $this->updateByPk(...);
            ...
            $this->afterSave();
            return true;
        } 
        return false;
    }
}

Эти методы до выполнения команды вызывают шаблонный метод beforeSave(), а после успешного сохранения запускают afterSave(). Здесь же в классе есть заготовки этих методов:

class CActiveRecord extends CModel 
{
    protected function beforeSave() {
        if($this->hasEventHandler('onBeforeSave')) {
            $event = new CModelEvent($this);
            $this->onBeforeSave($event);
            return $event->isValid;
        } else return true;
    }
 
    protected function afterSave() {
        if($this->hasEventHandler('onAfterSave'))
            $this->onAfterSave(new CEvent($this));
    }
 
    public function onBeforeSave($event) {
        $this->raiseEvent('onBeforeSave', $event);
    }
 
    public function onAfterSave($event) {
        $this->raiseEvent('onAfterSave', $event);
    }
}

При вызове parent::beforeSave() и parent::afterSave() в переопределённых нами методах вызываются именно эти оригинальные методы.

Здесь и происходит самое интересное. Строкa

$event = new CModelEvent($this);

есть ни что иное, как ручное создание переменной event:

$event = new CModelEvent();
$event->sender = $this;

где $event->sender повторяет event.target из прошлых примеров. Таким образом, строки

$event = new CModelEvent($this);
$this->raiseEvent('onBeforeSave', $event);

являются аналогом запуска события в нашей игре:

var event:MapEvent = new MapEvent('start', true, false); 
this.dispatchEvent(event);

Если на это событие кто-либо был подписан, то у всех подписчиков запустится переданная ими функция с параметром $event.

Для подписки на события у класса CComponent, соответственно, имеются методы attachEventHandler() и detachEventHandler():

class TestController extends Controller
{
    public function actionEvents() {
        $user = new User();
        $user->name = 'Вася';
        $user->attachEventHandler('onBeforeSave', array($this, 'userBeforeSaveListener'));
        $user->attachEventHandler('onBeforeSave', 'simpleBeforeSaveListener');
        $user->attachEventHandler('onAfterSave', array('EventListener', 'userAfterSave'));
        $user->save();
    }
 
    public function userBeforeSaveListener($event) {
        echo 'Ещё не сохранили пользователя ' . CHtml::encode($event->sender->name);
    }
}
 
function simpleBeforeSaveListener($event) {
    echo 'Ещё не сохранили пользователя ' . CHtml::encode($event->sender->name);
}
 
class EventListener
{
    public static function userAfterSave($event) {
        echo 'Уже сохранили пользователя ' .  CHtml::encode($event->sender->name);
    }
}

Здесь мы навесили несколько обработчиков, оформленных различным образом.

Если Вам не нужно добавлять несколько обработчиков для одного и того же события, то можно использовать свойство вида on*, действующее через магический сеттер __set():

$user->onAfterSave = array('EventListener', 'userAfterSave');

Используя этот вариант можно нечаянно перезаписать все предыдущие обработчики (например, пропадут подписки всех поведений модели User). В то же время такая запись позволяет задавать обработчики событий прямо в конфигурационном файле. Например, возьмём пример из рецепта по событиям:

return array(
 
    ...
 
    'onBeginRequest' => function($event){
        return ob_start('ob_gzhandler');
    },
 
    'onEndRequest' => function($event){
        return ob_end_flush();
    }
);

Здесь мы подписались на события onBeginRequest и onEndRequest самого приложения для gzip-сжатия всего HTML-кода. Если нужно подключить несколько обработчиков, то лучше эти две функции объединить в класс-поведение и указать его в списке behaviors, как мы подключали DModuleUrlRulesBehavior.

Варианты передачи обработчиков событий

Метод attachEventHandler() не очень привиредливый, так как может принимать либо строковое название функции, либо массив из класса и метода или массив из объекта и метода, либо анонимную функцию:

// передача имени обычной функции
$user->onAfterSave = 'handleEvent';
 
// передача анонимной функции
$user->onAfterSave = function($event){
    ...
};
 
$handler = function($event){...};
$user->onAfterSave = $handler;
 
// статический метод класса
$user->onAfterSave = array('MyEventHandler', 'handleEvent');
$user->onAfterSave = 'MyEventHandler::handleEvent';
 
// статический метод класса с namespace
$user->onAfterSave = array('\MyLibrary\MyEventHandler', 'handleEvent');
$user->onAfterSave = '\MyLibrary\MyEventHandler::handleEvent';
 
// нестатический метод объекта
$handler = new MyEventHandler();
$user->onAfterSave = array($handler, 'handleEvent');
$user->onAfterSave = array(new MyEventHandler(), 'handleEvent');
 
// метод текущего объекта
$user->onAfterSave = array($this, 'handleEvent');

Пример использования пользовательских событий в Yii

Пусть в нашем интернет-магазине мы подключили сервис для приёма оплаты (например, мерчант Robokassa или Яндекс.Деньги) и настроили свой аккаунт так, чтобы при оплате мерчант посылал POST запрос на наш URL /payment/process.

Добавим в наш компонент оплаты вызов события 'onPaid' при успешной оплате:

class PaySystem extends CAppicationComponent
{
    public $client_id;
    public $client_password;
 
    ...
 
    public function process($order_id) {
 
        // запрашиваем статус заказа у мерчанта
        ...
 
        // если заказ оплачен, то запускаем наше событие        
        if ($paid) {
            // если есть подписчики
            if($this->hasEventHandler('onPaid')){
                // создаём объект события, передавая себя в качестве sender
                $event = new CEvent($this);
                // событию можно присвоить для передачи любые параметры 
                // используя его свойство params
                $event->params = array(
                    'order_id' => $order_id,
                ); 
                $this->onPaid($event);
            }
            return true;
        }
 
        return false;
    }
 
    public function onPaid($event) {
        $this->raiseEvent('onPaid', $event);
    }
}

Теперь в конфигурационном файле вместе с другими параметрами укажем обработчик для события onPaid:

return array(
 
    ...
 
    'components' => array(
 
        ...
 
        'paySystem' => array(
            'class' => 'application.components.PaySystem',
            'client_id' => 'myId',
            'client_password' => 'qwer',
            'onPaid' => array('Order', 'onOrderPaid'),
        },
    },
 
    ...
);

Это указание статического метода. Создадим его прямо в модели Order. Он будет помечать соответствующий заказ оплаченным:

class Order extends CActiveRecord
{
    ...
 
    public static function onOrderPaid($event) {
        $order = Order::model()->findByPk($event->params['order_id']);
        $order->paid = 1;
        $order->save();
    }
}

В нашем контроллере просто примем пришедший от мерчанта номер заказа и передадим его компоненту оплаты:

class PaymentController extends Controller
{
    public function actionProcess() {
        $order_id = Yii::app()->request->getParam('id');
        Yii::app()->paySystem->process($order_id);
    }
}

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

Это можно было бы сделать и без использования событий и производить изменение статуса заказа в контроллере или самом компоненте. Но сейчас можно легко поделиться этим компонентом с соседом, и он сам может вешать любой свой обработчик, не задумываясь, как устроен ваш компонент. Так что можно и не использовать свои события, полностью полагаясь на шаблонные методы моделей и поведений, но при желании можно пойти и этим путём.

Некоторые другие нюансы можно подсмотреть в рецепте о событиях на официальном сайте.

События в Yii используются и в поведениях. Про них есть отдельный пост.

На десерт рекомендую посмотреть классный доклад о событиях:

UPD: Провели недавно похожий вебинар по событиям в Yii2.

Другие статьи

Каждый разработчик, знакомящийся с каким-либо фреймворком, проходит некий путь знакомства с системой маршрутизации запросов в нём. Данный компонент присутствует во многих системах и служит для использования «красивых» адресов страниц. В этой статье мы познакомимся с работой этой системы.

Нравятся ли вам компьютерные игры? Нравится ли то приятное чувство, когда растут проценты прохождения миссий и от уровня к уровню повышаются умения вашего персонажа или скорость и мощь вашего автомобиля? Как за неделю игры набираются десятки артефактов и растут запасы золота? Прямо сейчас вам предоставляется исключительная возможность сыграть в стратегию от первого лица, в которой «прокачать» нового, более чем реального героя.

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

Для вывода ленты записей на страницу в Yii имеется очень удобный готовый виджет CListView. Совместно с провайдером данных он позволяет выводить элементы с разбивкой на страницы и сортировкой. Но при разработке некоторых интернет-магазинов и всевозможных каталогов часто возникает необходимость в переключении числа элементов на странице. Попробуем добавить меню «Выводить по: 10 20 30» в нашу ленту записей.

Комментарии

 

Виталий Иванов

Классная тема. Спасибо. Очень познавательно!

Ответить

 

Максим

Спасибо. Доступно и очень подробно. Помогло разобраться с событиями.

Ответить

 

Vench

Хорошая статья, думаю полезно отметить что "события" это шаблон (паттерн) проектирования Observer (наблюдатель.

Ответить

 

Sirega

Что то мне тяжело даются события

Ответить

 

eSolo

Видео доклад ни о чем.

Ответить

 

Dakilla

как-то запутанно

Ответить

 

Dakilla

не пойму обработчики нужно вешать до или после вызова события?

Ответить

 

Дмитрий Елисеев

До. Потом событие сработает и запустит все навешанные обработчики.

Ответить

 

Andrey

Добрый день, понравилась статья, начал эксперименты, интересует этот кусочек:

return array(
 
    ...
 
    'onBeginRequest' => function($event){
        return ob_start('ob_gzhandler');
    },
 
    'onEndRequest' => function($event){
        return ob_end_flush();
    }
);

Вы указываете, что данный кусочек должен быть в файле конфигурации? Какой именной файл Вы имеете ввиду? Я попробовал вставить в config/main.php , но мне вышла ошибка, т.е. в массиве {} странно использовать. На просторах гугла нашел такой вариант:

'onBeginRequest'=>create_function('$event', 'return ob_start("ob_gzhandler");'),
'onEndRequest'=>create_function('$event', 'return ob_end_flush();'),

Вроде рабочий, просто интересно, какой файл Вы имели ввиду?

Ответить

 

Дмитрий Елисеев

Да, основной конфиг веб-приложения config/main.php.
Анонимные функции появились в PHP 5.3, так что ошибка может быть из-за старой версии.

Ответить

 

Andrey

> Но сейчас можно легко поделиться этим компонентом с соседом...

В данном случае компонентом является элемент

'onPaid' => array('Order', 'onOrderPaid')

в конфиге и класс process или что-то другое?

Ответить

 

Дмитрий Елисеев

Имеется в виду компонент PaySystem. Сосед может просто взять его у Вас и легко навесить на него любой свой обработчик в конфиге.

Ответить

 

Andrey

Спасибо! Вы не планируете написать книгу по Yii/Yii2? Я бы купил.

Ответить

 

Дмитрий Елисеев

По Yii1 смысла уже особого нет. А по Yii2 вполне можно.

Ответить

 

Александр Махорин

Добрый день. Подскажите, как лучше всего поступить с передаваемыми параметрами, если у события предполагается много обработчиков? Например, после успешной оплаты необходимо:
- отправить письмо админу
- отправить письмо клиенту
- добавить в лог отметку об оплате

В $event->params можно передавать вложенные массивы с парамсами, а каждый обработчик будет уже выгребать своё - насколько такой подход оптимален? Yii ведь не позволяет для каждого обработчика указывать свои парамсы...

Ответить

 

Дмитрий Елисеев

Обычно вся информация берётся из самой модели заказа, доступной в $event->sender, и связанных с ней моделях. Так что необходимости в дополнительных параметрах у меня не возникало.

Ответить

Оставить комментарий

Войти | Завести аккаунт


(никто не увидит)



Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>