<?php
namespace Jackbooted\Util;
use \Jackbooted\Time\Stopwatch;
/**
* @copyright Confidential and copyright (c) 2016 Jackbooted Software. All rights reserved.
*
* Written by Brett Dutton of Jackbooted Software
* brett at brettdutton dot com
*
* This software is written and distributed under the GNU General Public
* License which means that its source code is freely-distributed and
* available to the general public.
*/
/**
* The ClassLocator scans all the files in all the class folders and associates the name of the class
* with the file that it is contained in. The way that it does this is by recursing through all the files
* and looking for the string <b>class MyClass {</b> at the beginning of a line. The system then registers that
* class as being in the associated file. This information is used by the AutoLoader to locate classes.
*
* This class is only used by the autoloader when it cannot fine the class by normal means.
*
* Once the array has been created in memory it is serialized out to file <i>(/tmp/ClassLocator.ser)</i>
* for fast loading. If a class does not exist in the array6 when it is queried, then the file is recreated
* This will ensure that you can create classes and the system will continue to know where they are.
*
* <b>Third Party Libraries</b><br>
* You can load third party libraries with the autoloader by creating a class with a dummy class name in comments and
* require_once the file containing the third party library. Example:
* <pre>
* <?php
* /*
* The tag below will fool the ClassLocator to associating this class with this file
* class Smarty
* * /
* // This is a dummy include so that the autoloader finds this class and loads it up
* require_once( dirname ( dirname( __FILE__) ) . "/3rdparty/smarty/Smarty.class.php" );
* ?>
* </pre>
* @see AutoLoader
*/
class ClassLocator extends \Jackbooted\Util\JB {
/**
* Location of the serialization file
*/
const LOCATOR_FILE = '/tmp/R_U_ClassLocator.ser';
/**
*
* @var string Regular expression that searches for the classes, abstracts, interfaces etc
*/
private static $regexClassSearch = '/^\s*\b(interface|trait|class|abstract\s*class|final\s*class)\b/';
private static $regexNameSpaceSearch = '/namespace\s*([\\\\[:alnum:]]*)\s*/';
private static $defaultInstance;
private static $log = null;
/**
* Initializes the system. This is called by the Autoloader
* @param string $classDirectory You can pass in the name of the classes folder for the program to scan.
*/
public static function init ( $classDirectory=null ) {
self::$log = Log4PHP::logFactory( __CLASS__ );
if ( $classDirectory == null ) $classDirectory = dirname( __FILE__ );
self::$defaultInstance = new ClassLocator ( $classDirectory );
}
public static function getLocation ( $className ) {
return self::$defaultInstance->getClassLocation ( $className );
}
public static function getDefaultClassLocator ( ) {
return self::$defaultInstance;
}
private $locationArray;
private $classesDir;
private $locatorFile;
public function __construct ( $classDirectory=null ) {
parent::__construct();
$this->classesDir = $classDirectory;
$this->locatorFile = PHPExt::getTempDir () . '/ClassLocator' . md5 ( var_export ( $classDirectory, true ) ) . '.ser';
self::$log->trace ( "Locator File: {$this->locatorFile}" );
}
/**
* Get the locator array. This is mostly used for testing, and not generally
* required for most applications
* @return array The locator array.
*/
public function getLocatorArray() {
return $this->locationArray;
}
/**
* This is the method that you call to locate the class.
* @param string $className Name of the class that youb are trying to locate
* @return string The name of the file that it is contained in otherwise FALSE
*/
public function getClassLocation ( $className ) {
if ( ! isset( $this->locationArray ) ) $this->loadArrayFromDisk ();
//echo '<pre>';
//print_r ( $this->locationArray );
//echo '<pre>';
// If the class location exists then send it back
if ( isset ( $this->locationArray[$className] ) &&
file_exists ( $this->locationArray[$className] ) ) {
return $this->locationArray[$className];
}
else if ( substr( $className, 0, 1 ) == '\\' ) {
$relativeClassName = substr( $className, 1 );
if ( isset ( $this->locationArray[$relativeClassName] ) &&
file_exists ( $this->locationArray[$relativeClassName] ) ) {
return $this->locationArray[$relativeClassName];
}
}
// If made it to here then regenerate the array
$this->locationArray = [];
$timer = new Stopwatch ( 'ClassLocator Regen' );
if (is_string ( $this->classesDir ) ) {
$cDir = $this->classesDir;
}
else if (is_array( $this->classesDir ) ){
$cDir = join(', ', $this->classesDir );
}
self::$log->info ( "Regenerating class locator array ({$cDir})" );
if ( is_string ( $this->classesDir ) ) {
$this->regenerateLocationArray ( $this->classesDir );
}
else if ( is_array ( $this->classesDir ) ) {
foreach ( $this->classesDir as $dir ) {
$this->regenerateLocationArray ( $dir );
}
}
$this->saveLocationArray ();
$timer->logLoadTime();
if ( isset( $this->locationArray[$className] ) ) return $this->locationArray[$className];
self::$log->error( "$className not found. Continual calls to this class will affect system performance" );
return false;
}
/**
* Loads the location array from the file
* @return void
*/
private function loadArrayFromDisk () {
if ( ! file_exists( $this->locatorFile ) ) return;
$fd = fopen( $this->locatorFile , 'r' );
if ( $fd === false ) return;
$serializeLocator = fgets ( $fd );
$locatorArray = @unserialize ( $serializeLocator );
fclose( $fd );
if ( $locatorArray === false ) return;
$this->locationArray = $locatorArray;
}
/**
* Searches all the files in the passed directory and scans them for classes
* @param string $classesDir
*/
private function regenerateLocationArray ( $classesDir ) {
$handle = opendir ( $classesDir );
while ( false !== ( $file = readdir ( $handle ) ) ) {
if ( strpos ( $file, '.' ) === 0 ) continue;
$fullPathName = $classesDir . '/' . $file;
if ( is_dir ( $fullPathName ) ) {
$this->regenerateLocationArray ( $fullPathName );
}
else {
$this->scanFileForClasses ( $fullPathName );
}
}
closedir ( $handle );
}
/**
* Scans the file for class declarations. Looks for name space declarations and
* adds them to the class name
* @param string $fullPathName
* @return void
*/
private function scanFileForClasses ( $fullPathName ) {
if ( ! file_exists( $fullPathName ) ) return;
// Do not bother with this file
if ( $fullPathName == __FILE__ ) return;
$namespace = '';
$nameSpaceMatches = null;
$fd = fopen( $fullPathName , 'r' );
while ( false !== ( $line = fgets( $fd ) ) ) {
// Check if this has a name space.
if ( preg_match ( self::$regexNameSpaceSearch, $line, $nameSpaceMatches ) ) {
if ( isset( $nameSpaceMatches[1] ) && $nameSpaceMatches[1] != false ) {
$namespace = $nameSpaceMatches[1] . '\\';
}
}
if ( preg_match ( self::$regexClassSearch, $line ) ) {
$className = preg_replace ( self::$regexClassSearch, '', $line );
$className = preg_replace ( '/\b(extends|implements).*/', '', $className );
$className = preg_replace ( '/\{.*/', '', $className );
$className = preg_replace ( '/\s*/', '', $className );
$className = $namespace . $className;
if ( isset ( $this->locationArray[$className] ) ) {
self::$log->warn ( "Duplicate class found ({$className}) in file {$fullPathName} and " . $this->locationArray[$className] );
}
$this->locationArray[$className] = $fullPathName;
}
}
fclose( $fd );
}
/**
* Saves the array out to disk
* @return void
*/
private function saveLocationArray () {
$fd = fopen( $this->locatorFile , 'w' );
if ( $fd === false ) return;
@fputs( $fd, serialize( $this->locationArray ) );
fclose( $fd );
}
/**
* Deletes the serialization file. Protected, only used for testing
* @return void
*/
public function getLocatorFile () {
return $this->locatorFile;
}
}
|