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

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

    Комментарии 41

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

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

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

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

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

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

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

                                В принципе непонятно зачем нужно генерить какой-то файл, если достаточно только структуры директорий и включений (замечу, что автоладер использовать тоже не нужно) — это даёт все необходимые зависимости. В итоге имеем подход для сборки любых больших библиотек (взятых из того же PEAR). Это более серьёзный результат.
                                  +1
                                  Короче, это 2 разных подхода: мой позволяет получить фаил легче и без лишнего, ваш — собрать любую библиотеку полностью без лишних телодвижений. Хватит холивара 8)
                                    +1
                                    Ну хватит, так хватит… да и поздно уже =)
                        0
                        Ждем :)
                        0
                        Делаю проще:

                        function __autoload($classname){
                        $name=str_replace("_", "/", $classname).".php";
                        file_put_contents(dirname(__FILE__)."/includes.log", "$name\n", FILE_APPEND);
                        require_once($name);}

                        А для сборки скрипт типа такого:

                        if(!defined('T_ML_COMMENT'))
                        define('T_ML_COMMENT', T_COMMENT);
                        else
                        define('T_DOC_COMMENT', T_ML_COMMENT);

                        function filter_php_source($fname=""){
                        $content=file_get_contents($fname != ""? $fname: «php://stdin»);
                        $open=false; $l=0;
                        foreach(token_get_all($content) as $token){
                        if(is_string($token))
                        print $token;
                        else{
                        list($id, $text)=$token;
                        switch($id){
                        case T_COMMENT:
                        case T_DOC_COMMENT:
                        case T_ML_COMMENT:
                        break;

                        case T_INCLUDE_ONCE:
                        case T_REQUIRE:
                        case T_REQUIRE_ONCE:
                        break;

                        case T_OPEN_TAG:
                        $open=true;
                        print $text;
                        break;
                        case T_CLOSE_TAG:
                        $open=false;
                        print $text;
                        break;
                        default:
                        print $text;
                        break;}}}
                        if($open) print " ?>";}

                        $path=".";
                        isset($_SERVER[«argv»][1]) and $path=$_SERVER[«argv»][1];
                        is_dir($path) or die(«Path $path — not exists\n»);

                        $files=file($path."/includes.log");
                        $files=array_unique($files);
                        file_put_contents(«errors.log», "");
                        file_put_contents(«includes.log», implode("", $files));

                        foreach($files as $fname) if(($fname=trim($fname)) != ""){
                        $fname="$path/includes/".$fname;
                        if(!file_exists($fname))
                        file_put_contents(«errors.log», $fname."\n", FILE_APPEND);
                        filter_php_source($fname);}
                          0
                          у меня не прокатил __autoload за счет структуры директорий с плагинами
                          0
                          Господа, с ZF сталкиваюсь не часто, но приходится… Я точно помню что для ZF был олнайн сервис, где ты указываешь калссы которые тебе нужны, а сервис сам определяет зависимости и выдает тебе архив с минимальным набором классов.
                          Кто ни будь может подсказать урл? а то я находил, даже делал мини сборку которую сейчас успешно юзаю в проекте… но урл вот забыл и как то нагуглить не могу :)
                            0
                            Он не собирает классы в один файл, а здесь обсуждается именно эта проблема.
                              +2
                              Я понимаю и спрашиваю тут, потому что собрались люди явно в теме и есть шанс получить ответ. прошу прощения за офтопик… :) но если ктото все же знает урл этого сервиса, я буду очень благодарен. Или в личку, если не сложно, дабы не засорять топик.
                              0
                              Пожалуйста: epic.codeutopia.net/pack/
                                0
                                Спасибо большое — думаю не только мне пригодиться. Кстати использую как раз Kohana упомянутой выше.
                            • НЛО прилетело и опубликовало эту надпись здесь
                                0
                                ам… код без подсветки читать не удобно, но мне кажется здесь отсутствует самое главное — проверка файлов на актуальность. собственно из-за этого и желательно сливать в один файл, т.к. проверка времени последнего обновления и становится бутылочным горлышком.
                                Решение гораздо проще — отключить проверки кэшера опкода, всё равно это для продакшина где обнавления не частые.

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

                                Самое читаемое