2010-07-30

Права в Git и развертывание

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

Раньше было так: на девелоперской машине сайт /var/www/site был во владении основного пользователя и IDE могла спокойно работать с этими файлами. Если серверу нужны были права на запись в директорию, то директории выставлялись права 0777, благо через SVN дальше компьютера разработчика это не уходило. Был такой вот мелочный хак.

Все бы хорошо, но после перехода на Git, отправлять на продакшн права 0777 было бы совсем нехорошо. Решением этой дилеммы стал запуск сервера apache от основного юзера операционки. В Ubuntu ключевой файл находится в следующем месте:

/etc/apache2/envvars

В нем следует изменить строки


export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data


на


export APACHE_RUN_USER=user
export APACHE_RUN_GROUP=user

Где user - имя основного юзера системы.
И стало щастье и IDE, и веб-серверу. В итоге мы можем хранить в Git "правильные" права и разрабатывать приложение в любимом текстовом редакторе запущенном не от www-data или, что еще хуже root'а.


P.S. Под основным юзером я имел ввиду юзера, который собственно работает за компьютером.

2010-07-15

Игнорирование изменений пробельного пространства в svn diff

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

Команда svn diff не поддерживает функции игнорирования изменений пробелов и табов. Но зато GNU программа diff умеет это делать.

Чтобы использовать стороннюю diff команду в svn diff, нужно воспользоваться ключом -x.
Если мы желаем напрямую сравнить два файла, игнорируя пробелы, то нам следует использовать diff -uw /path/to/file1 /path/to/file2

Если мы желаем посмотреть наши локальные изменения, игнорируя пробелы, то нам нужна следующая команда:

svn diff --diff-cmd diff -x -uw /path/to/file

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

Это был относительно вольный перевод этой записки:
http://goo.gl/DnKh

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? По-моему, вполне :)

2010-05-24

Дружим XAMPP с ImageMagick

Необходимо мне вдруг стало отлаживать мой хобби-проект http://spaceismine.org/ на Windows-машине, который в production-версии работает на Fedora. А на linux-системах нет проблем с установкой расширений, подобных imagick. Для Fedora, к примеру, достаточно установить расширение через yum и перезапустить apache:
yum install php-pecl-imagick
service httpd restart
В Windows же не все так просто. Менеджер пакетов PECL под окнами очень капризен и сразу работать не хочет. Для него нужны танцы с бубном, описанные в мануалах. Я решил пойти слегка другим путем: установить imagick, вручную прикрутив его к php.
В целом, ImageMagick - открытое бесплатное программное обеспечение, которое служит для обработки графической информации. А imagick - это php-wrapper (обвязка) для библиотек ImageMagick. Файлы, которые мне понадобились:

http://www.imagemagick.org/download/binaries/ImageMagick-6.6.2-0-Q16-windows-dll.exe
http://valokuva.org/builds/ext/vc9/ts/imagick/2010-05-24_1007/php_imagick_ts.dll

Был выбран wrapper thread-safe, так как версия php, которая была установлена в XAMPP была так же thread-safe. В итоге, я установил ImageMagick и скопировал скачанный php_imagick_ts.dll в место:
C:\xampp\php\ext\php_imagick_ts.dll
И тут мне попался огромный подводный камень. Проблемы возникли с тем, что php в XAMPP был скомпилирован с помощью Visual Studio 6.0, а модуль php_imagick_ts.dll - в Visual Studio 9.0 и из-за разных подписей они вместе напрочь отказались работать. А модуль imagick, скомпилированный в VC6 найти так и не удалось.
Я взглянул на версию php, она была 5.3.1, а на production'е у меня 5.3.2. Не очень страшно, но захотелось все же обновить и php. Итак моя, казалось бы безобидная идея, превращается в эпопею. :)
Что ж, я люблю все новое и решил выкачать thread-safe php, собранный в VC9, чтобы он был совместим с imagick. Долго искать не пришлось.

http://windows.php.net/downloads/releases/php-5.3.2-Win32-VC9-x86.zip

Итак, переименовав на случай отката C:\xampp\php в C:\xampp\php5vc6, я распаковал содержимое свежего php в чистую папку C:\xampp\php. Затем скопировал модуль imagick в C:\xampp\php\ext\php_imagick_ts.dll
Понадобилось скопировать C:\xampp\php\php.ini-development в C:\xampp\php\php.ini, так как последнего не было на месте. Также пришлось немного подкорректировать этот дефолтный php.ini, патч (unified diff) примерно такой:
--- C:\xampp\php\php.ini Mon May 24 16:09:59 2010 UTC
+++ C:\xampp\php\php.ini-development Wed Mar 3 13:40:56 2010 UTC
@@ -784,7 +784,7 @@
 ;include_path = ".:/php/includes"
 ;
 ; Windows: "\path1;\path2"
-include_path = ".;C:\xampp\php\PEAR"
+;include_path = ".;c:\php\includes"
 ;
 ; PHP's default setting for include_path is ".;/path/to/php/pear"
 ; http://php.net/include-path
@@ -806,7 +806,7 @@
 ; http://php.net/extension-dir
 ; extension_dir = "./"
 ; On windows:
-extension_dir = "ext"
+; extension_dir = "ext"
 
 ; Whether or not to enable the dl() function.  The dl() function does NOT work
 ; properly in multithreaded servers, such as IIS or Zeus, and is automatically
@@ -876,7 +876,7 @@
 
 ; Maximum allowed size for uploaded files.
 ; http://php.net/upload-max-filesize
-upload_max_filesize = 10M
+upload_max_filesize = 2M
 
 ; Maximum number of files that can be uploaded via a single request
 max_file_uploads = 20
@@ -948,7 +948,7 @@
 ;extension=php_curl.dll
 ;extension=php_dba.dll
 ;extension=php_exif.dll
-extension=php_fileinfo.dll
+;extension=php_fileinfo.dll
 ;extension=php_gd2.dll
 ;extension=php_gettext.dll
 ;extension=php_gmp.dll
@@ -956,17 +956,17 @@
 ;extension=php_imap.dll
 ;extension=php_interbase.dll
 ;extension=php_ldap.dll
-extension=php_mbstring.dll
+;extension=php_mbstring.dll
 ;extension=php_ming.dll
 ;extension=php_mssql.dll
-extension=php_mysql.dll
+;extension=php_mysql.dll
 ;extension=php_mysqli.dll
 ;extension=php_oci8.dll      ; Use with Oracle 10gR2 Instant Client
 ;extension=php_oci8_11g.dll  ; Use with Oracle 11g Instant Client
 ;extension=php_openssl.dll
 ;extension=php_pdo_firebird.dll
 ;extension=php_pdo_mssql.dll
-extension=php_pdo_mysql.dll
+;extension=php_pdo_mysql.dll
 ;extension=php_pdo_oci.dll
 ;extension=php_pdo_odbc.dll
 ;extension=php_pdo_pgsql.dll
@@ -985,7 +985,6 @@
 ;extension=php_xmlrpc.dll
 ;extension=php_xsl.dll
 ;extension=php_zip.dll
-extension=php_imagick_ts.dll
 
 ;;;;;;;;;;;;;;;;;;;
 ; Module Settings ;

Таким образом мне удалось подружить XAMPP и imagick, и обновить PHP в XAMPP до версии 5.3.2. Надеюсь, я смог спасти хотя бы кого-нибудь от граблей. :)