Zend Framework

индекс
75,79

Сборка Zend Framework

Для ускорения Zend Framework очень действенен такой финт: собираем все классы, которые нам нужны, в один фаил, а потом включаем eAccelerator и инклудим его в самом начале. Один фаил + еАкселератор круче, чем много фаилов.

Под катом — рассказ, как я это сделал. Это не самое умное, лучшее и красивое решение, поэтому я рад послушать ваши советы и замечания. В общем, топик этот — ради ваших советов и замечаний — тех, что по делу, а не по поводу всякой херни типа орфографических ошибок. Спасибо!



Я сначала сам с собой договорился, что буду использовать Zend_Loader (точнее, Zend_Loader_Autoloader — ну что-то такое, чего там использует Zend_Application), а не грузить фаилы инклудами.

После чего я решил действовать так:
  1. Собираем все волшебные фаилы, которые нам нужны, в APPLICATION_PATH. '/../data/files.txt'
  2. Открываем APPLICATION_PATH. '/../data/files.txt' и собираем их в один волшебный APPLICATION_PATH. '/../data/HotPlug.php', по дороге вырезая инклуды и комментарии


Итак, первое: сбор фаилов в data.txt.

Оказалось, что Zend_Loader_PluginLoader умеет составлять такой список сам (но немного криво), а Zend_Loader_Autoloader — не умеет. Но это не беда. Тут стоило бы унаследовать автолоадер и сделать все по честному, но мне было впадлу и я похачил сам ZF. Благо, на продакшн все равно не нужно будет лить хаченую версию: HotPlug.php можно собирать и дома:
/* library/Zend/Loader.php */

public static function loadClass($class, $dirs = null)
    {
        if (class_exists($class, false) || interface_exists($class, false)) {
            return;
        }

        if ((null !== $dirs) && !is_string($dirs) && !is_array($dirs)) {
            //require_once 'Zend/Exception.php';
            throw new Zend_Exception('Directory argument must be a string or an array');
        }

        // autodiscover the path from the class name
        $file = str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
        if (!empty($dirs)) {
            // use the autodiscovered path
            $dirPath = dirname($file);
            if (is_string($dirs)) {
                $dirs = explode(PATH_SEPARATOR, $dirs);
            }
            foreach ($dirs as $key => $dir) {
                if ($dir == '.') {
                    $dirs[$key] = $dirPath;
                } else {
                    $dir = rtrim($dir, '\\/');
                    $dirs[$key] = $dir . DIRECTORY_SEPARATOR . $dirPath;
                }
            }
            $file = basename($file);
            self::loadFile($file, $dirs, true);
        } else {
            self::_securityCheck($file);
            include $file;
        }
// добавляем отсюда		
		$files = file( APPLICATION_PATH . '/../data/files.txt' );
		$files[] = $file;
		$files = array_unique($files);
		file_put_contents( APPLICATION_PATH . '/../data/files.txt', implode("\n", $files) );
// досюда
        if (!class_exists($class, false) && !interface_exists($class, false)) {
            //require_once 'Zend/Exception.php';
            throw new Zend_Exception("File \"$file\" does not exist or class \"$class\" was not found in the file");
        }
    }


Я открыл свой сайт и побегал по нему некоторое время. Сайт мне нравился, а разрастающийся APPLICATION_PATH. '/../data/files.txt' — нет. Но это ничего страшного, подумал я, и набросал скрипт для сборки. Опять же, стоило сделать его умно, красиво, объектно-ориентированно и консольно, но мне было впадлу и я тупо создал combine.php в /htdocs/

<?
	$skip = array(
		T_COMMENT, T_OPEN_TAG, T_CLOSE_TAG, T_DOC_COMMENT, T_ML_COMMENT // нафиг комменты из HotPlug! и всякие <? и ?> тоже нафиг
	);
	
	$dir = "d:\work\нескажуназвание\library\\";
	
	$files = file('d:\work\нескажуназвание\app\data\files.txt');	
	
	$res = '<?';
	
	foreach ($files as $file) {
		if (substr(trim($file), -4) != '.php')
			$file = str_replace('_', '\\', trim($file)) . ".php"; // если там имя класса, а не фаила - переделываем
			
		if (is_file($fileName = trim($dir . $file))) {
			$res .= "\n/* $file */\n";
			$tokens = token_get_all(file_get_contents($fileName));
			
			$was_require_once = 0;
			$was_shit_require_once = 0;
			
			foreach($tokens as $token) {
				if (is_array($token)) {
					if (in_array($token[0], $skip))
						continue;
						
					if ($token[0] == T_WHITESPACE) {
						$res .= ' '; // поменьше места на всякие табы
						continue;
					}
				
					if ($was_require_once) {
						if ($token[0] == T_CONSTANT_ENCAPSED_STRING) { // скипаем require_once, после которых идет строка в кавычках. если потом идет что-то вроде $file - скипать не надо! тоже достаточно грязный метод, стоило бы подумать
							$was_shit_require_once = 1;
						} else {
							$res .= 'require_once ' . $token[1];
						}
							
						$was_require_once = 0;
						continue;
					}
					
					if ($token[0] == T_REQUIRE_ONCE) {
						$was_require_once = 1;
					} else {
						$res .= $token[1];
					}
					
					
				} else {
					if (!$was_shit_require_once) // чтобы ";" после удаленных require_once удалять
						$res .= $token; 
						
					$was_shit_require_once = 0;
				}
			}
			$res .= "\n";
		}
	}
	
	file_put_contents("d:\work\нескажуназвание\app\data\HotPlug.php", $res);



