В этой статье я хочу рассказать об использовании 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.
Если возникли вопросы или замечания, не стесняемся, комментарии открыты 😉 .
До встречи!


