Archive for the ‘Сайтостроение’ Category.

Smarty в управляемой представлениями модели

Обычно шаблонизатор Smarty применяется так: вы пишете код, который создаёт набор данных для шаблона, отдаёте вместе с названием шаблона смарти и тот «соединяет бренды», выводя в браузер результат.

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

Ещё есть модель с ведущими представлениями. В ней представление говорит контроллеру «а дай-ка мне, дорогой друг, список последних постов», после чего контроллер дёргает модель, добывает этот список, обрабатывает и отдаёт представлению (в нашем случае — шаблонизатору).

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

  1. <?php
  2. /**
  3. * index.php
  4. *
  5. * View-driven smarty usage sample
  6. *
  7. * Use {data id="SOMEID" var="TPLVAR"} construction in template
  8. * to fetch data for template. Return of the DataFetcher object
  9. * will be assigned to the variable with TPLVAR name.
  10. *
  11. * DataFetcher object must have function called fetch() with only
  12. * parameter which value will be SOMEID.
  13. *
  14. * Smarty included in this archieve only for your convenience.
  15. *
  16. * Remember: it's just a sample
  17. *
  18. * @copyright (c) Gregory Sitnin, 2009. All rights reserved.
  19. * @author Gregory Sitnin <gregor@gregor.ru>
  20. *
  21. */
  22.  
  23. $config['base'] = dirname(__FILE__);
  24. $config['tmp'] = $config['base'].'/tmp';
  25.  
  26. final class DataFetcher extends ConfiguratedObject {
  27.   function fetch($name) {
  28.     return 'Hello, World!';
  29.   }
  30. }
  31.  
  32. final class SmartyRender extends ConfiguratedObject {
  33.   private $smarty;
  34.   
  35.   function __construct($config) {
  36.     parent::__construct($config);
  37.     require_once $this->config['base'].'/smarty/Smarty.class.php';
  38.     $this->smarty = new Smarty();
  39.     $this->smarty->template_dir = $this->config['base'];
  40.     $this->smarty->compile_dir = $this->config['tmp'];
  41.     $this->smarty->register_function('data', array ($this, 'getDataProxy'));
  42.   }
  43.   
  44.   public function getDataProxy($params, &$smarty) {
  45.     if (!isset($params['id'])) {
  46.       $smarty->trigger_error("data: id parameter must be set.");
  47.     } elseif (!isset($params['var'])) {
  48.       $smarty->trigger_error("data: var parameter must be set.");
  49.     } else {
  50.       try {
  51.         $fetcher = new DataFetcher($this->config);
  52.         $smarty->assign($params['var'], $fetcher->fetch($params['id']));
  53.       } catch (Exception $e) {
  54.         $smarty->trigger_error('data: ('.$e->getCode().')'.$e->getMessage());
  55.       }
  56.     }
  57.   }
  58.   
  59.   function render($template, array $vars = null) {
  60.     $this->smarty->assign($vars);
  61.     return $this->smarty->fetch($template);
  62.   }
  63. }
  64.  
  65. final class App extends ConfiguratedObject {
  66.   function run() {
  67.     $render = new SmartyRender($this->config);
  68.     echo $render->render('index.tpl');
  69.   }
  70. }
  71.  
  72. abstract class ConfiguratedObject {
  73.   protected $config;
  74.   
  75.   function __construct($config) {
  76.     $this->config = $config;
  77.   }
  78. }
  79.  
  80. $app = new App($config);
  81. $app->run();
  82.  
  83. ?>

* This source code was highlighted with Source Code Highlighter.

Интересное происходит на строках 41-57. Воспользовавшись методом $smarty->register_function() я объявил новую функцию шаблона «data». Когда эта конструкция встречается в шаблоне, происходит вызов метода $this->getDataProxy() объекта SmartyRender.

На вход метода попадает ссылка на массив параметров, заданных в шаблоне и ссылка на объект smarty, который оперирует шаблоном, вызвавшим смарти-функцию.

Как здесь, так и далее нет никакой магии. Мы проверяем параметры и, если с ними всё в порядке, то создаём экземпляр класса DataFetcher. Результат вызова метода fetch() объекта DataFetcher станет переменной шаблона, которую далее можно использовать как угодно.

