июл 29, 20:06
Как-то недавно я наткнулся на статью под названием Читай код, где автор доказывал важность такого скилла, как умение читать чужой код:
Создать свою структуру и пришлепать ее сбоку может любой дурак. Квалифицированный инженер-программист (с упором на первом слове, не путать с “программером”) умеет проводить анализ “чужой” подсистемы, восстановит мысль и идею автора, сможет мысль автора развить, продолжить ее, и эффективно решить свою задачу в рамках чужого подхода к проблеме. Все это – работая с кодом. Это отличительная компетенция архитектора, высший уровень инженерного мастерства. И это имеет весьма отдаленное отношение к “рефакторингу”.
Тогда у меня возникла идея провести эксперемент:
- Взять некоторый известный паттерн проектирования.
- C одной стороны показать, как выглядит код до применения паттерна.
- Рядом же, с другой стороны, показать как выглядит этот же код с применением паттерна.
- Попытаться понять, что этот код делает и как применяется паттерн.
- Сравнить такой вариант описания паттернов с традиционным подходом “UML-диаграмма, словесное объяснение, пример использования”.
Я заметил, что мне легче понять, что делает программа, увидев ее код, а не услышав или прочитав ее описание. С паттернами это чувствуется очень сильно. От такой шпаргалки у меня в глазах рябит:

Итак, сам эксперимент. Для описания я выбрал паттерн Декоратор.
Традиционный подход
Декоратор, Decorator — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.
Задача
Объект, который предполагается использовать, выполняет основные функции. Однако может потребоваться добавить к нему некоторую дополнительную функциональность, которая будет выполняться до или после основной функциональности объекта.
Реализация
Создается абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах-декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта.
При желании остаётся возможность использовать исходный класс (без расширения функциональности), если на его объект сохранилась ссылка.
UML-диаграмма

Описание паттерна взято из Википедии. А теперь перейдем ко второму способу объяснения.
Подход «Читай код, сука»

Итак, Вопрос
Что лучше? Что легче для понимания? Подход «Читай код, сука» сработал или нет?
Программирование
июл 25, 21:01

Недавно начал просматривать лекции по алгоритма, которые читают в MIT. Решил для большей полезности делать это в некотором процессе, который подсмотрел у других умных людей.
Прежде всего, нужно определится зачем эти лекции смотреть, какова цель. Тут все просто. Если вспомнить Programmer Competency Matrix, то выяснится, что алгоритмы – это один из тех скиллов, которые востребованы. Интересная цитата оттуда:
Working with someone who has a good topcoder ranking would be an unbelievable piece of luck!
Как мне кажется, дело здесь даже не совсем в том, что хорошее знание алгоритмов дает тебе возможность писать интересный, быстрый и хитроумный код или что-либо еще в этом роде. На себе почувствовал, что порешав несколько алгоритмов, разобравшись в них, начинаешь чувствовать себе увереннее, когда сталкиваешься с обычными программистскими задачами и пишешь обычный код. Возможно, это только у одного меня такое чувство, не знаю.
Итак, что касается процесса как смотреть эти лекции с большой для себе пользы, то тут дела обстоят так:
- Качаем лекцию. Я ходил по ссылкам отсюда. Начинаем ее смотреть.
- С этого же сайта парень предлагает ввести параллельный конспект по ходу просмотра так как будто ты находишься на лекции. Кстати, полезно также после просмотра лекции сравнить свой конспект с тем, что выложен на этом сайте. Такая себе проверка.
- Кроме конспекта, можно еще записывать интересные мысли или высказывания профессора, которые могут касаться или не касаться изучаемого материала.
- Самое полезное часть изучение происходит после просмотра лекции. Либо в этот же день, либо на следующий просто необходимо открыть книгу, по которой идет этот курс.
Книга переведена на русский язык и ее даже можно скачать с торрентов или натахауса. Называется «Алгоритмы. Построение и анализ» Томаса Кормена, Чарльза Лейзерсона и других. Выглядит она следующим образом:

Замечательно в этой книге то, что профессор Чарльз Лейзерсон, который читает лекции, есть один из ее соавторов. Да и сама книга – одна из самых популярный по алгоритмам. Кстати, второй профессор Eric Damaine – живой гений, самый молодой профессор MIT, – стал им в 20 лет.
Без лекций книгу читать сложновато и немного скучно, но после просмотра – идет на ура. В общем, советую.
Программирование
июл 19, 14:15
В предыдущей части был описан общий процесс разработки API. Эта часть состоит из более общих советов или принципов проектирования, которые я выделил по собственному усмотрению и ориентируюсь на вышеупомянутую презентацию.
Для начала вспомним, как выглядит процесс разработки API по Блоху. Он состоит условно из трех этапов: сбор сведений, краткая спецификация и программирование. На последнем этапе должен получится прототип API, некоторый каркас будущей программы, который состоит из интерфейсов, прототипов классов и методов с комментариями и юнит-тестами.
При написании прототипа, как уже говорилось, могут возникнуть проблемы или недостатки разработанной спецификации, который тут же можно исправить, внеся нужные изменения.
Следующий шаг, он же последний, – реализация. Здесь мы создаем реальный код. В оставшейся части презентации Джошуа Блох дает набор общих принципов по проектированию в целом. Ниже я привел список наиболее существенных, по-моему мнению. Все они сформулированы кратко, но очень точно.
Если это трудно назвать, то это плохой знак
Или, другими словами, Good Names Drive Development. Под этим понимается, прежде всего, что API должно выполнять одну функцию и делать это хорошо. Хороший признак такой модульности – это понятные и простые имена для классов и методов. Если возникли проблемы с именами, возможно следует каким-либо образом применить композицию или декомпозицию для того, чтобы функции, которые выполняет разрабатываемый модуль, были легко понятны и их можно было бы дать подходящее имена.
Если сомневаешься, оставь
Интересный совет, который понравится ленивым программистам. Основывается он на том, что API должно настолько минимально, насколько это возможно. Естественно, что программа должно соответствовать предъявляемым требованиям. Но стоит перегружать его функциями, по-тому что не всегда большое количество является признаком качества.
Хороший совет в случае, когда вы решаете, стоит ли добавить новый параметр в метод или стоит выделить новый метод, или сделать еще один класс – если сомневаешься, бросай. Не стоит делать этого сейчас. Лучше сделать это потом, когда реальность сама рассеет все сомнении. Добавлять легче, чем изменять.
Документируй дотошно
Одна из прописных истин, но все же часта про нее забывают. Любой самый совершенный дизайн, который, кстати, не так часто и увидишь, не может обойтись без документации. Следует документировать каждый класс, интерфейс, метод, конструктор, параметр и исключение.
Не заставляй клиента делать то, что может сделать твоя программа
Хороший пример того, что под понимается, приводит Джошуа Блох, описывая дизайн DOM API:
import org.w3c.dom.*;
import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
// DOM code to write an XML document to a specified output stream.
private static final void writeDoc(Document doc, OutputStream out)throws IOException{
try {
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doc.getDoctype().getSystemId());
t.transform(new DOMSource(doc), new StreamResult(out));
} catch(TransformerException e) {
throw new AssertionError(e); // Can’t happen!
}
}
Метод writeDoc() пишет XML документ в заданный поток вывода. Такая простоя вещь как запись документа в поток заняла 15 строк кода, большая часть из которых вообще непонятного предназначения: исключения, которые не могут возникнуть, создание дополнительного объекта Transformer и так далее.
Общее правило в этом случае – уменьшайте код, который нужно будет писать клиенту для использования вашего API. Клиенту не нужно знать, какие дополнительные действия нужно совершить прежде, чем вызвать тот или иной метод, какие исключения нужно отлавливать, какие параметры установить. Проблемы такого рода решается, в основном, с помощью copy-paste, что само по себе есть неприятно, скучно и приводит к ошибкам.
Совершенства не достичь, но можно попытаться
Как и везде, будут ошибки и недостатки. Не стоит их пугаться. Продолжайте учиться и будет счастья. На этом все.
Конечно тут представлена только малая часть из того, о чем говорил Джошуа Блох в своей презентации. Надеюсь хотя бы в таком малом объеме, такая информация все равно будет полезна. Саму презентацию очень рекомендую посмотреть не только Java программистам.
Программирование
июл 17, 15:19
Сделал релиз библиотеки, разработку которой недавно описывал (вторая часть, кстати, скоро будет). Пока не хватает документации и примеров. Наверняка есть ошибки и недочеты, но надеюсь все это исправить в ближайшее время.

