Archive for the ‘Программирование’ Category.

Git и Svn — братья навек

Я тут носился с простым вопросом: как именно происходят коммиты, когда git забирает реп из subversion. Дошли руки и поставил дерзкий нечеловеческий эксперимент.

Допустим, есть у меня репозиторий SVN (http://domain.tld/svn/repo/), работать с которым я хочу исключительно из-под гита.

Достаём его гитом: «git svn clone http://domain.tld/svn/repo/ foldername» и теперь у меня в папке foldername лежит нужный клон, с которым работать по уже знакомой схеме: делаем изменение, фиксируем его коммитом.

Как только мы готовы отправить изменение в subversion, запускаем команду git svn dcommit, которая аккуратно сформирует из всех локальных коммитов столько же коммитов в svn-репозиторий, забабахает туда ваши лог-сообщения и отправит на сервер.

Собственно, это мне и было интересно: когда происходит коммит в subversion-репозиторий и сколько будет changeset-ов (один или несколько). Итого: когда скажу, несколько.

Почему-то нигде в сети я этого простого знания не нашёл, поэтому делюсь с вами здесь. Вероятно, это всё слишком просто, но мне стоило минут 5-и, потраченных на эксперимент )

UPD: Кстати, гит высосет при клонировании все changeset-ы, будьте терпеливы, внимательны и осторожны! )

UPD2: Нашёл интересную ссылку. Там советуют делать не так, как сделал я, экспериментируя. И это, в общем-то, правильно.

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

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

Уже некоторое время пытаюсь решить простую, казалось бы, задачу (на 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-а. Именно фреймворки для разработки интерфейсов управления.

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

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

Архитектура высоких нагрузок

Давно и с большим интересом читаю блог «Insight IT«. В частности, потрясающие статьи об архитектуре высоконагруженных веб-сайтов.

Больше ничего говорить не буду, да и любой здравомыслящий человек уже давно ушёл по ссылкам =)

rss-фиды изменений сайта

Раздумываю над тем, как лучше сделать rss изменений совершенно разных объектов на сайте.

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

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

Но коллективное бессознательное поселило в мою голову новую дельную мысль: надо дополнить методы сохранения объектов вызовом логгера, который бы записывал в отдельную таблицу сообщения вида «дата, время, вид изменения, субъект, объект».

Тогда, если мы хотим выбрать все последние посты юзера «пупкин», то просто делаем поиск по «посты, пупкин, последние 10″ и формируем из этого ленту.

А вы как решаете подобные задачи?