внезапно обнаружил, что если переменные в шаблоне не определены, возникает Notice теперь понятно, почему psih делает в принципе, можно делать так PHP: <?php public function compile(){ $err_level = error_reporting(0); extract($this->vars); ob_start(); include($this->path); error_reporting($err_level); return ob_get_clean(); } тогда на этапе отладки или вообще когда вклчены все ошибки, не будут мешаться ненужные нотисы.
Если заданы не все переменные в шаблоне, то это как раз нотис - сигнал подумать. Может шаблон не совсем верен? Может я переменную забыл впихнуть?
Luge Т.е. когда-то надо записать в инпут значение, а когда-то нет? Тогда надо присвоить переменной пустую строку. Все равно считается, что переменные надо инициализировать начальными значениями явно. PHP: <?php $inp=""; if (что-то){ Что-то делаем; $inp="Начальное значение"; } $tpl= new Template(t.tpl); $tpl->field_value=$inp; Ну или можно сделать шаблон формы: PHP: <?php class AuthFormTpl extends Template{ function __construct($cache=null, $dir=null){ parent::__construct(DOCROOT."/Templates/AuthForm.tpl", $cache, $dir); $this->login=""; $this->pass=""; } }
Volt(220) Проблема возникнет, если несколько контроллеров используют какой-то один шаблон. Допустим мне надо чтобы в неком таком глобальном шаблоне на конкретной странице выводился дополнительный блок. Я пишу в шблоне HTML: <?php echo $block?> теперь мне надо по твоей логике в каждом контроллере, которые используют этот шаблон когда будут выводить другие страницы, дописать PHP: $template -> block = null; либо выносить логику в шаблон чтобы проверять, та ли это страница.
Volt(220) Мы с Luge работали вместе почти полтора года, так что подход он мой знает. Плюс такого похода в том, что один и тот же шаблон у нас использовался как для добавления, так и для редактирования. Обработка ошибок сводится к выбрасыванию Exception и подстановку через простейший foreach данных из POST в хранилище шаблонизатора. А если такой переменной нет, он просто возвращает пустую строку. Вот вырезка из проекта с шаблонами и кодом, страница из админки: список, редактирование, добавление: PHP: <?php class Loads extends ModuleCore { // Constructor. destructor and other stuff /** * Show load list * */ public function showloads() { $sql = 'SELECT * FROM phone_loads LEFT JOIN phone_load_operators ON plo_id = pld_plo_id LEFT JOIN currencies ON cur_id = pld_cur_id ORDER BY plo_id ASC, pld_load ASC'; $res = $this->db->query($sql); if ($res && $res->num_rows) { $rows = array(); while ($row = $res->fetch_assoc()) { $row = $this->utils->htmlsc($row); $rows[] = $row; } $this->parser->set('data', $rows); } $this->parser->append('content_center', $this->box(_("Loads"), $this->template('showloads.htm'))); } /** * Edit existing load * */ public function editLoad() { Core::setLayout('layout_popup.htm'); if (!isset($this->session['auth']) || $this->session['auth']['class'] < self::UC_SYSADMIN) { $this->parser->set('content_popup', CLOSE_AND_REFRESH); return; } $pld_id = (int)Core::getArg(2); $this->parser->set('operators', $this->getOperators()); $this->parser->set('currencies', $this->getCurrencies()); $this->parser->set('loadtypes', $this->getLoadTypes()); $sql = 'SELECT * FROM phone_loads WHERE pld_id = '.$pld_id; $res = $this->db->query($sql); if (!$res || $res->num_rows == 0) { $this->parser->append('content_popup', CLOSE_AND_REFRESH); return; } foreach ($res->fetch_assoc() as $k => $v) { $this->parser->set($k, $this->utils->htmlsc($v)); } if (is_post() && isset($_POST['do'])) { try { $post = $this->db->escape($this->validateLoad($_POST)); $sql = 'UPDATE phone_loads SET pld_plo_id = '.(int)$post['pld_plo_id'].', pld_load = '.(int)$post['pld_load'].', pld_cur_id = '.(int)$post['pld_cur_id'].', pld_status = "'.$post['pld_status'].'", pld_sms = '.(int)$post['pld_sms'].', pld_expire = '.(int)$post['pld_expire'].', pld_code = "'.$post['pld_code'].'", pld_type = "'.$post['pld_type'].'" WHERE pld_id = '.$pld_id; $this->db->query($sql); $this->parser->append('content_popup', CLOSE_AND_REFRESH); return; } catch (CoreException $e) { $this->parser->set('error', $e->getMessage()); foreach ($_POST as $k => $v) { $this->parser->set($k, $this->utils->htmlsc($v)); } } } $this->parser->append('content_popup', $this->box(_("Edit load"), $this->template('showloads_edit.htm'))); } /** * Add new load * */ public function addLoad() { Core::setLayout('layout_popup.htm'); if (!isset($this->session['auth']) || $this->session['auth']['class'] < self::UC_SYSADMIN) { $this->parser->set('content_popup', CLOSE_AND_REFRESH); return; } $this->parser->set('operators', $this->getOperators()); $this->parser->set('currencies', $this->getCurrencies()); $this->parser->set('loadtypes', $this->getLoadTypes()); if (is_post() && isset($_POST['do'])) { try { $post = $this->db->escape($this->validateLoad($_POST)); $sql = 'INSERT INTO phone_loads (pld_plo_id, pld_load, pld_cur_id, pld_status, pld_sms, pld_expire, pld_code, pld_type) VALUES('.(int)$post['pld_plo_id'].', '.(int)$post['pld_load'].', '.(int)$post['pld_cur_id'].', "'.$post['pld_status'].'", '.(int)$post['pld_sms'].', '.(int)$post['pld_expire'].', "'.$post['pld_code'].'", "'.$post['pld_type'].'")'; $this->db->query($sql); $this->parser->append('content_popup', CLOSE_AND_REFRESH); return; } catch (CoreException $e) { $this->parser->set('error', $e->getMessage()); foreach ($_POST as $k => $v) { $this->parser->set($k, $this->utils->htmlsc($v)); } } } $this->parser->append('content_popup', $this->box(_("Add load"), $this->template('showloads_edit.htm'))); } /** * Validate and filter all form fields * * @param array $post * @return array */ private function validateLoad(array $post) { $post = $this->utils->stripTags($post); if (!array_key_exists($post['pld_plo_id'], $this->getOperators())) { throw new CoreException(_("Please select operator!")); } if (!array_key_exists($post['pld_cur_id'], $this->getCurrencies())) { throw new CoreException(_("Please select currency!")); } if (!in_array($post['pld_type'], $this->getLoadTypes())) { throw new CoreException(_("Please select load type!")); } if (!is_numeric($post['pld_load'])) { throw new CoreException(_("Please enter only numbers into load field!")); } if (!is_numeric($post['pld_expire'])) { throw new CoreException(_("Please enter only numbers in expires field!")); } if (!is_numeric($post['pld_sms'])) { throw new CoreException(_("Please enter only numbers in free sms amount field!")); } if (mb_strlen($post['pld_code']) > 15) { throw new CoreException(_("Load code is 15 symbols max!")); } $post['pld_status'] = (isset($post['pld_status']) && $post['pld_status'] == 'enabled') ? 'enabled' : 'disabled'; return $post; } } И шаблоны: showloads.htm PHP: <a href="/loads/show.html">Show transactions</a> | <a href="/loads/operators.html">Show operators</a> | <a href="/loads/merchants.html">Show merchants</a> | Show loads | <a href="/loads/loadprices.html">Show load prices</a> <br /> <br /> <?php if (get('user_class', true) === true):?> <input type="button" value="<?php echo _("Add new load")?>" onclick="return openWindow('/loads/addLoad.html', 300, 400);"> <?php endif;?> <table cellspacing="0"> <tr> <th><?php echo _("ID")?></th> <th><?php echo _("Operator")?></th> <th><?php echo _("Load")?></th> <th><?php echo _("Expires in days")?></th> <th><?php echo _("Free SMS")?></th> <th><?php echo _("Type")?></th> <th><?php echo _("Code")?></th> <th><?php echo _("Status")?></th> <th> </th> </tr> <?php if (is_set('data')): foreach (get('data') as $v):?> <tr> <td><?php echo $v['pld_id']?></td> <td><?php echo $v['plo_name']?></td> <td><?php echo $v['pld_load']?> <?php echo $v['cur_code']?></td> <td><?php echo $v['pld_expire']?></td> <td><?php echo $v['pld_sms']?></td> <td><?php echo $v['pld_type']?></td> <td><?php echo $v['pld_code']?></td> <td><?php echo $v['pld_status']?></td> <td><?php if (get('user_class', true) === true):?> <a href="#" onclick="return openWindow('/loads/editLoad/<?php echo $v['pld_id']?>.html', 350, 400);"><?php echo _("Edit")?></a> <?php endif;?> </td> </tr> <?php endforeach; else:?> <tr> <td colspan="4"><?php echo _("No loads avaliable")?></td> </tr> <?php endif;?> </table> showloads_edit.htm PHP: <form method="post" action=""> <table cellspacing="0"> <?php if (is_set('error')):?> <tr> <td><label><?php echo _("Error")?></label></td> <td class="error"><?php echo get('error')?></td> </tr> <?php endif;?> <tr> <td><label><?php echo _("Operator")?></label></td> <td><?php $op = get('pld_plo_id')?> <select name="pld_plo_id"> <?php foreach (get('operators') as $k => $v):?> <option value="<?php echo $k?>"<?php echo $op == $k ? ' selected="selected"' : '' ?>><?php echo $v?></option> <?php endforeach;?> </select> </td> </tr> <tr> <td><label><?php echo _("Load")?></label></td> <td><input type="text" name="pld_load" value="<?php echo get('pld_load')?>" maxlength="4" /></td> </tr> <tr> <td><label><?php echo _("Currency")?></label></td> <td><?php $cur = get('pld_cur_id')?> <select name="pld_cur_id"> <?php foreach (get('currencies') as $k => $v):?> <option value="<?php echo $k?>"<?php echo $cur == $k ? ' selected="selected"' : '' ?>><?php echo $v?></option> <?php endforeach;?> </select> </td> </tr> <tr> <td><label><?php echo _("Expires")?></label></td> <td><input type="text" name="pld_expire" value="<?php echo get('pld_expire')?>" maxlength="4" /></td> </tr> <tr> <td><label><?php echo _("Free sms")?></label></td> <td><input type="text" name="pld_sms" value="<?php echo get('pld_sms')?>" maxlength="4" /></td> </tr> <tr> <td><label><?php echo _("Type")?></label></td> <td><?php $type = get('pld_type')?> <select name="pld_type"> <?php foreach (get('loadtypes') as $v):?> <option value="<?php echo $v?>"<?php echo $type == $v ? ' selected="selected"' : '' ?>><?php echo $v?></option> <?php endforeach;?> </select> </tr> <tr> <td><label><?php echo _("Code")?></label></td> <td><input type="text" name="pld_code" value="<?php echo get('pld_code')?>" maxlength="15" /></td> </tr> <tr> <td><label><?php echo _("Status")?></label></td> <td><input type="checkbox" name="pld_status" value="enabled" <?php echo get('pld_status') == 'enabled' ? 'checked="checked" ' : ''?>/></td> </tr> <tr> <td colspan="2" style="text-align: center;"> <input type="submit" value="<?php echo _("Save")?>" /> <input type="button" value="<?php echo _("Close")?>" onclick="window.close();" /> </td> </tr> </table> <input type="hidden" name="do" value="1" /> </form> А вот так выглядит класс Parser PHP: <?php <?php /* * Templates parser class */ class Parser{ /** * Template name array. * * @var array */ private $_template; /** * Variable array * * @var array */ private $_variables; /** * Global variable array * * @var array */ private $_global; /** * Constructor * * @return void */ public function __construct(Core $core) { $this->_template = array(); $this->_variables = array(); $this->_global = array(); } public function __destruct() { } /** * Set template variable * * @param string $var_name Name, under witch data will be stored in internal associative array * @param mixed $data * @param bool $global If is set to "true", then global variable is created. This means it doesn't get deleted when fetched. */ public function set($var_name, $data, $global = false) { $this->{$global ? '_global' : '_variables'}[$var_name] = $data; } /** * Load template by include and return processed string. * If file wasn't found - give a fatal error. * * @param string $file * @return string */ public function load($file) { if (file_exists($file)) { ob_start(); include($file); return ob_get_clean(); }else { trigger_error('File '.$file.' doesn\'t exist', E_USER_ERROR); } } /** * Appends data to existing variable. If variable doesn't exists - creates it. * Works only with string or int type data. * * @param string $var_name Name, under witch data will be stored in internal associative array * @param string $data * @param bool $global If is set to "true", then global variable is created. This means it doesn't get deleted when fetched. * @return void */ public function append($var_name, $data, $global = false){ $arr = $global ? '_global' : '_variables'; if (isset($this->{$arr}[$var_name])){ $this->{$arr}[$var_name] .= $data; } else { $this->{$arr}[$var_name] = $data; } } /** * Get stored data by key name. If data isn't global, it get's deleted after first access. * Global variables never get deleted * * @param string $var_name * @param bool $global * @return mixed */ public function get($var_name, $global = false) { if ($global && isset($this->_global[$var_name])) { return $this->_global[$var_name]; } elseif (isset($this->_variables[$var_name])) { $var = $this->_variables[$var_name]; unset($this->_variables[$var_name]); return $var; } return null; } /** * Check if variable with such key exists * * @param string $var_name * @param bool $global * @return bool */ public function is_set($var_name, $global = false) { return (!empty($this->{$global ? '_global' : '_variables'}[$var_name])) ? true : false; } /** * Clear global data vars * * @param mixed $var_names Deletes a var from parser globav variable poll. An array of names can be given to preform multiple deletes */ public function deleteGlobal($var_names) { if (is_array($var_names)) { foreach ($var_names as $v) { unset($this->_global[$v]); } } else { unset($this->_global[$var_names]); } } } И соотвественно глобальные функции для упрощения доступа: PHP: <?php /* short parser functions */ function get($name,$global=false) { return Core::getInstance('parser',true)->get($name,$global); } function is_set($name,$global=false) { return Core::getInstance('parser',true)->is_set($name,$global); }
Psih глядя на твой валидатор создается впечатление, что ошибки не все сразу выкатываются а по очереди
Simpliest Ну и представь себе код, который проверяет наличие переменной и подставляет её в шаблоне? Это же капец будет уже а не шаблон. И так приходится писать <?php echo get('...')?> вместо <?=get('...')?>, добавить сюда ещё кучу if-ов? Извращенец вы батенька, пожалейте верстальщика. Там где проверку сделать надо, там есть для этого is_set('...'). Просмотрите внимательно шаблоны.
Отлично себе представляю. Если у меня переменная может быть, а может и не быть - будет проверка. Если переменная должна быть всегда, то проверки не будет. Но в случае ее(переменной) отсутствия я схлопочу notice - и это правильно!
Psih я понимаю, у тебя опыт, хайлоад и все такое, но код грустный. Глянь на мой обработчик формы в админке http://pastebin.mozilla-russia.org/105792 и шаблон этой формы http://pastebin.mozilla-russia.org/105793 хотя Simpliest наверно скажет что все равно мой код гавно
Simpliest В случае шаблона в форме если нету такой переменной, значит мы данные добавляем, а если есть - редактируем. Не превращать же шаблон в кашу из PHP и HTML. Notice тут совсем не причём.
Koc У меня просто всё проще и я не пытаюсь сделать из своей системы продвинутый фреймворк или CMF. Времени на это не дают, так что работаем с чем есть. Хотя на самом деле этого хватает и позволяет делать всё быстро.
Notice тут как раз причем. Из-за неявного приведения, которое в данном случае не нужно, ты плодишь потенциальные баги в своем коде. Если мне надо добавлять, то я это знаю еще "до" шаблона и в шаблон отдам пустую структуру. И никаких get('var') мне не будет нужно. Поскольку выводиться будет точно также.
Simpliest так на самом деле удобно. И всякие «что будет если» и потенциально возможные проблемы не возникают. Шаблонизатор должен вернуть отпарсенный шаблон, что он и делает. Остальное — это уже к cms-lite и прочим фантазёрам.
Аргумент. Хотя если такой блок один, то можно поставить одну проверку. Если их много для одного контроллера, то скорее всего надо выделить подшаблон или вообще создать для этого контроллера новый шаблон. Если много новых блоков для разных контроллеров, то опять же надо смотреть и комбинировать проверки, подшаблоны и т.д. Psih На данном этапе своего развития я бы наверное создал подкласс для такой формы (как писал выше). Или создал бы функцию типа echoIfIsSet.
есть такой вариант, разбить страницу на контент зоны, запрос от пользователя идёт к опред контент зоне, передаёт ей параметры. контент зона, используя забитые параметры и дефолтные установки, дёргает нужные модули и плагины. те в свою очередь возвращают массивы данных. далее всё это передаётся в главный контроллер, который дёргает шаблонизатор и пихает данные по шаблонам, принадлежащим контент зоне или если таковых нет, то в дефолтные. и вывод. например, есть дефолтная контент зона main. включает зоны auth - авторизация; menu - меню; content - модуль из параметров от пользователя; юзверь заходит на главную, получает соответственно дефолтные значения модулей. вбивает например content=zlo получает в контенте модуль зла далее, идёт на /news/ попадает в контент зону news. а там разметка такая: auth - внизу, menu - там же; content - где-нибудь. шаблончики модулей пойдут ещё из контент зоны news, если их там нет, то дефолтные вбивает /news/?content[module]=news&content[act]=fullnews&content[id]=10 получает в контенте модуль news действие fullnews и id=10. и фишка, вбивает /news/?auth[act]=logout&content[module]=news&content[act]=fullnews&content[id]=10. и таким образом мы пришли к полному автоматизированию вывода шаблонов. а шаблонизатор по сути просто функция: аля взял массив -> отдал html отсюда получается вопрос, стоит ли эта функция обсуждения на 15 страниц?
Всё это конечно хорошо, умно и типа правильно. Только вот я какраз делаю наоборот - чем проще, тем лучше. Я иногда сознательно не наворачиваю сложные вещи что бы люди после меня могли свободно разобратся что к чему. С моим кодом щас работает студент, который с PHP знаком относительно недавно. У него не возникает проблем с чтением и пониманием моего кода, с пониманием того, что я сделал и как сделал. Он за 10-15 минут разбирает даже внешне сложные вещи. Потому что сделано просто, без наворачивания кучи иерархий и классов. Только то, что надо для code reuse и абстракции определённых вещей. Вот сам засяду щас за изучение и работу с Yii, посмотрим как пойдёт. Нафиг мне класс для формы? У меня есть модуль, он умеет выводить список, добавлять и редактировать данные. Весь reusable функционал вынесен в хелперы. Если у меня идёт сравнение на true, false, 0 и тому подобные не явные вещи, то я делаю соответствующие аккуратные проверки. Они у меня в шаблонах есть, то что я привёл самые простые шаблоны не значит что я идиот и не помню про приведение типов и тому подобные вещи, специфичные для PHP. В 90% шаблонах все переменные всегда проставлены и они просто выводятся, т.е всякие списки, пейджеры и тому подобное. Могу привести более типичный пример из фронтэнда. Я ещё ни разу не натыкался на баг в шаблонах. Вот именно. У меня шаблонизатор умеет только три вещи - принять данные для шаблона, отпарсить шаблон и подставить данные во время парсинга. Всё, остальное всё уходит на уровень кода приложения в модули. З.Ы. Да, админка у меня немного отстаёт от фронтэнда, в фронтэнде там есть специальный статический класс Validator, который плюётся исключениями.
Psih да тут реализация максимум 600строк кода. а дальше только модули чиркай я тоже класс монстров не перевариваю если чо =)
Если это был расчет на то, что я пойду его смотреть... то ты ошибся - смотреть не пойду.. Я не использую "шаблонизаторы", которые существуют ради самих себя. Если речь идет о представлении, то возлагать на него логику сокрытия собственных ляпов в других местах, как это сделано в примере Psih - это маразм. Это сродни Код (Text): define(true, false); // счастливой отладки ..... Я не знаю кому так удобно Корень всего обсуждения в том, что люди не понимают что такое представление и чем оно должно заниматься. А теперь можно немного внимания? То, что у тебя не пришли данные в шаблон - это баг не шаблона. Но ты о нем узнаешь далеко не сразу, благодаря фееричности самого шаблона.
то, что данные не пришли — это проблема обработчика данных, как, собственно, и решение как с непришедшими данными поступить. Псих не говорил? Там не мвц и говирить о классических представлениях говорить не уместно.
Это прикольно. Но я именно об этом и говорю. Проблема обработчика, но данные не пришли в шаблон. И в логах об этом пусто.... благодаря мега-шаблону и его логике. Чего у нас там с отладкой?