<?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; 
    } 
}
 
 |