X-Git-Url: https://git.mxchange.org/?p=core.git;a=blobdiff_plain;f=framework%2Floader%2Fclass_ClassLoader.php;fp=framework%2Floader%2Fclass_ClassLoader.php;h=808edcf5b83fd0d77e69b9bae98ce023a643d95e;hp=0000000000000000000000000000000000000000;hb=78a010fef84895720e796842208f01dfb619c332;hpb=7629f2314d517561d4301ddfb068a797b6ed8700 diff --git a/framework/loader/class_ClassLoader.php b/framework/loader/class_ClassLoader.php new file mode 100644 index 00000000..808edcf5 --- /dev/null +++ b/framework/loader/class_ClassLoader.php @@ -0,0 +1,524 @@ + + * @version 1.5.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 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.5 + * - Namespace scheme Project\Package[\SubPackage...] is fully supported and + * throws an InvalidArgumentException if not present. The last part will be + * always the class' name. + * 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 found classes + */ + private $foundClasses = 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 + 'classes', // 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 = json_encode($this->foundClasses); + 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 (array_keys($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 () { + // Trace message + //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__); + + // Cache loader instance + $loaderInstance = self::getSelfInstance(); + + // Load all classes + foreach (self::$frameworkPaths as $pathName) { + // Debug message + //* NOISY-DEBUG: */ printf('[%s:%d]: pathName=%s' . PHP_EOL, __METHOD__, __LINE__, $pathName); + + // Try to load the framework classes + $loaderInstance->scanClassPath('framework/main/' . $pathName . '/'); + } // END - foreach + } + + /** + * Scans for application's classes, etc. + * + * @return void + */ + public static function scanApplicationClasses () { + // Trace message + //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__); + + // Get config instance + $cfg = FrameworkConfiguration::getSelfInstance(); + + // Load all classes for the application + foreach (self::$frameworkPaths as $class) { + // Create path name + $pathName = sprintf('%s/%s/%s', $cfg->getConfigEntry('application_path'), $cfg->getConfigEntry('app_name'), $class); + + // Debug message + //* NOISY-DEBUG: */ printf('[%s:%d]: pathName=%s' . PHP_EOL, __METHOD__, __LINE__, $pathName); + + // Is the path readable? + if (is_dir($pathName)) { + // Try to load the application classes + ClassLoader::getSelfInstance()->scanClassPath($pathName); + } // 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 (BaseFrameworkSystem::isReadableFile($this->listCacheFQFN)) { + // Get content + $cacheContent = file_get_contents($this->listCacheFQFN); + + // And convert it + $this->foundClasses = json_decode($cacheContent); + + // List has been restored from cache! + $this->listCached = TRUE; + } // END - if + + // Does the class cache exist? + if (BaseFrameworkSystem::isReadableFile($this->listCacheFQFN)) { + // 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 + self::getSelfInstance()->loadClassFile($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->foundClasses[$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') . 'framework/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 + */ + private function loadClassFile ($className) { + // Trace message + //* NOISY-DEBUG: */ printf('[%s:%d] className=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $className); + + // The class name should contain at least 2 back-slashes, so split at them + $classNameParts = explode("\\", $className); + + // At least 3 parts should be there + if (count($classNameParts) < 3) { + // Namespace scheme is: Project\Package[\SubPackage...] + throw new InvalidArgumentException(sprintf('Class name "%s" is not conform to naming-convention: Project\Package[\SubPackage...]\SomeFooBar', $className)); + } // END - if + + // Get last element + $shortClassName = array_pop($classNameParts); + + // Create a name with prefix and suffix + $fileName = $this->prefix . $shortClassName . $this->suffix; + + // Now look it up in our index + //* NOISY-DEBUG: */ printf('[%s:%d] ISSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); + if ((isset($this->foundClasses[$fileName])) && (!isset($this->loadedClasses[$this->foundClasses[$fileName]]))) { + // 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->foundClasses[$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. + $this->loadedClasses[$this->foundClasses[$fileName]] = TRUE; + + // 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->foundClasses[$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->foundClasses as $fileName => $fqfn) { + // Is this a config? + if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) { + // Then include it + //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - START' . PHP_EOL, __METHOD__, __LINE__, $fileName); + require($fqfn); + //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - END' . PHP_EOL, __METHOD__, __LINE__, $fileName); + + // Remove it from the list + //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); + unset($this->foundClasses[$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 main/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; + } + +}