﻿<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	>

<channel>
	<title>Software People</title>
	<atom:link href="http://softwarepeople.ru/feed/" rel="self" type="application/rss+xml" />
	<link>http://softwarepeople.ru</link>
	<description>Software People</description>
	<pubDate>Thu, 02 Feb 2012 08:40:29 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.7.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>HTML5+ JS приложения для Windows 8. Что внутри + холиварные мысли</title>
		<link>http://softwarepeople.ru/blog/2012/02/02/html5-js-for-windows-8/</link>
		<comments>http://softwarepeople.ru/blog/2012/02/02/html5-js-for-windows-8/#comments</comments>
		<pubDate>Thu, 02 Feb 2012 08:37:57 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[HTML5]]></category>

		<category><![CDATA[Javascript]]></category>

		<category><![CDATA[Программирование]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5778</guid>
		<description><![CDATA[Автор статьи: Игорь Сычев
На Build были широко разрекламированы приложения для windows8, которые можно писать на html5. Я раньше считал, что MS взялись за html5 больше из политических-маркетинговых соображений, и не обращал внимание на маркетинговые сказки. Но после build мне стало понятно, что, судя по всему, они взялись очень серьезно, и поэтому на них стоит обратить ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор статьи</strong>: <a href="http://twitter.com/sychvigor">Игорь Сычев</a><br/><br />
На Build были широко разрекламированы приложения для windows8, которые можно писать на html5. Я раньше считал, что MS взялись за html5 больше из политических-маркетинговых соображений, и не обращал внимание на маркетинговые сказки. Но после build мне стало понятно, что, судя по всему, они взялись очень серьезно, и поэтому на них стоит обратить внимание.<br/><br />
Хочу немного показать, что внутри себя содержат html5 metro приложения под win8.<br/></p>
<h3>Общий вид пустого приложения.</h3>
<p><a href="http://softwarepeople.ru/files/2012/01/e3076a65.jpg"><img src="http://softwarepeople.ru/files/2012/01/e3076a65.jpg" alt="e3076a65" title="e3076a65" width="600" height="320" class="aligncenter size-full wp-image-5782" /></a><br />
Приложение содержит в себе css,js,html которые и есть основной контент приложений. Так же есть отдельная папка с js,css файлами в которой находятся код framework html5 приложений под win8-WINJS. Собственно мы можем внимательно изучить внутренности, но по лицензии это как .net framework, который можно скачать в исходных кодах, но нельзя по лицензии модифицировать, да и очень сомневаюсь, что кто-нибудь решится “подправить” что-то в такой большой библиотеке.<br/><br />
<a href="http://softwarepeople.ru/files/2012/01/255130c4.jpg"><img src="http://softwarepeople.ru/files/2012/01/255130c4.jpg" alt="255130c4" title="255130c4" width="466" height="445" class="aligncenter size-full wp-image-5783" /></a><br />
<a href="http://softwarepeople.ru/files/2012/01/30de5470.jpg"><img src="http://softwarepeople.ru/files/2012/01/30de5470.jpg" alt="30de5470" title="30de5470" width="600" height="422" class="aligncenter size-full wp-image-5787" /></a><br />
Внутри приложения есть файл манифеста, такой же как в xaml metro application, о котором я уже писал <a href="http://habrahabr.ru/blogs/vs/112983/">ранее</a></p>
<h3>Типы страниц</h3>
<p>Типы шаблонных страниц в html-js приложениях и xaml идентичные.<br/><br />
<a href="http://softwarepeople.ru/files/2012/01/f47e838c.jpg"><img src="http://softwarepeople.ru/files/2012/01/f47e838c.jpg" alt="f47e838c" title="f47e838c" width="600" height="517" class="aligncenter size-full wp-image-5790" /></a></p>
<h3>Отладка. </h3>
<p>Я думаю, самой большой проблемой для разработчиков .net – это отладка js. Все таки мы привыкли к статически типизированным языкам, к отладке их, отлове ошибок еще на этапе компиляции. В Win8 есть новая консоль отладчика js, без которой отлаживаться на мой взгляд вообще невозможно.</br><br />
<a href="http://softwarepeople.ru/files/2012/02/1cee6a43.jpg"><img src="http://softwarepeople.ru/files/2012/02/1cee6a43.jpg" alt="1cee6a43" title="1cee6a43" width="600" height="321" class="aligncenter size-full wp-image-5798" /></a></p>
<h3>Картинки</h3>
<p>В WP7 надо было сделать картинки одних размеров, но для win8 придется сделать еще нарезочку из<br/></p>
<ul>
<li>150*150 logo</li>
<li>310*150 winw logo</li>
<li>30*30 small logo</li>
<li>56*56 storeloge</li>
<li>24*24 badge logo </li>
<li>624*304 SplashScreen</li>
</ul>
<p><br/></p>
<h3>Дизайн</h3>
<p>MS рекомендует(активно рекламирует) использовать Blend 5 версии, для создания дизайна. В нем можно и js поправить, но по назначению — это именно дизайн. Мое личное мнение, что 99% разработчиков, как и раньше, не будут использовать Expression, а по-прежнему будут верстать что xaml, что html руками. А также непонятна логика, почему надо было вставлять редактор html в blend, когда есть Expression Web, в котором редактор html уже есть. Кто-то говорит, что “НУ ЭТО ЖЕ НЕ ВЕБ”, но по мне проще переименовать в Expression HTML, чем вставлять поддержку html в другой редактор. Но все же это просто мое непонимание.</br><br />
<a href="http://softwarepeople.ru/files/2012/02/56f5ad55.jpg"><img src="http://softwarepeople.ru/files/2012/02/56f5ad55.jpg" alt="56f5ad55" title="56f5ad55" width="600" height="320" class="aligncenter size-full wp-image-5804" /></a></br></p>
<h3>Среда исполнения для html приложений</h3>
<p>Во время Build нам рассказали, что будет 2 версии браузер ie. Обычный Internet Explorer и IE для Metro приложений. Второй и будет средой исполнения для HTML Metro приложений.<br/><br />
Движек будет общий, но у браузера для Metro приложений будут отключены плагины типа Flash. По этому поводу было разведено очень много статей и холивара в инете, но лично я считаю это решение правильным.<br/></p>
<h3>Холиварные мысли </h3>
<p>Microsoft хоронят уже столько лет, а он все жив. IE хоронят столько же наверное. .Net пытаются хоронить с самого его появления. <br/><br />
Сейчас с появлением возможности писать HTML приложения для Win8 я заметил новый виток холиваров на тему, что MS предала .net разработчиков и мол агитирует за html5. Мол Синофски не любит managed код и по этому появилась WinRT и типа будут двигать С++ как в старину. Мол .net разработчиков 10млн, а html 100 млн (я не верю что так много разработчиков, но важно соотношение 1:10 в которой я склонен верить) и акцент решили сделать на большинство.<br/><br />
<b>Все это на мой взгляд происки заядлых холиварщиков.</b><br/><br />
По поводу кросслатформенности html5 я имею огромные сомнения. Была уже одна ПАНАЦЕЯ кроссплатформенная, которая в рекламном слогане говорила- «Пиши 1 раз и запускай везде». <b>JAVA </b>называется. В общем убить все остальные платформозависимые языки у нее не получилось, а теперь и о самой java слухов о том, что она умирает более чем достаточно.<br/><br />
Лично я считаю, что <b>HTML5- это СТИЛЬНО, МОДНО, МОЛОДЕЖНО</b> и маркетинг идет через это. + Я пользуюсь Win8 ctp с 20 сентября или уже 2 месяца. В ней много крутых вещей- таких как hyper-v на клиентской ОС, монтирование дисков, новый дизайн проводника, она грузится быстрее… НО это больше для продвинутых пользователей. А для совсем простых пользователей и планшетов нужны именно казуальные приложения для Tile панели. Сейчас приложений просто нет тк win8 еще в CTP. Как только Win8 выйдет, нужно очень быстро будет Market Place заполнить приложениями. Понятно, что их можно портировать с WP7.5, но приложений на html казуальных можно портировать тоже много и быстро. <br/><br />
А Главое- это ведь Tile приложения, никто .net с обычного desktop не выгоняет ведь.<br/><br />
По поводу C++, можно говорить что угодно, но думаю работа с указателями, памятью и прочее- это то, от чего большая часть разработчиков ушла как от страшного сна+ очень много уж кода написано на .net и идти обратно на C++ тяжело будет.<br/><br />
Вот оцените сколько кода надо написать на С++ и на C#/VB.NET чтобы сделать класс <br/><br />
<a href="http://msdn.microsoft.com/en-us/library/windows/apps/br211380(v=VS.85).aspx">FeedData </a> и скажите, кто готов писать столько строк, явно не самых важных.<br/><br />
<a href="http://softwarepeople.ru/files/2012/02/57d0a406.jpg"><img src="http://softwarepeople.ru/files/2012/02/57d0a406.jpg" alt="57d0a406" title="57d0a406" width="600" height="476" class="aligncenter size-full wp-image-5810" /></a></br><br />
<a href="http://softwarepeople.ru/files/2012/02/92052e93.jpg"><img src="http://softwarepeople.ru/files/2012/02/92052e93.jpg" alt="92052e93" title="92052e93" width="600" height="498" class="aligncenter size-full wp-image-5811" /></a></br></p>
<h3>Ссылки:</h3>
<ul>
<li><a href="http://msdn.microsoft.com/library/d1et7k7c(v=VS.94).aspx">http://msdn.microsoft.com/library/d1et7k7c(v=VS.94).aspx</a> </li>
<li><a href="http://msdn.microsoft.com/en-us/library/windows/apps/br230301(v=VS.85).aspx">Creating Windows Runtime Components for JavaScript, in C# and Visual Basic</a> </li>
<li>WIN8 Emulator <a href="http://blogs.msdn.com/b/visualstudio/archive/2011/09/29/first-look-at-windows-simulator.aspx">Ч1</a>, <a href="http://blogs.msdn.com/b/visualstudio/archive/2011/09/30/microsoft-windows-simulator-touch-emulation.aspx">Ч2 </a></li>
<li><a href="http://blogs.msdn.com/b/visualstudio/archive/2011/11/01/debugging-contracts-using-windows-simulator.aspx">Debugging Contracts using Windows Simulator</a></li>
<li><a href="http://vk.com/doc987353_24900511?dl=e8efb49452ea248a36">Список статей, которые я рекомендую прочесть</a><br />
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2012/02/02/html5-js-for-windows-8/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Многоязычные модели Django для начинающих</title>
		<link>http://softwarepeople.ru/blog/2012/01/12/django-for-beginners/</link>
		<comments>http://softwarepeople.ru/blog/2012/01/12/django-for-beginners/#comments</comments>
		<pubDate>Thu, 12 Jan 2012 13:47:23 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[web-разработка]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5742</guid>
		<description><![CDATA[Автор статьи: Вадим Лопатюк (сайт автора: http://lopatyuk.com/)
Вводная
Работаю я над социальным порталом, который покорит всех от грудничков до маразматиков. И так как цели наполеоновские, нужно учесть возможность использования сервиса даже теми представителями человечества, которые не владеют великим и могучим. Знаю, что многие долго боролись с вопросом локализации именно контента (перевод отдельных полей модели на разные языки), ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор статьи: Вадим Лопатюк</strong> (сайт автора: http://lopatyuk.com/)<br/></p>
<h3>Вводная</h3>
<p>Работаю я над социальным порталом, который покорит всех от грудничков до маразматиков. И так как цели наполеоновские, нужно учесть возможность использования сервиса даже теми представителями человечества, которые не владеют великим и могучим. Знаю, что многие долго боролись с вопросом локализации именно контента (перевод отдельных полей модели на разные языки), т.к. локализация интерфейса, в пределах общепринятых, в Django решена. Суть проблемы в том, чтобы одна и та же модель (это важно! к примеру, пункты единого для всех языков каталога) имела переводы своих полей на разные языки. Те, кто давно «на игле» этого фреймворка, уже наверняка нашли для себя наиболее подходящую методу решения этой проблемы, я же хочу предложить способ решения и набросать порядок действий для начинающих, чтобы они не сбежали по рельсам на рубиновые копи.</p>
<h3>Отмазка</h3>
<p>Совсем недавно мне подвернулся счастливый случай начать превращение из личинки-PHP&#8217;шника в жука-Python&#8217;оида. Так что сразу не бросайтесь в критику. Эта заметка (не статья) представляет из себя HowTo для таких же начинающих.</p>
<h3>Используемые материалы</h3>
<p>Для локализации моделей мы будем использовать приложение для Django — <a href="http://code.google.com/p/django-modeltranslation/"><code>django-modeltranslation</code></a>. Я выбрал именно его среди прочих по следующим причинам:<br/></p>
<ul>
<li>работает с <code>Django 1.3</code></li>
<li>добавляет переводимые поля для языков в ту же таблицу, т.е. не будет лишних <code>JOIN</code>&#8216;ов</li>
<li>не требует внесения никакого кода в уже существующие модели</li>
<li>почти безболезненно включается и отключается</li>
<li>в админке локализованная модель интегрируется с другими модулями (меня волновал <code>mptt</code>)</li>
</ul>
<p>Ещё понадобится <code>South</code> — установленный и работающий.<br/><br />
Собственно всё (не считая рабочего/разрабатываемого сайта на <code>django</code>).</p>
<h3>HowTo</h3>
<p>Устанавливаем <code>modeltranslation</code> командой <code>pip install django-modeltranslation</code>. <br/><br />
Или качаем последнюю версию <a href="http://code.google.com/p/django-modeltranslation/downloads/list">django-modeltranslation</a>. Распаковываем, заходим в распакованный каталог и устанавливаем <code>python setup.py install</code>. Можно сразу из распакованного каталога руками закинуть в папку для статических файлов директорию <code>modeltranslation/static/modeltranslation</code>.</p>
<h3>Далее работаем уже с нашим проектом</h3>
<p>Добавляем <code>'modeltranslation'</code> в <code>INSTALLED_APPS</code> файла настроек<br/> <code>settings.py</code>.<br/><br />
В тот же файл добавляем описание необходимых локализаций, язык по-умолчанию и импортируемый файл регистрации локализуемых моделей. В последнем <code>MYPROJECT</code> — нужно заменить на имя вашего проекта (оно же имя папки в которой он размещается), а <code>translation</code> — на имя самого <code>.py</code> файла без расширения, по умолчанию это <code>translation.py</code>. Этот файл один на весь проект и кладётся, соответственно, в корень проекта.<br/></p>
<pre><code class="python">LANGUAGES = (
    (<span class="string">'ru'</span>, <span class="string">'Russian'</span>),
    (<span class="string">'en'</span>, <span class="string">'English'</span>),
)

MODELTRANSLATION_DEFAULT_LANGUAGE = <span class="string">'ru'</span>

MODELTRANSLATION_TRANSLATION_REGISTRY = <span class="string">'MYPROJECT.translation'</span>
</code></pre>
<p><br/><br />
<br/><br />
В этом самом файле нам нужно создать классы содержащие указание какие поля (здесь это <code>'name'</code> и <code>'description'</code>) нужно переводить и привязать эти классы к переводимым моделям (здесь это <code>'Modelka'</code>) Ваших приложений (здесь это <code>'app'</code>) сответствтенно:<br/><br />
<br/></p>
<pre><code class="python"><span class="comment"># -*- coding: utf-8 -*-</span>

<span class="keyword">from</span> modeltranslation.translator <span class="keyword">import</span> translator, TranslationOptions
<span class="keyword">from</span> app.models <span class="keyword">import</span> Modelka

<span class="class"><span class="keyword">class</span> <span class="title">ModelkaTranslationOptions</span><span class="params">(TranslationOptions)</span>:</span>

    <span class="string">"""
    Класс настроек интернационализации полей модели Modelka.
    """</span>

    fields = (<span class="string">'name'</span>, <span class="string">'description'</span>,)

translator.register(Modelka, ModelkaTranslationOptions)
</code></pre>
<p><br/><br />
В этом месте можно сгенерировать миграцию <code>python manage.py schemamigration app --auto</code>. <br/><br />
<code>South</code> обнаружит новые поля для локализации и сгенерирует миграцию для их добавления. К слову если отключить приложение <code>'modeltranslation'</code> — то также можно сгенерировать и миграцию для их удаления. Кроме того отдельное удобство в том, что <code>modeltranslation</code> привязывает язык по-умолчанию к оригинальным полям модели, т.е. это поле локализации и переводимое оригинальное поле модели будут идентичны.<br/><br />
Не забудьте потом применить миграцию: <code>python manage.py migrate app</code>.</p>
<h3>И, наконец, настраиваем админку. </h3>
<p><b>Очень важно чтобы админка описывалась в отдельном файле <code>admin.py</code> для каждого приложения!!!</b> А не в файле моделей, как, признаюсь честно, по-началу пытался сделать я.<br/><br />
Для образца сразу приведу пример использования локализованной админки с админкой <code>mptt</code>:<br/></p>
<pre><code class="python"><span class="comment"># -*- coding: utf-8 -*-</span>

<span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin
<span class="keyword">from</span> mptt.admin <span class="keyword">import</span> MPTTModelAdmin
<span class="keyword">from</span> modeltranslation.admin <span class="keyword">import</span> TranslationAdmin

<span class="keyword">from</span> models <span class="keyword">import</span> Modelka

<span class="class"><span class="keyword">class</span> <span class="title">ModelkaAdmin</span><span class="params">(MPTTModelAdmin, TranslationAdmin)</span>:</span>
    <span class="string">"""
    Настройка админки для Modelka.
    """</span>

    list_display = (<span class="string">'name'</span>,)

    <span class="class"><span class="keyword">class</span> <span class="title">Media</span>:</span>

        js = (
            <span class="string">'/static/modeltranslation/js/force_jquery.js'</span>,
            <span class="string">'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js'</span>,
            <span class="string">'/static/modeltranslation/js/tabbed_translation_fields.js'</span>,
        )
        css = {
            <span class="string">'screen'</span>: (<span class="string">'/static/modeltranslation/css/tabbed_translation_fields.css'</span>,),
        }

    fieldsets = [
        (<span class="built_in">None</span>, {
            <span class="string">'fields'</span>: [
                <span class="string">'name'</span>,
                <span class="string">'slug'</span>,
                <span class="string">'type'</span>,
                <span class="string">'description'</span>,
                <span class="string">'parent'</span>,
            ]
        }),
        (<span class="string">u'Лог'</span>, {
            <span class="string">'fields'</span>: [
                <span class="string">'author'</span>,
                <span class="string">'editor'</span>,
            ],
            <span class="string">'classes'</span>: [<span class="string">'collapse'</span>]
        })
    ]

admin.site.register(Modelka, ModelkaAdmin)

</code></pre>
<p><br/><br />
Обращаю Ваше внимание, что в примере сразу же включены скрипты для отображения переводов на разных вкладках. Это как раз те файлы которые я в начале предлагал скопировать в каталог для статического контента. Если Вы установили modeltranslation через pip — не забудьте собрать статические фалы командой <code>python manage.py collectstatic</code> (<a href="https://docs.djangoproject.com/en/1.3/howto/static-files/">подробнее о статических файлах</a>). Соответственно не забудьте подправить пути во вложенном <code>Meta</code> классе, если они у Вас отличаются.<br/><br />
<a href="http://softwarepeople.ru/files/2012/01/aa016fbb.png"><img src="http://softwarepeople.ru/files/2012/01/aa016fbb.png" alt="aa016fbb" title="aa016fbb" width="422" height="636" class="aligncenter size-full wp-image-5751" /></a><br/><br />
На скриншоте видны вкладки языков для переводимых полей модели (скриншот с разрабатываемого приложения, потому языков больше чем в примере)<br/></p>
<h3>Работа с локализованной моделью</h3>
<p><br/><br />
Собственно сама работа абсолютно прозрачна и также не затрагивает уже существующий код (наилучший язык отображения сайта может определяться как самим фреймворком, так и сторонними модулями, но это отдельная тема), если не понадобится где-то руками объявлять используемый язык через <code>set_language</code>. Т.е. модель будет возвращать значения полей на текущем языке приложения возвращаемом <code>get_lang</code>. </p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2012/01/12/django-for-beginners/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Что такое этот новый jQuery.Callbacks Object</title>
		<link>http://softwarepeople.ru/blog/2012/01/11/jquerycallbacks-object/</link>
		<comments>http://softwarepeople.ru/blog/2012/01/11/jquerycallbacks-object/#comments</comments>
		<pubDate>Tue, 10 Jan 2012 21:11:31 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[Программирование]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5765</guid>
		<description><![CDATA[Автор статьи: Зайцев Андрей (блог автора)
В не столь давно вышедшей версии jQuery 1.7 появился новый объект Callbacks, о котором сегодня и пойдёт речь.
В официальной документации jQuery.Callbacks описан, как многоцелевой объект, представляющий собой список функций обратного вызова (callbacks — далее просто колбэков) и мощные инструменты по управлению этим списком.
Я просматривал возможности этого объекта, когда он был ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор статьи: Зайцев Андрей</strong> (<a href="http://blog.zalab.net/">блог</a> автора)</br><br />
В не столь давно вышедшей версии jQuery 1.7 появился новый объект <b>Callbacks</b>, о котором сегодня и пойдёт речь.<br/><br />
В официальной документации <a href="http://api.jquery.com/jQuery.Callbacks/">jQuery.Callbacks</a> описан, как многоцелевой объект, представляющий собой список функций обратного вызова (callbacks — далее просто колбэков) и мощные инструменты по управлению этим списком.<br/><br />
Я просматривал возможности этого объекта, когда он был ещё только в разработке, и надо сказать, что возможностей у него изначально было немного больше, чем осталось в релизной версии. Например, сейчас отсутствует возможность создания очереди (queue) колбэков, которые вызываются по одному на каждый вызов <code>fire()</code>. Видимо, команда jQuery, решила немного подсократить код, убрав «ненужные/редкоиспользуемые» возможности, чтобы сэкономить в весе библиотеки. Это маленький экскурс в историю Callbacks, но далее я буду описывать только доступные сейчас функции и в конце напишу небольшое возможное улучшение этого объекта.<br/></p>
<h4>Назначение</h4>
<p>Прежде чем приступить к подробному изучению этого нового объекта jQuery.Callbacks, хочу остановиться на том, для чего же вообще нужен этот объект. Довольно часто в JavaScript коде используются колбэки — функции, которые вызываются при наступлении некоторого события, например, после завершения какого-то действия, самым ярким примером может послужить запрос AJAX. И при этом часто возникает потребность вызвать не одну функцию, а сразу несколько (заранее неизвестно сколько, может быть пару, может быть пару десятков, а может вообще ни одной) — это известный и простой <a href="http://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)">паттерн «Наблюдатель»</a>. И вот для таких случаев и оказывается полезен рассматриваемый объект jQuery.Callbacks. В самом jQuery этот объект используется (начиная с версии 1.7) внутри jQuery.Deferred и jQuery.ajax. Также авторы jQuery сделали этот объект общедоступным и задокументировала его, чтобы другие разработчики могли его использовать при реализации собственных компонентов.<br/></p>
<h4>Конструктор: jQuery.Callbacks(flags)</h4>
<p>Вызовом конструктора создаётся объект callbacks, который имеет ряд методов для управления списком колбэков.<br/><br />
Параметр <code>flags</code> необязательный и позволяет задать параметры работы объекта, возможные значения параметра мы рассмотрим ниже.<br/></p>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks();</code></pre>
<p><br/><br />
К созданному объекту <code>callbacks</code> мы сможем теперь добавлять функции-колбэки в список, удалять их, вызывать, снова вызывать (если это не было запрещено при создании объекта), проверять статус объекта (был ли уже вызов или ещё нет) с помощью таких методов объекта, как <code>add()</code>, <code>remove()</code>, <code>fire()</code> и пр. Выполняются колбэки, кстати, в порядке их добавления в список.<br/><br />
Отмечу, что это не «настоящий» конструктор экземпляра класса, поэтому использовать оператор <code>new</code> при его вызове не требуется (и даже бессмысленно).<br/><br />
По этой причине не получится проверить, является ли объект экземпляром Callbacks, способом, стандартным для JS:<br/></p>
<pre><code class="javascript"><span class="keyword">if</span> (obj <span class="keyword">instanceof</span> $.Callbacks) {
    obj.add(fn);
}</code></pre>
<p><br/><br />
Выражение под if всегда возвращает false. Но можно положиться на один из известных методов этого объекта (или сразу на несколько), например, можно проверить так:<br/></p>
<pre><code class="javascript"><span class="keyword">if</span> (obj.fire) {
    obj.add(fn);
}</code></pre>
<p><br/><br />
На самом деле, внутри этой функции создаётся обычный JS-объект с определённым набором методов, которые опираются на своё замыкание — это довольно распространённый в JavaScript способ задания приватных (private) переменных, недоступных вне этого псевдоконструктора.<br/><br />
Также, благодаря такому псевдоконструктору, методы этого объекта никак не зависят от контекста вызова — объекта, которому они принадлежат, а это значит, что их можно смело присваивать в свойства другого объекта, не заботясь о смене контекста, — всё по прежнему будет работать корректно. Это справедливо для всех методов, кроме <code>fire</code>, он как раз таки зависит от контекста, но использует его в качестве контекста выполнения колбэков из списка, т.е. этот метод в ряде случаев не просто можно, а именно <b>нужно</b> присваивать свойствам другого объекта со сменой контекста. Например:<br/></p>
<pre><code class="javascript"><span class="keyword">var</span> c = $.Callbacks(), obj = {};
obj.register = c.add;
obj.register(<span class="function"><span class="keyword">function</span><span class="params">()</span> {</span> console.log(<span class="string">'fired'</span>); });
c.fire();

<span class="comment">// output: 'fired'</span></code></pre>
<p><br/></p>
<h4>Флаги</h4>
<p style="margin-left:1cm;">
<p><i><b>Примечание</b>: далее по тексту под словами «вызов метода <code>fire()</code>» понимается вызов выполнения колбэков из списка в том числе и методом <code>fireWith()</code>.</i></p></blockquote>
<p>Параметр конструктора <code>flags</code> — это строка, в которой через пробел можно указать флаги — опции, в соответствии с которыми будет работать созданный объект <code>callbacks</code>. Поддерживаются такие флаги:<br/><br />
<b>once</b> — указывает, что список колбэков может быть выполнен только единожды, второй и последующие вызовы метода <code>fire()</code> будут безрезультатны (как это сделано в объекте deferred), если этот флаг не указан, то можно несколько раз вызывать метод <code>fire()</code>.<br/><br />
<b>memory</b> — указывает, что необходимо запоминать параметры последнего вызова метода <code>fire()</code> (и выполнения колбэков из списка) и немедленно выполнять добавляемые колбэки с соответствующими параметрами, если они добавляются уже после вызова метода <code>fire()</code> (как это сделано в объекте deferred).<br/><br />
<b>unique</b> — указывает, что каждый колбэк может быть добавлен в список только один раз, повторная попытка добавить колбэк в список ни к чему ни приведёт.<br/><br />
<b>stopOnFalse</b> — указывает, что нужно прекратить выполнение колбэков из списка, если какой-то из них вернул <code>false</code>, в пределах текущей сессии вызова <code>fire()</code>. Следующий вызов метода <code>fire()</code> начинает новую сессию выполнения списка колбэков, и они будут выполняться опять до тех пор, пока один из списка не вернёт <code>false</code> либо пока не закончатся.<br/></p>
<h4>Методы</h4>
<p>Ниже я приведу список методов с кратким описанием, примеры есть в официальных доках и для некоторых методов в следующем разделе. В общем-то методы довольно просты и ведут себя вполне ожидаемо.<br/><br />
<b>callbacks.add(callbacks)</b> <i>returns: callbacks</i> — добавляет в список колбэки, можно одновременно передавать в аргументах этого метода несколько функций (несколько аргументов) или массивов функций (можно одновременно и то и другое), можно даже вложенные массивы передавать. Все аргументы (или элементы массива), не являющиеся функциями, просто игнорируются. Метод (этот и некоторые далее) возвращает контекст своего вызова, позволяя тем самым организовать цепочку вызовов нескольких методов одного объекта подряд, как это принято в jQuery.<br/><br />
<b>callbacks.remove(callbacks)</b> <i>returns: callbacks</i> — удаляет колбэки из списка, причем даже если колбэк был добавлен дважды, удаление его произойдёт с обеих позиций. Т.о. если вызвать метод удаления некоторого колбэка из списка, то можно быть уверенным, что его в списке больше нет, сколько бы раз его не добавляли. Можно передавать несколько функций одновременно как несколько аргументов, массивы передавать нельзя, все аргументы не функции игнорируются.<br/><br />
<b>callbacks.has(callback)</b> <i>returns: boolean</i> — проверяет, есть ли указанная функция в списке колбэков.<br/><br />
<b>callbacks.empty()</b> <i>returns: callbacks</i> — очищает список колбэков.<br/><br />
<b>callbacks.disable()</b> <i>returns: callbacks</i> — «отключает» объект callbacks, все действия с ним будут безрезультатны. При этом перестают работать вообще все методы: <code>add</code> — ни к чему не приводит, <code>has</code> — всегда возвращает <code>false</code> и пр.<br/><br />
<b>callbacks.disabled()</b> <i>returns: boolean</i> — проверяет, отключен ли объект callbacks, после вызова <code>disable()</code> будет возвращать <code>true</code>.<br/><br />
<b>callbacks.lock()</b> <i>returns: callbacks</i> — фиксирует текущее состояние объекта callbacks относительно параметров и состояния выполнения списка колбэков. Этот метод актулен при использовании флага <i>memory</i> и предназначен для блокирования только последующих вызовов <code>fire()</code>, в остальных случаях он равносилен вызову <code>disable()</code>.<br/></p>
<blockquote><p>Детально этот метод работает так: если флаг <i>memory</i> не указан или ещё ни разу не был вызван метод <code>fire()</code> или последняя сессия выполнения колбэков была прервана возвратом <code>false</code> одним из них, то вызов <code>lock()</code> равносилен вызову <code>disable()</code> (именно он и вызывается внутри) и вызов <code>disabled()</code> в таком случае вернёт <code>true</code>, иначе будут заблокированы только последующие вызовы <code>fire()</code> — они не приведут ни к выполнению колбэков, ни к изменению параметров выполнения добавляемых колбэков (при наличии флага <i>memory</i>).</p></blockquote>
<p><br/><br />
<b>callbacks.locked()</b> <i>returns: boolean</i> — проверяет, зафиксирован ли объект callbacks методом <code>lock()</code>, также верёт <code>true</code> после вызова <code>disable()</code>.<br/><br />
<b>callbacks.fireWith( [context] [, args] )</b> <i>returns: callbacks</i> — запускает выполнение всех колбэков в списке с указанным контекстом и аргументами. <b>context</b> — указывает контекст выполнения колбэка (объект, доступный через <code>this</code> внутри функции). <b>args</b> — массив (именно массив) аргументов, передаваемых в колбэк.<br/><br />
<b>callbacks.fire( arguments )</b> <i>returns: callbacks</i> — запускает выполнение всех колбэков в списке с контекстом вызова и аргументами этого метода. <b>arguments</b> — список аргументов (не массив, как в методе <code>fireWith()</code>). Т.е. контекстом вызова и аргументами колбэков будут контекст и аргументы метода <code>fire()</code>.<br/><br />
Пример, как можно эквивалентно запустить исполнение колбэков с одинаковыми параметрами и контекстом:<br/></p>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks(),
    context = { test: <span class="number">1</span> };
callbacks.add(<span class="function"><span class="keyword">function</span><span class="params">(p, t)</span> {</span> console.log(<span class="keyword">this</span>.test, p, t); });

callbacks.fireWith(context, [ <span class="number">2</span>, <span class="number">3</span> ]);

<span class="comment">// output: 1 2 3</span>

context.fire = callbacks.fire;
context.fire(<span class="number">2</span>, <span class="number">3</span>);
<span class="comment">// output: 1 2 3</span></code></pre>
<p><br/><br />
Обратные вызовы (callbacks) из списка выполняются в том порядке, в котором они в этот список добавлялись. После выполнения колбэков при указанном флаге <i>once</i> список будет очищен, а если при этом не указан флаг <i>memory</i> или выполнение обратных вызовов было прервано возвратом <code>false</code>, то объект callbacks будет отключен методом <code>disable()</code>.<br/></p>
<h4>Примеры</h4>
<p><br/><br />
Давайте посмотрим как работают флаги на примерах. Во всех примерах используются такие функции:<br/></p>
<pre><code class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">fn1</span><span class="params">( value )</span>{</span>
    console.log( value );
}

<span class="function"><span class="keyword">function</span> <span class="title">fn2</span><span class="params">( value )</span>{</span>

    fn1(<span class="string">"fn2 says:"</span> + value);
    <span class="keyword">return</span> <span class="literal">false</span>;
}</code></pre>
<p><br/><br />
<br/></p>
<h5>$.Callbacks():</h5>
<p><br/></p>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( <span class="string">"foo"</span> );
callbacks.add( fn2 );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"bar"</span> );
callbacks.remove( fn2 );
callbacks.fire( <span class="string">"foobar"</span> );
console.log(callbacks.disabled());

<span class="comment">/*
output:
foo
bar
fn2 says:bar
bar
foobar
foobar
false
*/</span></code></pre>
<p><br/><br />
Никакие флаги не указали — вполне ожидаемое поведение.<br/><br />
<br/></p>
<h5>$.Callbacks(&#8217;once&#8217;):</h5>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks( <span class="string">"once"</span> );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"foo"</span> );
callbacks.add( fn2 );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"bar"</span> );
callbacks.remove( fn2 );
callbacks.fire( <span class="string">"foobar"</span> );
console.log(callbacks.disabled());

<span class="comment">/*
output:
foo
true
*/</span></code></pre>
<p><br/><br />
Тут всё понятно — один раз выполнили, что было, далее ничего не происходит, что ни делали, т.к. список уже отключен.<br/></p>
<h5>$.Callbacks(&#8217;memory&#8217;):</h5>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks( <span class="string">"memory"</span> );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"foo"</span> );
callbacks.add( fn2 );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"bar"</span> );
callbacks.remove( fn2 );
callbacks.fire( <span class="string">"foobar"</span> );
console.log(callbacks.disabled());

<span class="comment">/*
output:
foo
fn2 says:foo
foo
bar
fn2 says:bar
bar
foobar
foobar
false
*/</span></code></pre>
<p><br/><br />
Здесь, вроде, тоже ничего сложного — после первого выполнения каждое добавление колбэка вызывает его немедленное выполнение, а потом снова вызываем выполнение всего списка. При этом одну функцию мы добавили в список дважды — она дважды и срабатывает.<br/></p>
<h5>$.Callbacks(&#8217;unique&#8217;):</h5>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks( <span class="string">"unique"</span> );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"foo"</span> );
callbacks.add( fn2 );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"bar"</span> );
callbacks.remove( fn2 );
callbacks.fire( <span class="string">"foobar"</span> );
console.log(callbacks.disabled());

<span class="comment">/*
output:
foo
bar
fn2 says:bar
foobar
false
*/</span></code></pre>
<p><br/><br />
А в этом случае повторное добавление функции <code>fn1</code> было проигнорировано.<br/></p>
<h5>$.Callbacks(&#8217;stopOnFalse&#8217;):</h5>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks( <span class="string">"stopOnFalse"</span> );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"foo"</span> );
callbacks.add( fn2 );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"bar"</span> );
callbacks.remove( fn2 );
callbacks.fire( <span class="string">"foobar"</span> );
console.log(callbacks.disabled());

<span class="comment">/*
output:
foo
bar
fn2 says:bar
foobar
foobar
false
*/</span></code></pre>
<p><br/><br />
Обратный вызов <code>fn2</code> прерывает цепочку выполнения, т.к. возвращает <code>false</code>.<br/><br />
Это простые примеры, а теперь давайте попробуем поиграться с комбинациями флагов — будет немного интереснее:<br/></p>
<h5>$.Callbacks(&#8217;once memory&#8217;):</h5>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks( <span class="string">"once memory"</span> );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"foo"</span> );
callbacks.add( fn2 );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"bar"</span> );
callbacks.remove( fn2 );
callbacks.fire( <span class="string">"foobar"</span> );
console.log(callbacks.disabled());

<span class="comment">/*
output:
foo
fn2 says:foo
foo
false
*/</span></code></pre>
<p><br/><br />
Видим, что сработали только первый <code>fire()</code> и добавление новых колбэков привело к их немедленному выполнению с параметрами первого <code>fire()</code>.<br/></p>
<h5>$.Callbacks(&#8217;once memory unique&#8217;):</h5>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks( <span class="string">"once memory unique"</span> );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"foo"</span> );
callbacks.add( fn2 );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"bar"</span> );
callbacks.remove( fn2 );
callbacks.fire( <span class="string">"foobar"</span> );
console.log(callbacks.disabled());

<span class="comment">/*
output:
foo
fn2 says:foo
foo
false
*/</span></code></pre>
<p><br/><br />
Здесь результат тот же, несмотря на то, что мы указали флаг <i>unique</i> и дважды добавляем <code>fn1</code>, — второй раз добавление этой функции в список сработало, потому что при указанном флаге <i>once</i> после выполнения колбэков список очищается, а флаг <i>memory</i> указывает, что последующие добавления колбэков будут приводить к их немедленному выполнению без помещения в список, а так как список пуст — то добавление любой функции всегда уникально. Но этот флаг сыграет свою роль при попытке добавить за раз несколько колбэков, среди которых есть дублирующиеся, если в предыдущем коде изменить 4-ю строку как показано ниже, то <code>fn2</code> всё равно выполнена будет только один раз (а без флага <i>unique</i> была бы выполнена три раза):<br/></p>
<pre><code class="javascript">callbacks.add( fn2, fn2, fn2 );</code></pre>
<p><br/></p>
<h5>$.Callbacks(&#8217;once memory stopOnFalse&#8217;):</h5>
<pre><code class="javascript"><span class="keyword">var</span> callbacks = $.Callbacks( <span class="string">"once memory stopOnFalse"</span> );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"foo"</span> );
callbacks.add( fn2 );
callbacks.add( fn1 );
callbacks.fire( <span class="string">"bar"</span> );
callbacks.remove( fn2 );
callbacks.fire( <span class="string">"foobar"</span> );
console.log(callbacks.disabled());

<span class="comment">/*
output:
foo
fn2 says:foo
true
*/</span></code></pre>
<p><br/><br />
Возврат <code>false</code> заблокировал все дальнейшие выполнения колбэков и при наличии флага <i>once</i> вообще привёл к отключению объекта callbacks.<br/><br />
Я не буду рассматривать все возможные комбинации флагов, я постарался выбрать наиболее интересные (не совсем простые) и объяснить поведение callbacks. Остальные комбинации можно протестировать самостоятельно, например, воспользовавшись заготовкой: <a href="http://jsfiddle.net/zandroid/JXqzB/">http://jsfiddle.net/zandroid/JXqzB/</a><br/></p>
<h4>Обещанное улучшение</h4>
<p>Улучшение, конечно, совсем не обязательное и даже, может быть, в какой-то степени надуманное, не судите строго.<br/><br />
Идея улучшения в том, чтобы опустить вызов метода <code>fire()</code>, а вместо этого сам объект callbacks использовать как функцию. Для этого пишем такую функцию:<br/></p>
<pre><code class="javascript">(<span class="function"><span class="keyword">function</span><span class="params">($, undefined)</span>{</span>
    $.FCallbacks = <span class="function"><span class="keyword">function</span><span class="params">(flags, fns)</span> {</span>

        <span class="keyword">var</span> i = $.type(flags) === <span class="string">'string'</span> ? <span class="number">1</span> : <span class="number">0</span>,
            callbacks = $.Callbacks(i ? flags : undefined);
        callbacks.add(Array.prototype.slice.call(arguments, i))
        <span class="keyword">return</span> $.extend(callbacks.fire, callbacks, { fcallbacks: <span class="literal">true</span> });
    };
})(jQuery);</code></pre>
<p><br/></p>
<p>И без лишних слов посмотрим пример использования:<br/></p>
<pre><code class="javascript"><span class="function"><span class="keyword">function</span> <span class="title">fn1</span><span class="params">(p1, p2)</span> {</span> console.log(<span class="string">'fn1 says:'</span>, <span class="keyword">this</span>, p1, p2); }
<span class="function"><span class="keyword">function</span> <span class="title">fn2</span><span class="params">(p1, p2)</span> {</span> console.log(<span class="string">'fn2 says:'</span>, <span class="keyword">this</span>, p1, p2); }

<span class="keyword">var</span> callbacks = $.FCallbacks(<span class="string">'once'</span>, fn1, rn2);
callbacks.add(fn2);
callbacks(<span class="number">2</span>, <span class="number">3</span>);</code></pre>
<p><br/><br />
Также ещё у нового «конструктора» появилась возможность сразу же передавать начальные колбэки в параметрах, без лишнего вызова <code>add()</code>.<br/><br />
Ну и в работе: <a href="http://jsfiddle.net/zandroid/RAVtF/">jsfiddle.net/zandroid/RAVtF/</a><br/></p>
<h4>Реальное использование</h4>
<p><b>Callbacks</b> на самом деле теперь используется <b>очень многими</b> пользователями jQuery 1.7+ и был сделан командой разработчиков не просто, потому что им захотелось сделать новый фичер. Смотрите, цепочка и логика этого вопроса довольно проста: <br/><br />
В библиотеке был реализован метод <code>$.ajax()</code>, который по своей природе не что иное, как надстройка над неким Deferred — разработчики улучшили код, вынесли его отдельно от основного кода <code>$.ajax()</code> (для возможности повторного использования и упрощения тестирования) и решили, а почему бы не опубликовать этот код (дать доступ пользователям библиотеки к нему и задокументировать его) — получился <code>$.Deferred</code>.<br/><br />
В свою очередь <code>$.Deferred</code> — это изначально два (<code>done()</code> и <code>fail()</code>), а теперь три (+ ещё <code>progress()</code>) надстройки над Callbacks, который был сделан как внутренний код <code>$.Deferred</code>. И снова, разработчики улучшили и отделили этот код от <code>$.Deferred</code>, реализовав последний через <code>$.Callbacks</code> (кстати, source-код <code>$.Deferred</code> стал при этом намного понятнее и читабельнее).<br/><br />
Вывод: разработчики не ставят главной целью добавление новых «никому не нужных» фичеров, они оптимизируют уже существующий внутренний код, попутно публикуя побочные, но не менее полезные от этого, результаты. И каждый раз, когда вы используете <code>$.ajax()</code> — знайте, вы используете <code>$.Deferred</code>, а значит и <code>$.Callbacks</code>. Это и есть пример реального использования.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2012/01/11/jquerycallbacks-object/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Backup Time Machine своими руками</title>
		<link>http://softwarepeople.ru/blog/2012/01/09/backup-time-machine/</link>
		<comments>http://softwarepeople.ru/blog/2012/01/09/backup-time-machine/#comments</comments>
		<pubDate>Mon, 09 Jan 2012 19:54:19 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[Системное администрирование]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5725</guid>
		<description><![CDATA[Автор статьи: Дорощенко Юрий (сайт автора)
Как ни крути, а в новогодние праздники, риск порчи файлов значительно возрастает. Не миновала сия беда и меня. Как не трудно догадаться, я перепутал диск при форматировании и… да-да, все, что было нажито неправедным путем непосильным трудом, в один момент было уничтожено.
Помянув сборник софта и архив отсканированных справочников, я задумался ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор статьи: Дорощенко Юрий</strong> (<a href="http://derlab.ru/">сайт</a> автора)</p>
<p>Как ни крути, а в новогодние праздники, риск порчи файлов значительно возрастает. Не миновала сия беда и меня. Как не трудно догадаться, я перепутал диск при форматировании и… да-да, все, что было нажито <s>неправедным путем</s> непосильным трудом, в один момент было уничтожено.<br/><br />
Помянув сборник софта и архив отсканированных справочников, я задумался над вопросом бекапов. И… Пришел к выводу, что того, что мне в самом деле требуется, нет. Точнее конечно же есть, но либо стоит дорого, либо работает не так, как мне бы того хотелось. <br/><br />
Закончив с пытками гугла на тему: «сделай мне хорошо», решил поступить как истинный Unix`оид, хоть и работающий в форточках. А именно: не выпендривайся, чем проще — лучше.<br/><br />
Тут я вспомнил презентацию MacOS на которой демонстрировали их Time Machine. Ведь если подумать, очень удобно иметь возможность получить доступ к любому файлу за любой день. Но… Если делать полные копии в виде архивов, то никаких объемов не хватит, чтобы это все хранить. Дальше мысль зацепилась за инкрементные бекапы. То есть в первый раз вы делаете полный архив, а затем архивируете только то, что изменилось.<br/><br />
И… Этот вариант я тоже отбросил, как не удобный для моего случая. Во-первых, существует необходимость в возможности удалять произвольные «дни». А во-вторых, я очень часто переименовываю свои файлы и время от времени перекладываю из директории в директорию. А это, в свою очередь, к моему удивлению, срезало почти всех кандидатов в «бекаперы». То есть, софт тупо смотрел на имя файла, дату его изменения и… и все. В итоге бекап разбухал.<br/><br />
Итак, спасли меня две идеи:<br/><br />
Во-первых, не важно как называется файл, важно содержание. Таким образом с каждого файла должно сниматься несколько хешей и на основе этой сигнатуры можно достаточно точно судить, что это за файл. В моем случае я ограничился снятием md5 суммы и размером файла. Выбор конечно спорный, но для сканов этого вполне достаточно.<br/><br />
Во-вторых, если файл не изменился или изменилость только его имя, надо копировать не весь файл, а делать на него жесткую ссылку, благо в NTFS такие есть.<br/><br />
Если кто не знает, то благодаря команде:<br/><br />
<code>fsutil hardlink create &lt;ссылка&gt; &lt;файл&gt;</code><br/><br />
<br/><br />
в windows можно получить настоящую жесткую ссылку.<br/><br />
<br/><br />
В итоге, получился простой алгоритм, который я, не заморачиваясь, оформил на консольном PHP. Теперь бекап происходит при подключении переносного диска к компьютеру, либо (если диск уже подключен) раз в сутки.<br/><br />
И вот собственно сам «бекапер».<br/></p>
<pre><code class="php"><span class="preprocessor">&lt;?php</span>
<span class="comment">//</span>
<span class="variable">$dir</span> = <span class="keyword">array</span>();

<span class="variable">$hah</span> = <span class="keyword">array</span>();
<span class="variable">$hah_new</span> = <span class="keyword">array</span>();
<span class="variable">$file</span> = <span class="keyword">array</span>();
<span class="variable">$copy</span> = <span class="number">0</span>;

<span class="variable">$link</span> = <span class="number">0</span>;
<span class="keyword">include</span> <span class="string">'conf.php'</span>;
<span class="variable">$date</span> = date(<span class="string">'Y-m-d'</span>);

<span class="comment">// выход если бекап на сегодня уже существует</span>
<span class="keyword">if</span>(is_dir(<span class="variable">$date</span>)){
    <span class="keyword">exit</span>(<span class="string">"Backup already exists\n"</span>);
}

<span class="comment">// составляем список существующих файлов</span>
<span class="keyword">foreach</span>(glob(<span class="string">'*'</span>, GLOB_ONLYDIR) <span class="keyword">as</span> <span class="variable">$v</span>){
    <span class="keyword">if</span>(is_file(<span class="variable">$v</span>.<span class="string">'/hah.db'</span>)){
        <span class="variable">$hah</span> = array_merge(<span class="variable">$hah</span>, unserialize(file_get_contents(<span class="variable">$v</span>.<span class="string">'/hah.db'</span>)));
    }
}

<span class="comment">// создаем дерево директорий</span>
<span class="keyword">foreach</span>(<span class="variable">$dir</span> <span class="keyword">as</span> <span class="variable">$v</span>){
    <span class="variable">$x</span> = explode(<span class="string">'/'</span>, <span class="variable">$v</span>);
    array_unshift(<span class="variable">$x</span>, <span class="variable">$date</span>);
    <span class="variable">$x</span>[<span class="number">1</span>] = substr(<span class="variable">$x</span>[<span class="number">1</span>], <span class="number">0</span>, <span class="number">1</span>);
    <span class="keyword">foreach</span>(<span class="variable">$x</span> <span class="keyword">as</span> <span class="variable">$k</span>=&gt;<span class="variable">$v</span>){
        <span class="variable">$y</span> = implode(<span class="string">'/'</span>, array_slice(<span class="variable">$x</span>, <span class="number">0</span>, <span class="variable">$k</span>+<span class="number">1</span>));
        <span class="keyword">if</span>(!is_dir(<span class="variable">$y</span>)){
            mkdir(<span class="variable">$y</span>);
        }
    }
}

<span class="comment">// получаем список файлов</span>
<span class="keyword">while</span>(<span class="variable">$n</span> = array_pop(<span class="variable">$dir</span>)){
    <span class="keyword">if</span>(!is_dir(<span class="variable">$date</span>.<span class="string">'/'</span>.substr(<span class="variable">$n</span>, <span class="number">0</span>, <span class="number">1</span>).<span class="string">'/'</span>.substr(<span class="variable">$n</span>, <span class="number">3</span>))){
        mkdir(<span class="variable">$date</span>.<span class="string">'/'</span>.substr(<span class="variable">$n</span>, <span class="number">0</span>, <span class="number">1</span>).<span class="string">'/'</span>.substr(<span class="variable">$n</span>, <span class="number">3</span>));
    }
    <span class="variable">$dir</span> = array_merge(<span class="variable">$dir</span>, glob(<span class="variable">$n</span>.<span class="string">'/*'</span>, GLOB_ONLYDIR));
    <span class="variable">$file</span> = array_merge(<span class="variable">$file</span>, array_diff(glob(<span class="variable">$n</span>.<span class="string">'/*'</span>), glob(<span class="variable">$n</span>.<span class="string">'/*'</span>, GLOB_ONLYDIR)));
}

<span class="comment">// копируем новые и линкуем старые файлы</span>
<span class="keyword">foreach</span>(<span class="variable">$file</span> <span class="keyword">as</span> <span class="variable">$k</span>=&gt;<span class="variable">$v</span>){
    <span class="variable">$x</span> = md5_file(<span class="variable">$v</span>).filesize(<span class="variable">$v</span>);
    <span class="keyword">if</span>(!<span class="variable">$x</span>){
        <span class="keyword">continue</span>;
    }
    <span class="variable">$f</span> = <span class="variable">$date</span>.<span class="string">'/'</span>.substr(<span class="variable">$v</span>, <span class="number">0</span>, <span class="number">1</span>).<span class="string">'/'</span>.substr(<span class="variable">$v</span>, <span class="number">3</span>);
    <span class="keyword">if</span>(<span class="variable">$hah</span>[<span class="variable">$x</span>]){
        exec(<span class="string">'fsutil hardlink create "'</span>.<span class="variable">$f</span>.<span class="string">'" "'</span>.<span class="variable">$hah</span>[<span class="variable">$x</span>].<span class="string">'"'</span>);
        <span class="variable">$hah_new</span>[<span class="variable">$x</span>] = <span class="variable">$f</span>;
        <span class="variable">$link</span>++;
    }<span class="keyword">else</span>{
        copy(<span class="variable">$v</span>, <span class="variable">$f</span>);
        <span class="variable">$hah_new</span>[<span class="variable">$x</span>] = <span class="variable">$f</span>;
        <span class="variable">$copy</span>++;
    }
    <span class="keyword">print</span> ceil(<span class="variable">$k</span>*<span class="number">100</span>/count(<span class="variable">$file</span>)).<span class="string">"%\r"</span>;
}

<span class="keyword">print</span> <span class="string">"\nLink: "</span>.<span class="variable">$link</span>.<span class="string">"\n"</span>;
<span class="keyword">print</span> <span class="string">"Copy: "</span>.<span class="variable">$copy</span>.<span class="string">"\n"</span>;

<span class="comment">// сохраняем информацию о файлах</span>

file_put_contents(<span class="variable">$date</span>.<span class="string">'/hah.db'</span>, serialize(<span class="variable">$hah_new</span>));
<span class="keyword">exit</span>;
</code></pre>
<p><br/><br />
<br/><br />
Его конфигурация:<br/></p>
<pre><code class="php"><span class="preprocessor">&lt;?php</span>
date_default_timezone_set(<span class="string">'Asia/Novosibirsk'</span>);

<span class="variable">$dir</span>[] = <span class="string">'c:/scan'</span>; <span class="comment">// сканы</span>
<span class="variable">$dir</span>[] = <span class="string">'c:/web'</span>; <span class="comment">// корень локального сервера</span>
<span class="variable">$dir</span>[] = <span class="string">'c:/gohsrf'</span>; <span class="comment">// приказы</span>

<span class="variable">$dir</span>[] = <span class="string">'q:'</span>; <span class="comment">// флешка</span>
</code></pre>
<p><br/><br />
<br/><br />
Ну и BAT`ник его запускающий:<br/></p>
<pre><code class="bat bash">@<span class="keyword">echo</span> off<br />
cls<br />
php backup.php<br />
pause</p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2012/01/09/backup-time-machine/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Простая real-time коммуникация с посетителем</title>
		<link>http://softwarepeople.ru/blog/2012/01/08/simple-real-time/</link>
		<comments>http://softwarepeople.ru/blog/2012/01/08/simple-real-time/#comments</comments>
		<pubDate>Sun, 08 Jan 2012 11:54:51 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[Программирование]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5716</guid>
		<description><![CDATA[Автор статьи: Евгений Лисицкий, технический директор Sports.ru

Я давно интересуюсь вебом в реальном времени. На сегодня уже есть ряд библиотек для этого. В этой статье я хочу рассказать про недавно опробованный нами самый простой способ — использование внешнего сервиса Pusher.com.
Если сегодня асинхронно отправить сообщение на сервер проще простого, то с обратным транспортом пока не все так ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор статьи: Евгений Лисицкий, технический директор Sports.ru</strong><br />
<br />
Я давно интересуюсь вебом в реальном времени. На сегодня уже есть ряд библиотек для этого. В этой статье я хочу рассказать про недавно опробованный нами самый простой способ — использование внешнего сервиса Pusher.com.</p>
<p>Если сегодня асинхронно отправить сообщение на сервер проще простого, то с обратным транспортом пока не все так радужно. Сервис как раз берет на себя эту задачу.</p>
<p><a href="http://softwarepeople.ru/files/2012/01/b1c8246b.png"><img src="http://softwarepeople.ru/files/2012/01/b1c8246b.png" alt="b1c8246b" title="b1c8246b" width="640" height="233" class="aligncenter size-full wp-image-5717" /></a><br />
Все работает очень просто. На страницу вставляется JavaScript пушера, он открывает подключение к серверам пушера и подписывается на сообщения по какому-нибудь каналу. Когда вы хотите отправить сообщение, вы вызываете API пушера и отправляете сообщение в канал. Все пользователи, подписавшиеся на канал, его получат.<br />
Таким образом можно очень легко организовать массовую рассылку сообщений всем нужным пользователям в онлайне. Что позволяет очень просто создать чат — достаточно вставить пушер на страницу, скрипт на вашем сервере будет принимать сообщения POST-ом, «дергать» API Pusher`a, а тот будет рассылать их всем клиентам через веб сокеты. Аналогично можно сделать и более сложные вещи, например, совместные графические редакторы — все есть в <a href="http://pusher.com/examples">примерах</a> и <a href="http://pusher.com/docs">документации</a>.</p>
<p>От себя замечу — мы несколько месяцев назад перевели чат и онлайны на пушер, тем самым решили сразу несколько проблем:<br />
 — значительно ускорили доставку сообщений<br />
 — снизили пользователям трафик<br />
 — уменьшили нагрузку на на наши сервера — теперь мы только принимаем сообщения, а рассылкой по пользователям занимается пушер<br />
 — улучшили масштабируемость системы</p>
<p>Когда переходили, были некоторые сомнения — не столкнутся ли пользователи с проблемами. Насколько хорошо будут поддерживаться разные браузеры. Опасения были напрасными — пушер использует WebSockets, а при их недоступности (например, в древних недобраузерах ИЕ) старую добрую эмуляцию через Flash Sockets — именно то, что я использовал в своем модельном <a href="http://chat.websockets.ru">чате на вебсокетах</a>. Практика показала, что этого достаточно. Усложнять и деградировать до Long Polling, Multi Part и прочего не требуется.</p>
<p>Недавно мы решили использовать его еще в одном более сложном проекте.<br />
Если интересно, то как реализуем, подробно опишу.</p>
<h5>Подведем итог</h5>
<p>
Плюсы:<br />
 — очень просто поключать к сайту — есть готовые либы на PHP и Python<br />
 — если нет готовых либ для вашего языка, то можно использовать простое API, для этого достаточно curl`a<br />
 — снимает проблему масштабирования — пушер размещается на серверах Амазона</p>
<p>Минусы:<br />
 — платный</p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2012/01/08/simple-real-time/feed/</wfw:commentRss>
		</item>
		<item>
		<title>WebSockets — полноценный асинхронный веб</title>
		<link>http://softwarepeople.ru/blog/2011/12/24/websockets-async-web/</link>
		<comments>http://softwarepeople.ru/blog/2011/12/24/websockets-async-web/#comments</comments>
		<pubDate>Sat, 24 Dec 2011 19:41:33 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[Программирование]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5705</guid>
		<description><![CDATA[Автор: Евгений Лисицкий, технический директор Sports.ru
Недавно разработчики Google Chromium опубликовали новость о поддержке технологии WebSocket. В айтишном буржунете новость произвела эффект разорвавшейся бомбы. В тот же день различные очень известные айтишники опробовали новинку и оставили восторженные отзывы в своих блогах. Моментально разработчики самых разных серверов/библиотек/фреймворков (в их числе Apache, EventMachine, Twisted, MochiWeb и т.д.) объявили ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор: Евгений Лисицкий</strong>, технический директор Sports.ru</p>
<p>Недавно разработчики Google Chromium опубликовали <a href="http://blog.chromium.org/2009/12/web-sockets-now-available-in-google.html">новость</a> о поддержке технологии WebSocket. В айтишном буржунете новость произвела эффект разорвавшейся бомбы. В тот же день различные очень известные айтишники опробовали новинку и оставили восторженные отзывы в своих блогах. Моментально разработчики самых разных серверов/библиотек/фреймворков (в их числе <i>Apache, EventMachine, Twisted, MochiWeb</i> и т.д.) объявили о том, что поддержка ВебСокетов будет реализована в их продуктах в ближайшее время.<br />
Что же такого интересного сулит нам технология? На мой взгляд, <b>WebSocket</b> — это самое кардинальное расширение протокола HTTP с его появления. Это не финтифлюшки, это <b>сдвиг&nbsp; парадигмы&nbsp;HTTP</b>. Изначально синхронный протокол, построенный по модели «запрос — ответ», становится <b>полностью асинхронным и симметричным</b>. Теперь уже нет клиента и сервера с фиксированными ролями, а есть два равноправных участника обмена данными. Каждый работает сам по себе, и когда надо отправляет данные другому. Отправил — и пошел дальше, ничего ждать не надо. Вторая сторона ответит, когда захочет — может не сразу, а может и вообще не ответит. Протокол дает полную свободу в обмене данными, вам решать как это использовать.</p>
<p>Я считаю, что веб сокеты придутся ко двору, если вы разрабатываете:<br />
 — веб-приложения с интенсивным обменом данными, требовательные к скорости обмена и каналу;<br />
 — приложения, следующие стандартам;<br />
 — «долгоиграющие» веб-приложения;<br />
 — комплексные приложения со множеством различных асинхронных блоков на странице;<br />
 — кросс-доменные приложения.<br />
<b>И как это работает?</b></h4>
<p>Очень просто! Как только ваша страница решила, что она хочет открыть веб сокет на сервер, она создает специальный javascript-объект:<br />
<code><font color="black">
<ol>
<li><font color="#0000ff">&lt;</font><font color="#800000">script</font><font color="#0000ff">&gt;</font></li>
<li>ws = <font color="#0000ff">new</font> WebSocket(<font color="#A31515">&#8220;ws://site.com/demo&#8221;</font>);</li>
<li>&nbsp;</li>
<li><font color="#008000">// и навешивает на новый объект три колл-бека:</font></li>
<li>&nbsp;</li>
<li><font color="#008000">// первый вызовется, когда соединение будет установлено:</font></li>
<li>ws.onopen = <font color="#0000ff">function</font>() { alert(<font color="#A31515">&#8220;Connection opened&#8230;&#8221;</font>) };</li>
<li>&nbsp;</li>
<li><font color="#008000">// второй - когда соединено закроется</font></li>
<li>ws.onclose = <font color="#0000ff">function</font>() { alert(<font color="#A31515">&#8220;Connection closed&#8230;&#8221;</font>) };</li>
<li>&nbsp;</li>
<li><font color="#008000">// и, наконец, третий - каждый раз, когда браузер получает какие-то данные через веб-сокет</font></li>
<li>ws.onmessage = <font color="#0000ff">function</font>(evt) { $(<font color="#A31515">&#8220;#msg&#8221;</font>).append(<font color="#A31515">&#8220;&lt;p&gt;&#8221;</font>+evt.data+<font color="#A31515">&#8220;&lt;/p&gt;&#8221;</font>); };</li>
<li>&nbsp;</li>
<li><font color="#0000ff">&lt;/</font><font color="#800000">script</font><font color="#0000ff">&gt;</font></li>
</ol>
<p></font><font color="gray">* This source code was highlighted with <a href="http://virtser.net/blog/post/source-code-highlighter.aspx"><font color="gray">Source Code Highlighter</font></a>.</font></code></p>
<h5><b>А что при этом происходит в сети?</b></h5>
<p>
Все начинается так же как в обычном HTTP-запросе. Браузер подключается по протоколу TCP на 80 порт сервера и дает немного необычный GET-запрос:<br />
<code><font color="black">GET /demo HTTP/1.1<br />
Upgrade: WebSocket</p>
<p>Connection: Upgrade<br />
Host: site.com<br />
Origin:&nbsp;http://site.com</font><br />
</code><br />
Если сервер поддерживает ВебСокеты, то он отвечает таким образом:<br />
<code><font color="black">HTTP/1.1 101 Web Socket Protocol Handshake<br />
Upgrade: WebSocket<br />
Connection: Upgrade<br />
WebSocket-Origin:&nbsp;http://site.com</p>
<p>WebSocket-Location: ws://site.com/demo</font><br />
</code></p>
<p>Если браузер это устраивает, то он просто оставляет <i>TCP-соединение открытым</i>. Все — «рукопожатие» совершено, канал обмена данными готов.<br />
Как только одна сторона хочет передать другой какую-то информацию, она отправляет дата-фрейм следующего вида:<br />
<code><font color="black">0&#215;00, &lt;строка в кодировке UTF-8&gt;, 0xFF </font></code><br />
То есть просто строка текста — последовательность байт, к которой спереди приставлен нулевой байт 0&#215;00, а в конце — 0xFF. И все — никаких заголовков, метаданных! Что именно отправлять, разработчики полностью оставили на ваше усмотрение: хотите XML, хотите JSON, да хоть стихи Пушкина.<br />
Каждый раз, когда браузер будет получать такое сообщение, он будет «дергать» ваш колл-бек onmessage. </p>
<p>Легко понять, что КПД такого протокола стремится к 95%. Это не классический AJAX-запрос, где на каждую фитюльку приходится пересылать несколько килобайт заголовков. Разница будет особенно заметна если делать частый обмен небольшими блоками данных. Скорость обработки так же стремится к скорости чистого TCP-сокета — ведь все уже готово — соединение открыто — всего лишь байты переслать.<br />
<code><font color="gray">Лирическое отступление:<br />
И еще одна вещь, которая меня очень радует - в качестве единственной разрешенной кодировки выбрана UTF-8! Я уже робко надеюсь, что через некоторое время мы уйдем от одного из костылей веба.</font><br />
</code></p>
<h5><b>А картинку можно отправить?</b></h5>
<p>
С помощью WebSockets так же можно передавать и бинарные данные. Для них используется другой дата-фрейм следующего вида:<br />
<code><font color="black">0&#215;80, &lt;длина - один или несколько байт&gt;, &lt;тело сообщения&gt;</font></code></p>
<p>Что значит «один или несколько байт»? Чтобы не создавать ограничений на длину передаваемого сообщения и в тоже время не расходовать байты нерационально, разработчики использовали очень хитрый способ указания длины тела сообщения. Каждый байт в указании длины рассматривается по частям: самый старший бит указывает является ли этот байт последним (0) либо же за ним есть другие (1), а младшие 7 битов содержат собственно данные. Обрабатывать можно так: как только вы получили признак бинарного дата-фрейма 0&#215;80, вы берете следующий байт и откладываете его в отдельную «копилку», смотрите на следующий байт — если у него установлен старший бит, то переносите и его в «копилку», и так далее, пока вам не встретится байт с 0 старшим битом. Значит это последний байт в указателе длины — его тоже переносите в «копилку». Теперь из всех байтов в «копилке» убираете старшие биты и слепляете остаток. Вот это и будет длина тела сообщения. Еще можно интерпретировать как 7-битные числа без старшего бита.</p>
<p>Например, самую главную картинку веб-дизайна — прозначный однопиксельный GIF размером 43 байта можно передать так:<br />
<code><font color="black">0&#215;80, 0&#215;2B, &lt;тело сообщения&gt;</font></code><br />
Объект размером 160 байт закодируется 2 байтами длины:<br />
<code><font color="black">0&#215;80, 0&#215;81, 0&#215;20, &lt;байты объекта&gt;</font></code><br />
Не правда ли, очень элегантно?</p>
<h4><b>Что это нам дает?</b></h4>
<p></p>
<h5><b>Скорость и эффективность</b></h5>
<p>
Высокую скорость и эффективность передачи обеспечивает малый размер передаваемых данных, который иногда даже будет помещаться в один TCP-пакет — здесь, конечно, же все зависит от вашей бизнес-логики. (В дата-фрейм можно засунуть и БСЭ, но для такой передачи потребуется чуть больше 1 TCP- пакета. <img src='http://softwarepeople.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ).<br />
Так же учтите, что соединение уже готово — не надо тратить время и трафик на его установление, хендшейки, переговоры.</p>
<h5><b>Стандартность</b></h5>
<p>
Самим своим выходом WebSockets отправит на свалку истории Comet и все приблуды накрученные поверх него — Bayuex, LongPolling, MultiPart и так далее. Это все полезные технологии, но по большей части, они работают на хаках, а не стандартах. Отсюда периодески возникают проблемы: то прокся ответ «зажевала» и отдала его только накопив несколько ответов. Для «пробивания» проксей часто использовался двух-килобайтный «вантуз» — т.е. объем передаваемых данных увеличивался пробелами (или другими незначащими символами) до 2К, которые многие прокси передавали сразу, не задерживая. Периодически антивирусы выражали свое желание получить ответ полностью, проверить его, и только потом передать получателю. Конечно, сегодня все эти проблемы более-менее решены — иначе бы не было такого большого кол-ва веб-приложений. Однако, развитие в этом направлении сопряжено с появлением новых проблем — именно потому, что это попытка делать в обход стандарта.</p>
<p>На мой взгляд, через некоторое время останется только 2 технологии: чистый AJAX и WebSockets. Первая хороша для одно- или несколькоразовых обновлений на странице — действительно, врядли рационально для этого раскочегаривать мощную машину веб-сокетов. А все остальное, что сейчас делается кометом и коллегами, переедет на веб-сокеты, т.к. это будет проще и эффективнее. Например, вы хотите<b> вживую мониторить цены на рынке форекс</b>. Пожалуйста: открывайте сокет — сервер вам будет присылать все обновления. Ваша задача только повесить правильный колл-бек на событие onmessage и менять циферки на экране. Вы решили что-то прикупить, отправьте серверу асинхронное сообщение, а параллельно продолжайте получать циферки. Элегантно? По сравнению с этим LongPolling с необходимостью периодческого рестарта даже неактивного канала (чтобы его прокся или еще кто не прихлопнул) выглядит грязным хаком.</p>
<h5><b>Время жизни канала</b></h5>
<p>
В отличие от HTTP веб-сокеты не имеют ограничений на время жизни в неактивном состоянии. Это значит, что больше не надо периодически рефрешить соединение, т.к. его не вправе «прихлопывать» всякие прокси. Значит, соединение может висеть в неактивном виде и не требовать ресурсов. Конечно, можно возразить, что на сервере будут забиваться TCP-сокеты. Для этого достаточно использовать хороший мультиплексор, и нормальный сервер легко потянет до миллиона открытых коннектов.</p>
<h5><b>Комплексные веб-приложения</b></h5>
<p>
Как известно в HTTP предусмотрено ограничение на число одновременных октрытых сессий к одному серверу. Из-за этого если у вас много различных асинхронных блоков на странице, то вам приходилось делать не только серверный, но и клиентский мультиплексор — именно отсюда идет <a href="http://svn.cometd.com/trunk/bayeux/bayeux.html">Bayeux Protocol</a>. <br />
К счастью, это ограничение не распространяется на веб-сокеты. Открываете столько, сколько вам нужно. А сколько использовать — одно (и через него все мультиплексировать) или же наоборот — на каждый блок свое соединение — решать вам. Исходите из удобства разработки, нагрузки на сервер и клиент.</p>
<h5><b>Кросс-доменные приложения</b></h5>
<p>
И еще один «камень в ботинке» AJAX-разработчика — проблемы с кросс-доменными приложениями. Да, и для них тоже придумана масса хаков. Помашем им ручкой и смахнем скупую слезу. WebSockets не имеет таких ограничений. Ограничения вводятся не по принципу «из-того-же-источника», а из «разрешенного-источника», и определяются не на клиенте, а на сервере. Думаю, внимательные уже заметили новый заголовок Origin. Через него передается информация откуда хотят подключиться к вашему websocket-у. Если этот адрес вас не устраивает, то вы отказываете в соединение. <br />
Все! Конец кросс-доменной зопяной боли!</p>
<h5><b>А руками пощупать можно?</b></h5>
<p>
Можно!</p>
<p><b>UPDATE:</b> Одной из открытых реализаций веб-сокетов является чат на <a href="http://www.mibbit.com">www.mibbit.com</a> (<a href="http://blog.mibbit.com/?p=487">заметка</a> в их блоге).</p>
<p>PHP-реализация сервера WebSocket также представлена модулем к асинхронному фреймворку <a href="http://phpdaemon.googlecode.com/">phpDaemon</a>, модуль называется <a href="http://code.google.com/p/phpdaemon/source/browse/trunk/applications/WebSocketServer.php">WebSocketServer</a>. Пример простейшего приложения, которое отвечает «pong» на фрейм (пакет) «ping» — <a href="http://code.google.com/p/phpdaemon/source/browse/trunk/applications/ExampleWebSocket.php">ExampleWebSocket</a>.<br />
Вы можете попутно прослушать соедиение с помощью например tcpdump или любой другой программы и увидеть в действии всю ту механику, которую я описал выше.</p>
<h4><b>Светлое будущее</b></h4>
<p>
И когда же оно настанет? На самом деле очень скоро. Гугл в очередной раз дал «волшебного пендаля» всей веб-индустрии, и все зашевелились. Вы удивитесь, но тут же люди вспомнили, что в багзилле фаерфокса уже год(!) <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=472529">висит задача</a> на эту тему. В Хроме все изменения сделаны в WebKit — а значит очень скоро появится поддержка в Safari. Скоро подтянутся и остальные браузеры.</p>
<h5><b>А если нельзя, но очень хочется?</b></h5>
<p>
 На этот случай придуман временный заменитель — библиотечка <a href="http://github.com/gimite/web-socket-js">web-socket-js</a> с помощью флеша эмулирующая веб-сокеты. К сожалению, у нее есть небольшие проблемы с проксями и кросс-доменной работой. Но в качестве временного решения ее стоит опробовать.</p>
<h4><b>Выводы</b></h4>
<p>
На мой взгляд, как только люди распробуют, эта технология получить очень широкое распространение. К весне-лету мы получим массу сайтов с ней. И как в свое время несколько лет прошло «под звездой AJAX», так и здесь год-другой мы будем слышать отзывы о внедрении WebSockets повсеместно.</p>
<h4><b>&#8230;</b></h4>
<p></p>
<p><i>Осторожно, двери закрываются. Не опоздайте на поезд в будущее&#8230;</i></p>
<p>Оригинал статьи размещен здесь: <a href="http://websockets.ru/tech/intro">Введение в WebSockets — полноценный асинхронный веб</a></p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2011/12/24/websockets-async-web/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Версионная миграция структуры базы данных: основные подходы</title>
		<link>http://softwarepeople.ru/blog/2011/12/22/database-structure-version-migration/</link>
		<comments>http://softwarepeople.ru/blog/2011/12/22/database-structure-version-migration/#comments</comments>
		<pubDate>Thu, 22 Dec 2011 17:04:37 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[Базы данных]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5670</guid>
		<description><![CDATA[Автор статьи: Дмитрий Шевченко
Проблемы контроля версий баз данных и миграций между версиями уже не раз поднимались как в русскоязычном (1, 2, 3 и др.), так и в англоязычном Интернете.
В первом разделе этой статьи я рассматриваю основные проблемы, которые возникают в командах программистов при внесении любых изменений в структуру базы данных. Во втором разделе я попытался ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор статьи: <a href="http://habrahabr.ru/users/Shedal/">Дмитрий Шевченко</a></strong></p>
<p>Проблемы контроля версий баз данных и миграций между версиями уже не раз поднимались как в русскоязычном (<strong><a title="Простой подход к версионированию баз данных MS SQL Server" href="http://habrahabr.ru/blogs/sql/89181/">1</a></strong>, <strong><a title="Версионирование структуры БД в MySQL: MySQL Migration with PHP" href="http://habrahabr.ru/blogs/php/90052/">2</a></strong>, <strong><a title="Миграции БД для .NET" href="http://habrahabr.ru/blogs/net/56175/">3</a></strong> и др.), так и в англоязычном Интернете.</p>
<p>В первом разделе этой статьи я рассматриваю основные проблемы, которые возникают в командах программистов при внесении любых изменений в структуру базы данных. Во втором разделе я попытался выделить основные общие подходы к тому, в каком виде изменения структуры базы данных можно хранить и поддерживать в процессе разработки.</p>
<h1>Терминология</h1>
<p><strong><em>База данных</em></strong> — совокупность всех объектов БД (таблиц, процедур, триггеров и т.д.), статических данных (неизменяемых данных, хранящихся в lookup-таблицах) и пользовательских данных (которые изменяются в процессе работы с приложением).</p>
<p><strong><em>Структура базы данных</em></strong> — совокупность всех объектов БД и статических данных. Пользовательские данные в понятие структуры БД не входят.</p>
<p><strong><em>Версия базы данных</em></strong> — определенное состояние структуры базы данных. Обычно у версии есть номер, связанный с номером версии приложения.</p>
<p><strong><em>Миграция</em></strong>, в данном контексте, — обновление структуры базы данных от одной версии до другой (обычно более новой).</p>
<p>В этом смысле термин миграция, похоже, используется во многих источниках (особенно этому поспособствовали <a title="Ruby on Rails Active Record Migrations" href="http://guides.rubyonrails.org/migrations.html">миграции</a> из gem&#8217;а Active Record, входящего в состав Ruby on Rails). Однако при использовании этого термина возникает двусмысленность: человек, который не знает контекста, скорее подумает, что речь идет о переносе базы данных с одной СУБД на другую (MySQL =&gt; Oracle), а то и вовсе о миграции процессов/данных между нодами кластера. Поэтому предлагаю в случаях, когда контекст неочевиден, использовать более точный термин: <strong><em>версионная</em> миграция</strong> баз данных.</p>
<h1>Зачем это нужно?</h1>
<p>Разработчики, которые уже сталкивались с проблемой рассинхронизации версий БД и приложения, могут пропустить этот раздел. Здесь я напомню, почему нужно соблюдать паритет версий приложения и базы данных и какая общая проблема при этом возникает.</p>
<h3>Версия базы данных должна соответствовать версии приложения</h3>
<p>
Итак, представьте себе следующую ситуацию: команда из нескольких программистов разрабатывает приложение, которое активно использует базу данных. Время от времени приложение поставляется в продакшн — например, это веб-сайт, который деплоится на веб-сервер.<br />
Любому программисту в процессе написания кода приложения может понадобиться изменить структуру базы данных, а также, сами данные, которые в ней хранятся. Приведу простой пример: допустим, есть необнуляемое (not nullable) строковое поле в одной из таблиц. В этом поле не всегда есть данные, и в этом случае там хранится пустая строка. В какой-то момент вы решили, что хранить пустые строки — семантически неправильно в некоторых случаях (см. <strong><a title="MySQL, better to insert NULL or empty string?" href="http://stackoverflow.com/questions/1267999/mysql-better-to-insert-null-or-empty-string">1</a></strong>, <strong><a title="Handling Null Values" href="http://msdn.microsoft.com/en-us/library/ms172138%28VS.80%29.aspx">2</a></strong>), а правильно — хранить NULL&#8217;ы. Для того, чтобы это реализовать, понадобятся следующие действия:</p>
<p>1. Изменить тип поля на nullable:</p>
<p><code><font color="#0000ff">ALTER</font> myTable <font color="#0000ff">CHANGE COLUMN</font> myField myField <font color="#0000ff">VARCHAR</font>(255) <font color="#0000ff">NULL DEFAULT</font> <font color="#A31515">NULL</font></code>;<br />
2. Так как в этой таблице на продакшн БД уже наверняка есть пустые строки, вы принимаете волевое решение и трактуете их как отсутствие информации. Следовательно, нужно будет заменить их на NULL:<br />
<code><font color="#0000ff">UPDATE</font> myTable <font color="#0000ff">SET</font> myField = <font color="#A31515">NULL</font> <font color="#0000ff">WHERE</font> myField = <font color="#A31515">&#8221;</font></code>;</p>
<p>3. Изменить код приложения так, чтобы при получении из БД данных, хранящихся в этом поле, он адекватно реагировал на NULL&#8217;ы. Записывать в это поле тоже теперь нужно NULL&#8217;ы вместо пустых строк.</p>
<p>Из пункта 3 можно видеть, что приложение и база данных — неразрывные части одного целого. Это означает, что при поставке новой версии приложения в продакшн, нужно обязательно обновлять и версию базы данных, иначе приложение попросту не сможет правильно работать. В данном примере, если до новой версии будет обновлено только приложение, то в какой-то момент произойдет вставка NULL в необнуляемое поле, а это очевидная ошибка.</p>
<p>Таким образом, <strong>обновление версии приложения требует корректной версионной миграции базы данных</strong>.</p>
<h3>Так ли это просто?</h3>
<p>
Осознав, что паритет версий БД и приложения необходим, вам нужно удостовериться, что миграции БД до нужной версии всегда будут выполняться правильно. Но в чём здесь проблема? Ведь, на первый взгляд, сложного здесь ничего нет!</p>
<p>Тут снова обратимся к живому примеру. Допустим, программисты в процессе разработки записывают свои изменения структуры и данных БД в отдельный файл в виде SQL-запросов (как <a title="Data Definition Language" href="http://ru.wikipedia.org/wiki/Data_Definition_Language">DDL</a>-, так и <a title="Data Modification Language" href="http://ru.wikipedia.org/wiki/DML">DML</a>-запросов). А при каждом деплое последней версии приложения вы одновременно обновляете до последней версии и базу данных, выполняя запросы из того самого SQL-файла… Но погодите, <strong>с какой версии</strong> вы обновляете БД до последней версии? «С прошлой»? Но так ли хорошо вы помните, что конкретно из себя представляла прошлая версия (её выпустили 2 месяца назад)? Если нет, то как вы собрались её обновлять? Ведь без точной информации о состоянии структуры и данных выполнить корректную миграцию невозможно: если вы непредумышленно выполните запросы, которые уже когда-то выполнялись, это может привести к потере данных или нарушению их целостности.</p>
<p>Простой пример — замена паролей на их MD5-суммы. Если повторно выполнить такой запрос, то данные можно будет восстановить только из бэкапа. Да и вообще, любые <code>UPDATE</code>&#8216;ы, <code>DELETE</code>&#8216;ы, и даже <code>INSERT</code>&#8216;ы, выполненные повторно, могут привести к крайне нежелательным последствиям. Не говоря уже о несвоевременных <code>TRUNCATE</code>&#8216;ах и <code>DROP</code>&#8216;ах (хотя такие случаи намного менее вероятны).<br />
Кстати говоря, с этой точки зрения, недовыполнить — не меньшая опасность для работоспособности приложения, чем перевыполнить.</p>
<p>Таким образом, можно сделать вывод, что в процессе версионной миграции все запросы должны выполняться <strong>только один раз</strong> и, к тому же, <strong>в правильной последовательности</strong>. Последовательность важна потому, что одни изменения могут зависеть от других (как в примере с обнуляемым полем).</p>
<p></p>
<h1>Общие принципы версионной миграции</h1>
<p>В предыдущем разделе мы выделили важные критерии, которые показывают, что же требуется от процесса версионной миграции. Это:</p>
<ul>
<li>единоразовое выполнение каждого изменения (SQL-запроса);</li>
<li>строго предустановленный порядок изменений.</li>
</ul>
<p>Теперь выделим более практические критерии, чтобы понять, чего требовать от самого процесса создания и хранения миграций. На мой взгляд, для большинства команд разработчиков будет важно:</p>
<ul>
<li>чтобы любую версию базы данных можно было обновить до любой (обычно, самой последней) версии;</li>
<li>чтобы набор SQL-запросов, реализующих миграцию между любыми двумя версиями, можно было получить как можно быстрее и проще;</li>
<li>чтобы всегда можно было создать с нуля базу данных со структурой самой последней версии. Это очень полезно как в процессе разработки и тестирования, так и при развертывании нового продакшн-сервера;</li>
<li>чтобы, в случае работы над разными ветками, при последующем их слиянии ручное редактирование файлов БД было сведено к минимуму;</li>
<li>чтобы откатить БД на более раннюю версию было так же просто, как и обновить на более новую.</li>
</ul>
<p></p>
<h3>Основание миграции</h3>
<p>
Как оказалось, у большинства подходов есть общий принцип: им необходимо <strong><em>основание</em></strong> (baseline) — некоторое эталонное состояние БД, от которого можно отталкиваться. Эта концепция довольно хорошо описана в статье <a title="Versioning Databases – The Baseline" href="http://odetocode.com/blogs/scott/archive/2008/01/31/versioning-databases-the-baseline.aspx">«Versioning Databases – The Baseline»</a> Скотта Аллена.</p>
<p>Попросту говоря, основание — это дамп структуры базы данных для версии, которая принята за базовую. Имея на руках <em>основание</em>, впоследствии всегда можно будет создать БД с нуля. После применения к этой БД всех миграций, созданных в процессе разработки, получим БД со структурой самой последней версии.</p>
<p>Далее будут рассмотрены три подхода к организации версионной миграции баз данных.</p>
<p></p>
<h1>Метод инкрементных изменений</h1>
<p>Этот метод хорошо описан в статье <a title="Versioning Databases – Change Scripts" href="http://odetocode.com/blogs/scott/archive/2008/02/02/versioning-databases-change-scripts.aspx">«Versioning Databases – Change Scripts»</a> все того же Скотта Аллена. Схожий подход также описан в статье <a title="Managing SQL scripts and continuous integration" href="https://michaelbaylon.wordpress.com/2011/04/13/managing-sql-scripts-and-continuous-integration/">«Managing SQL scripts and continuous integration»</a> Майкла Бэйлона.</p>
<h3>Структура файлов</h3>
<p>
Пример того, как в этом случае может выглядеть папка с файлами-миграциями:<br />
<code>Database<br />
 |- Baseline.sql<br />
 |- 0001.<strong>03</strong>.01.sql<br />
 |- 0002.<strong>03</strong>.01.sql</p>
<p> |- 0003.<strong>03</strong>.01.sql<br />
 |- 0004.<strong>03</strong>.02.sql<br />
 |- 0005.<strong>03</strong>.02.sql<br />
 |- 0006.<strong>03</strong>.02.sql</p>
<p> &#8216;- 0007.<strong>03</strong>.02.sql</code><br />
В этом примере в папке хранятся все файлы, созданные при разработке версии <strong>03</strong>. Впрочем, папка может быть и общей для всех версий приложения.</p>
<p>В любом случае, самый первый файл, который появится в такой папке, — <strong>основание</strong> (Baseline.sql). После этого любое изменение в БД сабмиттится в репозиторий в виде нового файла-миграции с именем вида <code>[номер файла].[версия].[подверсия].sql</code>.</p>
<p>Фактически, в этом примере в имени файла содержится полный номер версии БД. То есть после выполнения файла-миграции с именем <code><b>0006</b>.03.02.sql</code> база данных обновится с состояния, соответствующего версии <code>03.02.<b>0005</b></code>, до версии <code>03.02.<b>0006</b></code>.</p>
<h3>Хранение истории версий</h3>
<p></p>
<p>Следующий шаг — добавление в базу данных специальной таблицы, в которой будет храниться история всех изменений в базе данных.<br />
<code><font color="black"><font color="#0000ff">CREATE TABLE</font> MigrationHistory<br />
(<br />
&nbsp;&nbsp;&nbsp;&nbsp;Id <font color="#0000ff">INT</font>,<br />
&nbsp;&nbsp;&nbsp;&nbsp;MajorVersion <font color="#0000ff">VARCHAR</font>(2),<br />
&nbsp;&nbsp;&nbsp;&nbsp;MinorVersion <font color="#0000ff">VARCHAR</font>(2),</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;FileNumber <font color="#0000ff">VARCHAR</font>(4),<br />
&nbsp;&nbsp;&nbsp;&nbsp;Comment <font color="#0000ff">VARCHAR</font>(255),<br />
&nbsp;&nbsp;&nbsp;&nbsp;DateApplied <font color="#0000ff">DATETIME</font>,</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">PRIMARY KEY</font>(Id)<br />
)</font></p>
<p></code><br />
Это всего лишь пример того, как может выглядеть таблица. При необходимости, её можно как упростить, так и дополнить.</p>
<p>В файле <code>Baseline.sql</code> в эту таблицу нужно будет добавить первую запись:<br />
<code><font color="black"><font color="#0000ff">INSERT</font> <font color="#0000ff">INTO</font> <br />
MigrationHistory ( MajorVersion, MinorVersion, FileNumber, Comment,&nbsp;&nbsp;&nbsp;&nbsp;DateApplied )</p>
<p><font color="#0000ff">VALUES</font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( <font color="#A31515">&#8216;03&#8242;</font>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#A31515">&#8216;01&#8242;</font>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#A31515">&#8216;0000&#8242;</font>,&nbsp;&nbsp;&nbsp;&nbsp; <font color="#A31515">&#8216;Baseline&#8217;</font>, NOW() )</font><br />
</code><br />
После выполнения каждого файла-миграции в эту таблицу будет заноситься запись со всеми данными о миграции.</p>
<p>Текущую версию БД можно будет получить из записи с максимальной датой.<br />
</p>
<h3>Автоматическое выполнение миграций</h3>
<p>
Завершающий штрих в этом подходе — программа/скрипт, который будет обновлять БД с текущей версии до последней.</p>
<p>Выполнять миграцию БД автоматически довольно просто, т.к. номер последней выполненной миграции можно получить из таблицы MigrationHistory, а после этого остается только применить все файлы с бо́льшими номерами. Сортировать файлы можно по номеру, поэтому с порядком выполнения миграций проблем не будет.</p>
<p>На такой скрипт также возлагается задача добавления записей о выполненных миграциях в таблицу MigrationHistory.</p>
<p>В качестве дополнительных удобств, такой скрипт может уметь создавать текущую версию БД с нуля, сначала накатывая на БД <em>основание</em>, а затем выполняя стандартную операцию по миграции до последней версии.</p>
<h3>Плюсы, минусы, выводы</h3>
<p>
<img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> Быстрое и удобное выполнение миграции до последней версии;<br />
<img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> Механизм нумерации версий. Номер текущей версии хранится прямо в БД;<br />
<img src="http://softwarepeople.ru/files/2011/12/a4e8274f.png"> Для максимального удобства нужны средства автоматизации выполнения миграций;<br />
<img src="http://softwarepeople.ru/files/2011/12/e6c3e0b8.png"> Неудобно добавлять комментарии к структуре БД. Если их добавлять в Baseline.sql, то в следующей версии они пропадут, т.к. основание будет сгенерировано с нуля вновь, в качестве дампа новой версии структуры. Вдобавок, такие комментарии будут быстро устаревать;<br />
<img src="http://softwarepeople.ru/files/2011/12/e6c3e0b8.png"> Возникают проблемы в процессе параллельной разработки в нескольких ветках репозитория. Так как нумерация файлов-миграций — последовательная, то под одинаковыми номерами в разных ветках могут возникнуть файлы с разными DDL-/DML-запросами. Как следствие, при слиянии веток придется либо вручную редактировать файлы и их последовательность, либо же в новой, «слитой» ветке начинать с нового Baseline.sql, учитывающего изменения из обеих веток.</p>
<p>Этот метод в различных формах довольно широко распространен. К тому же, он легко поддается упрощению и модификации под нужды проекта.<br />
В интернете можно найти готовые варианты скриптов по инкрементному выполнению миграций и встроить в свой проект.</p>
<p></p>
<h1>Метод идемпотентных изменений</h1>
<p>Этот метод описан в статье <a title="Bulletproof Sql Change Scripts Using INFORMATION_SCHEMA Views" href="http://haacked.com/archive/2006/07/05/bulletproofsqlchangescriptsusinginformation_schemaviews.aspx">«Bulletproof Sql Change Scripts Using INFORMATION_SCHEMA Views»</a> Фила Хэка. Описание схожего подхода также изложено в ответе на <a title="What are the best practices for database scripts under code control" href="http://stackoverflow.com/questions/340614/what-are-the-best-practices-for-database-scripts-under-code-control">этот вопрос</a> на StackOverflow.</p>
<p>Под <a title="Идемпотентность — Википедия" href="http://ru.wikipedia.org/wiki/%D0%98%D0%B4%D0%B5%D0%BC%D0%BF%D0%BE%D1%82%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C">идемпотентностью</a> понимается свойство объекта оставаться неизменным при повторной попытке его изменить.<br />
<font color="#aaaaaa">В тему вспоминается смешная <a title="Joey — omnipotent" href="http://www.youtube.com/watch?v=Mz0diDqM3Z0">сцена</a> из «Друзей» <img src='http://softwarepeople.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </font></p>
<p>Основная идея этого подхода — написание миграционных файлов таким образом, чтобы их можно было выполнить на базе данных больше одного раза. При первой попытке выполнить любую из SQL-команд, изменения будут применены; при всех последующих попытках ничего не произойдет.</p>
<p>Эту идею проще всего уяснить на примере. Допустим, вам нужно добавить в БД новую таблицу. Если вы хотите, чтобы если она уже существует, при выполнении запроса не возникло ошибки, — в MySQL для этих целей есть краткий синтаксис:<br />
<code><font color="black"><font color="#0000ff">CREATE</font> <font color="#0000ff">TABLE</font> <font color="#ff0000"><strong>IF NOT EXISTS</strong></font> myTable</p>
<p>(<br />
&nbsp;&nbsp;&nbsp;&nbsp;id <font color="#0000ff">INT</font>(10) <font color="#0000ff">NOT</font> <font color="#0000ff">NULL</font>,<br />
&nbsp;&nbsp;&nbsp;&nbsp;myField <font color="#0000ff">VARCHAR</font>(255) <font color="#0000ff">NULL</font>,<br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">PRIMARY</font> <font color="#0000ff">KEY</font>(id)</p>
<p>);</font></code><br />
Благодаря ключевой фразе <code>IF NOT EXISTS</code>, MySQL попытается создать таблицу только в том случае, если таблицы с таким именем еще не существует. Однако такой синтаксис доступен не во всех СУБД; к тому же, даже в MySQL его можно использовать не для всех команд. Поэтому рассмотрим более универсальный способ:<br />
<code><font color="black"><font color="#0000ff">IF NOT EXISTS</font><br />
(<br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">SELECT</font> *<br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">FROM</font> information_schema.tables</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">WHERE</font> table_name = <font color="#A31515">&#8216;myTable&#8217;</font><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">AND</font> table_schema = <font color="#A31515">&#8216;myDb&#8217;</font><br />
)<br />
<font color="#0000ff">THEN</font><br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">CREATE</font> <font color="#0000ff">TABLE</font> myTable</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;(<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id <font color="#0000ff">INT</font>(10) <font color="#0000ff">NOT</font> <font color="#0000ff">NULL</font>,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;myField <font color="#0000ff">VARCHAR</font>(255) <font color="#0000ff">NULL</font>,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">PRIMARY</font> <font color="#0000ff">KEY</font>(id)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;);<br />
<font color="#0000ff">END</font> <font color="#0000ff">IF</font>;</font></code><br />
В последнем примере роль параметра условного выражения играет запрос, который проверяет, существует ли таблица <code>myTable</code> в базе данных с именем <code>myDb</code>. И только в том случае, если таблица отсутствует, произойдет, собственно, ее создание. Таким образом, приведенный запрос является идемпотентным.</p>
<p>Стоит отметить, что в MySQL по какой-то причине запрещено выполнять <a title="Data Definition Language" href="http://ru.wikipedia.org/wiki/Data_Definition_Language">DDL</a>-запросы внутри условных выражений. Но этот запрет легко обойти — достаточно включить все подобные запросы в тело хранимой процедуры:</p>
<p><code><font color="black">DELIMITER $$</p>
<p><font color="#0000ff">CREATE</font> <font color="#0000ff">PROCEDURE</font> sp_tmp() <font color="#0000ff">BEGIN</font></p>
<p><font color="#0000ff">IF</font> <font color="#0000ff">NOT</font> <font color="#0000ff">EXISTS</font></p>
<p>(<br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">&#8211; </font><br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">&#8211; Условие.</font><br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">&#8211; </font><br />
)<br />
<font color="#0000ff">THEN</font><br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">&#8211; </font><br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">&#8211; Запрос, изменяющий структуру БД.</font><br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">&#8211; </font></p>
<p><font color="#0000ff">END</font> <font color="#0000ff">IF</font>;</p>
<p><font color="#0000ff">END</font>;<br />
$$</p>
<p>DELIMITER;</p>
<p><font color="#0000ff">CALL</font> sp_tmp();</p>
<p><font color="#0000ff">DROP</font> <font color="#0000ff">PROCEDURE</font> sp_tmp;</font></code></p>
<h3>Что за птица такая — information_schema?</h3>
<p>
Полную информацию о структуре базы данных можно получить из специальных системных таблиц, находящихся в базе данных с именем <code>information_schema</code>. Эта база данных и ее таблицы — часть стандарта SQL-92, поэтому этот способ можно использовать на любой из современных СУБД. В предыдущем примере используется таблица <code>information_schema.tables</code>, в которой хранятся данные о всех таблицах. Подобным образом можно проверять существование и метаданные полей таблиц, хранимых процедур, триггеров, схем, и, фактически, любых других объектов структуры базы данных.</p>
<p>Полный перечень таблиц с подробной информацией об их предназначении можно посмотреть <a title="Стандарт SQL-92" href="http://www.contrib.andrew.cmu.edu/%7Eshadow/sql/sql1992.txt">в тексте стандарта</a>. Краткий перечень можно увидеть в уже упоминавшейся выше <a title="Bulletproof Sql Change Scripts Using INFORMATION_SCHEMA Views" href="http://haacked.com/archive/2006/07/05/bulletproofsqlchangescriptsusinginformation_schemaviews.aspx">статье Фила Хэка</a>. Но самый простой способ, конечно же, — просто открыть эту базу данных на любом рабочем сервере БД и посмотреть, как она устроена.</p>
<h3>Пример использования</h3>
<p>
Итак, вы знаете, как создавать идемпотентные SQL-запросы. Теперь рассмотрим, как этот подход можно использовать на практике.</p>
<p>Пример того, как в этом случае может выглядеть папка с sql-файлами:<br />
<code>Database<br />
&nbsp;|- 3.01</p>
<p>&nbsp;|&nbsp;&nbsp;&nbsp;|- Baseline.sql<br />
&nbsp;|&nbsp;&nbsp;&nbsp;&#8217;- Changes.sql<br />
&nbsp;|<br />
&nbsp;&#8217;- 3.02<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|- Baseline.sql<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#8217;- Changes.sql</code><br />
В этом примере для каждой минорной версии базы данных создается отдельная папка. При создании каждой новой папки генерируется основание и записывается в Baseline.sql. Затем в процессе разработки в файл Changes.sql записываются все необходимые изменения в виде идемпотентных запросов.</p>
<p>Предположим, в процессе разработки в разное время программистам понадобились следующие изменения в БД:<br />
a) создать таблицу myTable;<br />
b) добавить в нее поле newfield;<br />
c) добавить в таблицу myTable какие-то данные.</p>
<p>Все три изменения написаны так, чтобы не выполняться повторно. В результате, в каком бы из промежуточных состояний не находилась база данных, при выполнении файла Changes.sql всегда будет выполнена миграция до самой последней версии.</p>
<p>К примеру, один из разработчиков создал на своей локальной копии БД таблицу myTable, записал изменение a) в хранящийся в общем репозитории кода файл Changes.sql, и на какое-то время забыл о нём. Теперь, если он выполнит этот файл на своей локальной БД, изменение a) будет проигнорировано, а изменения b) и c) будут применены.</p>
<h3>Плюсы, минусы, выводы</h3>
<p></p>
<p><img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> Очень удобное выполнение миграций с любой промежуточной версии до последней — нужно всего лишь выполнить на базе данных один файл (Changes.sql);<br />
<img src="http://softwarepeople.ru/files/2011/12/e6c3e0b8.png"> Потенциально возможны ситуации, в которых будут теряться данные, за этим придется следить. Примером может служить удаление таблицы, и затем создание другой таблицы с тем же именем. Если при удалении проверять только имя, то обе операции (удаление и создание) будут происходить каждый раз при выполнении скрипта, несмотря на то, что когда-то уже выполнялись;<br />
<img src="http://softwarepeople.ru/files/2011/12/e6c3e0b8.png"> Для того, чтобы изменения были идемпотентными, нужно потратить больше времени (и кода) на их написание.</p>
<p>Благодаря тому, что обновить базу данных до последней версии очень просто, и делать это можно вручную, этот метод показывает себя в выгодном свете в том случае, если у вас много продакшн-серверов и их нужно часто обновлять.</p>
<p></p>
<h1>Метод уподобления структуры БД исходному коду</h1>
<p>Отдельных статей, посвященных этому подходу, я, к сожалению, не нашел. Буду благодарен за ссылки на существующие статьи, если таковые имеются. <b>UPD:</b> В <a href="http://habrahabr.ru/blogs/sql/121909/">своей статье</a> <a href="http://habrahabr.ru/users/absent/" class="user_link">Absent</a> рассказывает о своем опыте реализации схожего подхода при помощи самописной diff-утилиты.</p>
<p>Основная идея этого метода отражена в заголовке: структура БД — такой же исходный код, как код PHP, C# или HTML. Следовательно, вместо того, чтобы хранить в репозитории кода файлы-миграции (с запросами, изменяющими структуру БД), нужно хранить только актуальную структуру базы данных — в декларативной форме.</p>
<h3>Пример реализации</h3>
<p>
Для простоты примера будем считать, что в каждой ревизии репозитория всегда будет только один SQL-файл: CreateDatabase.sql. В скобках замечу, что в аналогии с исходным кодом можно пойти еще дальше и хранить структуру каждого объекта БД в отдельном файле. Также, структуру можно хранить в виде XML или других форматов, которые поддерживаются вашей СУБД.</p>
<p>В файле CreateDatabase.sql будут храниться команды <code>CREATE TABLE</code>, <code>CREATE PROCEDURE</code>, и т.д., которые создают всю базу данных с нуля. При необходимости изменений структуры таблиц, эти изменения вносятся непосредственно в существующие DDL-запросы создания таблиц. То же касается изменений в хранимых процедурах, триггерах, и т.д.</p>
<p>К примеру, в текущей версии репозитория уже есть таблица myTable, и в файле CreateDatabase.sql она выглядит следующим образом:</p>
<p><code><font color="black"><font color="#0000ff">CREATE TABLE</font> myTable<br />
(<br />
&nbsp;&nbsp;&nbsp;&nbsp;id <font color="#0000ff">INT</font>(10) <font color="#0000ff">NOT NULL</font>,<br />
&nbsp;&nbsp;&nbsp;&nbsp;myField <font color="#0000ff">VARCHAR</font>(255) <font color="#0000ff">NULL</font>,</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">PRIMARY</font> <font color="#0000ff">KEY</font>(id)<br />
);</font></code><br />
Если вам нужно добавить в эту таблицу новое поле, вы просто добавляете его в имеющийся DDL-запрос:<br />
<code><font color="black"><font color="#0000ff">CREATE TABLE</font> myTable<br />
(<br />
&nbsp;&nbsp;&nbsp;&nbsp;id <font color="#0000ff">INT</font>(10) <font color="#0000ff">NOT NULL</font>,</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;myField <font color="#0000ff">VARCHAR</font>(255) <font color="#0000ff">NULL</font>,<br />
&nbsp;&nbsp;&nbsp;&nbsp;<strong><font color="#ff0000">newfield INT(4) NOT NULL</font></strong>,<br />
&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">PRIMARY</font> <font color="#0000ff">KEY</font>(id)<br />
);</font></code><br />
После этого измененный sql-файл сабмиттится в репозиторий кода.</p>
<h3>Выполнение миграций между версиями</h3>
<p>
В этом методе процедура обновления базы данных до более новой версии не так прямолинейна, как в других методах. Поскольку для каждой версии хранится только декларативное описание структуры, для каждой миграции придется генерировать разницу в виде <code>ALTER</code>-, <code>DROP</code> — и <code>CREATE</code>-запросов. В этом вам помогут автоматические diff-утилиты, такие, как Schema Synchronization Tool, входящая в состав <a title="Домашняя страница SQLyog" href="http://www.webyog.com/en/">SQLyog</a>, <a title="Домашняя страница TOAD for MySQL" href="http://toadformysql.com/index.jspa">TOAD</a>, доступный для многих СУБД, <a href="http://dklab.ru/lib/dklab_pgmigrator/">Dklab_pgmigrator</a> для PostgreSQL от <a href="http://habrahabr.ru/users/dmitrykoterov/" class="user_link">DmitryKoterov</a>, а также, <a title="Домашняя страница SQL Comparison SDK" href="http://www.red-gate.com/products/SQL_Comparison_SDK/index.htm">SQL Comparison SDK</a> от RedGate.</p>
<p>Чтобы выполнить миграцию с одной версии БД до другой, вам придется восстановить на двух временных БД структуру исходной и конечной версий, и затем сгенерировать миграционный скрипт. Впрочем, эта процедура может быть автоматизирована и много времени занимать не должна.</p>
<h3>Как быть с изменениями данных?</h3>
<p>
Время от времени, при обновлении версии базы данных на продакшн-серверах, нужно обновлять не только структуру БД, но и хранящиеся в ней данные. В качестве примера можно привести перенос данных из таблицы со старой структурой в новые таблицы — в целях нормализации. Поскольку данные на продакшн-серверах уже существуют и используются, недостаточно просто создать новые таблицы и удалить старые, нужно еще и перенести имеющиеся данные.</p>
<p>В предыдущих методах, в контексте хранения и выполнения миграций, данные мало чем отличались от структуры БД. Но в данном методе изменения в данных стоят особняком, ведь хранить их в репозитории кода в декларативной форме невозможно: данные на всех серверах разные. А автоматически сгенерировать такие запросы для изменения данных также невозможно: это требует человеческого вмешательства.</p>
<p>У этой проблемы есть несколько более или менее приемлемых решений:</p>
<ul>
<li>хранить изменения данных согласно методу инкрементных изменений (возможно, в упрощенной форме) и добавлять их в результирующий diff-скрипт уже после его генерации, вручную;</li>
<li>нигде не хранить запросы-изменения данных, и, когда diff-скрипт будет сгенерирован, анализировать его и добавлять по месту все необходимые <a title="Data Modification Language" href="http://ru.wikipedia.org/wiki/DML">DML</a>-запросы. Привожу здесь это решение только потому, что мой коллега настаивает на том, что оно рабочее и не имеет недостатков; я же нахожу его слишком опасным, т.к. генерация diff-скрипта потенциально может происходить через несколько месяцев после работы над связанным с изменением участком приложения, и детали, необходимые для корректной миграции данных, могут быть уже забыты.</li>
</ul>
<p></p>
<h3>Плюсы, минусы, выводы</h3>
<p>
<img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> Удобно наблюдать изменения в структуре между версиями при помощи средств системы контроля версий;<br />
<img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> Как и любой исходный код, структуру БД удобно комментировать;<br />
<img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> Для того, чтобы с нуля создать чистую базу данных последней версии, нужно выполнить всего лишь один файл;<br />
<img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> Скрипты-миграции более надежны, чем в других методах, так как генерируются автоматически;</p>
<p><img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> Мигрировать с новых версий на старые почти так же просто, как со старых на новые (проблемы могут возникнуть только с пресловутыми изменениями данных);<br />
<img src="http://softwarepeople.ru/files/2011/12/a79a7f32.png"> В случае слияния двух веток репозитория, merge структуры БД осуществляется проще, чем при использовании других подходов;<br />
<img src="http://softwarepeople.ru/files/2011/12/e6c3e0b8.png"> Изменения данных придется хранить отдельно, и затем вручную вставлять в сгенерированные скрипты-миграции;<br />
<img src="http://softwarepeople.ru/files/2011/12/e6c3e0b8.png"> Вручную выполнять миграции очень неудобно, необходимы автоматизированные средства.</p>
<p>Этот метод имеет много позитивных качеств. Если вас не страшат описанные проблемы с изменениями данных, и если обновления продакшн-серверов случаются редко, рекомендую использовать именно этот метод.</p>
<p></p>
<h1>Готовые решения для версионной миграции БД</h1>
<p>Описанные выше методы могут использоваться без сторонних решений, однако существуют и готовые к использованию продукты, каждый со своей идеологией и оригинальным подходом, достойные отдельной статьи. При выборе решения версионной миграции, обязательно рассмотрите и такие продукты.</p>
<p>Некоторые из них рассмотрены в недавней статье <a title="Подходы для версионирования баз данных" href="http://outcoldman.ru/ru/blog/show/283">«Подходы для версионирования баз данных»</a> <a href="http://outcoldman.habrahabr.ru/">Дениса Гладких</a>.</p>
<p>Ниже перечислена лишь малая часть готовых к использованию систем версионной миграции:</p>
<ul>
<li><a title="Migrator.NET" href="http://code.google.com/p/migratordotnet/">Migrator.NET</a>;</li>
<li><a title="ECM7.Migrator" href="http://code.google.com/p/ecm7migrator/">ECM7.Migrator</a> — форк Migrator.NET за авторством <a href="http://habrahabr.ru/users/dima117/" class="user_link">dima117</a>. <a title="Автоматизация изменений БД в .NET" href="http://habrahabr.ru/blogs/net/70884/">Его статья</a> на Хабре;</li>
<li><a title="Ruby on Rails Active Record Migrations" href="http://guides.rubyonrails.org/migrations.html">Active Record Migrations</a>, входящие в Ruby on Rails;</li>
<li><a title="SQL Source Control" href="http://www.red-gate.com/products/sql-development/sql-source-control/">SQL Source Control</a> от RedGate;</li>
<li><a title="DotNetMigrations" href="http://github.com/jpoehls/dotnetmigrations/">DotNetMigrations</a>;</li>
<li><a title="Fluent Migrator" href="https://github.com/schambers/fluentmigrator/">Fluent Migrator</a>. <a href="http://habrahabr.ru/blogs/net/129242/">Статья</a> на Хабре за авторством <a href="http://habrahabr.ru/users/tabushi/" class="user_link">tabushi</a>, создавшего <a href="http://github.com/tabushi/fluentmigrator">форк</a> проекта;</li>
<li><a title="DbDeploy.NET" href="http://dbdeploy.com/software/net/">DbDeploy.NET</a>;</li>
<li><a title="Tarantino" href="http://code.google.com/p/tarantino/">Tarantino</a>;</li>
<li><a title="Mygrate" href="http://code.google.com/p/mygrate/">Mygrate</a>;</li>
<li><a title="DBUpdater" href="http://sourceforge.net/projects/dbupdater/">DBUpdater</a>;</li>
<li><a title="Wizardby" href="http://code.google.com/p/octalforty-wizardby/">Wizardby</a> за авторством <a href="http://habrahabr.ru/users/ostapbender/" class="user_link">ostapbender</a>. <a href="http://habrahabr.ru/blogs/net/56175/">Его статья</a> на Хабре</li>
</ul>
<h1>В заключение</h1>
<p>Существует огромное количество разных способов хранить и применять изменения в базах данных. Какой из них вы выберете для своего проекта — не так уж и важно, главное — принять один из методов и неуклонно ему следовать. Остальное — детали. Именно эту мысль я попытался донести этой статьей, добавив классификацию простейших методов со своими мыслями — в надежде, что они помогут вам выбрать наиболее подходящее для вас решение. </p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2011/12/22/database-structure-version-migration/feed/</wfw:commentRss>
		</item>
		<item>
		<title>SWP-дайджест №61</title>
		<link>http://softwarepeople.ru/blog/2011/12/22/swp-digest-6/</link>
		<comments>http://softwarepeople.ru/blog/2011/12/22/swp-digest-6/#comments</comments>
		<pubDate>Thu, 22 Dec 2011 08:17:14 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[SWP Дайджест]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5664</guid>
		<description><![CDATA[Статья Сергея Симоненко «Малоизвестные особенности Java» познакомит с возможностями этого популярного языка программирования, не так часто используемыми и потому нуждающимися в особом внимании. Статья будет полезна всем, кто собирается освежить в памяти синтаксис этого языка, а может быть, открыть что-то новое для себя. Сегодня мы публикуем первую часть статьи.
Статья Владимира Попова «Пишем модульное приложение на ...]]></description>
			<content:encoded><![CDATA[<p>Статья Сергея Симоненко «<a href="http://softwarepeople.ru/blog/2011/12/04/unknown-java/">Малоизвестные особенности Java</a>» познакомит с возможностями этого популярного языка программирования, не так часто используемыми и потому нуждающимися в особом внимании. Статья будет полезна всем, кто собирается освежить в памяти синтаксис этого языка, а может быть, открыть что-то новое для себя. Сегодня мы публикуем первую часть статьи.</p>
<p>Статья Владимира Попова «<a href="http://softwarepeople.ru/blog/2011/12/04/module-applications-net-framework/">Пишем модульное приложение на .Net Framework</a>» посвящена проблемам проектирования систем, создаваемых на основе .Net Framework. В статье рассматриваются основные этапы проектирования. Статья будет полезна системным архитекторам и ведущим программистам, специализирующимся на создании масштабируемых приложений на этой платформе.</p>
<p>Статья Сергея Теплякова «<a href="http://softwarepeople.ru/blog/2011/12/05/passing-the-enumerations/">Проблемы передачи списка перечислений или Почему абстракции „текут“</a>» также посвящена архитектурным проблемам. В продолжение темы о «дырявых» абстракциях, начатой Джоэлем Спольски, Сергей пишет о проблемах, возникающих в том случае, когда нам нужно знать не только нам нужно знать не только видимое поведение (абстракцию), но и понимать внутреннее устройство (реализацию) описываемого объекта.</p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2011/12/22/swp-digest-6/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Расширяем C# с помощью Roslyn. Безопасные вызовы</title>
		<link>http://softwarepeople.ru/blog/2011/12/21/expand-csharp/</link>
		<comments>http://softwarepeople.ru/blog/2011/12/21/expand-csharp/#comments</comments>
		<pubDate>Wed, 21 Dec 2011 13:31:51 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[Программирование]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5654</guid>
		<description><![CDATA[Автор статьи: Дмитрий Ейбоженко (сайт автора: eibozhenko.ru) 
У вас никогда не возникало ощущения, что в языке X, на котором вы в данный момент программируете чего-то не хватает? Какой-нибудь небольшой, но приятной плюшки, которая может и не сделала бы вашу жизнь абсолютно счастливой, но определенно добавила бы немало радостных моментов. И вот вы с черной завистью ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор статьи: Дмитрий Ейбоженко</strong> (сайт автора: <a href="http://eibozhenko.ru/">eibozhenko.ru</a>) </p>
<p>У вас никогда не возникало ощущения, что в языке X, на котором вы в данный момент программируете чего-то не хватает? Какой-нибудь небольшой, но приятной плюшки, которая может и не сделала бы вашу жизнь абсолютно счастливой, но определенно добавила бы немало радостных моментов. И вот вы с черной завистью посматриваете на язык Y, в котором эта штуковина есть, грустно вздыхаете и тайком льете по ночам слезы бессилия в любимую подушку. Бывало?</p>
<p>Пожалуй, C# дает своим адептам и меньше поводов для такой зависти, в сравнении с многими другими, поскольку динамично развивается, добавляя все новые и новые упрощающие жизнь фичи. И все же, нет предела совершенству, причем для каждого из нас — своего.</p>
<p>Сразу отмечу, что приоритетом в данной работе для меня было желание попробовать на зуб Roslyn, а сама идея, которую я дальше опишу, была скорее поводом и тестовым примером для испытаний этой библиотеки. Однако в процессе изучения и реализации я выяснил, что хоть и с некоторыми бубноплясками, но результат действительно можно использовать на практике для реального расширения синтаксиса языка. Как это сделать, кратко опишу в самом конце. А пока приступим.</p>
<h3>Безопасные вызовы и монада Maybe</h3>
<p>
Идея безопасных вызовов заключается в том, чтобы избавиться от надоедающих проверок любых классов на null, которые являясь необходимостью, в то же время значительно засоряют код и ухудшают его читабельность. В то же время, нет никакого желания находиться под постоянной угрозой выпадения NullReferenceException.</p>
<p>Данная проблема решена в функциональных языках программирования с помощью <a href="http://en.wikipedia.org/wiki/Monad_%28functional_programming%29#Maybe_monad">монады Maybe</a>, суть которой заключается в том, что после boxing&#8217;а использующийся в конвеерных вычислениях тип может содержать некоторое значение, либо значение Nothing. В случае, если предыдущее вычисление в конвеере дало некоторый результат, то производится следующее вычисление, если же оно вернуло Nothing, то вместо следующего вычисления вновь возвращается Nothing. </p>
<p>В С# созданы все условия для реализации данной монады — вместо Nothing используется null, для структурных типов могут использоваться их Nullable&lt;T&gt; версии. В принципе, идея уже витает в воздухе, и было несколько статей, в которых реализовывалась данная монада в C# с помощью LINQ. <a href="http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/">Одна</a> из них принадлежит Дмитрию Нестеруку, есть еще и <a href="http://mikehadlow.blogspot.com/2011/01/monads-in-c-5-maybe.html">другая</a>. </p>
<p>Но нельзя не отметить, что при всей заманчивости такого подхода, результирующий код с использованием монады выглядит весьма туманно, из-за необходимости использовать вместо прямых вызовов обертки из лямбда-функций и LINQ. Однако без синтаксических средств языка реализовать ее более элегантно вряд ли представляется возможным. </p>
<p>Достаточно элегантный, как мне показалось, способ реализации данной идеи я обнаружил в спецификации еще пока не созданного языка Kotlin для JDK от ребят из горячо мною любимой компании JetBrains (<a href="http://confluence.jetbrains.net/display/Kotlin/Null-safety">Null-safety</a>). Как оказалось, такой есть уже в Groovy, возможно и еще где-то.</p>
<p>Итак, что же это за оператор безопасного вызова? Предположим, у нас есть выражение:</p>
<pre><code class="cs"><span class="keyword">string</span> text = SomeObject.ToString();</code></pre>
<p>
В случае, если SomeObject является null, мы неминуемо, как уже говорилось, получим NullReferenceException. Чтобы этого избежать, определим в дополнение к оператору прямого вызова &#8216;.&#8217; еще и оператор безопасного вызова &#8216;?.&#8217; который выглядит следующим образом:</p>
<pre><code class="cs"><span class="keyword">string</span> text = SomeObject?.ToString();</code></pre>
<p>
и представляет собой на самом деле выражение: </p>
<pre><code class="cs"><span class="keyword">string</span> text = SomeObject != <span class="keyword">null</span> ? SomeObject.ToString() : <span class="keyword">null</span>;</code></pre>
<p>
В случае, если безопасно вызываемый метод или свойство возвращает структурный тип, необходимо чтобы присваиваемая переменная имела тип Nullable. </p>
<pre><code class="cs"><span class="keyword">int</span>? count = SomeList?.Count;</code></pre>
<p>
Как и обычные вызовы, такие безопасные вызовы можно использовать цепочками, например:</p>
<pre><code class="cs"><span class="keyword">int</span>? length = SomeObject?.ToString()?.Length;</code></pre>
<p>
который преобразуется в выражение:</p>
<pre><code class="cs"><span class="keyword">int</span>? length = SomeObject != <span class="keyword">null</span> ? SomeObject.ToString() != <span class="keyword">null</span> ? SomeObject.ToString().Length : <span class="keyword">null</span> : <span class="keyword">null</span>;</code></pre>
<p></p>
<p>Здесь скрывается некоторый недостаток предлагаемого мной преобразования, поскольку оно порождает дополнительные вызовы функций. На самом деле желательно было бы преобразовывать его, например, к виду:</p>
<pre><code class="cs"><span class="keyword">var</span> temp = SomeObject;
<span class="keyword">string</span> text = <span class="keyword">null</span>;
<span class="keyword">if</span> (temp != <span class="keyword">null</span>)
    text = temp.ToString();</code></pre>
<p>
Однако в ввиду некоторой многословности Roslyn, для того, чтобы примеры не были бы чересчур раздутыми и занудными, я решил сделать преборазование попроще. Впрочем об этом в следующих частях.</p>
<h3>Project Roslyn</h3>
<p>
Как вы может уже слышали, совсем недавно была выпущена CTP версия <a href="http://habrahabr.ru/blogs/net/130884/">проекта Roslyn</a>, в рамках которого разработчиками языков C# и VB были полностью переписаны компиляторы языков с использованием managed кода, и открыт доступ к этим компиляторам в виде API. С его помощью разработчики могут делать много полезных вещей, например очень удобно и просто анализировать, оптимизировать, генерировать код, писать экстеншны и код фиксы для студии, а возможно и собственные DSL. Выйдет она, правда, еще не скоро, аж через одну версию Visual Studio, но пощупать хочется уже сейчас.</p>
<p>Перейдем к решению нашей задачи и прежде всего представим, как бы нам хотелось видеть использование данного расширения языка в действии? Очевидно: мы пишем код, как обычно, в любимой IDE, используем где надо операторы безопасного вызова, жмем Build, во время компиляции написанная нами с помощью Project Roslyn утилита преобразует все это в синтаксически верный C#-код и вуа-ля, все скомпилировано. Спешу вас разочаровать — Roslyn не позволяет вмешиваться в процесс работы текущего компилятора csc.exe, что в принципе довольно объяснимо. Вполне вероятно, если в той самой vNext студии компилятор заменят на его Managed аналог, то такая возможность появится. Но пока ее нет.</p>
<p>В то же время, существует аж два обходных пути:</p>
<ol>
<li>Можно создать свой собственный компилятор взамен нынешнему csc.exe с использованием все того же Roslyn API, и изменить свою build-систему, заменив csc.exe на свой аналог, включив в него помимо дефолтной компиляции (довольно, кстати, просто программирующейся) свои предварительные преобразования кода.</li>
<li>Вы можете использовать свою консольную программу в качестве Pre-Build задачи, которая преобразует файлы исходного кода и сохраняет полученные новые исходники в папку Obj. Очень похожим образом осуществляется в данный момент компиляция WPF, когда xaml файлы в фазе pre-build преобразуются в .g.cs файлы. </li>
</ol>
<p>Project Roslyn предоставляет несколько видов функциональности, однако одна из ключевых — построение, разбор и преобразование абстрактного синтаксического дерева. Именно эту его функциональность мы и будем использовать далее.</p>
<h3>Имплементация</h3>
<p>
Конечно, все написанное ниже лишь пример, страдает от множества пороков и не может использоваться в реальности без существенных доработок, однако показывает, что такие вещи сделать в принципе можно.<br />
Перейдем к реализации. Для того, чтобы написать программу, нам прежде всего надо установить Roslyn SDK, который скачивается по <a href="http://www.microsoft.com/download/en/details.aspx?id=27746">ссылке</a>, также предварительно придется поставить Service Pack 1 для Visual Studio 2010, и Visual Studio 2010 SDK SP1. <br />
После всех этих операций в меню создания новых проектов появится подпункт Roslyn, который включает в себя несколько шаблонов проектов (некоторые из которых могут интегрироваться в IDE). Мы создадим простое консольное приложение. <br />
Для примера будем использовать следующий «исходный код»:</p>
<pre><code class="cs"><span class="keyword">public</span> <span class="keyword">class</span> Example
{
    <span class="keyword">public</span> <span class="keyword">const</span> <span class="keyword">string</span> CODE =
    <span class="string">@"using System;
    using System.Linq;
    using System.Windows;

    namespace HelloWorld
    {
        public class TestClass
        {
            public string TestField;
            public string TestProperty { get; set; }
            public string TestMethod() { return null; }
            public string TestMethod2(int k, string p) { return null; }
            public TestClass ChainTest;
        }

        public class OtherClass
        {

            public void Test()
            {
                TestClass test;
                string testStr1;
                testStr1 = test?.TestField;
                string testStr3 = test?.TestProperty;
                string testStr4 = test?.TestMethod();
                string testStr5 = test?.TestMethod2(100, testStr3);
                var test3 = test?.ChainTest?.TestField;
            }
        }
    }"</span>;
}</code></pre>
<p>
Данный исходный код за исключением операторов безопасного вызова являются не только синтаксически правильным, но и компилируемым, хотя для нашего преобразования это и не обязательно.</p>
<p>Прежде всего, необходимо по файлу с исходным кодом построить абстрактное синтаксическое дерево. Делается это в два счета:</p>
<pre><code class="cs">SyntaxTree tree = SyntaxTree.ParseCompilationUnit(Example.CODE);
SyntaxNode root = tree.Root;</code></pre>
<p>
Синтаксическое дерево задается классом SyntaxTree и представляет собой, как ни странно, дерево узлов, наследуемых от базового типа SyntaxNode, каждый из которых представляет некоторое выражение — бинарные выражения, условные выражения, выражения вызова методов, определения свойств и переменных. Естественно, абсолютно любая конструкция C# может быть отображена некоторым экзмепляром класса-наследника SyntaxNode. Кроме того, класс SyntaxTree содержит в себе наборы SyntaxToken, определяющих разбор исходного кода на уровне минимальных синтаксических блоков — ключевых слов, литералов, идентификаторов и пунктуации (фигурные и круглые скобки, запятые, точки с запятыми). Наконец, SyntaxTree в содержит в себе элементы SyntaxTrivia — те, которые по большому счету не важны для понимания кода — пробелы и табуляции, комментарии, директивы препроцессора и.т.д. </p>
<p>Тут следует знать одну небольшую деталь — Roslyn является очень толерантным к синтаксическому разбору файлов. То есть, хотя по-хорошему, ему для разбора надо подавать синтаксически корректный исходный код, на самом деле он абсолютно любой текст пытается некоторым образом преобразовать в некоторое AST. В том числе и наш синтаксически неверный код. Этим фактом мы и воспользуемся. Попробуем построить синтаксическое дерево, и выяснить, каким же образом Roslyn отображает в дереве наш оператор безопасного вызова. </p>
<p>Оказывается все просто: с точки зрения Roslyn выражение test?.TestField является тернарным оператором с условием — «test», выражением «когда верно» — &#8220;.TestField&#8221;, и пустым выражением «когда неверно». Вооружившись этой информацией, будем преобразовывать наше дерево. Тут натыкаемся еще на одну особенность Roslyn — строимое им синтаксическое дерево является неизменяемым, т. е. поправить что-либо прямо в имеющейся структуре не получится. Но не беда. Roslyn предлагает для такой операции использовать класс SyntaxRewriter, который наследует класс SyntaxVisitor, который, как следует из названия, имплментирует небезызвестный паттерн <a href="http://en.wikipedia.org/wiki/Visitor_pattern">Visitor</a>. Он содержит в себе множество виртуальных методов, обрабатывающих посещение узла каждого конкретного типа (например VisitFieldDeclaration, VisitEnumMemberDeclaration,… всего их порядка 180 штук).</p>
<p>Нам необходимо создать своего наследника класса SyntaxRewriter и переопределить метод VisitConditionalExpression, который вызывается, когда визитор обходит выражение, являющееся тернарным оператором. Далее я приведу целиком код имплементации, тем более, что он невелик, и добавлю лишь некоторые пояснения:</p>
<pre><code class="cs"><span class="comment">// Находит в синтаксическом дереве операторы безопасного вызова и заменяет их на тернарные операторы</span>
<span class="keyword">public</span> <span class="keyword">class</span> SafeCallRewriter : SyntaxRewriter
{
    <span class="comment">//Был ли в данный проход заменен хотя бы один оператор ?.</span>
    <span class="keyword">public</span> <span class="keyword">bool</span> IsSafeCallRewrited { <span class="keyword">get</span>; <span class="keyword">set</span>; }

    <span class="keyword">protected</span> <span class="keyword">override</span> SyntaxNode VisitConditionalExpression(ConditionalExpressionSyntax node)
    {
        <span class="keyword">if</span> (IsSafeCallExpression(node))
        {
            <span class="comment">//Строим expression для объекта, проверяемого на null</span>

            <span class="keyword">string</span> identTxt = node.Condition.GetText();
            ExpressionSyntax ident = Syntax.ParseExpression(identTxt);

            <span class="comment">//Строим expression для кода, вызываемого при успешной проверка на != null</span>
            <span class="keyword">string</span> exprTxt = node.WhenTrue.GetText();
            exprTxt = exprTxt.Substring(<span class="number">1</span>, exprTxt.Length - <span class="number">1</span>);<span class="comment">//убираем точку из записи выражения</span>
            exprTxt = identTxt + <span class="string">'.'</span> + exprTxt;
            ExpressionSyntax expr = Syntax.ParseExpression(exprTxt);

            ExpressionSyntax synt =
                Syntax.ConditionalExpression(<span class="comment">//тернарный оператор</span>

                condition: Syntax.BinaryExpression(<span class="comment">//проверяемое условие ident != null</span>
                    SyntaxKind.NotEqualsExpression,
                    left: ident, <span class="comment">//левый операнд - проверяемый объект</span>
                    right: Syntax.LiteralExpression(SyntaxKind.NullLiteralExpression)), <span class="comment">//литерал null</span>
                whenTrue: expr,
                whenFalse: Syntax.LiteralExpression(SyntaxKind.NullLiteralExpression));
            IsSafeCallRewrited = <span class="keyword">true</span>;
            <span class="keyword">return</span> synt;

        }
        <span class="keyword">return</span> <span class="keyword">base</span>.VisitConditionalExpression(node);
    }

    <span class="comment">//Является ли тернарный оператор на самом деле оператором безопасного вызова</span>

    <span class="keyword">private</span> <span class="keyword">bool</span> IsSafeCallExpression(ConditionalExpressionSyntax node)
    {
        <span class="keyword">return</span> node.WhenTrue.GetText()[<span class="number">0</span>] == <span class="string">'.'</span>;
    }
}</code></pre>
<p>
Отмечу, что первая моя реализация пыталась работать только с логической структурой AST, брезгуя работой с текстовым представлением выражений, но сложность ее очень скоро стала превышать все мыслимые пределы. Одних только функций для определения безопасного вызова и его типа было три штуки: для полей и свойств, для вызова методов, для цепочек безопасных вызовов, ибо все это представлялось разными наследниками класса SyntaxNode, и еще множество функций для преобразования различных типов безопасных операторов. Совершенно выдохнувшись, я выбросил первый вариант в мусорку и во второй раз я воспользовался удобными функциями GetText и ParseExpression, которые предоставляет Roslyn и некоторыми грязными хаками на уровне строк :).</p>
<p>Также советую обратить внимание на процесс создания синтаксического узла (в данном случае ConditionalExpression) и приятность использования в этом случае такой фишки C#, как именованные параметры. Ручаюсь, если бы ее не было, в процессе построения синтаксических узлов можно было бы сойти с ума.</p>
<p>Приведем теперь код основной процедуры:</p>
<pre><code class="cs"><span class="keyword">static</span> <span class="keyword">void</span> Main(<span class="keyword">string</span>[] args)
{
    <span class="comment">//Строим синтаксическое дерево</span>
    SyntaxTree tree = SyntaxTree.ParseCompilationUnit(Example.CODE);
    SyntaxNode root = tree.Root;
    SafeCallRewriter rewriter = <span class="keyword">new</span> SafeCallRewriter();
    <span class="keyword">do</span>

    {
        rewriter.IsSafeCallRewrited = <span class="keyword">false</span>;
        <span class="comment">//Обходим дерево, производя заданные операции в различных типах узлов и переписывая дерево</span>
        root = rewriter.Visit(root);
    } <span class="keyword">while</span> (rewriter.IsSafeCallRewrited);<span class="comment">//за предыдущий проход был найден и преобразован хоть 1 maybe-оператор</span>

    root = root.Format();<span class="comment">//программный Ctrl+K, Ctrl+D</span>

    Console.WriteLine(root.ToString());
}</code></pre>
<p>
Поясню, что несколько перезаписей дерева необходимо для того, чтобы обработать цепочки вызовов. Конечно это можно было сделать рекурсией, но пожалуй в данном случае это только затуманило бы код. Также обратите внимание на чудесную функцию Format. Она программно делает заданное стилистическое форматирование кода, т.е. добавляет в AST все необходимые SyntaxTrivia.</p>
<p>В результате имеем следующий код:</p>
<pre><code class="cs"><span class="keyword">using</span> System;
<span class="keyword">using</span> System.Linq;
<span class="keyword">using</span> System.Windows;

<span class="keyword">namespace</span> HelloWorld
{
    <span class="keyword">public</span> <span class="keyword">class</span> TestClass
    {
        <span class="keyword">public</span> <span class="keyword">string</span> TestField;
        <span class="keyword">public</span> <span class="keyword">string</span> TestProperty
        {
            <span class="keyword">get</span>;
            <span class="keyword">set</span>;
        }

        <span class="keyword">public</span> <span class="keyword">string</span> TestMethod()
        {
            <span class="keyword">return</span> <span class="keyword">null</span>;
        }

        <span class="keyword">public</span> <span class="keyword">string</span> TestMethod2(<span class="keyword">int</span> k, <span class="keyword">string</span> p)
        {
            <span class="keyword">return</span> <span class="keyword">null</span>;
        }

        <span class="keyword">public</span> TestClass ChainTest;
    }

    <span class="keyword">public</span> <span class="keyword">class</span> OtherClass
    {
        <span class="keyword">public</span> <span class="keyword">void</span> Test()
        {
            TestClass test;
            <span class="keyword">string</span> testStr1;
            testStr1 = test != <span class="keyword">null</span> ? test.TestField : <span class="keyword">null</span>;
            <span class="keyword">string</span> testStr3 = test != <span class="keyword">null</span> ? test.TestProperty : <span class="keyword">null</span>;
            <span class="keyword">string</span> testStr4 = test != <span class="keyword">null</span> ? test.TestMethod() : <span class="keyword">null</span>;
            <span class="keyword">string</span> testStr5 = test != <span class="keyword">null</span> ? test.TestMethod2(<span class="number">100</span>, testStr3) : <span class="keyword">null</span>;
            <span class="keyword">var</span> test3 = test != <span class="keyword">null</span> ? test.ChainTest != <span class="keyword">null</span> ? test.ChainTest.TestField : <span class="keyword">null</span> : <span class="keyword">null</span>;
        }
    }
}</code></pre>
<p></p>
<p>Итак, первое знакомство с Roslyn прошло успешно, и перспективы его в целом, не обязательно для написания языковых расширений, видятся очень неплохие. Возможно, если есть энтузиасты, этим можно было бы заняться глубже и серьезнее. В C# же есть еще много, чего нам не хватает. <img src='http://softwarepeople.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>P. S. Еще один пример подобного использования Roslyn, который мне значительно помог, приведен <a href="http://www.mindscapehq.com/blog/index.php/2011/10/20/in-bed-with-roslyn/">здесь</a>. </p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2011/12/21/expand-csharp/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Миграции баз данных — интеграция с вашим приложением</title>
		<link>http://softwarepeople.ru/blog/2011/12/15/database-migration-02/</link>
		<comments>http://softwarepeople.ru/blog/2011/12/15/database-migration-02/#comments</comments>
		<pubDate>Thu, 15 Dec 2011 14:45:09 +0000</pubDate>
		<dc:creator>Software People Team</dc:creator>
		
		<category><![CDATA[Программирование]]></category>

		<guid isPermaLink="false">http://softwarepeople.ru/?p=5647</guid>
		<description><![CDATA[Автор статьи: Демишев Игорь, CTO Picrand
Данная статья посвящена практическому использованию библиотеки Migraton, появившейся в обновлении CodeIgniter версии 2.1.0. Настоятельно рекомендую вам перед ознакомлением с данным материалом прочесть первую часть статьи, в которой говорится непосредственно о создании миграций.

Постановка задачи и ее решение

Для начала договоримся, что приложение должно будет обновляться через интерфейс админки, только по логину, а ...]]></description>
			<content:encoded><![CDATA[<p><strong>Автор статьи: Демишев Игорь</strong>, CTO <a href="http://picrand.com/">Picrand</a></p>
<p>Данная статья посвящена практическому использованию библиотеки <a href="http://codeigniter.com/user_guide/libraries/migration.html">Migraton</a>, появившейся в обновлении CodeIgniter версии 2.1.0. Настоятельно рекомендую вам перед ознакомлением с данным материалом прочесть <a href="http://habrahabr.ru/blogs/codeigniter/133312/">первую часть статьи</a>, в которой говорится непосредственно о создании миграций.<br />
<a name="habracut"></a></p>
<h4>Постановка задачи и ее решение</h4>
<p>
<i>Для начала договоримся, что приложение должно будет обновляться через интерфейс админки, только по логину, а до этого, соответственно, на запросы на любую из страниц будет отдавать 503 ответ.<br />
Промежуточные версии, как и откаты нам пока не нужны, так что сайт будет обновляться сразу до последней версии.</i></p>
<h5>Добавляем оповещение для посетителей</h5>
<p>
В проекте, для которого я делал интеграцию миграций, все контроллеры также наследуют не базовый <i>CI_Controller</i>, а расширенный <i> MY_Controller</i>, который содержит в своем конструкторе некие служебные действия, в том числе и с базой данных. Поэтому для файла <i>%site_path%/application/core/MY_Controller.php</i> нам будет необходимо добавить несколько строчек:</p>
<pre><code class="php"><span class="preprocessor">&lt;?php</span> <span class="keyword">if</span> ( ! defined(<span class="string">'BASEPATH'</span>)) <span class="keyword">exit</span>(<span class="string">'No direct script access allowed'</span>);

<span class="keyword">class</span> MY_Controller <span class="keyword">extends</span> CI_Controller {
    <span class="keyword">protected</span> <span class="variable">$db_update</span> = FALSE; <span class="comment">// it's TRUE if site's database  needs update, FALSE otherwise</span>

    <span class="keyword">public</span> <span class="keyword">function</span> __construct() {
        <span class="keyword">parent</span>::__construct();
        <span class="variable">$is_admin_page</span> = is_a(<span class="variable">$this</span>, <span class="string">'Admin'</span>);
        <span class="variable">$this</span>-&gt;db_update = <span class="variable">$this</span>-&gt;migration-&gt;get_fs_version()!=<span class="variable">$this</span>-&gt;migration-&gt;get_db_version();
        <span class="keyword">if</span> (<span class="variable">$this</span>-&gt;db_update) {
            <span class="keyword">if</span> (!<span class="variable">$is_admin_page</span>)
                show_error(<span class="string">'Пожалуйста, попробуйте перезайти через несколько минут'</span>, <span class="number">503</span>, <span class="string">'Простите, на сайте в настоящее время проходят технические работы'</span>);
            <span class="keyword">else</span> <span class="keyword">return</span> TRUE;
        }
        <span class="comment">### Your super overpowered code goes here..</span>

    }
}
</code></pre>
<p>
<i>Примечание: как и в прошлой статье, все файлы с исходным кодом приведены <a href="#ending">в конце статьи</a></i></p>
<p>Я думаю, что код в целом понятен, стоит лишь заметить, что ошибку мы будем показывать везде, кроме страницы админки (контроллер <i>Admin</i>, который унаследует <i>MY_Controller</i>). Но для админки не будем выполнять конструктор дальше, т.к. он может содержать в себе обращения к базе данных, которая имеет устаревшую структуру. Кстати, как вы видите, сначала в конструкторе вызывается код родительского конструктора, что реализует паттерн <a href="http://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BA%D0%BE%D1%80%D0%B0%D1%82%D0%BE%D1%80_%28%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%29">Decorator</a>.</p>
<h5>Создаем скрипт обновления через админку </h5>
<p></p>
<p>В моем случае вся админка находилась в одном контроллере <i>Admin</i>, так что и мы для простоты примера возьмем такой же случай.<br />
Этот контроллер админки содержит несколько разноплановых методов, которые позволяют управлять содержимым сайта. Не очень хорошее и элегантное решение, если честно — все же настоятельно советую разносить это по разным контроллерам или даже использовать <a href="https://bitbucket.org/wiredesignz/codeigniter-modular-extensions-hmvc/wiki/Home">HMVC-плагин для CodeIgniter</a> (на хабре уже был <a href="http://habrahabr.ru/blogs/codeigniter/130387/">неплохой пример</a> его использования).</p>
<p>Итак, у нас есть задача сделать так, чтобы можно было авторизироваться в админке, и на любой из страниц выводилось предложение обновить базу данных. Для этого отлично подойдет волшебный метод <i>_remap</i>, который будет вызываться при обращении к любому из методов контроллера, и принимать решение, передать управление запрашиваемому методу или <s>показать кукиш</s> сделать какое-либо другое действие:</p>
<pre><code class="php">    <span class="keyword">public</span> <span class="keyword">function</span> _remap(<span class="variable">$method</span>, <span class="variable">$params</span> = <span class="keyword">array</span>()) {
        <span class="keyword">if</span> (!<span class="variable">$this</span>-&gt;m_user-&gt;authorised() &amp;&amp; <span class="variable">$method</span> != <span class="string">'index'</span>) {
            header(<span class="string">'Location:/admin/'</span>); <span class="comment">//If user isn't autorised, redirect him to the login form</span>

        }
        <span class="keyword">if</span> (!<span class="variable">$this</span>-&gt;db_update || (!<span class="variable">$this</span>-&gt;m_user-&gt;authorised() &amp;&amp; <span class="variable">$method</span>== <span class="string">'index'</span>) || <span class="variable">$method</span>==<span class="string">'logout'</span> || <span class="variable">$method</span>==<span class="string">'update_db'</span>) {
            <span class="keyword">return</span> call_user_func_array(<span class="keyword">array</span>(<span class="variable">$this</span>, <span class="variable">$method</span>), <span class="variable">$params</span>); <span class="comment">// Calls requested method if it is ok to do so</span>

        }
        <span class="keyword">else</span> {
            <span class="variable">$this</span>-&gt;data[<span class="string">'body'</span>] = <span class="string">'&lt;h1&gt;Внимание!&lt;/h1&gt;
                Необходимо &lt;a href="/admin/update_db"&gt;обновить базу данных&lt;/a&gt;'</span>; <span class="comment">// Show update database link</span>

            <span class="variable">$this</span>-&gt;load-&gt;view(<span class="string">'admin/default.phtml'</span>, <span class="variable">$this</span>-&gt;data);
        }
    }
</code></pre>
<p>
Тут стоит пояснить, что <i>m_user</i> — модель, которая содержит в себе все методы для работы с пользователями админки (предлагаю вам реализовать самостоятельно), метод <i>index</i> умеет показывать форму входа для незалогиненных пользователей, ну а <i>logout</i> понятно что делает.</p>
<p>Кроме того, создадим там же метод для обновления базы:</p>
<pre><code class="php">    <span class="keyword">public</span> <span class="keyword">function</span> update_db() {
        <span class="variable">$this</span>-&gt;data[<span class="string">'body'</span>] = <span class="string">'&lt;h1&gt;Обновление базы данных успешно завершено&lt;/h1&gt;'</span>;
        <span class="keyword">if</span> ( ! <span class="variable">$this</span>-&gt;migration-&gt;current()) {
            show_error(<span class="variable">$this</span>-&gt;migration-&gt;error_string());
        }
        <span class="variable">$this</span>-&gt;load-&gt;view(<span class="string">'admin/default.phtml'</span>, <span class="variable">$this</span>-&gt;data);
    }

</code></pre>
<h5>Правим конфиг и тестируем</h5>
<p>
Наконец, осталось поправить конфиг, и мы сможем воспользоваться всем тем, что написали для нашего проекта. Для этого добавим в автозагрузку класс <i>Migration</i>. Теперь строка с авто инициализацией библиотек в моем случае выглядит следующим образом (файл <i>%site_path%/application/config/autoload.php</i>):</p>
<pre><code class="php">    <span class="variable">$autoload</span>[<span class="string">'libraries'</span>] = <span class="keyword">array</span>(<span class="string">'database'</span>, <span class="string">'session'</span>, <span class="string">'migration'</span>);

</code></pre>
<p>
Еще нам необходимо проверить в файле <i>%site_path%/application/config/migration.php</i>, ключены ли миграции, посмотреть правильно ли указаны к ним путь и версия базы данных, требуемой для корректной работы нашего кода (все точно так же, как и в <a href="http://softwarepeople.ru/blog/2011/12/15/database-migration-01/">прошлой статье</a>):</p>
<pre><code class="php">    <span class="variable">$config</span>[<span class="string">'migration_enabled'</span>] = TRUE;
    <span class="variable">$config</span>[<span class="string">'migration_version'</span>] = <span class="number">1</span>;
    <span class="variable">$config</span>[<span class="string">'migration_path'</span>] = APPPATH . <span class="string">'migrations/'</span>;

</code></pre>
<p>
Обратите внимание, что я указал 1-ю версию, что подразумевает, что мы уже сделали функционал рассылок, миграцию для которого мы писали в <a href="http://softwarepeople.ru/blog/2011/12/15/database-migration-01/">предыдущем туториале</a>.</p>
<p>Теперь вы можете перейти на любую страницу вашего сайта, не относящуюся к админке и должны будете увидеть сообщение об ошибке, т.к. версия базы данных у нас сейчас <i>нулевая</i> (при первой инициализации библиотека <b>Migration</b> создала в вашей базе табличку migrations и указала там версию 0), а версия кода в конфиге указана <i>первая</i>. Перейдя же в админку и пройдя авторизацию, вы увидите предложение обновить базу данных, и нажав на ссылку получите свежее приложение, без необходимости выполнять все запросы вручную!</p>
<h4>Бонус — скрипт обновления через CLI</h4>
<p>
В качестве бонуса, рассмотрим создание скрипта для обновления через <a href="http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4%D0%BD%D0%BE%D0%B9_%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8">cli</a>, который позволит нам автоматизировать миграции при выгрузке их на сайт, например из систем контроля версий с помощью хуков. <br />
К счастью, CodeIgniter в своих последних версиях имеет возможность запускаться из командной строки, что называется из коробки. Поэтому для начала создадим контроллер <i>%site_path%/application/controllers/cli.php</i> с private переменной <b>$args</b>, которая будет содержать все аргументы, с которыми был вызван скрипт, кроме названия контроллера и его метода (отсечем их в конструкторе):</p>
<pre><code class="php"><span class="preprocessor">&lt;?php</span> <span class="keyword">if</span> ( ! defined(<span class="string">'BASEPATH'</span>)) <span class="keyword">exit</span>(<span class="string">'No direct script access allowed'</span>);

<span class="keyword">class</span> Cli <span class="keyword">extends</span> MY_Controller {
    <span class="keyword">private</span> <span class="variable">$args</span> = <span class="keyword">array</span>(); <span class="comment">// Contains CLI arguments, except controller name and its method</span>

    <span class="keyword">public</span> <span class="keyword">function</span> __construct() {
        <span class="keyword">parent</span>::__construct();
        <span class="keyword">if</span>(!<span class="variable">$this</span>-&gt;input-&gt;is_cli_request()) {
            show_404();
        }
        <span class="variable">$this</span>-&gt;output-&gt;enable_profiler(FALSE);
        <span class="variable">$this</span>-&gt;args = array_slice(<span class="variable">$_SERVER</span>[<span class="string">'argv'</span>], <span class="number">3</span>);

    }
    <span class="comment">// Other code goes here..</span>

}
</code></pre>
<p>
Кстати говоря, советую еще обратить внимание на место в конструкторе, где проверяется, пришел ли запрос через CLI. Мы же не хотим чтобы через сайт можно было выполнить служебные методы! Как видите, я еще и профайлер отключил тут, т.к. на тестовых серверах он у меня по умолчанию везде включен, а в CLI он явно был бы лишним.</p>
<p>Наконец, напишем метод для миграций:</p>
<pre><code class="php"><span class="keyword">public</span> <span class="keyword">function</span> migration() {
    <span class="keyword">if</span> ( !is_array(<span class="variable">$this</span>-&gt;args) || <span class="keyword">empty</span>(<span class="variable">$this</span>-&gt;args)) {
        <span class="keyword">print</span> ( <span class="string">"Usage: php index.php cli migration [OPTIONS]\n\n"</span> );
        <span class="keyword">print</span> ( <span class="string">"Options are:\n"</span> );
        <span class="keyword">print</span> ( <span class="string">"-l, --last\t\tupdate database to the latest version\n"</span> );
        <span class="keyword">print</span> ( <span class="string">"-c, --current\t\t show current versions of database and code\n"</span> );
        <span class="keyword">exit</span>;
    }
    <span class="keyword">for</span> ( <span class="variable">$i</span>=<span class="number">0</span>; <span class="variable">$i</span>&lt;count(<span class="variable">$this</span>-&gt;args); <span class="variable">$i</span>++ ) {
        <span class="variable">$arg</span> = <span class="variable">$this</span>-&gt;args[<span class="variable">$i</span>];
        <span class="keyword">if</span> ( <span class="variable">$arg</span>==<span class="string">"-l"</span> || <span class="variable">$arg</span>==<span class="string">"--last"</span> ) {
            <span class="keyword">print</span> <span class="string">"Updating your database to the latest version..\n"</span>;
            <span class="keyword">if</span> (!<span class="variable">$this</span>-&gt;migration-&gt;current()) {
                <span class="keyword">print</span> <span class="variable">$this</span>-&gt;migration-&gt;error_string().<span class="string">'\n'</span>;
                <span class="keyword">exit</span>;
            }
            <span class="keyword">else</span> <span class="keyword">print</span> <span class="string">"Update complete!\n"</span>;
        }
        <span class="keyword">elseif</span> ( <span class="variable">$arg</span>==<span class="string">"-c"</span> || <span class="variable">$arg</span>==<span class="string">"--current"</span> ) {
            <span class="keyword">print</span> <span class="string">'Current code version is:\t'</span>. <span class="variable">$this</span>-&gt;migration-&gt;get_fs_version().<span class="string">'\n'</span>;
            <span class="keyword">print</span> <span class="string">'Current database version is:\t'</span>.<span class="variable">$this</span>-&gt;migration-&gt;get_db_version().<span class="string">'\n'</span>;
        }
    }
}

</code></pre>
<p>
Для его вызова нужно всего лишь набрать в командной строке &#8220;<b>php /%index_dir%/index.php cli migration</b>&#8221; без дополнительных атрибутов, и скрипт вам любезно подскажет доступные для использования опции. Ну а если набрать &#8220;<b>php /%index_dir%/index.php cli migration -l</b>&#8220;, то метод попытается обновить вашу бд, и выдаст вам результат.<br />
Конечно, этот кусок кода всего-лишь пример, который выполняет лишь сами основы, но общее представление о использовании CLI и миграций он даёт, и вам не составит труда добавить, например, опцию &#8216;-r &#8216; для апдейта базы до указанной ревизии (что кстати будет простеньким домашним заданием для вас).</p>
<p><a name="ending"></a></p>
<h4>Заключение</h4>
<p>
Теперь у нас есть хоть и простой, но отлаженный механизм обновления базы данных, интегрированный в наш проект, который кроме всего прочего умеет предупреждать пользователей о текущем обновлении. Согласитесь, это лучше, чем если бы они увидели баги, связанные с нестыковкой версий базы и кода, или еще какое-либо непотребство. Кроме того, мы теперь можем добавить в нашу любимую систему контроля версий хук, который будет при выгрузке новой версии сайта автоматически обновлять базу.</p>
<p><a href="http://picrand.com/files/migrations2.zip">Скачать архив с полными исходниками</a></p>
]]></content:encoded>
			<wfw:commentRss>http://softwarepeople.ru/blog/2011/12/15/database-migration-02/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>

