Native-PHP шаблонизатор и приватные свойства объектов

Фанат

oncle terrible
Команда форума
Скорее всего решения нету, но на всякий случай спрошу.

У меня легаси система, которая характеризуется тремя свойствами
1. Native-PHP шаблонизатор
2. По старой доброй страдиции, в шаблон попадают не нормальные скаляры, а целиком модели. А чо, удобно же - в контроллере всего одно присвоение - и в шаблоне доступ к любым свойствам модели.
3. Причем лежат они в виде read-only свойств, выдаваемых строго через геттер. :(

Минусы этих пунктов очевидны, но в сочетании дают совсем грустную картину:
Для Native-PHP шаблонизатора единственный вариант сделать дефолтный искейпинг - это искейпить переменные при их отправке в шаблон, в tpl::set(),
В принципе, мгжно даже наговнокодить рекурсивный перебор назначаемых в шаблон переменных, чтобы внутренности тоже искейпились. Мало того, что этот подход сам по себе дурно пахнет - так еще и не поможет с приватными свойствами совсем - не дотянешься же. В общем - тупик.

Пока планирую переписывать весь сеттинг в шаблоны на нормальный - достали в контроллере все нужные данные из модели, да и присвоили переменным шаблона.

Но может всё-таки есть вариант искейпить автоматом?
 
Последнее редактирование:

Вурдалак

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

флоппик

promotor fidei
Команда форума
Партнер клуба
Наводящий вопрос - а у моделей есть общий предок?
 

Фанат

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

HraKK

Мудак
Команда форума
2. По старой доброй страдиции, в шаблон попадают не нормальные скаляры, а целиком модели. А чо, удобно же - в контроллере всего одно присвоение - и в шаблоне доступ к любым свойствам модели.
Я конечно извиняюсь, может я немного туплю или тугой, но чем это плохо? Я наоборот проповедую такой метод. А то что нельзя сделать автоматический эскейпинг - лично мне он не разу не нужен был, где надо пишешь автоматом, как при написании SQL запроса, автоматически пишешь же. Зато снимает тонну других проблем.

PHP:
<?php foreach($products as product): ?>
<td><?=$this->escape($product->getName()))</td>
<?php endforach ?>
да есть небольшой оверхеад от эскейпа но на пассивных шаблонах его будет еще больше

Ну и для особо извращенных есть:
http://www.php.net/manual/en/function.override-function.php
 
Последнее редактирование:

keltanas

marty cats
Зачем заставлять модель заниматься представлением самой себя?
PHP:
class ViewModel
{
    private $model;
    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function __call($name, $arguments)
    {
        return escape($this->model->$name($arguments));
    }
}
PHP:
$this->getTemplate()->tplVar = new ViewModel($model);
Ну и в зависимости от ситуации можно перейти к более частной реализации
...
Или как вариант, в зависимости от того, как модель в шаблон передается, можно заставить шаблон декорировать модель, чтобы все контроллеры не переписывать.
 

Тугай

Новичок
Как по мне то суть тут такая - можно ли это кешировать ? Если можно, то значит все в порядке, а толстые модели или контроллер это десятая проблема. :)
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Фанат, делать форматирование в самой модели или в общем предке - неправильно.
Надо подумать как вклиниться: написать свой простенький шаблонизатор, который отлавливает обращения к переменным, берет объекты, присвоенные из контроллера, и прозрачно искейпит их перед выводом.

Можно ли пропарсить все шаблоны и обернуть во что-нибудь все обращения к переменным?
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
Как-то делал демку прозрачного экспейпинга в нативных шаблонах. Вот, нашел:

PHP:
<?php

class UserContactsModel {

    public $messengers = array(
        'xmpp' => '"user<b>name"@gmail.com',
        'skype' => 'skype<h1>name',
    );

    public $phone = 'phone<i>number';

}

class UserModel {

    public $login = 'foo<b>bar';

    public $flags = array(
        'flag<b>1',
        'flag<b>2'
    );

    protected $contacts;

    public function __construct() {
        $this->contacts = new UserContactsModel;
    }

    public function getContacts() {
        return $this->contacts;
    }

}

interface EscaperInterface {

    public function escape($value);

}

class HtmlEscaper implements EscaperInterface {

    public function escape($value) {
        return htmlspecialchars((string)$value, ENT_QUOTES);
    }

}
// JsEscaper, etc

class ViewDecorator {

    protected $escaper;

    public function __construct(EscaperInterface $escaper) {
        $this->escaper = $escaper;
    }

    public function decorate($value) {
        if (is_object($value)) {
            return $this->decorateObject($value);
        }
        if (is_array($value)) {
            return $this->decorateArray($value);
        }
        return $this->decorateScalar($value);
    }

    public function decorateObject($value) {
        return new ViewDecoratedEntity($this, $value);
    }

    public function decorateArray(array $value) {
        return array_map(array($this, 'decorate'), $value);
    }

    public function decorateScalar($value) {
        return is_string($value) ? $this->escaper->escape($value) : $value;
    }

}

class ViewDecoratedEntity {

    protected $decorator;
    protected $decorated;

    public function __construct(ViewDecorator $decorator, $decoratedObject) {
        $this->decorator = $decorator;
        $this->decorated = $decoratedObject;
    }

    public function __get($value) {
        if (!isset($this->decorated->$value)) {
            return null;
        }
        return $this->decorator->decorate($this->decorated->$value);
    }

    public function __call($method, array $args) {
        if (!method_exists($this->decorated, $method)) {
            return null;
        }
        return $this->decorator->decorate(call_user_func_array(array($this->decorated, $method), $args));
    }

}

class ViewRenderer {

    protected $decorator;

    public function __construct(EscaperInterface $escaper) {
        $this->decorator = new ViewDecorator($escaper);
    }