Сам шаблон в моём случае выглядит вот так:

  1. <html>
  2. <head>
  3.   <title>View-driven Smarty test</title>
  4. </head>
  5. <body>
  6.   {data id="test" var="test"}
  7.   Fetched: {$test}
  8. </body>
  9. </html>

* This source code was highlighted with Source Code Highlighter.

Работающий код этого примера (включает Smarty): http://gregor.ru/files/blog/vdsmarty.zip

Ещё одна причина, почему OpenID хорош

Настраивал сейчас SSL для того, чтобы поиграть с HTTPS и, постоянно напарываясь на предупреждения браузера типа «сертификат сайта — полная туфта», подумал вот о чём.

Большие хорошие сервисы, предоставляющие OpenID уже давным давно авторизуют пользователей по HTTPS, купив у того же Verisign-а нормальные дорогие сертификаты. То есть, если мы используем для авторизации такие сервисы, то мы способствуем тому, что пользователь меньше рискует: ему не нужно входить в систему по небезопасному HTTP-протоколу, передавая логины-пароли в открытом виде.

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

Правильный OpenID

XHTML победил

Сейчас разбирал завалы rss-подписок и прочитал в блоге Алекса Москалюка крайне интересную заметку. В ней говорится о том, что Facebook запустила программу Facebook Connect, позволяющую сайтам авторизовать пользователей через эту крупнешую в мире социальную сеть.

Но интересным для меня стало не это, а тот факт, что для работы требуется в html-коде страницы написать “<fb:login-button></fb:login-button>”.

Казалось бы, ничего удивительного, если не заметить двух вещей: 1) это XML; 2) не используется быстрая форма пустого тэга (<fb:login-button />).

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

Теперь самое интересное. Использование разметки именно Xhtml для задействования сервисов сайта масштаба Facebook говорит о том, что про html-разметку можно начинать забывать. В любом случае, сейчас переходить на xhtml ломанётся огромная толпа мелких сайтов (благо различные Wordpress-ы и так уже давно размечены в xhtml, о чём красноречиво заявляет каждый подвал любой мало-мальски приличной темы).

Кроме того, Facebook Connect уже поддержали такие монстры, как TechCrunch, CBS и CNN.

И под конец ещё один не менее примечательный факт: FC не работает в IE6 и Facebook отвечает на это просто: обновите браузер. Но это уже так, вишенка на коктейле.

Цензура им. Халявина

Имел нескромность в недавно развязавшейся дискуссии о спаме в жж Никиты Халявина (Профессионалы.ру) задать прямой и честный вопрос о том, как удалиться нафиг из сервиса.

Халявин даже ответил, но вопрос вместе с ответом стёр. Привожу его тут:

Вот теперь сижу и пытаюсь понять, зачем удалять вопрос и ответ? Нежто боится, что народ ломанётся пользоваться предложенным способом?

Когда интерфейсы не помогают

Уже некоторое время пытаюсь решить простую, казалось бы, задачу (на php, естественно).

Есть у меня несколько классов, расходящихся веткой от одного базового. Например: ПростаяСтраница <- SmartyСтраница; ПростаяСтраница <- БдСтраница; ПростаяСтраница <- ЗащищённаяСтраница. Все классы — абстрактные.

Невооружённым глазом видно, что это расширение в разные стороны функциональности одного и того же базового класса.

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

Всё бы было хорошо, если бы только я мог в финальном собрать нечто вроде ЗащищённаяSmartyБдСтраница (то есть, набрать поведений из разных веток иерархии, в зависимости от сиюминутной потребности).

Однако, как известно, в пхп ни о каком множественном наследовании речи-то и нет.

Большинство людей, с которыми я пробовал обсудить эту тему, задают один и тот же вопрос: «А интерфейсы тебе тут не помогут?»

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

Наверняка я где-то рядом с решением, но в упор его не вижу. Кто видит?

По ком звонит __call-окол

К сожалению, сегодня у меня в коде довольно много строк вида

public function load($guid) {
	return $this->loadAndExec('load_user', array('guid' => $guid), false);
}

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

В php у каждого класса существует возможность перехватывать обращения к несуществующим методам класса и обрабатывать их так, как вздумается. Для этого достаточно определить в классе метод __call(), в который передаются два аргумента — имя вызываемого метода и массив аргументов.

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

