yii\base\View - добавить EVENT_BEFORE_OUTPUT

Предварительное обсуждение найденных ошибок перед отправкой их авторам фреймворка, а также внесение новых предложений.
Ответить
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

yii\base\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

Доброго всем дня.
Есть в yii\base\View метод

Код: Выделить всё

    public function afterRender($viewFile, $params, &$output)
    {
        if ($this->hasEventHandlers(self::EVENT_AFTER_RENDER)) {
            $event = new ViewEvent([
                'viewFile' => $viewFile,
                'params' => $params,
                'output' => $output,
            ]);
            $this->trigger(self::EVENT_AFTER_RENDER, $event);
            $output = $event->output;
        }
    }
Который позволяет получить через Event содержимое view файла.

Задача состоит в следующем
- в одном view файле у меня есть шорткод [mycode]....[/mycode].
- сейчас я создаю handler, чтобы заменить эту часть на Html
- НО на странице присутствует меню, listМiew, несколько виджетов, галерея, в которых тоже присутсвуют свои вьюшки (набирается 50 штук и более).
- таким образом, мой хендлер начинает цепляться ко всем этим вьюшкам и выполнять в каждой поиск и замену шорткода. т.е на данном примере отрабатывает 49 раз вхолостую.
- подразумевается, что шорткодов на странице будет десятки (как в WP). Таким образом получаем десятки циклов на каждую вьюшку, среди которых содержащая шорткоды - всего одна.

Предлагаю, по аналогии, добавить вывод всего контента страницы в триггер yii\web\View::beginPage,
чтобы заменить все шорткоды в уже сформированной странице за один проход, а не по частям.

Ну или сделать некое событие, например EVENT_BEFORE_OUTPUT, где можно получить доступ к подготовленной к выводу странице.
Последний раз редактировалось Loveorigami 2017.01.11, 16:33, всего редактировалось 2 раза.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение SiZE »

Loveorigami писал(а): 2017.01.11, 10:42 - таким образом, мой хендлер начинает цепляться ко всем этим вьюшкам и выполнять в каждой поиск и замену шорткода. т.е на данном примере отрабатывает 49 раз вхолостую.
кэшировать?
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

Кешировать что?
У меня три шорткода = 3 хендлера.
1. Если я закеширую после первого прохода - 2 не отработает.
2. Потом поменяю контент в шорткоде - а он мне будет возвращать кешированную версию. Это я про те, где шорткод есть.
3. Кроме всего прочего - это не снижает количества циклов. как было 50, так и осталось.
4. Я то как раз и хочу избавиться от холостых проходов в listview, галерее, где шорктодов нет.
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

Покопался в исходниках, и придумал решение, но оно не рабочее. ;)
Последний раз редактировалось Loveorigami 2017.01.11, 16:34, всего редактировалось 1 раз.
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

Решение нашли на github-e
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение SiZE »

Есть ли смысл изобретать велосипед?

https://github.com/yiisoft/yii2-smarty
https://github.com/yiisoft/yii2-twig

Данные расширения, кэшируют результат. При изменении данных кэш всегда можно сбросить. Какие конкурентные преимущества у вашего подхода?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение zelenin »

какого подхода? ТС говорит, что нет возможности зацепится за весь контент страницы (1 штука), а только за контент вьюшек, из которых страница генерится (50 штук), и просит добавить такую возможность ибо логично (согласен). На гитхабе нашли неплохое решение.
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

SiZE писал(а): 2017.01.11, 13:15 Есть ли смысл изобретать велосипед?

https://github.com/yiisoft/yii2-smarty
https://github.com/yiisoft/yii2-twig

Данные расширения, кэшируют результат. При изменении данных кэш всегда можно сбросить. Какие конкурентные преимущества у вашего подхода?
Допишу модуль - объясню отдельно.
В 2-х словах - Возможность конфигурировать виджеты из админки для последующей вставки через шорткод как в редакторе, так и прямо во вьюшку (как блок).

простой пример.
https://github.com/loveorigami/yii2-plu ... _plugin.md
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

