We increase the performance of Zend Framework by collecting its classes in a single file.

Each time you run a link and ZendFramwork processes it on the server, there are unpleasant performance overheads when the PHP interpreter compiles the executing code.

PHP certainly knows how to cache the opcode in memory using APC, Memcached, etc. But before you take the opcode from memory, you go to the hard drive in order to make sure the date of the last change was updated. When there are few files, this happens unnoticed. When there are a lot of them, a decrease in productivity begins to become noticeable.

(In APC, of ​​course, you can configure the PHP interpreter to not check the date of the files, but Apache must be restarted with any changes to the files, which is very inconvenient when developing).

Class collectors have already met on the network, but they have not always correctly collected what is needed.

Yes, this topic has already been raised repeatedly, but I have not found a single script that would correctly connect the Zend_Controller_Router_Route_Abstract and Zend_Controller_Router_Route_Chain classes.



Just go over the files and assemble them into one, it doesn’t work out. In the circle, inheritance from classes and interfaces is used. And for the correct compilation, it is necessary that the class that comes after implements or extends be declared in the code earlier.

This feature is implemented in the next class and during the assembly, I do not think that you will have problems.

So, the compiler can be run at the very end of the script after Zend_Controller_Front :: getInstance () -> dispatch (); (usually this line in index.php in the root folder of your project) using the following code:
ZCompile::make( 'd:/www/project/lib/zendframework-1.7.1/Zend.compiled.phplib' ,
array(),
'd:/www/project/lib/zendframework-1.7.1/'
);


* This source code was highlighted with Source Code Highlighter .


The first parameter - indicates the path to the file into which all classes will be collected.
The second parameter contains an array of additional files that need to be connected (about it a bit later).
The third parameter - contains the path to the framework library, at the end a direct slash '/' is required ! This directory is called Zend. Those. in my example, the Zend class files are in the d directory: /www/project/lib/zendframework-1.7.1/Zend/. I repeat, you must specify the path only to the Zend folder.

After running the script, ZCompile will automatically take a list of the required classes and write them to a single file, observing the inheritance hierarchy.

In code, I usually use the following construct to select the framework loading from the compiled file or from the standard library:
if (FRAMEWORK_LOAD_COMPILED_ZEND==1) {
require 'Zend.compiled.phplib' ;
} else {
require 'Zend/Loader.php' ;
}
Zend_Loader::registerAutoload( 'Zend_Loader' );


* This source code was highlighted with Source Code Highlighter .

It is very convenient to store the “compiled” file and the ZCompile class on the same level as the Zend folder (the framework library). Those. I have them in the d directory: /www/project/lib/zendframework-1.7.1/
So, remember, I told you about the second parameter , which is an array. So, it allows you to connect those classes that were not used in the current script, but they are needed in the project.
There, we simply indicate the main classes or directories that need to be connected. For example:
array(
'Zend/Auth/' , // подключить классы из директории, слеш обязателен на конце
'Zend/Acl/' , // подключить классы из директории, слеш обязателен на конце
'Zend/View/Helper/HeadTitle.php' , // подключить конкретный класс
'Zend/View/Helper/Url.php' , // подключить конкретный класс
)


* This source code was highlighted with Source Code Highlighter .


When the directory is connected, then all files are read only at the current level, we do not go into the subdirectory. When we connect a purely class, we read only it.
BUT when connecting a class (via a directory or directly) ZCompile will collect all the classes necessary for the specified file to work. Those. if you specified the directory 'Zend / Auth /', then it most likely will collect everything that is contained in the specified folder including subdirectories.

You can simply execute the following code in a separate file, which simply collects Acl and Auth for you:
<?php

set_include_path(
'd:/www/ksystem/lib/zendframework-1.7.1/'
. PATH_SEPARATOR . get_include_path());

ZCompile::make( 'd:/www/project/lib/zendframework-1.7.1/Zend.compiled.phplib' ,
array( 'Zend/Auth/' , 'Zend/Acl/' ),
'd:/www/ksystem/lib/zendframework-1.7.1/'
);
?>


* This source code was highlighted with Source Code Highlighter .


