2010-06-19

И снова коротко о множественных формах в Zend Framework

Довольно часто (сплошь и рядом) приходится выводить слова, принципы написания которых зависят от числительного, с которым они связаны.
В моем проекте http://spaceismine.org/ к каждой статье выводится количество просмотров. Если дописывать после количества слово "просмотров", то это не всегда будет корректно. Наверное, я достаточно сложно изъясняюсь. Попытаюсь поставить задачу и расписать способ ее решения.
Задача: сделать вывод вместо:
Прочитано раз: 1 / Прочитано раз: 3 / Прочитано раз: 11
такой:
Прочитано 1 раз / Прочитано 3 раза / Прочитано 11 раз
Не самый удачный пример на слове "раз", так как форма 1 и 11 не отличаются. В большинстве случаев они будут все же разными:
1 просмотр / 3 просмотра / 11 просмотров
В русском языке, таким образом, имеется три множественные формы: "просмотр", "просмотра", "просмотров". Начиная с версии 1.9, Zend Framework сильно облегчает решение такого рода задач.
Решение: итак, логика вывода у нас работает во view. И по такому случаю уже есть view helper translate. Пишем следующее:
Прочитано <?php
echo $article->views, ' ';
echo $this->translate(array('раз', 'раза', 'раз', intval($article->views), 'ru'));
?>
И получаем желаемый вывод.

2010-06-09

Медленный sendmail

Сегодня моя запись будет довольно короткой. Очередные грабли попались мне при отладке отправки сообщений на почту на локальной машине. Проблема была в очень медленной отправке почты посредством Zend_Mail, который по умолчанию использует транспорт Zend_Mail_Transport_Sendmail, то есть, грубо говоря, программу sendmail. Одно письмо отправлялось около минуты. Начав ковыряться в логах, я обнаружил, что в /var/log/maillog.err пишутся примерно следующие строчки (взяты не из моих логов, так как свои сейчас под рукой не имею):
Nov 16 18:15:32 desk sendmail[3106]: My unqualified host name (desk) unknown; sleeping for retry
Nov 16 18:16:32 desk sendmail[3106]: unable to qualify my own domain name (desk) -- using short name
Я для локальных адресов использую домены типа site.local (прописываю в /etc/hosts соответствие на петлю 127.0.0.1). При этом имя машины, ссылаясь на приведенные лже-записи логов, у меня desk - имя машины. Оно же записано в файле /etc/hostname

Решение: делаем vim /etc/hostname, меняем desk на desk.local, радуемся быстрой работе sendmail :)

2010-06-07

2 подводных камня и PHPUnit

С недавних пор решил встать на путь истинный и пока я еще совсем новичок в юнит-тестировании. Сегодня споткнулся на некоторых очень неприятных вещах. Использовал PHP 5.3.2 + PHPUnit 3.4.14 + Zend Framework 1.10.5
  1. Оказалось, что нельзя в классе, унаследованном от PHPUnit_Framework_TestCase, то есть в тест кейсе использовать свойства класса. Я пытался делать так: в одном методе-тесте в свойство записываю, в другом методе-тесте считываю. Это не работает. Свойство во втором тесте было пустым.
  2. Этот подводный камень очень близок к первому. Аннотация @dataProvider provider является поставщиком данных для тестового метода, но эти данные генерируются в момент создания объекта класса PHPUnit_Framework_TestCase, а не непосредственно перед запуском теста. То есть, опять же, если вы в провайдере используете свойства класса, которые заполняются во время прохождения тестов, то provider вернет пустое значение. Если быть точнее, он вернет значение таким, какое оно было на начало создания объекта класса.
  3. И еще один камень. Zend Studio генерирует тест кейсы и тесты, основываясь на существующих методах классов, что несомненно удобно. Но есть одна проблема: она также генерирует пустой конструктор, который является виной бага [#ZF-4496]. Суть бага в следующем. Если вы используете аннотацию @dataProvider в классе с конструктором, вы будете получать сообщение об ошибке тестов примерно следующего содержания Missing argument 1 for FooTest::testProvider(). Эта ошибка проявляется, если у класса, унаследованного от PHPUnit_Framework_TestCase существует конструктор. Конечно же от нее можно спастись, удалив ненужный конструктор.
Из первых двух "находок" можно сделать вывод: data provider методы могут быть спокойно описаны как static.
P.S. Ушел продолжать учить матчасть.

2010-06-02

Структура директорий и названия классов

Хотелось бы поведать свои находки в области наиболее удобной и даже несколько zend-way структуры директорий для проекта.
Глядя на исходники библиотеки ZF, мне хотелось использовать такое же именование классов внутри папки application. Я выбрал такой путь в котором папка application содержит в себе все классы, необходимые для работы проекта. Таким образом, у меня в папке application появились папки plugins, forms, models, controllers.
Как же правильно сделать так, чтобы все нормально функционировало и в файле Articles.php был класс Model_Articles вместо класса Articles? Это нужно, чтобы избавиться от старого метода автозагрузчика, который запускался следующим образом в файле Bootstrap.php и подключал все пространства имен (зачастую это совершенно излишне):
protected function _initAutoload()
{
  $loader = Zend_Loader_Autoloader::getInstance();
  $loader->setFallbackAutoloader(true);
}
В этом случае на помощь нам может прийти следующий метод нашего класса Bootstrap:
protected function _initAutoload()
{
  $loader = Zend_Loader_Autoloader::getInstance();
  $moduleLoader = new Zend_Application_Module_Autoloader(array(
    'namespace' => '',
    'basePath'  => APPLICATION_PATH,
  ));
  return $moduleLoader;
}
Таким образом мы создаем модуль автолоадера для загрузки файлов именно нашего приложения, то есть префикс неймспэйса у нас получается пустым и корневая директория - APPLICATION_PATH. Это позволит создавать классы (см. структуру директорий выше) с именами Model_Ratings, Model_Articles. А также уходить вглубь директорий, например положить класс Model_Row_Article в директорию application/models/Row/Article.php В стиле ZF? По-моему, вполне :)