После чего я радостно заинклудил этот фаил прямо в индексе. И знаете, что оно мне сказало? Что ему не хватает кучи классов. «Wtf?!» — подумал я и начал ловить недостающие классы, дописывая их ручками в files.txt, а потом пересобирая HotPlug.php :)

И тут до меня дошло, что Loader не знал о тех фаилах, которые инклудятся require_once в начале зендовских классов, а поэтому они не собираются. Но они и не инклудятся %)

Пришлось написать еще один скрипт, который комментирует все эти require_once:

<?
function getDirectoryTree( $outerDir ){ 
    $dirs = array_diff( scandir( $outerDir ), Array( ".", ".." ) ); 
    $dir_array = Array(); 
    foreach( $dirs as $d ){ 
        if( is_dir($outerDir."/".$d) ) $dir_array[ $d ] = getDirectoryTree( $outerDir."/".$d ); 
        else $dir_array[ $d ] = $d; 
    } 
    return $dir_array; 
} 

$dirs = getDirectoryTree("d:\work\пыщь\library\Zend");

function gotcha($fname, $key, $dir) {
	if (is_array($fname)) {
		array_walk($fname, 'gotcha', $dir . DIRECTORY_SEPARATOR . $key);
		return;
	}
	
    $fname = $dir . DIRECTORY_SEPARATOR . $fname;
	file_put_contents( $fname, preg_replace("/require_once\\s+\'Zend/", "//require_once \'Zend", file_get_contents( $fname )) ); // стоило бы написать /(require|include)_once\\s+(\'|\")/ или типа того, но мне было - ну вы догадались - впадлу тестировать этот прег и я запускал скрипт просто 4 раза подряд. Благо, после этого он больше не нужен вообще :)
}

array_walk($dirs, 'gotcha', "d:\work\пыщь\library\Zend");


Теперь я пересобрал HotPlug.php и все было просто чудесно!

— Шаг второй

Помимо Zend_Loader_Autoloader, инклудами ведает еще и Zend_Loader_PluginLoader, и инклудит он сам, без Zend_Loader'а. Зато он умеет собирать список. Вот так:

// Где-нибудь в Bootstrap.php, или - у меня - в /library/R00/Bootstrap.php, от которого наследуются бутстрапы моих проектов
Zend_Loader_PluginLoader::setIncludeFileCache( APPLICATION_PATH . '/../data/cache.php');


Этот cache.php после пары запусков содержит много include_once'сов, которые нужны, чтобы автоматом грузить эти плагины. Я так и не понял, какой от этого прирост и решил попросить его пихать эти фаилы в мой files.txt

Zend_Loader_PluginLoader::setIncludeFileCache( APPLICATION_PATH . '/../data/files.txt');


Чтобы не парсить строку include_once '...'; я похачил (какой я гад все-таки, а!) Zend/Loader/PluginLoader.php
protected static function _appendIncFile($incFile)
    {
        if (!file_exists(self::$_includeFileCache)) {
            $file = ''; // раз изменение
        } else {
            $file = file_get_contents(self::$_includeFileCache);
        }
        if (!strstr($file, $incFile)) {
            $file .= "\n$incFile\n"; // два изменение
            file_put_contents(self::$_includeFileCache, $file);
        }
    } 

Опять же, на продакшн это посылать не надо, так что ничего страшного. А дома — ну дома, пусть будет дома. Надо будет обновить ZF, а потом снова пересобрать HotPlug.php — ну похачу еще разок, или там сделаю все по человечески как-нибудь.

Теперь я еще побегал по сайту, собирая plugin-ы, а потом пересобрал HotPlug.php. Моя жизнь изменилась к лучшему!