So, the ZCompile class itself (we don’t find fault with comments, this is not the main thing! There was no time to fix them;)):
<?php
/**
* Класс для сборки модулей ZendFramwork в один файл.
*
* @author Nod nodkz.at.mail.ru
*/
class ZCompile
{
static private $path;

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

//убираем слеш в начале
foreach ($includes as $key=>&$ value ) {
if (substr($ value ,0,1)== '/' ) {
$includes[$key] = substr($ value ,1);
}
}

// получаем последовательную очередность загрузки файлов
$ordered_include=Array();
foreach ($includes as $class_file) {
self::_getClassOrderIncludes($class_file, $ordered_include);
}

// Удалить теги '<?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" ;
$worked_classes = Array();
foreach ($ordered_include as &$fname) {
if (!in_array($fname, $worked_classes)) {
$worked_classes[] = $fname;

$fname = self::$path.$fname;
if (@file_exists($fname)&&is_file($fname)) {
$body.= "/*** FILE: " .$fname. " ***/ \r\n" ;
$body .= preg_replace($pattern, $replacement, file_get_contents($fname, true ));
}
}
}

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

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

/**
* Просконировать названия на соответствие названию директории (т.е. оканчивается на /).
* И затем взять только те php файлы, которые лежат в этой папке (без рекурсивного прохода).
*
* @param array $add_includes
* @return array
*/
private static function _scanFolderFiles(&$add_includes) {
$add_includes_dirs=array();
foreach ($add_includes as $key=>$elem) {
if (substr($elem,-1)== '/' ) {
if (is_dir(self::$path.$elem)) {
if ($dh = opendir(self::$path.$elem)) {
while (($file = readdir($dh)) !== false ) {
if (strpos(strtolower($file), '.php' )!== false ) {
$add_includes_dirs[]=$elem.$file;
}
}
closedir($dh);
}
}
$add_includes[$key]= '' ;
}
}
return $add_includes_dirs;
}

/**
* Получаем массив файлов которые необходимо подключить для работы, переданного файла.
*
* @param string $fname
* @param array &$already_included
*/
private static function _getClassOrderIncludes($class_filename, array &$already_included=Array(), array &$stack=Array()) {
// проверяем на зацикливание в рекурсии
if (!in_array($class_filename, $stack)) {
array_push($stack, $class_filename);
} else {
if (!in_array($class_filename, $already_included)) {
$already_included[]=$class_filename;
}
return ;
}

// если файл существует и мы его еще не парсили
if (is_file(self::$path.$class_filename) && !in_array($class_filename, $already_included)) {
$class_file_content = file_get_contents(self::$path.$class_filename, true );

// выделяем наследование классов для первоначального подключения
// т.е. если класс расширяется через extends или implements,
// то расширяемый клас приводим к имени файла и подключаем
if (preg_match_all( '/class\s+[_\w]+\s+(extends|implements)\s+([_\w]+)/i' , $class_file_content, $arr)) {
foreach ($arr[2] as $new_class_name) {
$new_class_path = str_replace( '_' , '/' ,$new_class_name). '.php' ;
if (!in_array($new_class_path, $already_included)) {
self::_getClassOrderIncludes($new_class_path, $already_included, $stack);
}
}
}

// выдергиваем из него все строчки связанные с подключением других файлов
if (preg_match_all( '%(require_once|include_once|require|include) [("\'](.*?)[)"\'];%sm' , $class_file_content, $arr)) {
// для каждого инклудинного файла
foreach ($arr[2] as $new_class_path) {
// проверяем на то, что мы его еще не сканировали, это дешевле чем парсить файл
if (!in_array($new_class_path, $already_included) && !in_array($new_class_path, $stack)) {
// проверяем чтобы имя было не переменной
if (strpos($new_class_path, '$' )=== false ) {
// начинаем ранее подключать, то что инклудиться в текущем инклуде
self::_getClassOrderIncludes($new_class_path, $already_included, $stack);
}
}
}
}
if (!in_array($class_filename, $already_included)) {
$already_included[]=$class_filename;
}
}

array_pop($stack);
}

/**
* Выбрать уникальные файлы ZF заинклюденные в проект
*
* @return array
*/
private static function _getZendIncludes()
{
$required = array();
$included_files = get_included_files();
$included_files;
foreach ($included_files as $fname) {
$fname = str_replace( '/' , '\\',$fname);

if (!(strpos($fname, '
\\Zend\\ ') > 0) || (strstr($fname, __CLASS__ . ' .php '))) {
continue;
}

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

return array_unique($required);
}
}
?>


* This source code was highlighted with Source Code Highlighter .


Download the
Yuzayte class
to health.