Как написать задачу для Phing

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

Тема работы с Phing уже не однократно поднималась в этом блоге. Переоценить значение этого инструмента очень сложно. Но всегда наступает момент, когда стандартных возможностей становиться недостаточно. В такой ситуации вы можете сами написать нужные функции и оформить их в виде задач.

В этой статье я покажу пример создания такой задачи.

Примечание. Я предполагаю, что вы хотя бы в общих чертах знакомы с Phing и знаете как его установить и написать простейший build файл. Если нет, советую сначала почитать эти статьи: Программирование на PHP. Избавляемся от рутинных операций с помощью Phing и Использование Phing для сборки web приложений.

Допустим, нам нужно изменять кодировку файлов. На PHP сделать это можно, например, с помощью функции mb_convert_encoding. В качестве параметров она принимает:
— исходный текст;
— нужную кодировку;
— исходную кодировку.
А после выполнения возвращает перекодированный текст.

Т.е. можно написать элементарный PHP скрипт, который будет изменять кодировки указанных файлов.

Но если нужно выполнять эту операцию на каком-то промежуточном этапе при построении проекта, то пользоваться таким скриптом будет не очень удобно. Придется либо запускать его каждый раз вручную, либо писать еще один скрипт, который будет запускать перекодировщик.

С другой стороны, с помощью Phing мы можем запустить несколько операций нажатием одной кнопки. Почему бы не добавить сюда и перекодировку?

Итак, приступим.

Шаг 1. Составляем перечень исходных данных.

Исходя из перечня параметров функции mb_convert_encoding нам потребуются:
— название исходной кодировки файлов;
— название кодировки, в которую мы хотим преобразовать файлы;
— список файлов;
— папку, в которую будем складывать перекодированные файл.

Теперь взгляните на пример использования нашей задачи.

<taskdef name="convencoding" classname="phing.tasks.my.convEncodingTask" />

<target name="convertencoding">
	<convencoding fromencoding="windows-1251" toencoding="utf-8"
		targetdir="new_dir">
		<fileset dir="src_dir" />
	</convencoding>
</target>

Разберем его подробнее.

В первой строке мы подключаем нашу задачу. Конечно, на данный момент мы её еще не написали, но уже сейчас нужно определиться с её размещением и названием (задается в атрибуте classname).

В данном случае, она должна находиться в папке phing.tasks.my. Тут phing указывает на папку в которую он установлен, а все точки нужно заменить на «\» (или «/»).

После этого мы создаем новую операцию (target) и в неё добавляем нашу задачу (convencoding) (строки 4-7).
В её атрибутах указываем три параметра:
fromencoding — исходная кодировка;
toencoding — кодировка, которую нужно получить;
targetdir — папка, в которую будем складывать перекодированные файлы.

И добавляем тег fileset, в котором определяем список исходных файлов.

Шаг 2. Создаем скрипт задачи.

Для этого заходим в папку с phing/tasks/, создаем в ней папку my, а в ней файл convEncodingTask.php. Этот файл будет содержать весь код нашей задачи. Его название составляется из названия задачи и слова Task.

Этот файл должен содержать класс, название которого совпадает с именем файла и, кроме того, он должен быть потомком класса Task.

Объявляем класс.

require_once 'phing/Task.php';

class convEncodingTask extends Task {

}

Шаг 3. Работа с атрибутами.

Как я уже говорил, все исходные данные для задач Phing мы передаем в атрибутах. Поэтому объявляем свойства для каждого атрибута (область видимости — protected).

protected $targetDir;
protected $fromEncoding;
protected $toEncoding;
protected $filesets    = array();
protected $failonerror = false;

Названия свойств должны совпадать с названиями атрибутов.

Обратите внимание на свойство $filesets, которому мы присвоили пустой массив. В нем будет храниться список файлов, который мы задали с помощью задачи fileset (строка 7 в первом листинге).

Последнее свойство используется для того, чтобы разработчик build файла мог указать нужно ли продолжать выполнение при возникновении ошибки в этой задаче.

Каждый атрибут с исходными данными должен иметь соответствующее свойство и метод, который его устанавливает.

Тут важно понимать, что Phing вызывает эти методы сам, т.е. нам нужно их только объявить.

public function setTargetDir($targetDir) {
	$this->targetDir = $targetDir;
}

public function setFromEncoding($fromEncoding) {
	$this->fromEncoding = $fromEncoding;
}

public function setToEncoding($toEncoding) {
	$this->toEncoding = $toEncoding;
}

public function setFailonerror($value) {
	$this->failonerror = $value;
}

Как видите, эти методы просто устанавливают значения соответствующих свойств. Их имена образуются добавлением приставки set к имени свойства.

Шаг 4. Добавляем поддержку списка файлов.

Отдельных пояснений требует метод установки списка файлов. Называется он createFileSet.