Теперь — расскажите о своих бест практицес для решения подобной проблемы :) И давайте сделаем нормальное, разумное ОО-решение без лишних хаков?
+4
24 октября 2009, 17:59
31

комментарии (41)

0
BarsMonster #
Цифры в студию, насколько это ускоряет работу? :-)
С eAccelerator-ом в обоих случаях конечно.
0
va1en0k #
dklab.ru/chicken/nablas/50.html тут есть. Котерову я склонен верить :)

Я и сам чутка потестировал, но это для себя — даже не документировал. У меня числа чуть похуже, но это скорее из-за того что у меня там все-таки разное еще и происходит, а не только классы грузятся. Ну и запускаю я это хрен знает где на очень странно настроенной машине с кучей фигни. Как это все попадет на нормальный сервер — буду уже хвастаться или плакаться :)

По памяти: ab выдавал где-то 0.8 с Хотплагом, чуть меньше 0.1 без. Хотя может и вру. Давайте лучше Котеровским числам верить
0
va1en0k #
| 0.8 с Хотплагом, чуть меньше 0.1 без

наоборот, разумеется %)
+4
homm #
Когда-то давно делал для подобного топика:

image
0
va1en0k #
Похоже, такой топик уже есть? Неприятно :)
0
homm #
Был. Я тогда не знал о тукенайзере в пхп, и require вырезал весьма криво, а переделывать не захотел. Кроме того, изучения Zend Framework после этого забросил, так и не написав на нем ни одного проекта. Скриптина лежит здесь, можешь использовать любую понравившуюся часть :)
0
va1en0k #
Спасибо
Я сначала думал вырезать через Zend_Codegenerator, но после исправления в нем второй ошибки решил перейти к дедовским методам )
0
mironoff #
что такое «тукенайзере в пхп»?
+3
va1en0k #
token_get_all()
0
fog #
А почему с ZF не срослось? Перешел на другой фреймворк, или на другой язык?
0
homm #
Сначала на другой фреймворк (Kohana) и уже долгое время есть желание перейти на другой язык (Python+Django).
0
fog #
А чем Kohana больше понравился?
0
homm #
Зацепило низким стартом. Первая строчка, которую ты пишешь уже служит твоим целям, а не целям запуска фреймворка. Кстати, сами создатели коханы говорят о зенде: «Фреймвор задумывался как инструмент для быстрой разработки, но им не является». В какой-то степени я согласен, много движений уходит на связывание компонентов. В кохане расширяемость решена немного иначе.
0
Fesor #
Kohana и мне нравилась, но сейчас как-то больше нравится YII…
0
Shumkov #
Простите а как вы получили такие результаты? У нас прирост производительности получился очень незначительный.
+1
DonRamon #
Что-то Ваше решение напоминает наколенкособранное — «похачил», «пофиксил». Не пробовали, коль уж занялись слиянием более аккуратно пропарсить структуру «без хаков» и «фиксов»?
+1
va1en0k #
Я предлагаю так и сделать, просто может кто очень хочет этим заняться, или уже сделал, или есть готовое решение?
Еще там есть несколько фич, которые очень сложно сделать. Например, как избавиться от хака Zend_Loader? Унаследовать его? А как попросить _Autoloader использовать именно наш класс? И так далее.
В этом всём надо разбираться, а возможно, что ZF такие штуки пока не поддерживает. Чем больше я его использую, тем он оказывается недоделаннее — вот только что зааппрувил баг к ним в трекер :)

Поэтому я и сделал на хаках. Да, быстро и грязно. Но хватит один раз собрать HotPlug.php и всё, можно больше не париться, так что эти хаки неопасны и их можно в любой момент убрать, не опасаясь, что программа сломается.
0
DonRamon #
Ну, товарищ, не пытайтесь найти всегда чьи-либо готовые решения, иногда приходится писать самому и руками. Если Вы рассказываете про решение, то постарайтесь сделать его универсальнее. Пока я вижу только может, а вдруг, а что если, может унаследовать и так далее. Иначе, Ваша статья рассказывает лишь о том, что я молодец — решил поставленную передо мной задачу. Хабр же, на мой взгляд, предполагает несколько другой масштаб предоставления информации — я нашёл решение, готов поделиться. Ваше решение грязное, увы и ах.
0
va1en0k #
Окей, теперь вы можете вполне обоснованно нажать на стрелочку вниз :)
–6
egorinsk #
Еще одна причина не использовать ZF, слишком громоздкий, корявый и универсальный фреймворк, а судя по объему, авторы первоначально принесли свои идеи с явовских монстров.
–5
chetzof #
Троллить надо с умом, учись сопляк у старших.
–1
egorinsk #
Сам ты тролль, раз тебе всюду тролли видятся. ZF и правда громоздкий (не лень было отдельно делать обертки даже для функций типа include/is_readable и т д), и излишне универсальный (т к все компоненты могут использоваься отдельно или быть заменены на другие).