Написал тут про виджеты, и пошел проверить. Как оказалось, решение не совсем рабочее.
В шорткоде [gallery id=1] содержится виджет с ассетом со своими стилями и скриптами (галерея с лайтбоксом).
В response происходит просто замена, а ассет не добавляется.
В результате - на странице галерея получается не рабочей.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение zelenin »

значит надо вешаться до генерации вьюшек..
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение samdark »

Но при этом проверять заранее, не генерируется ли шорт-тег :)
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

zelenin писал(а): 2017.01.11, 15:09 значит надо вешаться до генерации вьюшек..
А что я тогда буду заменять? Контента то нет, который я получаю из вьюшек.
В событии - View::EVENT_AFTER_RENDER запускался handler, который менял шорткод из виджета и подтягивал его ассеты.
Но в таком навешивании события мой хендлер срабатывает, как я писал выше, вхолостую во всех видах.

Т.е. получается, нужно некое новое событие, которое даст возможность получить доступ ко всем фрагментам (можно и массивом) и даст возможность дозарегистрировать необходимые ассеты.

Upd - semdark тоже это подметил ))
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: yii\web\View - добавить EVENT_BEFORE_OUTPUT

Сообщение zelenin »

думаю, самый интерсный (заведомо без сайдэффектов) вариант - компилить шаблоны.
Например переопределив View::renderFile() или (не уверен) создав PhpRenderer, который будет компилить вместо renderPhpFile (If no renderer is available for the given view file, the view file will be treated as a normal PHP and rendered via [[renderPhpFile()]]). Но в простом случае (один-два шорткода) можно и вручную менять/регистрировать).
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\base\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

У меня идея следующая.
1. Есть сайт на yii2. В него нужно добавить рендер шорткодов без лишних правок в самом сайте. Т.е. отдельный независимый модуль.
2. Ставим этот модуль через композер. https://github.com/loveorigami/yii2-plugins-system
3. Конфигурируем в пару строк.
4. Подключаем хранилище плагинов, шорткодов свое или с гитхаба (будет отдельный пакет)
5. Выбираем, то что нужно, инсталлируем.
6. Там же на свое усмотрение конфигурируем сам шорткод. Например, тему подсветки кода.
https://github.com/loveorigami/yii2-plu ... ugins/code
7. По сути, как в WP.

пп 1-6 сейчас полностью реализован и все отрабатывает, как надо. Включая виджеты.
Окромя побочного эффекта, что приходится КАЖДЫЙ шорткод навешивать на ВСЕ вьюшки.
Получается даже на те, которые вывелись через шорткод виджета. Получилась рекурсия.

Поэтому я и в раздумьях - как можно оптимизировать этот момент. Может даже за счет новой реализации в ядре фреймворка ;)
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\base\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

Провел вчера весь вечер в безуспешных попытках найти боле-менее вразумительное решение рендера шорткода с виджетом.
- 1. Пришел к выводу, что на событие View::EVENT_AFTER_RENDER вешать обработчики нельзя ни в коем случае. Потому что, если шорткод будет рендерить вьюшку с шорткодом - приложение уйдет в бесконечный цикл.
Снова приходим к выводу, что нужен доступ ко всему контенту, а не его частям.

- 2. Событие, где можно получить доступ к контенту есть, https://github.com/samdark/yii2-cookboo ... cessing.md
Но оно сформирует страницу без ассетов, которые содержатся в виджете

Код: Выделить всё


This is user portfolio '[gallery]'.

