eZ Publish  [4.0]
ezautoloadgenerator.php
Go to the documentation of this file.
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 ?>