    public function render($templateFile, array $viewData) {
        return $this->renderTemplate($templateFile, $this->decorator->decorateArray($viewData));
    }

    protected function renderTemplate($__templateFile, array $__decoratedViewData) {
        extract($__decoratedViewData);
        ob_start();
        try {
            include($__templateFile);
        } catch (\Exception $e) {
            // pass
        }
        $result = ob_get_contents();
        ob_end_clean();
        if (isset($e)) {
            throw $e;
        }
        return $result;
    }

}

abstract class Controller {

    protected $renderer;

    public function __construct() {
        $this->renderer = new ViewRenderer(new HtmlEscaper);
    }

    protected function render($templateFile, array $viewData) {
        return $this->renderer->render($templateFile, $viewData);
    }

}

class MyController extends Controller {

    public function action() {
        $user = new UserModel;
        return $this->render('template.php', array(
            'user' => $user,
        ));
    }

}

$ctrl = new MyController;
print $ctrl->action();

///////////////////////////////////////////////////////////////////////////////
// template.php:
<p>
    Login: <?= $user->login ?>
</p>

<p>
    Flags:
    <ul>
        <?php foreach ($user->flags as $key => $value): ?>
            <li><?= $key ?> = <?= $value ?></li>
        <?php endforeach; ?>
    </ul>
</p>

<p>
    Phone: <?= $user->getContacts()->phone ?>
</p>

<p>
    Messengers:
    <ul>
        <?php foreach ($user->getContacts()->messengers as $key => $value): ?>
            <li><?= $key ?> = <?= $value ?></li>
        <?php endforeach; ?>
    </ul>
</p>
/////////////////////////////////////////////////////////////
//// result:

<p>
    Login: foo&lt;b&gt;bar</p>

<p>
    Flags:
    <ul>
                    <li>0 = flag&lt;b&gt;1</li>
                    <li>1 = flag&lt;b&gt;2</li>
            </ul>
</p>

<p>
    Phone: phone&lt;i&gt;number</p>

<p>
    Messengers:
    <ul>
                    <li>xmpp = &quot;user&lt;b&gt;name&quot;@gmail.com</li>
                    <li>skype = skype&lt;h1&gt;name</li>
            </ul>
</p>
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
Например, вместо возврата escaped-строки возвращать объект, __toString которого возвращает эскейпленное значение, а метод raw() - необработанное.
 

WMix

герр M:)ller
Партнер клуба
те тут ты поторопился эскейпить, нужно было еще в одну обертку складывать.
PHP:
public function decorateScalar($value) {
     return is_string($value) ? $this->escaper->escape($value) : $value;
}
 

Фанат

oncle terrible
Команда форума
Как-то делал демку прозрачного экспейпинга в нативных шаблонах. Вот, нашел:
Охохох, это мне на пару дней моими скрипучими мозгами разбираться.
Сорри за этот вопрос, но хочу только уточнить - оно работает и со скрытыми свойствами, получаемыми через геттер?
У меня тут все модели такие: все свойства протектед, доступ через геттер. Мало того, что иссет и емпти не работают (повбывав бы!) так ещё и вот эта проблема с шаблонизацией.
 

Фанат

oncle terrible
Команда форума
Я конечно извиняюсь, может я немного туплю или тугой, но чем это плохо?
у меня есть старое заскорузлое убеждение, что ВСЯ работа с БД должна быть закончена к моменту начала рендеринга.
А в этих ваших модных моделях с лэзи лоадингом вся работа при запросе свойства только начинается. И там могут полезть любые ошибки.

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

HraKK

Мудак
Команда форума
Не долен вью лезть в базу - пусть даже и опосредрованно, через обращение к свойству модели.
А сингл респонсибилити, как ты правильно заметил это не нарушает. Хотя, я тебя с одной стороны понимаю, но как по мне, кажется, это просто немного косное мышление и нежелание привыкать к разным подходам. Я сам таким же страдаю и к активным шаблонам шел долго, но я просто заставил себя сделать пару магазинов на них и честно - мне после было настолько плевать нарушает оно что-то или нет, потому что то сколько оно мне экономило времени, а так же простота и понимания даже тех же темплейтов увеличилась настолько что обратно вернуться меня вряд ли что-то уговорит.
 
Последнее редактирование модератором:

Фанат

oncle terrible
Команда форума
потому что то сколько оно мне экономило времени
С одной стороны, я тебя понимаю.
Передаёшь объект в шаблон, и дальше уже, какая бы тебе на странице инфа ни понадобилась - она уже есть, только обращайся.
С другой - у модели и у объекта, передаваемого в шаблон, разные задачи, в общем-то. И тащить всю эту кухню с котлами и вошебойкой в шаблон - тоже некрасиво.
Щас у меня в голове шевелится что-то вроде мини-модели, обхекта, содержащего только скаляры. Какой-нибудь вырожденный вариант, возвращаемый по __toTemplate %)
 

fixxxer

К.О.
Партнер клуба
те тут ты поторопился эскейпить, нужно было еще в одну обертку складывать.
PHP:
public function decorateScalar($value) {
     return is_string($value) ? $this->escaper->escape($value) : $value;
}
да, как то так
PHP:
public function decorateScalar($value) {
    return is_string($value) ? new ViewDecoratedScalar($this->escaper, $value) : $value;
}
PHP:
class ViewDecoratedScalar {
    protected $escaper;
    protected $value;

    public function __construct(EscaperInterface $escaper, $value) {
        $this->escaper = $escaper;
        $this->value = $value;
    }
    public function __toString() {
        return $this->escaper->escape($this->value);
    }
    public function raw() {
       return $this->value;
    }
}
 
  • Like
Реакции: WMix
Сверху