В нём мы создаем объект типа FileSet, сохраняем его в массиве $this->filesets. Этот метод должен возвратить сохраненный объект.

public function createFileSet() {
	$num = array_push($this->filesets, new FileSet());
	return $this->filesets[$num - 1];
}

Еще раз хочу напомнить. Напрямую к этим методам мы обращаться не будем. Это сделает Phing без нашего участия. Поэтому очень важно правильно записать имена методов и проследить за типом возвращаемых значений.

Шаг 5. Инициализация

Тут нам нужно просто объявить метод init

public function init() {
    return true;
}

Шаг 6. Пишем основной метод.

В нём мы будем выполнять изменение кодировок файлов. На момент его запуска Phing установит значения всех свойств, т.е. мы можем ими спокойно пользоваться (внутри этого метода).

Теперь взгляните на код метода.

public function main() {
	//проверяем, установлен ли модуль Multibyte String
	if (function_exists("mb_convert_encoding")) {
		//начинаем обработку
		//(у нас может быть несколько наборов файлов FileSet)
		foreach ($this->filesets as $fs) {
			try {
				//получаем массив со списком исходных файлов
				$files = $fs->getDirectoryScanner($this->project)->getIncludedFiles();
				$fullPath = realpath($fs->getDir($this->project));
				//изменяем кодировку каждого файла
				foreach ($files as $file) {
					$this->log('Converting file ' . $file);
					//формируем путь к конвертированному файлу
					$target = $this->targetDir . '/' . str_replace($fullPath, '', $file);
					if (file_exists(dirname($target)) == false) {
						mkdir(dirname($target), 0700, true);
					}
					//конвертируем файл
					file_put_contents($target, mb_convert_encoding(file_get_contents($fullPath . '/' . $file), $this->toEncoding, $this->fromEncoding));
				}
			} catch (BuildException $be) {
				// папка не существует или доступ к ней закрыт
				if ($this->failonerror) {
					throw $be;
				} else {
					$this->log($be->getMessage(), $this->quiet ? Project::MSG_VERBOSE : Project::MSG_WARN);
				}
			}
		}
	}
	else {
		//ошибка (недоступна функция mb_convert_encoding)
		$be = new BuildException('No Multibyte String support');
		if ($this->failonerror) {
			throw $be;
		}
		else {
			//если нужно продолжать выполнение build файла, несмотрая на ошибку, просто пишем в лог
			$this->log($be->getMessage(), $this->quiet ? Project::MSG_VERBOSE : Project::MSG_WARN);
		}
	}
}

Больше чем вы ожидали? Давайте посмотрим внимательнее. Большую часть здесь занимают: работа со списком файлов, различные проверки и обработка ошибок.

Рассмотрим все по порядку.

В строке 3 мы проверяем, доступна ли функция mb_convert_encoding. Если нет, то создаем исключение (BuildException) и в зависимости от значения свойства failonerror либо отправляем его, либо пишем сообщение в лог.

Если функция доступна, начинаем обработку файлов.

Как вы помните, список файлов хранится в свойстве filesets, точнее в нем хранится массив объектов FileSet, каждый из которых содержит список файлов.

Поэтому для обработки всех файлов нам нужно использовать два вложенных цикла. Внешний (строка 6) проходит по всем объектам FileSet, а внутренний (строка 12) – по всем файлам в этих объектах.

Вы, наверное, заметили, что здесь мы используем свойство, которого не объявляли ($this->project). Это свойство содержит объект с данными проекта и создается автоматически. В данном случае мы его используем при получении имен файлов.

Кроме того, мы используем несколько методов объекта FileSet. Например, getDirectoryScanner (объявлен в файле phing\types\AbstractFileSet.php). Подробно я на них останавливаться не буду, названия говорят сами за себя. В любом случае, они нам нужны только для того, чтобы получить список исходных файлов. И в большинстве задач этот код (строки 9-10) меняться не будет.

Во внутреннем цикле (строки 12-21) мы формируем пути к перекодированным файлам и изменяем кодировку (строка 20).

Обратите внимание на строку 13. В ней мы записываем в лог имена всех переименованных файлов. Эти записи очень помогают при поиске ошибок в build файлах.

В строках 22-29 мы перехватываем и обрабатываем возможные исключения.

Заключение.

Как видите, создание собственно задачи для Phing не намного сложнее разработки обычного PHP скрипта. Нужно только знать несколько правил и особенностей.

По большому счету, написать скрипт с аналогичной функциональностью было бы сложнее, т.к. пришлось бы писать парсер для xml файла или придумывать другой способ ввода информации.

Скачать.

Если вы хотите поэкспериментировать – качайте архив с этой задачей. В нем же находится небольшая инструкция по установке.

Интересно почитать.

Учимся профессионально водить с авто инструктором.
Рассказать о чем-то всему миру можно с помощью бесплатные объявления казахстан.
Позаботиться о своем здоровье вам помогут в клиника Марыныч.