X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=framework%2Floader%2Fclass_ClassLoader.php;h=b19d5dc5ce3ef33882ea71d576f46d2b37da5c65;hb=d831713579377eaedd277b577dcd9c73040d0767;hp=b47a2e6fb59bde86c133d824aeea5f5966448825;hpb=63261187e2d5767e127cfafe5c9b7abb4539b6d3;p=core.git diff --git a/framework/loader/class_ClassLoader.php b/framework/loader/class_ClassLoader.php index b47a2e6f..b19d5dc5 100644 --- a/framework/loader/class_ClassLoader.php +++ b/framework/loader/class_ClassLoader.php @@ -4,7 +4,6 @@ namespace Org\Mxchange\CoreFramework\Loader; // Import framework stuff use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap; -use Org\Mxchange\CoreFramework\Configuration\FrameworkConfiguration; // Import SPL stuff use \InvalidArgumentException; @@ -16,8 +15,8 @@ use \SplFileInfo; * This class loads class include files with a specific prefix and suffix * * @author Roland Haeder - * @version 1.5.0 - * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team + * @version 1.6.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.shipsimu.org * @@ -35,34 +34,48 @@ use \SplFileInfo; * along with this program. If not, see . * * ---------------------------------- - * 1.5 + * 1.6.0 + * - This class loader is now 100% singleton, no other instance is really + * required, therefore the factory method can be removed safely + * - renamed initLoader() to initClassLoader() + * - An instance of a FrameworkConfiguration is no longer set here as this + * violated the rule that there shall be no instance set of that class + * - .htaccess is marked as deprecated as this should be no longer done + * - scanClassPath() is now protected, please use other scan*() methods + * 1.5.0 * - 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 + * 1.4.0 * - Some comments improved, other minor improvements - * 1.3 + * 1.3.0 * - Constructor is now empty and factory method 'createClassLoader' is created * - renamed loadClasses to scanClassPath * - Added initLoader() - * 1.2 + * 1.2.0 * - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class - * 1.1 + * 1.1.0 * - loadClasses rewritten to fix some notices - * 1.0 + * 1.0.0 * - Initial release * ---------------------------------- */ -class ClassLoader { +final class ClassLoader { /** * Instance of this class */ private static $selfInstance = NULL; /** - * Array with all found classes + * Cached configuration entry 'developer_mode_enabled' */ - private $foundClasses = []; + private static $developerModeEnabled = false; + + /** + * Array with all valid but pending for loading file names (class, + * interfaces, traits must start with below prefix). + */ + private $pendingFiles = []; /** * List of loaded classes @@ -143,8 +156,9 @@ class ClassLoader { * * @return void */ - protected function __construct () { - // This is empty for now + private function __construct () { + // Cache config entry + self::$developerModeEnabled = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('developer_mode_enabled'); } /** @@ -154,14 +168,14 @@ class ClassLoader { */ public function __destruct () { // Skip here if dev-mode - if (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('developer_mode_enabled')) { + if (self::$developerModeEnabled) { return; } // Skip here if already cached if ($this->listCached === false) { // Writes the cache file of our list away - $cacheContent = json_encode($this->foundClasses); + $cacheContent = json_encode($this->pendingFiles); // Open cache instance $fileObject = $this->listCacheFile->openFile('w'); @@ -191,23 +205,6 @@ class ClassLoader { } } - /** - * 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. * @@ -218,25 +215,27 @@ class ClassLoader { //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__); $loaderInstance = self::getSelfInstance(); - // Get config instance + // "Cache" configuration instance and framework base path $configInstance = FrameworkBootstrap::getConfigurationInstance(); + $frameworkBasePath = $configInstance->getConfigEntry('framework_base_path'); // Load all classes + //* NOISY-DEBUG: */ printf('[%s:%d]: frameworkBasePath=%s,self::$frameworkPaths()=%d,' . PHP_EOL, __METHOD__, __LINE__, $frameworkBasePath, count(self::$frameworkPaths)); foreach (self::$frameworkPaths as $shortPath) { // Generate full path from it - //* NOISY-DEBUG: */ printf('[%s:%d]: shortPath=%s' . PHP_EOL, __METHOD__, __LINE__, $shortPath); + //* NOISY-DEBUG: */ printf('[%s:%d]: shortPath[%s]=%s' . PHP_EOL, __METHOD__, __LINE__, gettype($shortPath), $shortPath); $realPathName = realpath(sprintf( '%smain%s%s%s', - $configInstance->getConfigEntry('framework_base_path'), + $frameworkBasePath, DIRECTORY_SEPARATOR, $shortPath, DIRECTORY_SEPARATOR )); // Is it not false and accessible? - //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName=%s' . PHP_EOL, __METHOD__, __LINE__, $realPathName); + //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName[%s]=%s' . PHP_EOL, __METHOD__, __LINE__, gettype($realPathName), $realPathName); if (is_bool($realPathName)) { - // Skip this + // Skip this, it is not accessible continue; } elseif (!is_readable($realPathName)) { // @TODO Throw exception instead of break @@ -261,7 +260,7 @@ class ClassLoader { //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__); $loaderInstance = self::getSelfInstance(); - // Get config instance + // "Cache" configuration instance $configInstance = FrameworkBootstrap::getConfigurationInstance(); // Load all classes for the application @@ -295,18 +294,14 @@ class ClassLoader { * @return void */ public static function scanTestsClasses () { - // Trace message + // "Cache" configuration instance //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__); - - // Get config instance $configInstance = FrameworkBootstrap::getConfigurationInstance(); // Load all classes for the application foreach (self::$testPaths as $shortPath) { - // Debug message - //* NOISY-DEBUG: */ printf('[%s:%d]: shortPath=%s' . PHP_EOL, __METHOD__, __LINE__, $shortPath); - // Construct path name + //* NOISY-DEBUG: */ printf('[%s:%d]: shortPath=%s' . PHP_EOL, __METHOD__, __LINE__, $shortPath); $pathName = sprintf( '%s%s%s', $configInstance->getConfigEntry('root_base_path'), @@ -361,12 +356,27 @@ class ClassLoader { * * @param $className Name of the class to load * @return void + * @throws InvalidArgumentException If the class' name does not contain a namespace: Tld\Domain\Project is AT LEAST recommended! */ public static function autoLoad (string $className) { - // Trace message - //* NOISY-DEBUG: */ printf('[%s:%d]: className=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $className); + // Validate parameter + //* NOISY-DEBUG: */ printf('[%s:%d] className=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $className); + if (empty($className)) { + // Should not be empty + throw new InvalidArgumentException('Parameter "className" is empty'); + } + + // The class name MUST be at least Tld\Domain\Project\Package\SomeFooBar so split at them + $classNameParts = explode("\\", $className); + + // At least 3 parts should be there + if ((self::$strictNamingConvention === true) && (count($classNameParts) < 5)) { + // Namespace scheme is: Tld\Domain\Project\Package[\SubPackage...] + throw new InvalidArgumentException(sprintf('Class name "%s" is not conform to naming-convention: Tld\Domain\Project\Package[\SubPackage...]\SomeFooBar', $className)); + } // Try to include this class + //* NOISY-DEBUG: */ printf('[%s:%d]: Calling self->loadClassFile(%s) ...' . PHP_EOL, __METHOD__, __LINE__, $className); self::getSelfInstance()->loadClassFile($className); // Trace message @@ -381,14 +391,40 @@ class ClassLoader { public static final function getSelfInstance () { // Is the instance there? if (is_null(self::$selfInstance)) { - // Get a new one - self::$selfInstance = ClassLoader::createClassLoader(FrameworkBootstrap::getConfigurationInstance()); + // Get a new one and initialize it + self::$selfInstance = new ClassLoader(); + self::$selfInstance->initClassLoader(); } // Return the instance return self::$selfInstance; } + /** + * 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; + } + + // And return it + return $includeList; + } + /** * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix * @@ -396,21 +432,17 @@ class ClassLoader { * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored * @return void */ - public function scanClassPath (string $basePath, array $ignoreList = [] ) { + protected function scanClassPath (string $basePath, array $ignoreList = [] ) { // Is a list has been restored from cache, don't read it again + //* NOISY-DEBUG: */ printf('[%s:%d] basePath=%s,ignoreList()=%d - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $basePath, count($ignoreList)); if ($this->listCached === true) { // Abort here + //* NOISY-DEBUG: */ printf('[%s:%d] this->listCache=true - EXIT!' . PHP_EOL, __METHOD__, __LINE__); return; } - // 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'); + // Keep it in class for later usage, but flip index<->value + $this->ignoreList = array_flip($ignoreList); /* * Set base directory which holds all our classes, an absolute path @@ -420,6 +452,7 @@ class ClassLoader { $basePath2 = realpath($basePath); // If the basePath is false it is invalid + //* NOISY-DEBUG: */ printf('[%s:%d] basePath2[%s]=%s' . PHP_EOL, __METHOD__, __LINE__, gettype($basePath2), $basePath2); if ($basePath2 === false) { /* @TODO: Do not exit here. */ exit(__METHOD__ . ': Cannot read ' . $basePath . ' !' . PHP_EOL); @@ -441,90 +474,64 @@ class ClassLoader { $fileName = $currentEntry->getFilename(); // Current entry must be a file, not smaller than 100 bytes and not on ignore list - if ((!$currentEntry->isFile()) || (in_array($fileName, $this->ignoreList)) || ($currentEntry->getSize() < 100)) { + if (!$currentEntry->isFile() || isset($this->ignoreList[$fileName]) || $currentEntry->getSize() < 100) { // Advance to next entry $iteratorInstance->next(); // Skip non-file entries - //* NOISY-DEBUG: */ printf('[%s:%d] SKIP: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); + //* NOISY-DEBUG: */ printf('[%s:%d] SKIP: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName); continue; } // Is this file wanted? - //* NOISY-DEBUG: */ printf('[%s:%d] FOUND: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); + //* NOISY-DEBUG: */ printf('[%s:%d] FOUND: fileName=%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,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry); - $this->foundClasses[$fileName] = $currentEntry; + //* NOISY-DEBUG: */ printf('[%s:%d] ADD: fileName=%s,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry); + $this->pendingFiles[$fileName] = $currentEntry; } else { // Not added - //* NOISY-DEBUG: */ printf('[%s:%d] NOT ADDED: %s,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry); + //* NOISY-DEBUG: */ printf('[%s:%d] NOT ADDED: fileName=%s,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry); } // Advance to next entry - //* NOISY-DEBUG: */ printf('[%s:%d] NEXT: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); + //* NOISY-DEBUG: */ printf('[%s:%d] NEXT: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName); $iteratorInstance->next(); } - } - - /** - * 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; - } - // And return it - return $includeList; + // Trace message + //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__); } /** * Initializes our loader class * - * @param $configInstance Configuration class instance * @return void */ - private function initLoader (FrameworkConfiguration $configInstance) { - // Set configuration instance - $this->configInstance = $configInstance; - + private function initClassLoader () { // Construct the FQFN for the cache - if (!FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('developer_mode_enabled')) { + if (!self::$developerModeEnabled) { // Init cache instances - $this->listCacheFile = new SplFileInfo($this->configInstance->getConfigEntry('local_database_path') . 'list-' . FrameworkBootstrap::getDetectedApplicationName() . '.cache'); - $this->classCacheFile = new SplFileInfo($this->configInstance->getConfigEntry('local_database_path') . 'class-' . FrameworkBootstrap::getDetectedApplicationName() . '.cache'); + $this->listCacheFile = new SplFileInfo(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('local_database_path') . 'list-' . FrameworkBootstrap::getDetectedApplicationName() . '.cache'); + $this->classCacheFile = new SplFileInfo(FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('local_database_path') . 'class-' . FrameworkBootstrap::getDetectedApplicationName() . '.cache'); } // Set suffix and prefix from configuration - $this->suffix = $configInstance->getConfigEntry('class_suffix'); - $this->prefix = $configInstance->getConfigEntry('class_prefix'); + $this->suffix = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('class_suffix'); + $this->prefix = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('class_prefix'); // Set own instance self::$selfInstance = $this; // Skip here if no dev-mode - if (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('developer_mode_enabled')) { + if (self::$developerModeEnabled) { return; } // Is the cache there? if (FrameworkBootstrap::isReadableFile($this->listCacheFile)) { // Load and convert it - $this->foundClasses = json_decode(file_get_contents($this->listCacheFile->getPathname())); + $this->pendingFiles = json_decode(file_get_contents($this->listCacheFile->getPathname())); // List has been restored from cache! $this->listCached = true; @@ -541,25 +548,19 @@ class ClassLoader { } /** - * 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. + * Tries to find the given class in our list. It will ignore already loaded + * (to the program available) class/interface/trait files. But you SHOULD + * save below "expensive" code by pre-checking this->loadedClasses[], if + * possible. * * @param $className The class that shall be loaded * @return void - * @throws InvalidArgumentException If strict-checking is enabled and class name is not following naming-convention */ private function loadClassFile (string $className) { // The class name should contain at least 2 back-slashes, so split at them //* NOISY-DEBUG: */ printf('[%s:%d] className=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $className); $classNameParts = explode("\\", $className); - // At least 3 parts should be there - if ((self::$strictNamingConvention === true) && (count($classNameParts) < 5)) { - // Namespace scheme is: Project\Package[\SubPackage...] - throw new InvalidArgumentException(sprintf('Class name "%s" is not conform to naming-convention: Tld\Domain\Project\Package[\SubPackage...]\SomeFooBar', $className)); - } - // Get last element $shortClassName = array_pop($classNameParts); @@ -567,33 +568,36 @@ class ClassLoader { $fileName = sprintf('%s%s%s', $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]->getPathname()]))) { + //* NOISY-DEBUG: */ printf('[%s:%d] ISSET: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName); + if ((isset($this->pendingFiles[$fileName])) && (!isset($this->loadedClasses[$this->pendingFiles[$fileName]->getPathname()]))) { // File is found and not loaded so load it only once - //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - START' . PHP_EOL, __METHOD__, __LINE__, $fileName); - FrameworkBootstrap::loadInclude($this->foundClasses[$fileName]); - //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - END' . PHP_EOL, __METHOD__, __LINE__, $fileName); + //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: fileName=%s - START' . PHP_EOL, __METHOD__, __LINE__, $fileName); + FrameworkBootstrap::loadInclude($this->pendingFiles[$fileName]); + //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: fileName=%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]->getPathname()] = true; + $this->loadedClasses[$this->pendingFiles[$fileName]->getPathname()] = 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]); + //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName); + unset($this->pendingFiles[$fileName]); // Developer mode excludes caching (better debugging) - if (!FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('developer_mode_enabled')) { + if (!self::$developerModeEnabled) { // Reset cache //* NOISY-DEBUG: */ printf('[%s:%d] classesCached=false' . PHP_EOL, __METHOD__, __LINE__); $this->classesCached = false; } } else { // Not found - //* NOISY-DEBUG: */ printf('[%s:%d] 404: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName); + //* NOISY-DEBUG: */ printf('[%s:%d] 404: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName); } + + // Trace message + //* NOISY-DEBUG: */ printf('[%s:%d] EXIT!' . PHP_EOL, __METHOD__, __LINE__); } }