Начинаю готовить Natea к релизу

Собственно, завёл сайт проекта. Исходники пока не видно, их надо причесать и документировать.

Но процесс пошёл, что не может не радовать.

Процесс работы на веб-сайтом в одно рыло

Дано: один разработчик и проект «с нуля».

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

Попытка решения

1. Фиксация требований и пожеланий заказчика (как внешнего, так и внутреннего). Составить список функций продукта (feature set). Описать зависимости между функциями. Для каждой функции определить критерии её завершения.

2. Фиксация видения. Записать произвольным текстом своё личное видение проекта, отразив все требования, зафиксированные на предыдущем этапе.

3. Составление плана вех (итераций). Определить трудоёмкость и ценность (score) каждой функции. Распределить функции по вехам, учитывая из зависимость.

4. Исполнение. Создание ветки версионного хранилища. Последовательно исполнение вех.

4.1. Реализация функции. Выполнить работы по производству конкретной функции. Функция считается реализованной, когда все критерии завершения выполнены.

4.2. Фиксация изменений. Примененить изменения в версионное хранилище, включая актуализацию дампа БД.

4.3. Рефлексия. Оценка и фиксация реальной трудоёмкости и проблем, возникших при работе над функцией.

4.4. Пересмотр плана разработки. Отмена или перенос функций (если требуется).

5. Фиксация вехи. Фиксация ветки версионного хранилища. При необходимости, склеивание ветки с главной (merge в trunk). Веха считается достигнутой при завершении всех функций этой вехи.

Чего забыл?

GUID — наше всё

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

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

Посмотрите сами: методы запуска редакторов разных данных из БД одни и те же. Механизмы, которые реализуют проверку так же имеют одинаковый интерфейс. По крайней мере, могут иметь.

Теперь мы сократим схему до минимума. Пусть у нас будет один URL (скажем, /edit/{guid}/), который мапится на наш скрипт построения редактора. Так же создадим в БД таблицу objects с двумя полями — guid и class. В первое соберём все guid-ы, а второе заполним названиями классов. Я делал это примерно вот так:

INSERT INTO objects (guid, class)
SELECT guid, 'iteration' class FROM iterations UNION
SELECT guid, 'milestone' class FROM milestones UNION
SELECT guid, 'project' class FROM projects UNION
SELECT guid, 'task' class FROM tasks UNION
SELECT guid, 'user' class FROM users

После чего во всех таблицах, кроме самой objects я заменил первичные ключи по guid на простые индексы и связал их с таблицей objects, установив на удаление-изменение правило cascade. Конечно, для того чтобы положить новую запись в БД, придётся сначала создать запись в objects, однако это не страшно. Особенно в наш век транзакционных субд.

Дальше всё кристально просто: при обращении к нашему URL выделяется GUID редактируемого объекта, с помощью которого мы получаем класс сущности.

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

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

Небольшая ремарка: в БД все сущности у меня имеют первичный индекс в виде GUID-ов. Это мне нравится больше, хотя и не обязательно. Если у таблицы objects сделать составной первичный ключ из, скажем autoid сущностной таблицы и имени класса, то тоже будет работать. Но мне это кажется дурацкой идеей и без гуидов я бы до неё всё равно не додумался. =) К тому же, у GUID-ов есть ещё всякие вкусности.

Фреймворк для админок

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

Сначала делал по-старинке, используя голову, руки, формы и проверки. Некрасиво получалось.

Потом попробовал делать на ext-js. Красиво, но очень уж монструозно и методология большого фреймворка начала сильно влиять на код.

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

Теперь терзает мысль: а ведь наверняка есть фреймворки, которых я не знаю, предназначенные именно для такой работы — делать интерфейсы управления. Не идиотские конструкторы форм (да, в них есть автопроверки, но это не делает их хорошими), не интегрированные в универсайльные сайто-фреймворки типа Code Igniter-а. Именно фреймворки для разработки интерфейсов управления.

Ведь, если до сих пор нет, то надо такой сделать. А вы как думаете?

Если делать, то какие задачи он должен решать? Какую меру кастомизируемости должен содержать?