Лицензия GNU. Cкачать можно отсюда.
Понравился Google Code. Особенно понравилась возможность добавить счетчик Google Analytics и тут же следить за посещаемостью. Приятно удивило, что уже и без первого релиза народ начал приходить.
В общем, сделал доброе дело. Дальше – больше :)
Программирование
июл 17, 10:22
Решил выложить слайды с доклада, которою я делал для студенческой конференции. Старался сделать веселую и интересную презентацию. Тема – управление зависимостями, dependency injection и inversion of control. Доклад обзорный.
Программирование
июл 12, 18:49
Оригинальная презентация находится здесь. В своей презентации Джошуа Блох, один из дизайнеров Java libraries, дает советы о том, как создать хороший API, приводя большое количество примеров удачного и неудачного дизайна.
Мое вольное трактование этой презентации находится ниже. Здесь я попытался в удобной форме показать и рассказать, как эти советы воплощаются в жизнь. Такой себе reald-world example from scratch.
Для примера я выбрал разработку java-библиотеки – аналога diff-утилиты с возможностью применять патчи. Главная причина такого выбора – отсутствие удобного API для решения подобных задач на Java. Существующие программы (diff-match-patch от Гугла, java-diff, jrcs-diff) хотя и обладают некоторыми достоинствами, но чаще всего неудобны в использовании. Говорю это, опираясь на свой личный опыт.
В своей презентации Джошуа Блох выделяет некоторый процесс, по которому создавались хорошие API. Итак, начнем с самых первых шагов этого процесса.
Первый шаг: Сбор сведений
На этом этапе главная задача разработчика – собрать реальные требования к разрабатываемой системе. Не стоит углубляться в технические детали и фокусироваться на том, как должна работать система. Нас не интересует как она работает. Нам интересно, что именно она делает, какие задачи выполняет и для чего эта система нужна в принципе. It’s no about an activity, it’s about the result.
Джошуа Блох советует выделить такие требования в формате use cases (примеров использования) и наиболее общем виде. Такие use cases представляют из себя краткие описание задач, которые необходимо выполнять с помощью API.
Вернемся к примеру diff-библиотеки. Прежде всего у меня есть собственные требование на основе тех задач, которыми я планирую выполнять с помощью этой бибилотеки. Также уже есть и существующие библиотеки, со своим API и примерами использования. Подумав и подключив фантазию я остановился на следующем списке:
- Вычисления diff между двумя текстами
- Применения патча, который содержит diff, к оригинальном тексту
- Операция обратная к патчу, когда по пропатченному тексту и патчу вычисляется оригинальный текст (я этим не пользовался, но вдруг такая возможность будет необходима)
- Вывод diffов в разные формы для дальнейшего, более удобного просмотра (двух-панельный вид, отображение изменений внутристрочно).
- Возможность использовать разные форматы для diff (unified diff, normal format и т.д.)
На данном этапе мы также может попробовать провести некоторое обобщение и подумать над выделением компонентов. Хороший признак того, что программный код должен быть представлен в виде некоторого компонента – это большая вероятность того, что этот код будет меняться или работать по разному в будущем.
Для примера, мы можем предположить, что алгоритм вычисления diff возможно будет меняться в зависимости от многих факторов : обращать внимания на пробелы или нет, игнорировать пустые строки или считать их изменением, какую именно реализацию алгоритма стоит использовать (некоторые алгоритмы подходят для некоторых текстов лучше, чем другие) и так далее. Другой пример – это обработки разных форматов для диффа (unified diff, normal format и т.д.). В большинстве случаев будет использовать unified diff, как наиболее распространенный, но возможность других форматов нужно также брать во внимание.
Второй шаг: Написание краткой спецификации
Здесь главное не испугаться слова “спецификация”. Никаких 50 страниц и 2-недельного корпения. Все что необходимо – это одна страничка формата А4. Краткость является огромным плюсом, потому что спецификацию в одну страницу намного легче исправлять и даже переделывать заново.
А это возможно и необходимо будет сделать после совещаний, на которых вы эту спецификацию должны презентовать. Чем большей людей выскажет свое мнения по поводу вашего API, тем лучше для вас. Вы тут же можете найти проблемы, тут же их исправить и продолжать дальше. Все это может занять меньше, чем пол часа.
Итак, мой пример спецификация для diff-библиотеки, написанный на некотором псевдо-коде, который я придумал сам. Надеюсь, будет понятно:
DiffRow:
String tag;
String oldLine;
String newLine;
---------------------
Chunk:
int position;
int size;
List<String> changedLines;
---------------------
Delta:
Chunk original;
Chunk revised;
applyTo(target)
---------------------
AddDelta extends Delta:
applyTo(target):
go to line with position original.position
insert lines from revised.changedLines
---------------------
DeleteDelta extends Delta:
applyTo(target):
go to line with position original.position
delete n lines, where n = original.size
---------------------
ChangedDelta extends Delta:
applyTo(target):
go to line with position == original.position
delete n lines, where n = original.size
insert n lines from revised.changedLines
---------------------
Patch:
List<Delta> deltas;
Patch(String unifiedDiff);
Patch(String rcsDiff);
Patch()
addDelta()
getDeltas()
applyTo(target);
---------------------
DiffLib:
diff(String original, String revised, DiffAlgorytm algorytm): Patch
patch(String original, Patch patch): String revised
original(String revised, Patch patch): String original
getDiffRows(String original, String revised)
Основным кирпичиком diff файла является Delta. Дельта описывает какие именно изменения произошли (Insert, Add или Change). Дельта состоит из 2 Сhunk – один Chunk описывает оригинальный файл, другой – измененный. Chunk содержит описание того, начиная с какой строки произошли изменения и какие именно. Патч состоит из набора дельт. На выходе операции diff() получается патч. Патч можно применить к тексту, точно также как и любую дельту.
Третий шаг: Начинаем программировать API
На этом шаге Блох советует начинать писать API, не обращая внимания на реализацию. Здесь создаются прототипы классов и методов. Кстати, начать написание прототипа API можно даже не пройдя до конца шаг 2, т.е. не определив спецификацию до конца. В любом случае если что-то и изменится, прототип можно всегда легко изменить.
При написании прототипов, кстати, можно убить 2 зайцев.
В одном из своих подкастов Будам дал совет о том, как писать комментарии. Основная идея состоит в том, чтобы писать комментарии до того, как написан код. Что же на данном мы код и не пишем, только прототипы, по-этому никаких противоречий с тем, чтобы сразу написать комментарии не возникает.
Ну и это еще не все. Где-то в дали промаячил и третий заяц – юнит-тесты. Мы уже имеем некоторый интерфейс и можем попытаться его использовать в тестах. Возможно возникнут проблемы и сюрпризы, и нам нужно будет изменить прототипы – не беда, но то они и прототипы.
Пока все
Дальше – больше
Программирование
фев 28, 20:43
В каждом языке есть свои маленькие секреты и хитрости, о которых иногда полезно знать. Решил завести себе небольшой цикл для таких хитростей касательно Явы. Ну что ж, поехали!
Прелюдия
Есть код. Нужно ответить, что он выведет.
Integer a = 200;
Integer b = 200;
System.out.println(a == b);
Здесь все просто. Выведет False. Для тех, кто в танке и читают книжки невнимательно, объясню.
Ява сравнивает примитивные типы (byte, short, int, long и т.д.) и объектные (Byte, Short, Integer и Long, соответственно) по разному. У примитивных типов проверяется их значение. У объектных типов проверяется указывают ли они на один и тот же объект. А так как мы создали два разных объекта (пусть даже и с одинаковыми значениями), проверка возвращает False.
Завязка
Теперь немного изменим наш код. Всего лишь уберем по паре нулей у целых чисел. Что выведет?
Integer a = 2;
Integer b = 2;
System.out.println(a == b);
Не буду томить с ответом, выведет — True. Опять же — почему? Чтобы ответить на этот вопрос нужно знать как устроена Ява, а точнее JVM.
Устроена JVM так, как ни странно, что любит оптимизировать и кешировать. По-этому для нужд производительности виртуальная машина кеширует объекты для часто встречающихся примитивов (чисел от -127 до 127, некоторых символов, true и false). Другими словами, когда мы пишем:
Integer a = 2;
новый объект не создается, а берется уже готовый. Откуда? Из кеша. Это естественно повышает производительность наших программ и дает повод для гордости программистам, которые причастились к этой великой тайне.
Итог
Мораль этой истории проста — избегать сравнения объектных типов, соответствующим стандартным примитивам. Лучше приводить их вручную через type casting, а потому уже сравнивать их значения. Так кошернее.
Программирование
фев 9, 15:47
Твиттер твиттером, но и про блог забывать не стоит. Вот решил сюда кое-что написать. Решил написать про Ant. Такой себе небольшой урок, которого будет достаточно, чтобы понимать и самому пользоваться этой небольшой, но очень полезной программой.
Что мы узнаем?
- Что такое Ant и для чего он нужен.
- Как написать простой build.xml файл.
- Как скомпилировать с помощью Ant проект.
- Как подключить библиотеки при компиляции.
Про Ant двумя словами
Ant – это утилита для сборки java-программ. Это ее основное назначение. Она использует инструкции, определенными в файле build.xml, для того, чтобы собирать программу (компилировать, копировать куда надо и так далее). В Unix аналогом такой программы является команда make и написанный для нее makefile. В общем-то, здесь можно провести такую аналогию:

