В этой статье я хочу рассказать об использовании Phing при разработке небольшого web приложения. Почему небольшого? Все очень просто. С одной стороны в качестве примера будет использован реальный build файл, а не упрощенный вариант из учебника. А с другой – этот build файл достаточно короткий, и о нем можно было рассказать в рамках статьи 😉 .
Примечание. Phing – это система сборки проектов, основанная на Apache Ant. Я предполагаю, что вы знаете как её установить и настроить. Если нет – читайте «Программирование на PHP. Избавляемся от рутинных операций с помощью Phing».
Итак, приступим.
Недавно я писал о генераторе форм для фреймворка CodeIgniter. Как вы понимаете приложение небольшое, но, тем не менее, перед переносом его на сервер нужно сделать массу рутинных операций.
1) Subversion в каждой папке приложения создает скрытую папку “.svn”, что значительно усложняет «ручное» копирование файлов. Т.е. каждую папку придется копировать отдельно.
2) JavaScript и CSS файлы желательно сжать.
3) Кроме того, много маленьких файлов загружаются дольше, чем один большой. Т.к. в данном приложении используется 5 js-файлов, то разумно будет объединить их в один. С другой стороны, во время разработки удобнее работать именно с 5-тью маленькими файлами.
4) Раз мы объединяем несколько js-файлов в один, то необходимо изменить теги <script>
в заголовках страниц.
5) Практически все современные браузеры поддерживают gzip сжатие. Поэтому сделаем архивную версию объединенного JavaScript файла (для браузеров, которые поддерживают gzip, будем отдавать архивную версию, всем остальным — обычную).
Как видите, работы не много. Но представьте, вы подготовили приложения для размещения на сервере, и нашли ошибку или решили что-то изменить. Исправить сжатые js файлы конечно можно, но, поверьте, это далеко не самое увлекательное занятие 🙂 . Т.е. вам придется внести исправления в исходный вариант и выполнить все шаги с 1 по 5 заново.
Естественно такую работу можно автоматизировать и Phing отлично подойдет в этой ситуации.
Рассмотрим размещение файлов проекта.
css/ ie6styles.css styles.css images/ add.png ci_logo.jpg ....... lib/ jquery/ jquery.js tabs/ jquery-ui-personalized-1.5.2.js tooltip/ jquery.dimensions.js jquery.tooltip.js mainscripts.js index.html .htaccess build.xml
Здесь index.html
– единственная страница приложения, build.xml
– файл с задачами для Phing, .htaccess
– определяет в каком случае отправлять архивную версию js файла (подробнее здесь).
Переходим к build файлу. Т.к. он довольно объемный я буду приводить его по частям. Прежде всего, создаем проект и несколько свойств.
<project name="CIFormBuilder" default="deploy" basedir="."> <property name="DEPLOY_DIR" value="deploy" /> <property name="IMG_DIR" value="images" /> <property name="CSS_DIR" value="css" /> <property name="LIB_DIR" value="lib" /> <property name="GLOBAL_LIB_DIR" value="E:/www/libs" /> ............. </project>
Имя (name
) проекта выбираем произвольно. В атрибуте default
указываем название задачи, которая будет выполняться по умолчанию. basedir
– корневая папка проекта.
С помощью свойств (property
) мы объявили константы, которые в данном случае содержат имена папок.
Отдельно хочу остановиться на GLOBAL_LIB_DIR
. Это свойство содержит имя папки, в которой находятся PHP скрипты для сжатия файлов (подробнее о них ниже).
Как вы, наверное, догадались, все файлы проекта, предназначенные для размещения на сервере, мы скопируем в папку deploy
. Поэтому, прежде всего, создаем её.
<target name="prepare"> <echo msg="Removing old files..." /> <delete dir="${DEPLOY_DIR}" includeemptydirs="true" verbose="true" failonerror="true" /> <echo msg="Creating new dir..." /> <mkdir dir="${DEPLOY_DIR}" /> </target>
Эта задача сначала пытается удалить папку deploy
(операция delete
) вместе со всем содержимым, а затем с помощью mkdir
создает её заново. Таким образом, можно быть уверенным, что проект не будет содержать старых файлов.
Примечание. Операция echo
выводит произвольное сообщение. К сожалению, русские буквы отображаются не правильно.
Теперь копируем в эту папку все файлы кроме js и css. И сразу же исправляем теги <script>
в index.html
.
Разберем эту задачу подробнее. В первой строке мы задали название и указали, что эта задача зависит от prepare
. Т.е. при вызове copyfiles
всегда предварительно будет выполняться prepare
.
В строках 3-9 мы копируем файлы картинок. Для этого используется операция copy
, а список файлов, которые нужно скопировать, создается с помощью fileset
.
После этого копируем index.html
и .htaccess
. Здесь наибольший интерес представляет операция filterchain
. Она создает цепочку фильтров, через которые пропускаются файлы. В данном случае фильтр только один. В нем мы используем операцию замены по регулярному выражению replaceregexp
.
Первое правило (строка 18) удаляет все теги <script>
. Второе – находит тег </head>
и вставляет перед ним тег скрипт со ссылкой на объединенный js файл (global.js
). Третье – добавляет к именам css файлов суффикс -min
(сжатые версии файлов).
Таким образом, в папку deploy будет записан исправленный index.html.
Теперь займемся сжатием файлов.
В стандартную поставку Phing нужные нам задачи не входят. Поэтому нам понадобятся две библиотеки: JSMin и CSSMin. Размещаем их в папке, которую мы задали в свойстве GLOBAL_LIB_DIR
.
Напрямую работать с библиотеками Phing не может. Поэтому нужно создать соответствующие операции (tasks
). Каждая такая операция представляет собой PHP класс. Описать его в рамках одной статьи не реально, да и необходимости в этом нет. Для бибилиотеки JsMin я нашел готовое решение и в течении 15 минут переписал его для использования с CSSMin (в конце статьи приведена ссылка на архив с этими файлами).
Т.е. вам нужно просто распаковать этот архив в папку #PHING_DIR#/tasks
.
Для того, чтобы Phing узнал о том, что вы создали операцию необходимо в build файле использовать taskdef
. Выглядит это так:
<taskdef name="stubjsmin" classname="phing.tasks.my.stubJsMinTask" /> <taskdef name="stubcssmin" classname="phing.tasks.my.stubCssMinTask" />
Правила следующие. Имя должно совпадать с именем класса, но без окончания task. Имя класса (classname
) задает имя класса с учетом пути к нему (в качестве разделителя папок используется точка).
Теперь напишем задачу, которая будет сжимать js файлы. Сначала создадим набор файлов (fileset
).
<fileset id="srcjs" dir="${LIB_DIR}"> <include name="jquery/jquery.js" /> <include name="tabs/jquery-ui-personalized-1.5.2.js" /> <include name="tooltip/jquery.dimensions.js" /> <include name="tooltip/jquery.tooltip.js" /> <include name="mainscripts.js" /> </fileset>
Теперь сжимаем файлы.
<target name="minifyjs"> <echo msg="Minifying JavaScript files..." /> <stubjsmin jsminpath="${GLOBAL_LIB_DIR}/jsmin/jsmin-1.1.1.php" targetdir="${DEPLOY_DIR}/${LIB_DIR}"> <fileset refid="srcjs" /> </stubjsmin> </target>
Здесь нужно отметить два момента.
Первый – использование операции stubjsmin
. В её параметрах мы указываем размещение библиотеки JSmin и путь к папке, в которой будут размещены сжатые файлы. При записи сжатых файлов будет сохранена структура папок, в которых они размещены.
Второй – использование fileset
внутри stubjsmin
. Имя набора файлов указывается с помощью параметра refid
.
Сжатие CSS файлов выполняется точно также.
<target name="minifycss"> <echo msg="Minifying CSS files..." /> <stubcssmin cssminpath="${GLOBAL_LIB_DIR}/cssmin/cssmin_v.1.0.php" targetdir="${DEPLOY_DIR}/${CSS_DIR}"> <fileset dir="${CSS_DIR}" /> </stubcssmin> </target>
Теперь объединяем JavaScript файлы в один и удаляем сжатые версии js файлов.
<target name="combinejs" depends="minifyjs"> <echo msg="Creating single js file..." /> <append destFile="${DEPLOY_DIR}/${LIB_DIR}/global.js"> <filelist dir="${DEPLOY_DIR}/${LIB_DIR}" files="jquery/jquery-min.js, tabs/jquery-ui-personalized-1.5.2-min.js, tooltip/jquery.dimensions-min.js, tooltip/jquery.tooltip-min.js, mainscripts-min.js" /> </append> <echo msg="Deleting minified js files..." /> <delete includeemptydirs="true"> <fileset dir="${DEPLOY_DIR}/${LIB_DIR}"> <include name="**" /> <exclude name="global.js" /> </fileset> </delete> </target>
Операций всего две.
1) Объединение файлов (append
). Файл, в который будет записан результат, задается с помощью параметра destFile
, а файлы-источники – с помощью filelist
. Это важный момент, т.к. нам нужно объединять файлы в определенном порядке (сначала библиотеки, а потом функции, их использующие). А filelist
в отличие от fileset
сохраняет не только имена файлов, но и порядок в котором они перечислены.
2) Удаление (delete
) ненужных файлов. Здесь все просто. Удаляем все файлы и папки кроме global.js
(строка 11).
Теперь нам нужно заархивировать (gzip) файл global.js
.
Тут я столкнулся с одним непонятным моментом. В принципе, существует операция tar
, в параметрах которой можно указать метод сжатия – gzip
.
<tar destfile="archive_name.gz" basedir="." compression="gzip" />
Но почему-то браузеры не понимают созданные таким образом архивы.
В общем, разбираться было лень, и я решил задачу «в лоб». Использовал архиватор 7-zip. Для выполнения системных команд в Phing предусмотрена операция exec
.
Обратите внимание. Если путь к файлу содержит пробелы, то его нужно взять в кавычки, которые задаются с помощью эскейп последовательности "
. Параметр dir
указывает рабочую папку программы.
И завершающий штрих. Задача deploy
. Сама по себе она ничего не делает, но в её параметре depends перечислены задачи, которые должны быть выполнены при её вызове.
<target name="deploy" depends="copyfiles, gzipjs, minifycss"> </target>
Таким образом, если выполнить команду
e:\projectfolder> phing
в корневой папке проекта, то будут последовательно запущены все перечисленные в этой статье задачи.
Примечание. Если имя build файла не указано явно, то Phing ищет build.xml. Тоже самое касается задачи. По умолчанию выполняется та, которая указана в параметре default
(в данном случае – это deploy
).
Скачать
Архив с stubJsMinTask и stubCssMinTask.
Если возникли вопросы или замечания, не стесняемся, комментарии открыты 😉 .
До встречи!