* @version 0.0.0 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Core Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.shipsimu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ---------------------------------- * 1.4 * - Some comments improved, other minor improvements * 1.3 * - Constructor is now empty and factory method 'createClassLoader' is created * - renamed loadClasses to scanClassPath * - Added initLoader() * 1.2 * - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class * 1.1 * - loadClasses rewritten to fix some notices * 1.0 * - Initial release * ---------------------------------- */ class ClassLoader { /** * Instance of this class */ private static $selfInstance = NULL; /** * Array with all classes */ private $classes = array(); /** * List of loaded classes */ private $loadedClasses = array(); /** * Suffix with extension for all class files */ private $prefix = 'class_'; /** * Suffix with extension for all class files */ private $suffix = '.php'; /** * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner * @see scanLocalPath */ private $ignoreList = array(); /** * Debug this class loader? (TRUE = yes, FALSE = no) */ private $debug = FALSE; /** * Whether the file list is cached */ private $listCached = FALSE; /** * Wethe class content has been cached */ private $classesCached = FALSE; /** * Filename for the list cache */ private $listCacheFQFN = ''; /** * Cache for class content */ private $classCacheFQFN = ''; /** * Counter for loaded include files */ private $total = 0; /** * Framework/application paths for classes, etc. */ private static $frameworkPaths = array( 'exceptions', // Exceptions 'interfaces', // Interfaces 'main', // General main classes 'middleware' // The middleware ); /** * The protected constructor. Please use the factory method below, or use * getSelfInstance() for singleton * * @return void */ protected function __construct () { // Is currently empty } /** * The destructor makes it sure all caches got flushed * * @return void */ public function __destruct () { // Skip here if dev-mode if (defined('DEVELOPER')) { return; } // END - if // Skip here if already cached if ($this->listCached === FALSE) { // Writes the cache file of our list away $cacheContent = serialize($this->classes); file_put_contents($this->listCacheFQFN, $cacheContent); } // END - if // Skip here if already cached if ($this->classesCached === FALSE) { // Generate a full-cache of all classes $cacheContent = ''; foreach ($this->loadedClasses as $fqfn) { // Load the file $cacheContent .= file_get_contents($fqfn); } // END - foreach // And write it away file_put_contents($this->classCacheFQFN, $cacheContent); } // END - if } /** * Creates an instance of this class loader for given configuration instance * * @param $configInstance Configuration class instance * @return void */ public static final function createClassLoader (FrameworkConfiguration $configInstance) { // Get a new instance $loaderInstance = new ClassLoader(); // Init the instance $loaderInstance->initLoader($configInstance); // Return the prepared instance return $loaderInstance; } /** * Scans for all framework classes, exceptions and interfaces. * * @return void */ public static function scanFrameworkClasses () { // Cache loader instance $loaderInstance = self::getSelfInstance(); // Load all classes foreach (self::$frameworkPaths as $className) { // Try to load the framework classes $loaderInstance->scanClassPath('inc/classes/' . $className . '/'); } // END - foreach } /** * Scans for application's classes, etc. * * @return void */ public static function scanApplicationClasses () { // Get config instance $cfg = FrameworkConfiguration::getSelfInstance(); // Load all classes for the application foreach (self::$frameworkPaths as $class) { // Create path name $path = sprintf('%s/%s/%s', $cfg->getConfigEntry('application_path'), $cfg->getConfigEntry('app_name'), $class); // Is the path readable? if (is_dir($path)) { // Try to load the application classes ClassLoader::getSelfInstance()->scanClassPath($path); } // END - if } // END - foreach } /** * Initializes our loader class * * @param $configInstance Configuration class instance * @return void */ protected function initLoader (FrameworkConfiguration $configInstance) { // Set configuration instance $this->configInstance = $configInstance; // Construct the FQFN for the cache if (!defined('DEVELOPER')) { $this->listCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'list-' . $this->configInstance->getConfigEntry('app_name') . '.cache'; $this->classCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'class-' . $this->configInstance->getConfigEntry('app_name') . '.cache'; } // END - if // Set suffix and prefix from configuration $this->suffix = $configInstance->getConfigEntry('class_suffix'); $this->prefix = $configInstance->getConfigEntry('class_prefix'); // Set own instance self::$selfInstance = $this; // Skip here if no dev-mode if (defined('DEVELOPER')) { return; } // END - if // IS the cache there? if (file_exists($this->listCacheFQFN)) { // Get content $cacheContent = file_get_contents($this->listCacheFQFN); // And convert it $this->classes = unserialize($cacheContent); // List has been restored from cache! $this->listCached = TRUE; } // END - if // Does the class cache exist? if (file_exists($this->classCacheFQFN)) { // Then include it require($this->classCacheFQFN); // Mark the class cache as loaded $this->classesCached = TRUE; } // END - if } /** * Autoload-function * * @param $className Name of the class to load * @return void */ public static function autoLoad ($className) { // Try to include this class //* NOISY-DEBUG: */ printf('[%s:%d] className=%s' . PHP_EOL, __METHOD__, __LINE__, $className); self::getSelfInstance()->includeClass($className); } /** * Singleton getter for an instance of this class * * @return $selfInstance A singleton instance of this class */ public static final function getSelfInstance () { // Is the instance there? if (is_null(self::$selfInstance)) { // Get a new one self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getSelfInstance()); } // END - if // Return the instance return self::$selfInstance; } /** * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix * * @param $basePath The relative base path to 'base_path' constant for all classes * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored * @return void */ public function scanClassPath ($basePath, array $ignoreList = array() ) { // Is a list has been restored from cache, don't read it again if ($this->listCached === TRUE) { // Abort here return; } // END - if // Keep it in class for later usage $this->ignoreList = $ignoreList; /* * Ignore .htaccess by default as it is for protection of directories * on Apache servers. */ array_push($ignoreList, '.htaccess'); /* * Set base directory which holds all our classes, an absolute path * should be used here so is_dir(), is_file() and so on will always * find the correct files and dirs. */ $basePath2 = realpath($basePath); // If the basePath is FALSE it is invalid if ($basePath2 === FALSE) { /* @TODO: Do not exit here. */ exit(__METHOD__ . ': Cannot read ' . $basePath . ' !' . PHP_EOL); } else { // Set base path $basePath = $basePath2; } // Get a new iterator //* NOISY-DEBUG: */ printf('[%s:%d] basePath=%s' . PHP_EOL, __METHOD__, __LINE__, $basePath); $iteratorInstance = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath), RecursiveIteratorIterator::CHILD_FIRST); // Load all entries while ($iteratorInstance->valid()) { // Get current entry $currentEntry = $iteratorInstance->current(); // Get filename from iterator $fileName = $currentEntry->getFileName(); // Get the "FQFN" (path and file name) $fqfn = $currentEntry->getRealPath(); // Current entry must be a file, not smaller than 100 bytes and not on ignore list if ((!$currentEntry->isFile()) || (in_array($fileName, $this->ignoreList)) || (filesize($fqfn) < 100)) { // Advance to next entry $iteratorInstance->next(); // Skip non-file entries //* NOISY-DEBUG: */ printf('[%s:%d] SKIP: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); continue; } // END - if // Is this file wanted? //* NOISY-DEBUG: */ printf('[%s:%d] FOUND: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); if ((substr($fileName, 0, strlen($this->prefix)) == $this->prefix) && (substr($fileName, -strlen($this->suffix), strlen($this->suffix)) == $this->suffix)) { // Add it to the list //* NOISY-DEBUG: */ printf('[%s:%d] ADD: %s,fqfn=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $fqfn); $this->classes[$fileName] = $fqfn; } else { // Not added //* NOISY-DEBUG: */ printf('[%s:%d] NOT ADDED: %s,fqfn=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $fqfn); } // Advance to next entry //* NOISY-DEBUG: */ printf('[%s:%d] NEXT: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); $iteratorInstance->next(); } // END - while } /** * Load extra config files * * @return void */ public function loadExtraConfigs () { // Backup old prefix $oldPrefix = $this->prefix; // Set new prefix (temporary!) $this->prefix = 'config-'; // Set base directory $basePath = $this->configInstance->getConfigEntry('base_path') . 'inc/config/'; // Load all classes from the config directory $this->scanClassPath($basePath); // Include these extra configs now $this->includeExtraConfigs(); // Set back the old prefix $this->prefix = $oldPrefix; } /** * Tries to find the given class in our list. This method ignores silently * missing classes or interfaces. So if you use class_exists() this method * does not interrupt your program. * * @param $className The class that shall be loaded * @return void */ public function includeClass ($className) { // Create a name with prefix and suffix $fileName = $this->prefix . $className . $this->suffix; // Now look it up in our index //* NOISY-DEBUG: */ printf('[%s:%d] ISSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) { // File is found and not loaded so load it only once //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - START' . PHP_EOL, __METHOD__, __LINE__, $fileName); require($this->classes[$fileName]); //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - END' . PHP_EOL, __METHOD__, __LINE__, $fileName); // Count this loaded class/interface/exception $this->total++; // Mark this class as loaded for other purposes than loading it. array_push($this->loadedClasses, $this->classes[$fileName]); // Remove it from classes list so it won't be found twice. //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); unset($this->classes[$fileName]); // Developer mode excludes caching (better debugging) if (!defined('DEVELOPER')) { // Reset cache //* NOISY-DEBUG: */ printf('[%s:%d] classesCached=FALSE' . PHP_EOL, __METHOD__, __LINE__); $this->classesCached = FALSE; } // END - if } else { // Not found //* NOISY-DEBUG: */ printf('[%s:%d] 404: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); } } /** * Includes all extra config files * * @return void */ private function includeExtraConfigs () { // Run through all class names (should not be much) foreach ($this->classes as $fileName => $fqfn) { // Is this a config? if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) { // Then include it require($fqfn); // Remove it from the list //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); unset($this->classes[$fileName]); } // END - if } // END - foreach } /** * Getter for total include counter * * @return $total Total loaded include files */ public final function getTotal () { return $this->total; } /** * Getter for a printable list of included classes/interfaces/exceptions * * @param $includeList A printable include list */ public function getPrintableIncludeList () { // Prepare the list $includeList = ''; foreach ($this->loadedClasses as $classFile) { $includeList .= basename($classFile) . '
' . PHP_EOL; } // END - foreach // And return it return $includeList; } } // [EOF] ?>