Естественно знать, что такое make и makefile необязательно. Но с этого с этого можно сделать вывод, что понимая суть ant и build.xml вам уже не нужно объяснять, что такое make и makefile. И в обратную сторону, зная, что такой make и makefile, вам не нужно объяснять, для чего нужен ant и build.xml. Синтаксис у них совершенно разный, и по-этому такое понимание практических навыков вам никаких не даст, но все-таки это лучше, чем ничего. Но сейчас не об этом.
Для тех, кто не сталкивался ни с make, ни с ant мы уже сказали, что Ant – собирает программу. А как именно, определенно в файле build.xml. И этого вполне достаточно, чтобы начать.
h2 Написание первого, простого build.xml и компиляция нашей программы
Синтаксис build.xml вполне прост. Вот так выглядит обычный build файл:
<project name="My Sample Application" default="compile" basedir=".">
<target name="target" description="Some text here">
</target>
</project>
Один проект — один build.xml. При объявлении проекта мы должны указать его имя (name), команду, выполняемую по умолчанию (default) и папку, относительно которой высчитываются относительные пути (basedir). Проект может состоять из нескольких команд (Target). Каждый Target может состоять из нескольких действий (Actions).
Действием может быть копирование файлов из одной папку в другую, компилирование файлов, удаление, извлечение javadoc и так далее. Разработчики могут также создавать и свои собственные действия, и таким образом расширять Ant.
Напишем Target для компиляции. Взяли клавиатуру, и написали. И вот, что получилось:
<project name="My Sample Application" default="compile" basedir=".">
<target name="compile">
<javac destdir="./build/classes" srcdir="./src">
</javac>
</target>
</project>
Здесь мы объявили Target с именем compile. Внутри него находится действие javac, которое принимает как параметры папку с исходными текстами, и папку в которую будет записан результат в виде .class файлов.
Для того, чтобы вызвать наше действие следует выполнить ant в командой строке. При вызове ant мы можем передать какой build файл использовать, и какую команду следует выполнить. Если этого не сделать, то по умолчанию ant будет использовать build.xml, который находится в текущей папке и Target по умолчанию, указанный в нем.
Т.е. нам достаточно написать:
:/path/to/project$ ant
или
:/path/to/project$ ant compile
или
:/path/to/project$ ant -buildfile build.xml compile
Подключаем дополнительные библиотеки
Сегодня уже вряд ли найдешь сколько-нибудь сложное приложение, которое бы не использовало сторонние библиотеки. Мы можем указать Ant какие библиотеки стоит использовать следующим образом:
<path id="libs.dir">
<fileset dir="lib">
<include name="*.jar" />
</fileset>
</path>
<target name="compile">
<javac destdir="./build/classes" srcdir="${src.dir}">
<classpath refid="libs.dir" />
</javac>
</target>
Строка <classpath refid=“libs.dir” /> указывает, что необходимо подключить дополнительные библиотеки и что находятся они по адресу libs.dir. Чуть выше мы определили, что такое это самое libs.dir.
Здесь следует сделать отступление и сказать, что в Ant присутствует некоторая концепция типов данных. Подробнее о них стоит рассказать в отдельной статье, но пока стоит запомнить, что такие вещи как путь, списки файлов, упорядоченные или неупорядоченные, – все есть некоторый объект со своим типом, который можно использовать в действиях. Одним из примеров таких типов есть path, внутри которого используется другой тип fileset для представления списка файлов.
Делаем красиво. Выносим свойства за скобки
В прошлых примерах мы использовали путь ./build/classes для указания папки, в которую мы хотим скомпилировать наши классы. В больших build.xml файлах, в которых содержаться намного больше разных Targets велика вероятность, что эта же строка понадобиться нам еще где-либо.
По-этому хорошей практикой считается определение таких параметров отдельно от самих Targets. Приведу весь build.xml для примера:
<project name="My Sample Application" default="compile" basedir=".">
<property name="build.classes.dir" location="./build/classes" />
<property name="src.dir" location="./src" />
<path id="libs.dir">
<fileset dir="lib">
<include name="*.jar" />
</fileset>
</path>
<target name="compile">
<javac destdir="${build.classes.dir}" srcdir="${src.dir}">
<classpath refid="libs.dir" />
</javac>
</target>
</project>
Здесь есть еще один тонкий момент. Значения свойства могут быть присвоены как с помощью location атрибута, так и с помощью value. Разница между ними в том, что если location содержит путь к папке или файлу, разделитель между папками (/ или \) будет подставляться правильно в зависимости от того, под Виндовс или под Линуксом запущен Ant. Это просто удобно. Не придется при случае переименовывать все пути, как однажды пришлось мне на своей работе. Хотя вроде бы умные люди писали, а такой мелочи, наверное, не знали.
Заключение
То, что я тут рассказал хватит, чтобы делать 60-70% основной работы. Конечно, лучше знать больше. Возможно, в следующей статье расскажу подробнее на тему типов данных (path, fileset и так далее) и все, что с ними связано. Еще хотелось бы рассказать и про автоматизацию тестов с помощью Ant. Но время покажет как все получится.
Программирование
ноя 8, 20:01