Yii::$app->getResponse()->on(Response::EVENT_AFTER_PREPARE, function($event) {
    /** @var User $user */
    $user = Yii::$app->getUser()->getIdentity();
    $replacements = [
        '[gallery]' => MyGallery::widget(['id'='$user']);
    ];

    $event->sender->content = str_replace(array_keys($replacements), array_values($replacements), $event->sender->content);
});
- 3. Думал уже, как-то комбинировать. Регистрацию ассетов повесить на одно событие, а контент заменять в Response::EVENT_AFTER_PREPARE
Тоже не подходит. Т.к. на странице может и не быть шорткода [gallery], а ассеты я уже под него зарегистрировал.
Ко всему прочему - я не смогу зарегистрировать динамические параметры, настройки которого берутся из виджета (например, стиль темы для подсветки кода - https://github.com/loveorigami/yii2-plu ... et.php#L38)

- 4. Собственно, напрашивается некое событие View::EVENT_CONTENT_MANIPULATION, где можно получить доступ к контенту, навешать обработчики, "дозаявить" или перерегистрировать ассеты.
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\base\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

В общем, нашел я место, где можно объявить данное событие.
Если сделать так в yii\web\View

Код: Выделить всё

    /**
     * Marks the ending of an HTML body section.
     */
    public function endBody()
    {

        $event = new ViewEvent([
            'output' => '[gallery id=1]'
        ]);

        $this->trigger(self::EVENT_END_BODY, $event);

        echo $event->output; // получаю галерею из виджета 
        echo self::PH_BODY_END;

        foreach (array_keys($this->assetBundles) as $bundle) {
            $this->registerAssetFiles($bundle);
        }
    }
то в обработчике я делаю замену '[gallery id=1]' на виджет, Все ассеты регистрируются и попадают в $this->assetBundles)

Код: Выделить всё

    
class MyGalleryHandler{
    public static function parseShortcodes($event)
    {
        $content = $event->output; // [gallery id=1]
        $event->output = self::doShortcode($tag, $content);
    }
}
Осталось только подставить в событие $content

Код: Выделить всё

        $event = new ViewEvent([
            'output' => $content // ????
        ]);
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\base\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

Кажется решил!
Начнем с введения нового метода в yii\web\View

Код: Выделить всё

    const EVENT_CONTENT_MANIPULATION = 'contentManipulation';
    private $_content;

    public function contentManipulation()
    {
        if ($this->hasEventHandlers(self::EVENT_CONTENT_MANIPULATION)) {
            $event = new ViewEvent([
                'output' => $this->_content,
            ]);
            $this->trigger(self::EVENT_CONTENT_MANIPULATION, $event);
            $this->_content = $event->output;
        }
    }
приватное свойство $_content заполняется в 2-х местах
На примере Ajax рендера

Код: Выделить всё

    public function renderAjax($view, $params = [], $context = null)
    {
        $viewFile = $this->findViewFile($view, $context);
        $this->_content = $this->renderFile($viewFile, $params, $context);

        ob_start();
        ob_implicit_flush(false);

        $this->beginPage();
        $this->head();
        $this->beginBody();
        $this->contentManipulation();
        echo $this->_content;
        $this->endBody();
        $this->endPage(true);

        return ob_get_clean();
    }
и в endBody

Код: Выделить всё

    /**
     * Marks the ending of an HTML body section.
     */
    public function endBody()
    {
    // пропускаем повторную манипуляцию для Ajax и заполняем для обычной страницы
        if(!$this->_content){
            $this->_content = ob_get_clean();
            $this->contentManipulation();
            ob_start(); // ВАЖНО для следующей очистки в endPage()
        }

        $this->trigger(self::EVENT_END_BODY);
        echo self::PH_BODY_END;

        foreach (array_keys($this->assetBundles) as $bundle) {
            $this->registerAssetFiles($bundle);
        }
    }
и наконец, в endPage

Код: Выделить всё

    public function endPage($ajaxMode = false)
    {
        $this->trigger(self::EVENT_END_PAGE);
        $content = $this->_content.ob_get_clean(); // объединяем контент со скриптами

        echo strtr($content, [
            self::PH_HEAD => $this->renderHeadHtml(),
            self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(),
            self::PH_BODY_END => $this->renderBodyEndHtml($ajaxMode),
        ]);

        $this->clear();
    }
Последний раз редактировалось Loveorigami 2017.01.12, 11:10, всего редактировалось 1 раз.
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\base\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

В итоге - на выходе имеем событие, которое позволяет получить доступ к контенту и проделать с ним некие манипуляции. Думаю, что это будет полезно многим в решении типичных задач с шорткодами.
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: yii\base\View - добавить EVENT_BEFORE_OUTPUT

Сообщение Loveorigami »

Практическая реализация вышесказанного
viewtopic.php?f=9&t=42209

Дополнительное событие, о котором шла речь выше, назвал EVENT_DO_BODY (по аналогии с родными EVENT_BEFORE_BODY и EVENT_AFTER_BODY)
https://github.com/loveorigami/yii2-plu ... ew.php#L22
Ответить