Сборка 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. Моя жизнь изменилась к лучшему!

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