Хотя зачем объяснять это троллю, что подходы, применяемые к большим серверным явовским или другим долгоживущим приложениям, непрменимы в php хотя бы из-за разного способа взаимодействия приложения с веб-сервером.
0
kykapa4a #
Занимаюсь этой же задачей в настоящий момент. Решение подобное предложенному вами исключил по причине отсутствия универсальности: надо создавать файл со списками классов, а это как-то криво. Добился того, что собирается только Zend Framework со всеми зависимостями и покомпонентно (иначе полная сборка существенно увеличивает расход памяти, а использоваться могут не все классы). Именно такой вариант считаю правильной сборкой в один файл, т.к. даже при обновлении надо будет всего лишь перезапустить скрипт с таким же набором опций. Готового ничего не находил (поэтому и решил взяться за задачу). Скоро причешу код, добавлю скрипту управление через консоль и выложу здесь в статье. Надеюсь кому-то будет полезно.
0
va1en0k #
То есть вы собираете абсолютно все классы ZF в один фаил? Даже всякие там Zend_Console_Getopt?
0
kykapa4a #
Нет. Что-то типа такого: pack.php --disable-zend-amf --disable-zend-console. В итоге имеем собранный фреймворк только с нужными библиотеками.
0
va1en0k #
… Но со всеми адаптерами к БД, например? :) Или вы перечисляете все фаилы которые не нужны?
0
kykapa4a #
Пока я реализовал отключение только по папкам. Адаптеры подключатся все сразу, но не вижу именно в этом большой проблемы. В конце концов намного важнее отлючить неиспользуемые библиотеки, чем биться за каждый файл.
0
va1en0k #
Я бы не сказал. Там, скажем, хелперов к виду больше, чем фаилов в некоторых библиотеках :)
Нет, мне ваше решение совсем не нравится. Чем плох фаил со списком классов? Возможно, метод его создания кривоват, но сама практика не так плоха. Тем более, похожим образом работает Zend_Loader_PluginLoader
0
kykapa4a #
По поводу хэлперов вида: более 80% используются так или иначе на большом проекте (обеспечивается за счёт Zend_Form).

В принципе непонятно зачем нужно генерить какой-то файл, если достаточно только структуры директорий и включений (замечу, что автоладер использовать тоже не нужно) — это даёт все необходимые зависимости. В итоге имеем подход для сборки любых больших библиотек (взятых из того же PEAR). Это более серьёзный результат.
+1
va1en0k #
Короче, это 2 разных подхода: мой позволяет получить фаил легче и без лишнего, ваш — собрать любую библиотеку полностью без лишних телодвижений. Хватит холивара 8)
+1
kykapa4a #
Ну хватит, так хватит… да и поздно уже =)
0
bb5000 #
Ждем :)
НЛО прилетело и опубликовало эту надпись здесь
0
va1en0k #
у меня не прокатил __autoload за счет структуры директорий с плагинами
0
Tonik #
Господа, с ZF сталкиваюсь не часто, но приходится… Я точно помню что для ZF был олнайн сервис, где ты указываешь калссы которые тебе нужны, а сервис сам определяет зависимости и выдает тебе архив с минимальным набором классов.
Кто ни будь может подсказать урл? а то я находил, даже делал мини сборку которую сейчас успешно юзаю в проекте… но урл вот забыл и как то нагуглить не могу :)
0
kykapa4a #
Он не собирает классы в один файл, а здесь обсуждается именно эта проблема.
+2
Tonik #
Я понимаю и спрашиваю тут, потому что собрались люди явно в теме и есть шанс получить ответ. прошу прощения за офтопик… :) но если ктото все же знает урл этого сервиса, я буду очень благодарен. Или в личку, если не сложно, дабы не засорять топик.
0
xaxaTyH #
Пожалуйста: epic.codeutopia.net/pack/
0
Tonik #
Спасибо большое — думаю не только мне пригодиться. Кстати использую как раз Kohana упомянутой выше.
НЛО прилетело и опубликовало эту надпись здесь
0
AmdY #
ам… код без подсветки читать не удобно, но мне кажется здесь отсутствует самое главное — проверка файлов на актуальность. собственно из-за этого и желательно сливать в один файл, т.к. проверка времени последнего обновления и становится бутылочным горлышком.
Решение гораздо проще — отключить проверки кэшера опкода, всё равно это для продакшина где обнавления не частые.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.