Использование Phing для сборки web приложений

Владимир | | Phing, Web разработка.

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


	

Обратите внимание. Если путь к файлу содержит пробелы, то его нужно взять в кавычки, которые задаются с помощью эскейп последовательности &quot;. Параметр dir указывает рабочую папку программы.

И завершающий штрих. Задача deploy. Сама по себе она ничего не делает, но в её параметре depends перечислены задачи, которые должны быть выполнены при её вызове.

<target name="deploy" depends="copyfiles, gzipjs, minifycss">
</target>

Таким образом, если выполнить команду

e:\projectfolder> phing

в корневой папке проекта, то будут последовательно запущены все перечисленные в этой статье задачи.
Примечание. Если имя build файла не указано явно, то Phing ищет build.xml. Тоже самое касается задачи. По умолчанию выполняется та, которая указана в параметре default (в данном случае – это deploy).

Скачать

Архив с stubJsMinTask и stubCssMinTask.

Если возникли вопросы или замечания, не стесняемся, комментарии открыты 😉 .

До встречи!