Продолжаем продолжать
Итак, в последний раз мы прошли такую себе мини-лекцию по базам. Сегодня время пришло заняться практикой.
Начнем с первого пункта:
Создание базы данных
В Андроиде я насчитал 3 способа создания базы данных. Начнем с самого неправильно, и закончим самым православным.
1 способ – использовать класс SQLiteDatabase. Для начала сделаем небольшой обзор этого класса, так как в будущем он нам ой как пригодится.
Иерархия:
java.lang.Object
android.database.sqlite.SQLiteClosable
android.database.sqlite.SQLiteDatabase
Предков всего ничего: стандартный явовский Object и интерфейс SQLiteClosable. Последний мы вряд ли мы когда-нибудь будем использовать — это всего лишь внутренний интерфейс, которой исходя из названия содержит в себе методы, необходимые для закрытие (удаления) SQLite классов. В данном случае его наследует наш SQLiteDatabase, который следовательно представляет из себя ресурс, который можно прикрыть, если что)
Перейдем к рассмотрению наиболее популярных методов. Для начала просто опишем их, не вдаваясь в подробности, как с ними работать:
- void execSQL(String sql) — выполнение sql-запроса, который не является выборкой (т.е. без select). Это всякие CREATE TABLE, INSERT, DELETE и так далее.
- Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) — большой и важный метод с кучей параметров. Используем для выборки. Параметры сами за себя говорят. Подробнее обсудим позже
- Cursor rawQuery(String sql, String[] selectionArgs) — сырой, обычный, дикий sql-запрос. Очень полезно иметь возможность обратиться к базе, используя всякие хитрые Join’ы и так далее.
Дальше я не буду сильно описывать следующие методы. Скажу лишь, что они представляют цивильный способ совершения часто встречаемых запросов:
- insert() – возвращает id вставленной строки
- update() – возвращает количество обновленных строк
- replace() – не часто встречал такой метод в других API. Нужно поковыряться еще. Замена.
- delete() – возвращает количество удаленных строк
Дальше пойдут методы, которые возвращают ответ «Да» или «Нет»:
- isOpen() – true, если база была не закрыта
- isReadOnly() – true, если база была открыта только для чтения
Методы для работы с транзакциями:
- beginTransaction() – начало
- endTransaction() – конец
- inTransaction() – true, если открыта транзакция
- setTransactionSuccessful() – только после вызова этого метода транзакция будет закомичена.
Остальные вряд ли будут часто использоваться. С вышеперечисленными пока не будем сильно заморачиваться, а остановимся над тем, с чего собственно начали — создание базы.
Как я и говорил, существует 3 способа это сделать. И одним из этим способов является метод openDatabase(). Вот его полное описание:
static SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags)
где:
- path — путь к файлу базы данных. Путь для любой базы данных имеет вид: «/data/data/<package_name>/databases. <package_name> – имя пакета, которое вы указывали при создании проекта. Что-то вроде com.google.android.maps. Ну, или com.example.mysmallapp
- factory — хз, что такое. По умолчанию стоит null — пусть так и стоит, пока не будет трогать.
- flags — флаги или константы, определенные внутри класса SQLiteDatabase. Их всего четыре: CREATE_IF_NECESSARY (создавать базу, если это нужно), OPEN_READONLY (открыть только для чтения), OPEN_READWRITE (читать и писать), NO_LOCALIZED_COLLATORS (открыть без поддержки локализации)
Т.е. для того, чтобы создать базу с нуля нам нужно вызвать метод openDatabase() примерно таким образом:
SQLiteDatabase mDatabase = SQLiteDatabase.openDatabase("/data/data/com.example.app/databases/my.db", null, SQLiteDatabase.CREATE_IF_NECESSARY);
Все. Таким образом, мы получили новую, чистую базу данных для дальнейшей работы. Так как флаг SQLiteDatabase.CREATE_IF_NECESSARY пишут в конце очень часто при открытии, разработчики Андроида решили предложить метод openOrCreateDatabase() для краткости:
SQLiteDatabase mDatabase = SQLiteDatabase.openOrCreateDatabase("/data/data/com.example.app/databases/my.db", null);
Так даже удобнее. Но это только первый способ.
2 способ — это метод openOrCreateDatabase() в классе Context. О классе Context особо поговорим потом. Сейчас остановимся но том, что он представляет объект для общения с ОС Андроида.
Если вам доступен объект типа Context, то очень рекомендуется использовать его метод openOrCreateDatabase(). Почему? Для начала разберем его поподробнее:
SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory)
где
- name — имя базы данных, уникальное в рамках приложения
- mode — константа, для указания режима (MODE_PRIVATE или 0 – по умолчанию, MODE_WORLD_READABLE для чтения, MODE_WORLD_WRITEABLE для записи)
- factory — пока не трогаем, ставим null
Возвращает этот метод объект типа SQLiteDatabase.
Какие преимущества у такого способа? Оно одно — нам не нужно указывать полный путь к файлу базы данных. Достаточно только ее имени. Так как путь к приложению мы уже знаем исходя из данных, которые предоставляет Context.
Для полноты картины разберем еще несколько методов класса Context для работы с базами данных:
- boolean deleteDatabase(String name) — удаляем базу по имени. True, если завершилось успешно.
- File getDatabasePath(String name) — возвращает абсолютный путь по имени базы
Со вторым способом мы разобрались. Перейдем к следующему, самому православному способу, который советуют использовать разработчики Гугла.
3 способ — использовать SQLiteOpenHelper.
Как становится ясно из названия, SQLiteOpenHelper помогает нам открывать/создавать базы данных. Начнем с описания класса:
Иерархия:
java.lang.Object
android.database.sqlite.SQLiteOpenHelper
Здесь все. Также все просто и с методами. Их всего 6. 2 из них — абстрактные, и их следует переопределять в потомках. Перечислим первые 4:
- void close() – закрыть все открытые соединения
- SQLiteDatabase getReadableDatabase() – возвращает базу для чтения
- SQLiteDatabase getWritableDatabase() – база для чтения и записи
- onOpen(SQLiteDatabase db) — вызывается после того, как база будет открыта
И 2 абстрактных метода:
- onCreate(SQLiteDatabase db) — вызывается после того, как база создана в первый раз. Здесь мы создаем необходимые таблицы. И если нужно, заносим в них какие-либо начальные данные.
- onUpgrade(SQLiteDatabase db) — вызывается, когда базе нужен апгрейд. Здесь мы удаляем таблицы, создаем новые, добавляем поля и так далее.
Ну, и конструктор:
SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)
где
- context — контекст приложения. Именно через его метод openOrCreateDatabase() и работает SQLiteOpenHelper
- name — имя базы
- factory — говорили уже
- version — версия базы. Используется, когда схему базы данных нужно обновить.
Приведем пример наследования и использования SQLiteOpenHelper:
class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "data";
private static final String DATABASE_CREATE = "create table test_table (_id integer primary key autoincrement, some_test text not null);";
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}
Здесь мы использовали паттерн Шаблон для инкапсуляции внутренних данных. Для использования базы теперь нужно лишь вызвать getReadableDatabase() или getWriteableDatabase() метод. Т.е.:
DatabaseHelper mDbHelper = new DatabaseHelper(mCtx);
SQLiteDatabase mDb = mDbHelper.getWritableDatabase();
И мы получим нашу любимую базу. Все очень просто и лаконично смотриться.
Конец второй части
На этом пока остановимся. Дальше разберем, как же все таки работать со всеми этими методами — insert(), update(), replace(), delete() и тому прочее. Постараюсь этому как-нибудь выделить время. Пока всем.
Программирование
ноя 7, 17:22

