PHP класс для сборки инклюдов в один файл

    по мотивам коммента mocksoul из темы PHP: Введение в Zend Framework

    Проблема


    В кратце, речь шла о том, что одним из недостатков фреймворка является его концепция «один класс – один файл». И хотя, с моей точки зрения, это не является недостатком архитектуры ZF, это является недостатком PHP. Проблемы начинаются когда PHP начинает инклюдить десятки и сотни файлов, да еще проверять их на once.

    Предложенное решение – сборка всех инклюдов в один файл. Класс, представленный ниже – не претендует на окончательный универсальный вариант решения проблемы. Он всего лишь демонстрирует один из методов сборки инклюдов. В данном случае он собирает только файлы фреймворка, игнорируя все остальные.

    Класс


    <?php
    class ZendMake
    {
    /**
    * Сборка заинклюденных файлов с удалением комментов, require/include, и проч
    *
    * param string $dest Абсолютное имя скомпиленного файла
    * param string $includes Массив файлов ZF для сборки
    * return array
    */
    public static function make($dest, $includes = null)
    {
    $includes = is_array($includes)? $includes: self::_getZendIncludes();

    // Удалить теги '<?php' '? >', комменты, пустые перводы строк и require/include[_once]
    $pattern[] ='%(^\<\?php|\?\>$)%m';
    $replacement[] = '';
    $pattern[] ='%/\*.*?\*/%sm';
    $replacement[] = '';
    $pattern[] ='%//.*$%m';
    $replacement[] = '';
    $pattern[] ='%(require_once|include_once|require|include) [("\'](.*?)[)"\'];%sm';
    $replacement[] = '';
    $pattern[] ='%(\n){2,}%';
    $replacement[] = "\n";

    $body = "<?php\n";
    foreach ($includes as $fname) {

    $body .= preg_replace($pattern, $replacement, file_get_contents($fname, true));
    }

    $size = file_put_contents($dest, $body);

    return array('includes' => $includes, 'compiledBody' => $body, 'compiledSize' => $size);
    }

    /**
    * Выбрать уникальные файлы ZF заинклюденные в проект
    *
    * return array
    */
    private static function _getZendIncludes()
    {
    $required = array();
    $included_files = get_included_files();
    foreach ($included_files as $fname) {
    if (!(strpos($fname, 'Zend') > 0) || (strstr($fname, __CLASS__. '.php'))) {
    continue;
    }

    $required[] = str_replace('\\', '/', substr($fname, strpos($fname, 'Zend'), strlen($fname)));

    // Все requires
    $pattern ='%(require_once|include_once|require|include) [("\'](.*?)[)"\'];%sm';
    preg_match_all($pattern, file_get_contents($fname), $pocket);

    $required = array_merge($pocket[2], $required);
    }

    return array_unique($required);
    }
    }


    Использование


    В конце работы скрипта вставить строки
    require 'ZendMake.php';
    $result = ZendMake::make($libDir. 'Zend.Make.php');
    Zend_Debug::dump($result['includes']);
    Zend_Debug::dump($result['compiledSize']);


    Результаты


    С помощью этого класса я собрал более 60 файлов ZF из моего проекта (модули Controller, Session, Auth, View, Uri, Db, Config, Version, Debug, Registry). Собранный файл занял 231 кб.

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

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

      0
      способ определения "что же у нас из зенда тащится в проект" явно далёк от идеала :).
      почему не использовали tokenizer?

      да и какая-то каша у вас в примере. То ZendCompiler, то ZendMake. Тем более нету ZendCompiler::make() :).
        0
        пока писал заметку, кое чего поменял.
        исправил
            0
            чтот то недоступен сервер. http://www.mocksoul.ru/

            все хотел посмотреть этот пример(еще из статьи про оптимизацию) - но скипт выдает тока пхп код.
              0
              ooops :) try now!
                0
                там вообще впринципе мало что понятно. Но можно выдрать extract_good_source() (убивает require, комментарии, whitespace левый... :)

                принцип - запускать через бразуер, чтобы был доступ к APC (статистика по загрузке файлов тащится оттуда). В проекте уже это портировано с APC на XCache - если надо, могу выложить (почти тоже самое в общем-то). Так вот. Смотрит в xcache/apc по файлам. Находит те у которого больше всгео запросов. Считает их как выполняющиеся "всегда". Затем у остальных файлов выстраивает процент - сколько типа грузится. Рисует табличку на экране и красным показывет те новые штуки, что он выбрал но в массиве autoloadfiles не обнаружил. Берём файлик и руками пишем в autoloadfiles. Перезапускаем скрипт с ?SAVEFILE и он всё соберёт. Just for example.
            0
            посмотрел на ваш скрипт.
            не использовал tokenizer потому, что не знал о нем )) но, на мой взгляд регулярками нагляднее и короче)

            способ определения файлов для сборки может быть совершенно любой. Можно собирать отдельно ZF и отдельно файлы приложения. Можно ZF помодульно, пофайлово или в зависимости от APC-попаданий. Можно все вместе. По вкусу.
            0
            Смысл есть. Спасибо за идею и реализацию.
              0
              Уупс. Предложенный класс неправильно обрабатывает комментарии, точнее — принимает за комментарии то, что ими не является. Например, Zend_Locale_Data строка 162:
              if ($newpath != '//ldml') {
              после // код обрезается.
                0
                да с комментариями действительно проблемы. В Zend_CAche вырезаются строки типа $this->method('php://');
                Нужно либо вырезать комменты с начала строки, либо действительно исползьовать токенайзер.
                Мои реглярки после исправления выглядят так:

                $pattern[] = '%(^\$)%';
                $replacement[] = '';
                $pattern[] = '%^\s*#.*%m';
                $replacement[] = '';
                $pattern[] = '%/\*.*?\*/%sm';
                $replacement[] = '';
                $pattern[] = '%^\s*//.*$%m';
                $replacement[] = '';
                $pattern[] = '%(require_once|include_once|require|include) [("\'](.*?)[)"\'];%';
                $replacement[] = '';
                $pattern[] ='%^\s+$%sm';
                $replacement[] = '';
                $pattern[] ='%(\n){2,}%';
                $replacement[] = "\n";
                  0
                  да и в Zend_Cache есть еще проблема. Там подключаются Zend_Backend и Zend_Frontend в зависимости от переданных параметров. Вырезать их подключение автоматически не удастся. Остается только подключать их как обычно и исключать из сборки
                0
                Здравствуйте. :) Писал в прошлом такую вещь на основе tokenizer, в итоге, не понравилось. Советую взглянуть в сторону PHC, там есть очень интересный туториал: http://www.phpcompiler.org/doc/tutorial6…, на основе которого можно построить такую систему. Жаль, что все преимущества ручного труда начисто элиминируются акселераторами. По крайней мере, у меня при включенном Xcache с отключенной проверкой модификаций разницы между производительностью собранного и обычного проекта нет.
                  0
                  у меня разница при xcache даже с выключенной проверкой модификации файлов - трёхкратная. Инклудится примерно 140 файлов (т.е. собираются в один).
                    0
                    я заметил интересную вещь: чем больше файлов инклюдится, тем бесполезней становится кешер (в моем случае APC). Время выполнения увеличивается, но не критично.
                    0
                    Разница есть! У меня к примеру в 3-4 раза производительность стала выше!
                    0
                    Ни у кого случаем нет актуальной версии?

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

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