|
eZ Publish
[4.0]
|
00001 <?php 00002 /** 00003 * File containing the eZAutoloadGenerator class. 00004 * 00005 * @copyright Copyright (C) 2005-2008 eZ Systems AS. All rights reserved. 00006 * @license http://ez.no/licenses/gnu_gpl GNU GPL v2 00007 * @version //autogentag// 00008 * @package kernel 00009 */ 00010 00011 /** 00012 * Utility class for generating autoload arrays for eZ Publish. The class can 00013 * handle classes from the kernel and extensions. 00014 * 00015 * @package kernel 00016 * @version //autogentag// 00017 */ 00018 class eZAutoloadGenerator 00019 { 00020 /** 00021 * Contains the base path from which to root the search, and from which 00022 * to create relative paths 00023 * 00024 * @var string 00025 */ 00026 private $basePath; 00027 00028 /** 00029 * Flag for searching kernel files. 00030 * 00031 * @var bool 00032 */ 00033 private $searchKernelFiles; 00034 00035 /** 00036 * Flag for searching in extension files. 00037 * 00038 * @var bool 00039 */ 00040 private $searchExtensionFiles; 00041 00042 /** 00043 * Flag for verbose output 00044 * 00045 * @var bool 00046 */ 00047 private $verboseOutput; 00048 00049 /** 00050 * Flag for writing autoload arrays 00051 * 00052 * @var bool 00053 */ 00054 private $writeFiles; 00055 00056 /** 00057 * Holds the directory into which autoload arrays are written. 00058 * 00059 * @var string 00060 */ 00061 private $outputDir; 00062 00063 /** 00064 * Holds the directories to exclude from search 00065 * 00066 * @var array 00067 */ 00068 private $excludeDirs; 00069 00070 /** 00071 * Bitmask for searching in no files. 00072 */ 00073 const GENAUTOLOADS_NONE = 0; 00074 00075 /** 00076 * Bitmask for searhing in kernel files 00077 */ 00078 const GENAUTOLOADS_KERNEL = 1; 00079 00080 /** 00081 * Bitmask for search in extension files 00082 */ 00083 const GENAUTOLOADS_EXTENSION = 2; 00084 00085 /** 00086 * Bitmask for searching in both kernel and extension files 00087 */ 00088 const GENAUTOLOADS_BOTH = 3; 00089 00090 00091 /** 00092 * Constructs class to generate autoload arrays. 00093 * File search is rooted in $basePath, and the parameters 00094 * $searchKernelFiles and $searchExtensionFiles control which part of the 00095 * installation is searched for classes. 00096 * 00097 * $verboseOutput controls whether autoload arrays will be printed on 00098 * STDOUT. 00099 * 00100 * $writeFiles controls whether the the resulting autoload arrays are 00101 * written to disc. 00102 * 00103 * $outputDir is the directory into which the autoload arrays should be 00104 * written, defaults to 'autoload' 00105 * 00106 * $excludeDirs are the arrays which should not be included in the search 00107 * for PHP classes. 00108 * 00109 * @param string $basePath 00110 * @param bool $searchKernelFiles 00111 * @param bool $searchExtensionFiles 00112 * @param bool $verboseOutput 00113 * @param bool $writeFiles 00114 * @param string $outputDir 00115 * @param array $excludeDirs 00116 */ 00117 function __construct( $basePath, $searchKernelFiles, $searchExtensionFiles, $verboseOutput = false, $writeFiles = true, $outputDir = false, $excludeDirs = false ) 00118 { 00119 $this->basePath = $basePath; 00120 $this->searchKernelFiles = $searchKernelFiles; 00121 $this->searchExtensionFiles = $searchExtensionFiles; 00122 $this->verboseOutput = $verboseOutput; 00123 $this->writeFiles = $writeFiles; 00124 00125 if ( $outputDir === false ) 00126 { 00127 $this->outputDir = 'autoload'; 00128 } 00129 else 00130 { 00131 $this->outputDir = $outputDir; 00132 } 00133 00134 $this->excludeDirs = $excludeDirs; 00135 00136 } 00137 00138 /** 00139 * Searches specified directories for classes, and build autoload arrays. 00140 * 00141 * @throws Exception if desired output directory is not a directory, or if the autoload arrays are not writeable by the script. 00142 * @return void 00143 */ 00144 public function buildAutoloadArrays() 00145 { 00146 $runMode = $this->runMode( $this->searchKernelFiles, $this->searchExtensionFiles ); 00147 $phpFiles = $this->fetchFiles( $this->basePath, $runMode, $this->excludeDirs ); 00148 00149 $phpClasses = array(); 00150 foreach ($phpFiles as $mode => $fileList) { 00151 $phpClasses[$mode] = $this->getClassFileList( $fileList ); 00152 } 00153 00154 $maxClassNameLength = $this->checkMaxClassLength( $phpClasses ); 00155 $autoloadArrays = $this->dumpArray( $phpClasses, $maxClassNameLength ); 00156 00157 //Write autoload array data into separate files 00158 foreach( $autoloadArrays as $location => $data ) 00159 { 00160 if ( $this->verboseOutput ) 00161 { 00162 var_dump( $this->dumpArrayStart( $location) . $data . $this->dumpArrayEnd() ); 00163 } 00164 00165 if ( $this->writeFiles ) 00166 { 00167 // Check the targetDir 00168 if( file_exists( $this->outputDir ) && !is_dir( $this->outputDir ) ) 00169 { 00170 throw new Exception( "Specified target: {$this->outputDir} is not a directory." ); 00171 } 00172 elseif ( !file_exists( $this->outputDir ) ) 00173 { 00174 eZDir::mkdir( $this->outputDir ); 00175 } 00176 00177 $filename = $this->nameTable( $location ); 00178 $filePath = "{$this->outputDir}/$filename"; 00179 if ( is_writable( $filePath ) ) 00180 { 00181 $file = fopen( $filePath, "w" ); 00182 fwrite( $file, $this->dumpArrayStart( $location ) ); 00183 fwrite( $file, $data ); 00184 fwrite( $file, $this->dumpArrayEnd() ); 00185 fclose( $file ); 00186 } 00187 else 00188 { 00189 throw new Exception( __CLASS__ . ' - ' . __FUNCTION__ . ": The file {$filePath} is not writable by the system." ); 00190 } 00191 } 00192 } 00193 } 00194 00195 /** 00196 * Returns an array indexed by location for classes and their filenames. 00197 * 00198 * @param string $path The base path to start the search from. 00199 * @param string $mask A binary mask which instructs the function whether to fetch kernel-related or extension-related files. 00200 * @return array 00201 */ 00202 private function fetchFiles( $path, $mask, $excludeDirs = false ) 00203 { 00204 // make sure ezcFile::findRecursive and the exclusion filters we pass to it 00205 // work correctly on systems with another file seperator than the forward slash 00206 $sanitisedBasePath = DIRECTORY_SEPARATOR == '/' ? $path : strtr( $path, DIRECTORY_SEPARATOR, '/' ); 00207 00208 $extraExcludeDirs = array(); 00209 if ( $excludeDirs !== false and is_array( $excludeDirs ) ) 00210 { 00211 foreach ( $excludeDirs as $dir ) 00212 { 00213 $extraExcludeDirs[] = "@^{$sanitisedBasePath}/{$dir}/@"; 00214 } 00215 } 00216 00217 $retFiles = array(); 00218 00219 switch( $this->checkMode( $mask) ) 00220 { 00221 case self::GENAUTOLOADS_EXTENSION: 00222 { 00223 $retFiles = array( "extension" => $this->buildFileList( "$sanitisedBasePath/extension", $extraExcludeDirs ) ); 00224 break; 00225 } 00226 00227 case self::GENAUTOLOADS_KERNEL: 00228 { 00229 $extraExcludeDirs[] = "@^{$sanitisedBasePath}/extension/@"; 00230 $retFiles = array( "kernel" => $this->buildFileList( $sanitisedBasePath, $extraExcludeDirs ) ); 00231 break; 00232 } 00233 00234 case self::GENAUTOLOADS_BOTH: 00235 { 00236 $extraExcludeKernelDirs = $extraExcludeDirs; 00237 $extraExcludeKernelDirs[] = "@^{$sanitisedBasePath}/extension/@"; 00238 $retFiles = array( "extension" => $this->buildFileList( "$sanitisedBasePath/extension", $extraExcludeDirs ), 00239 "kernel" => $this->buildFileList( $sanitisedBasePath, $extraExcludeKernelDirs ) ); 00240 break; 00241 } 00242 } 00243 00244 //Make all the paths relative to $path 00245 foreach ( $retFiles as &$fileBundle ) 00246 { 00247 foreach ( $fileBundle as $key => &$file ) 00248 { 00249 // ezcFile::calculateRelativePath only works correctly with paths where DIRECTORY_SEPARATOR is used 00250 // so we need to correct the results of ezcFile::findRecursive again 00251 if ( DIRECTORY_SEPARATOR != '/' ) 00252 { 00253 $file = strtr( $file, '/', DIRECTORY_SEPARATOR ); 00254 } 00255 $fileBundle[$key] = ezcFile::calculateRelativePath( $file, $path ); 00256 } 00257 } 00258 unset( $file, $fileBundle ); 00259 return $retFiles; 00260 } 00261 00262 00263 /** 00264 * Builds a filelist of all PHP files in $path. 00265 * 00266 * @param string $path 00267 * @param array $extraFilter 00268 * @return array 00269 */ 00270 private function buildFileList( $path, $extraFilter = null ) 00271 { 00272 $exclusionFilter = array( "@^{$path}/(var|settings|benchmarks|autoload|port_info|templates|tmp|UnitTest|tests)/@" ); 00273 if ( !empty( $extraFilter ) and is_array( $extraFilter ) ) 00274 { 00275 foreach( $extraFilter as $filter ) 00276 { 00277 $exclusionFilter[] = $filter; 00278 } 00279 } 00280 00281 if (!empty( $path ) ) 00282 { 00283 return ezcFile::findRecursive( $path, array( '@\.php$@' ), $exclusionFilter ); 00284 } 00285 return false; 00286 } 00287 00288 /** 00289 * Extracts class information from PHP sourcecode. 00290 * @return array (className=>filename) 00291 */ 00292 private function getClassFileList( $fileList ) 00293 { 00294 $retArray = array(); 00295 foreach( $fileList as $file ) 00296 { 00297 $tokens = @token_get_all( file_get_contents( $file ) ); 00298 foreach( $tokens as $key => $token ) 00299 { 00300 if ( is_array( $token ) ) 00301 { 00302 switch( $token[0] ) 00303 { 00304 case T_CLASS: 00305 case T_INTERFACE: 00306 { 00307 // make sure we store cross-platform file system paths, 00308 // using a forward slash as directory separator 00309 if ( DIRECTORY_SEPARATOR != '/' ) 00310 { 00311 $file = str_replace( DIRECTORY_SEPARATOR, '/', $file ); 00312 } 00313 00314 $retArray[$tokens[$key+2][1]] = $file; 00315 } break; 00316 } 00317 } 00318 } 00319 } 00320 ksort( $retArray ); 00321 return $retArray; 00322 } 00323 00324 /** 00325 * Calculates the length of the longest class name present in $depdata 00326 * 00327 * @param array $depData 00328 * @return mixed 00329 */ 00330 private function checkMaxClassLength( $depData ) 00331 { 00332 $max = array(); 00333 foreach( array_keys( $depData) as $key ) 00334 { 00335 $max[$key] = 0; 00336 } 00337 00338 foreach( $depData as $location => $locationBundle ) 00339 { 00340 foreach ( $locationBundle as $className => $path ) 00341 { 00342 if ( strlen( $className ) > $max[$location] ) 00343 { 00344 $max[$location] = strlen( $className ); 00345 } 00346 } 00347 } 00348 return $max; 00349 } 00350 00351 /** 00352 * Build string version of the autoload array with correct indenting. 00353 * 00354 * @param array $sortedArray 00355 * @param int $length 00356 * @return string 00357 */ 00358 private function dumpArray( $sortedArray, $length ) 00359 { 00360 $retArray = array(); 00361 foreach ( $sortedArray as $location => $sorted ) 00362 { 00363 $ret = ''; 00364 $offset = $length[$location] + 2; 00365 foreach( $sorted as $class => $path ) 00366 { 00367 $ret .= sprintf( " %-{$offset}s => '%s'," . PHP_EOL, "'{$class}'", $path ); 00368 } 00369 $retArray[$location] = $ret; 00370 } 00371 return $retArray; 00372 } 00373 00374 /** 00375 * Checks which runmode the script should operate in: kernel-mode, extension-mode or both. 00376 * 00377 * @param int $mask Bitmask to check for run mode. 00378 * @return int 00379 */ 00380 private function checkMode( $mask ) 00381 { 00382 $modes = array( self::GENAUTOLOADS_KERNEL, self::GENAUTOLOADS_EXTENSION, self::GENAUTOLOADS_BOTH ); 00383 foreach( $modes as $mode ) 00384 { 00385 if ( ($mask & $mode)==$mask ) 00386 { 00387 return $mode; 00388 } 00389 } 00390 return false; 00391 } 00392 00393 /** 00394 * Generates the active bitmask for this instance of the autoload generation script 00395 * depending on the parameters it sets the corresponding flags. 00396 * 00397 * @param bool $useKernelFiles Whether kernel files should be checked 00398 * @param bool $useExtensionFiles Whether extension files should be checked 00399 * @return int 00400 */ 00401 private function runMode( $useKernelFiles, $useExtensionFiles ) 00402 { 00403 $mode = self::GENAUTOLOADS_NONE; 00404 //If no file selectors are chosen we will default to extension files. 00405 if ( !$useKernelFiles and !$useExtensionFiles ) 00406 { 00407 $mode |= self::GENAUTOLOADS_EXTENSION; 00408 } 00409 00410 if ( $useKernelFiles ) 00411 { 00412 $mode |= self::GENAUTOLOADS_KERNEL; 00413 } 00414 00415 if ( $useExtensionFiles ) 00416 { 00417 $mode |= self::GENAUTOLOADS_EXTENSION; 00418 } 00419 return $mode; 00420 } 00421 00422 /** 00423 * Table to look up file names to use for different run modes. 00424 * 00425 * @param string $lookup Mode to look up, can be extension, or kernel. 00426 * @return string 00427 */ 00428 private function nameTable( $lookup ) 00429 { 00430 $names = array( "extension" => "ezp_extension.php", 00431 "kernel" => "ezp_kernel.php" ); 00432 00433 if ( array_key_exists( $lookup, $names ) ) 00434 { 00435 return $names[$lookup]; 00436 } 00437 return false; 00438 } 00439 00440 /** 00441 * Prints generated code used for the autoload files 00442 * 00443 * @param string $part 00444 * @return string 00445 */ 00446 private function dumpArrayStart( $part ) 00447 { 00448 return <<<ENDL 00449 <?php 00450 /** 00451 * Autoloader definition for eZ Publish $part files. 00452 * 00453 * @copyright Copyright (C) 2005-2008 eZ Systems AS. All rights reserved. 00454 * @license http://ez.no/licenses/gnu_gpl GNU GPL v2 00455 * @version //autogentag// 00456 * @package kernel 00457 * 00458 */ 00459 00460 return array( 00461 00462 ENDL; 00463 } 00464 00465 /** 00466 * Prints generated code for end of the autoload files 00467 * 00468 * @return void 00469 */ 00470 private function dumpArrayEnd() 00471 { 00472 return <<<END 00473 ); 00474 00475 ?> 00476 END; 00477 } 00478 } 00479 ?>