В начале было слово
Я уже говорил в недавнем времени о выборе платформы Android в качестве хобби-изучения на ближайшее время. И так, чтобы провести учиться с пользой буду рассказывать о том, как проходит этот самый процесс.
У меня нет никакой четкой и выверенной программы обучения. Не никакого плана: сначала это, потом это, потом то. Единственный мой план — это собственная интуиция. В общем-то, как бог на душу положит одним словом.
По-этому не удивляйтесь, что на роль первого погружения в Андроид я выбрал трепанацию SQLite, как базы данных для хранения данных андроид-приложении.
Цели
- Понять, как устроена работа с базой данных в самом широком смысле
- Какие эффективные практики следует применять при работе?
- Какие классы и объекты чаще всего используются? Какие методы следует знать, как свои пять пальцев?
- Как база данных связана со остальными частями приложения? Где создается, где вызывается?
Начало
Базы данных имеют больше значение в программировании хотя бы по тому, что каждый из нас с ними сталкивался (или столкнется). В Андроиде реализована поддержка баз данных на уровне каждого приложения. Т.е. создать и работать с базой данных внутри приложения настолько естественно в Андроиде, как дышать воздухом. По умолчанию доступен интерфейс для работы с базами данных SQLite. Поддержки никаких других СУБД мною замечено не было, значит за основу берем единственно доступный SQLite.
Ради справедливости стоит отметить, что сторонние разработчики предлагают свои продукты, как альтернативу SQLite. Это db4object и mcobject.
Но о них мы поговорим, как-нибудь потом. Сейчас для этого не время.
Небольшое введение (SQLite)
SQLite — библиотека, которая позволяет создавать база данных, который обладают рядом приятных и уникальных особенностей. Во-первых – это независимость. Под независимостью я понимаю минимальный объем сторонних библиотек и функций, которые ей нужны. Создатели говорят, что SQLite можно скомпилировать любым С компилятором и с использованием только таких библиотечных функций, как:
- memset()
- memcpy()
- memcmp()
- strcmp()
- malloc()
- free()
- realloc()
Это позволяет использовать эту библиотеку в разнообразных мобильных устройствах, mp3-плеерах, да и просто в любом месте, где требуется компактная база данных.
Во-вторых, в SQLite нет никакого серверного процесса. База данных хранится в файле. И весь доступ к ней осуществляется, как к файлу. Следовательно ничего не надо устанавливать. Нет никакого процесса установки. Ты просто подключил и используешь.
В-третьих, все запросы являются транзакциями, что обеспечивает такие свойства, как Атомарность, Непротиворечивость, Изоляцию и Долговечность (ACID)
Вывод из этой все прелести очень простой. В Андроиде можно работать с базой данных, как с настоящей базой данных со всеми запросами, insert’ами и update’ами не задумываясь о том, как все это храниться в чистоте и порядке.
Конечно, свойства SQLite откладывают свой отпечаток на работу с ней. Так, например, открытие доступа к базе может потребовать создание нового файла и так далее. Но API Андроида делает большинство работы за нас. И многом мы можем и не заботиться, как убедимся позже.
Продолжение введения (паттерн Адаптер и Шаблон )
Одно из любимых слов в Android API — это Адаптер. Встречаются они повсюду, и по-этому следует уяснить, что же это такое. Хорошо, что это совсем не трудно.
Выжимка из книги Design Patterns:
Паттерн Адаптер (известен также как Wrapper, «Обертка»)
Назначение: Преобразует интерфейс одного класса к интерфейсу другого.
Мотивация: Иногда класс из инструментальной библиотеки, спроектированный для повторного использования, не удается использовать только потому, что его интерфейс не соответствует тому, который нужен конкретному приложению.
Если быть понятнее, то Адаптер используют для предоставления доступа к объекту через специальный интерфейс. Одной из причин этому может быть несоответствие исходного интерфейса. Другая причина — просто создание более удобного интерфейса, который скрывает ненужные подробности работы с исходным объектом, и предоставляет удобную альтернативу.
С последней причиной очень тесно паттерн Шаблон. Цель этого паттерна — создание класса, который модифицирует логику своего подкласса таким образом, чтобы сделать его завершенным — например, скрыть конфигурационные параметры, методы для проверки прав, да и вообще все , что угодно.
Для удобства приведу пример некоторого псевдо-класса для работы с базой данных. Пусть у нас есть класс для доступа к базе. Назовем его Database. Для создания соединения мы передаем в конструкторе необходимые реквизиты: имя базы данных, пользователя, пароль:
class Database {
private Connection mConnection;
Database(String database_name, String username, String password) {
connection = getDbConnection( database_name, username, password);
}
....
}
Это утомительно — каждый раз помнить и передавать параметры к базе, когда нам понадобится к ней доступ. Как выход, можно создать класс MyDatabase, который дополнял бы класс Database нужным нам образом. Выглядеть это будет примерно так:
class MyDatabase extends Database {
private static final String DATABASE_NAME = "sample_database";
private static final String USERNAME = "user";
private static final String PASSWORD = "password";
MyDatabase() {
super(DATABASE_NAME, USERNAME, PASSWORD);
}
...
}
Все. Теперь достаточно вызвать new MyDatabase() и все будет сделано за нас.
Для чего я все это рассказываю? Для того, что приведенный выше прием станет одним из наших любимых при написании классов для работы с базой данных, да и вообще в большинстве аспектов работы над приложениями в Андроиде.
Работа с базой данных (общая картина)
Начнем с простого. Любой процесс работы с базой данных включает в себе четко определенный набор методов.
- Создание базы данных. Или ее открытие базы.
- Работа с базой данных:
- Заполнение базы данными (вставка)
- Изменение существующих данных (обновление)
- Выбор данных по различным критерия (выборка)
- Удаление данных
Конец первой части
На сегодня все. Завтра будем самое основное – увидим Андроид в действии, разберем классы и там уже пойдем дальше.
Программирование
окт 25, 19:46

Скачал не так давно Android SDK. Совсем для себя неожиданно.
Захотелось просто изучить что-то новое, абсолютно не связанное с вебом. Как говорил Дмитрий Честных изучать новые языки, технологии полезно и интересно. И я с ним полностью согласен.
Что касается языков и технологий, если они уже упомянули, то не так давно (месяца 2-3 назад) я уже успел переквалифицироваться с программиста на PHP на программиста J2EE. Переквалифицировался скорее номинально, чем реально. Потому что с явой до текущего своего места работы сталкивался очень туманно и давно (первый курс университета => прочтение книги Дейтеля по Си => прочтение несколько глав из книги Дейтеля по Java, последнее только из уважения к Дейтелю).
Из этого всего вытекает, что осваивать Андроид вдвойне полезнее.
- Что-то новое
- Java, java
Третьим пунктом можно было бы приписать Андроид Маркет, но я об этом совсем не думаю.
Что касается самого Андроида, то не так давно вышла новая версия sdk – 1.0 по счету. Прям как знак с выше – вот тебе, парень, новая версия. Бери, специально для тебя сделали.
Я, конечно, спасибо. Посмотрю, что там под коробкой и как оно работает. Но об этом поподробнее в следующий раз. Сначала попробую рассказать о первых впечатлениях. Потом уже о впечатлениях в работе.
Программирование
сен 5, 22:39
Программирование
фев 22, 13:53
Итак, в прошлый раз разговор был о Symfony. Сегодня речь пойдет о Akelos. Кто не в курсе Akelos, скажу лишь то, что этот фреймворк является полным клоном RoR с единственным отличием – написан он на PHP. Правда ли это для вопросов интернационализации я не знаю. Но как бы то ни было все равно посмотрим, что Akelos нам приготовил.
Пару слов о документации
Как таковой для Akelos ее нет. Есть пару отписок, небольшой Readme, плюс сгенерированная с помощью phpxref дока и демонстрационный скринкаст. Все. Может быть в этом плане разработчик понадеялся на прошлый опыт с Ruby On Rails. Не знаю, но факт, как говорится, на лицо и с этим следует смириться. В оправдание могу сказать, что фреймворк достаточно само документированный и разобраться как и что происходит не составляет особого труда. Исходники благо открыты для любого применения.
Интернационализация интерфейса, сообщений и тд
Здесь Akelos ничем примечательным от Symfony не отличается. Если помните в Symfony для проблемы поддержки нескольких языков и стран использовался параметр Culture ( язык_СТРАНА в двухбуквенных кодах). Akelos используют более простую модель. В файле config задаются разрешенные локали, которые можно увидеть в банальном HTTP-заголовке «Accept-Language» (например, «ru, en_us»). После того как настроены необходимые языки Akelos будет самостоятельно создавать и следить за словарями для этих языков с помощью специального метода t() или специальных тегов _{} в виде.
Каждый раз когда вызываются эти методы, фреймворк, во-первых, обновит словарь, если переданного слова или фразы не будет в словаре, и, во-вторых, при выводе возьмет перевод слова или фразы из словаре, соответствующему выбранной локали.
Для того, чтобы сменить локаль достаточно в URL добавить код языка. Например, вот так будет выглядить классическая строка с доступными языками:
<div align="right">
<?php echo $url_helper->link_to($controller->t('en'), array('lang' => 'en')) ?> |
<?php echo $url_helper->link_to($controller->t('de'), array('lang' => 'de')) ?> |
<?php echo $url_helper->link_to($controller->t('ru'), array('lang' => 'ru')) ?> |
</div>
Это все. Akelos по щелчку на ссылку прибавит к URL выбранный язык. И даже более того, во-всех ссылках, созданных с помощью link_to(), отобразится изменения, связанные с выбором языка. Ни о чем разработчику беспокоится не приходится.
Что касается словарей, то никого XML подобия вы не встретите. Обыкновенные ассоциативные массивы:
$dictionary = array();
$dictionary['Pages'] = 'Страницы';
$dictionary['OK'] = 'OK';
$dictionary['Cancel'] = 'Отмена';
Локализация содержимого
Если с интернационализацией интерфейса все очень просто и понятно, то почему же должно быть по-другом с содержимым подумали разработчики и упростили работу нам до безобразия.
Для начала следует обьявить что твоя любимая модель имеет многоязыковую структуру и что даже несколько столбцов в соответствующей ей таблицы из базы данных имеет перевод на другие языки мира. Как это делается? А вот так:
function up_1()
{
$this->createTable('pages', "
id,
en_title,
de_title,
ru_title,
en_body text,
de_body text,
ru_body text
");
}
Все это естественно должно находится в инсталлере приложения. Как видите, в одной таблице мы имеет сразу же и английский, и немецкий, и русский вариант заголовка и текста страницы. Ну что же, разработчики доупрощались настолько, что нарушили святые нормальные формы старика Кодда, так ревностно оберегаемые математиками и профессорам ВУЗов. Мне если честно, такой поворот событий тоже не очень нравится. Не правильно это по единственной простой причине – если не все страницы могут быть переведены на все три языка, то на что будет похожа наша таблица? Пустое пространство – это не есть хорошо и все-таки следует беречь ресурсы данные нам Богом.
Ну оставим и пойдем дальше. Дальше никаких сюрпризов не ожидается. Все те же get- и set-методы автоматом подхватываются фрейморков. И мы просто лениво наслаждаться тем, что мир прекрасен и чуден, и все так хорошо и просто. На всякий случай приведу пример как же это делается:
Контроллер:
$this->Page->setAttribute('title', $this->params['title']);
$this->Page->setAttribute('body', $this->params['body']);
$this->Page->save();
Вид:
<h1>En</h1>
<p>
<label for=“page_title”>_{Title}</label><br />
<?php echo $form_helper->text_field(‘title’, ‘en’)?>
</p>
<p>
<label for=“page_body”>_{Body}</label><br />
<?php echo $form_helper->text_area(‘body’, “en”) ?>
</p>
<h1>Ru</h1>
<p>
<label for=“page_title”>_{Title}</label><br />
<?php echo $form_helper->text_field(‘title’, ‘ru’)?>
</p>
<p>
<label for=“page_body”>_{Body}</label><br />
<?php echo $form_helper->text_area(‘body’, ‘ru’) ?>
</p>
Модель (инсталлер):
$this->createTable('pages', "
id,
en_title,
ru_title,
en_body text,
ru_body text
");
И все. Красота да и только.
Выводы
Akelos прост на уровня интуитивного понимания. Конечно хранение в одной таблицы сразу всех версий контента не очень красиво. Гораздо приятным в этом отношении выглядит Symfony, которые как заядлый отличник старается все время вытянуть на пятерку.
Но и у Symfony есть свои недостатки, которые, как мне кажется, все-таки сглаживаются на больших проектах. Взять хотя бы тот же XML для словарей. Его неудобно писать вручную. Но кто знает, возможно на большом сайте такой необходимости уже и не будет, так как можно будет использовать генерирующие XLIFF программы.
По большому счету, что Akelos, что Symfony — без разницы. Все они используют ту же модель – хеллпер, словарь, ORM, автоматом подхватывающие get- и set-методы.
Надеюсь, что это небольшая обзорная статья в чем-то поможет в реализации многоязычных приложения самостоятельно. Курс и направление движения есть. Существуют ли какие-либо другие варианты? Наверняка существуют. Если мне что-нибудь интересного в этом плане попадется, обязательно напишу.
В следующей статье я планирую немножко развить эту тему и немножко отойти к другой. Меня интересует ORM-фреймворк Doctrine. Что за вкусности там есть, опять же поддержка интернационализации, удобно ли пользоваться, насколько эффективен для применения «один разработчик – небольшой приложение». Словом, не буду загадывать – все равно сами все увидите.
Программирование
фев 16, 14:16
Предисловие
Вообще говоря, слово «интернацинализация» для веб-разработчика очень часто означает кошмар. Чаще всего то, как это делается, понимается смутно.
Я решил провести небольшое исследование на эту тему. Причем рассмотреть этот вопрос на примере того, как это делает два PHP-фреймворка — Symfony и не шибко известный Akelos.
Почему Akelos? Во-первых, потому что в недавнем времени мне пришлось писать под него приложение с поддержкой 3 человеческих языков. Akelos, как мне сообщили некие авторитетные люди, есть полный клон RoR. Не могу сказать правда это или нет, так не имею опыта работы с последним.
Теория (свободный перевод из мануала Symfony)
Слово «интернационализация» (internationalization) довольно длинное. Поэтому разработчики привыкли сокращать его до простого «i18n» (понимается как i, 18 английских букв и n в конце. Локализация (localization) точно таким же образом сводится к лаконичному «l10n». Вот эти два понятия и покрывают 2 разных аспекта разработки мультиязычных веб-приложений.
Под интернационализацией понимают несколько версий одного и того же контента на разных языках или в разных форматах. Например, интерфейс gmail может предлагать один и тот же сервис на разных языках. Меняются только надписи на кнопках.
Локализация же предполагает определенный контент, зависящий от страны, из который ты пришел. Возьмем для примера новостной портал. Если ты находишься в Украине, то тебе показывают последние заголовки об Украине. Если же ты находишься в России, то тебе показывают последние заголовки об Путине. Все просто. Стоит сказать, что локализация может включать в себя не только перевод контента, но и сам контент может отличаться в разных локализированных версиях.
Если все обобщить, то получится что i18n и l10n означает, что приложение умеет справляется с:
- Переводом текста на разные языки (как в интерфейсе, так и в самом содержании)
- Стандартами и форматами (даты, суммы, числа и тому подобное)
- Локализацией контента (множество версий заданного объекта в зависимости от страны)
Как же это делает Symfony?
Здесь я не собираюсь переводить целую главу из учебника по Symfony. Все там достаточно толково и понятно описано. Просто остановимся вкратце на некоторых вещах.
В Symfony есть такой замечательный объект как sfUser, который содержит все рутину по работе с пользовательской сессией. По идее, про $_SESSION вы должны забыть совсем. Так что имейте это ввиду.
Итак, в этом объекте sfUser есть замечательный параметр “Culture”. В настройках для приложения вы можете установить его по умолчанию и переопределить, если надо, в контроллере.
Интересно, что Culture, кодируется двумя кодами – двухбуквенным кодом для языка в нижнем регистре (по ISO 639-1) и и двухбуквенным кодом для страны в верхнем регистре (по ISO 3166-1) примерно так – «fr_FR», «ru_UA». Это сделано для таких случаев, когда нам нужен один контент на одном языке, но для разных стран. Например, один контент на испанском языке для Мексики, и совершенно другой для Испании.
Что еще интересного? Поддерживается автоматическое добавление параметра Culture в URL. Для этого необходимо настроить параметры роутинга для нужных страниц и все.
Info Symfony, как и большинство приложений в таких случаях, при первом запросе определяет язык по HTTP-заголовку «Accept-Language».
Поддерживается форматирование дат, денежных сумм, чисел. с помощью хелперов. Плюс поддерживается ввод и перевод во внутренний формат всех данных, зависящих от культурных особенностей (См. класс sfI18N).
Теперь об локализации приложений. Рассмотрим случай с существованием одно и того же контента на разных языках и хранение этой информации в базе данных.
В Symfony каждая таблица, которая содержит данные для разных языков, должна быть разделена на 2 таблицы. Первая – это та, которая не содержит никакой связанной с локализацией информации. И вторая – это та, которая содержит только локализированние данные. Эти две таблицы связаны с друг другом связью «один ко многим».
Пример задание схемы базы данных:
my_product:
_attributes: { phpName: Product, isI18N: true, i18nTable: my_product_i18n }
id: { type: integer, required: true, primaryKey: true, autoincrement: true }
price: { type: float }
my_product_i18n:
_attributes: { phpName: ProductI18n }
id: { type: integer, required: true, primaryKey: true, foreignTable: my_product, foreignReference: id }
culture: { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
name: { type: varchar, size: 50 }
Как видим, первая таблица содержит только первичный ключ «id» и цену товара. Во второй таблице, объявлен внешний ключ на «id» из первой таблицы. К тому же присутствует дополнительно поле «culture» для задание параметра Culture, и естественно само поле?содержащее необходимый контент, «name». Symfony позволяет не описывать все так подробно. Все будет сделано само собой, если придерживаться соглашения об именах (см. мануал).
Теперь о сохранении данных. Для необходимо перед сохранением устанавливать Culture c помощью метода setCulture() класса модели. Можно автоматизировать этот процесс и делать все эти необходмые дела внутри метода hydrate(), перопределив его примерно таким образом в своей модели:
public function hydrate(ResultSet $rs, $startcol = 1)
{
parent::hydrate($rs, $startcol);
$this->setCulture(sfContext::getInstance()->getUser()->getCulture());
}
Выборка осуществляется с помощью метода doSelectWithI18n() в классах Peer’ах.
Info Для справедливости стоит сказать, что все эти выборки и set’ы справедливы для ORM-фреймворка Propel, который по умолчанию использует Symfony. Есть еще и Doctrine. Но его мы рассмотрим как-нибудь потом.
Перевод интерфейса (всяких там сообщений, лейбочек и тому подобное) сделан просто и со вкусом. Для этого всего лишь необходимо использовать хелпер __($phrase) – два символа подчеркивания, если кто не понял. Каждый раз, когда вызывается этот хелпер, Symfony смотрит перевод $phrase в словаре в зависимости от выбранного языка.
Словари написаны в c использованием XML Localization Interchange File Format (XLIFF). Первый раз встретил этот формат именно в Symfony. Вид имеет простецкий. К сожалению чего не скажешь о самом XML, который читается, честно говоря, с трудом.
Пример:
<?xml version="1.0" ?>
<xliff version="1.0">
<file orginal="global" source-language="en_US" datatype="plaintext">
<body>
<trans-unit id="1">
<source>Welcome to our website.</source>
<target>Bienvenue sur notre site web.</target>
</trans-unit>
<trans-unit id="2">
<source>Today's date is </source>
<target>La date d'aujourd'hui est </target>
</trans-unit>
</body>
</file>
</xliff>
Есть еще мелкие приятности, которые имеет Symfony. Это и перевод в зависимости от параметров (полезно для сообщений типа «1 комментарий», «2 комментария». Это и перевод предложений, содержащих переменные. И тому подобное.
Конец первой части
Хватит наверно для начала. Писалось это все в субботу утром за чашечкой чая, когда 2 чувака в квартире, где я проживаю, меняли двери, сверлили там вовсю, хлопали и топали. Пока все.
Программирование