3 namespace Org\Mxchange\CoreFramework\Loader;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
10 use \InvalidArgumentException;
11 use \RecursiveDirectoryIterator;
12 use \RecursiveIteratorIterator;
14 use \UnexpectedValueException;
17 * This class loads class include files with a specific prefix and suffix
19 * @author Roland Haeder <webmaster@shipsimu.org>
21 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
22 * @license GNU GPL 3.0 or any newer version
23 * @link http://www.shipsimu.org
25 * This program is free software: you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation, either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program. If not, see <http://www.gnu.org/licenses/>.
38 * ----------------------------------
40 * - "Cached" more like config instance and root/application base path for
41 * shorter call stacks and lesser methods invoked
42 * - More debug logging
44 * - This class loader is now 100% singleton, no other instance is really
45 * required, therefore the factory method can be removed safely
46 * - renamed initLoader() to initClassLoader()
47 * - An instance of a FrameworkConfiguration is no longer set here as this
48 * violated the rule that there shall be no instance set of that class
49 * - .htaccess is marked as deprecated as this should be no longer done
50 * - scanClassPath() is now protected, please use other scan*() methods
52 * - Namespace scheme Project\Package[\SubPackage...] is fully supported and
53 * throws an InvalidArgumentException if not present. The last part will be
54 * always the class' name.
56 * - Some comments improved, other minor improvements
58 * - Constructor is now empty and factory method 'createClassLoader' is created
59 * - renamed loadClasses to scanClassPath
60 * - Added initLoader()
62 * - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class
64 * - loadClasses rewritten to fix some notices
67 * ----------------------------------
69 final class ClassLoader {
71 * Instance of this class
73 private static $selfInstance = NULL;
76 * Instance of a FrameworkConfiguration class
78 private static $configInstance = NULL;
81 * Cached configuration entry 'is_developer_mode_enabled'
83 private static $developerModeEnabled = NULL;
86 * Array with all valid but pending for loading file names (class,
87 * interfaces, traits must start with below prefix).
89 private $pendingFiles = [];
92 * List of loaded classes
94 private $loadedClasses = [];
97 * Suffix with extension for all class files
99 private $prefix = 'class_';
102 * Suffix with extension for all class files
104 private $suffix = '.php';
107 * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner
110 private $ignoreList = [];
113 * Debug this class loader? (true = yes, false = no)
115 private $debug = false;
118 * Whether the file list is cached
120 private $listCached = false;
123 * Wethe class content has been cached
125 private $classesCached = false;
128 * SplFileInfo for the list cache
130 private $listCacheFile = NULL;
133 * SplFileInfo for class content
135 private $classCacheFile = NULL;
138 * Counter for loaded include files
143 * By default the class loader is strict with naming-convention check
145 private static $strictNamingConvention = true;
148 * Framework/application paths for classes, etc.
150 private static $frameworkPaths = [
151 'classes', // Classes
152 'exceptions', // Exceptions
153 'interfaces', // Interfaces
154 'middleware', // The middleware
159 * Registered paths where test classes can be found. These are all relative
162 private static $testPaths = [];
165 * Cached includes that needs to be flushed
167 private $flushCache = [];
170 * The protected constructor. Please use the factory method below, or use
171 * getSelfInstance() for singleton
175 private function __construct () {
176 // Is developerModeEnabled set?
177 //* NOISY-DEBUG: */ printf('[%s:%d]: CONSTRUCTED!' . PHP_EOL, __METHOD__, __LINE__);
178 if (is_null(self::$developerModeEnabled)) {
179 // Cache config instance
180 self::$configInstance = FrameworkBootstrap::getConfigurationInstance();
182 // Cache config entry
183 self::$developerModeEnabled = self::$configInstance->isEnabled('developer_mode');
187 //* NOISY-DEBUG: */ printf('[%s:%d]: self::developerModeEnabled=%d - EXIT!' . PHP_EOL, __METHOD__, __LINE__, intval(self::$developerModeEnabled));
191 * The destructor makes it sure all caches got flushed
195 public function __destruct () {
196 // Skip here if dev-mode
197 //* NOISY-DEBUG: */ printf('[%s:%d]: self::developerModeEnabled=%d - DESTRUCTED!' . PHP_EOL, __METHOD__, __LINE__, intval(self::$developerModeEnabled));
198 if (self::$developerModeEnabled) {
199 // Is enabled, don't cache
200 //* NOISY-DEBUG: */ printf('[%s:%d]: Developer mode enabled, not caching classes - EXIT!' . PHP_EOL, __METHOD__, __LINE__);
207 // Skip here if already cached
208 //* NOISY-DEBUG: */ printf('[%s:%d]: this->listCached=%d' . PHP_EOL, __METHOD__, __LINE__, intval($this->listCached));
209 if ($this->listCached === false) {
210 // Writes the cache file of our list away
211 $cacheContent = json_encode($this->pendingFiles);
213 // Open cache instance
214 //* NOISY-DEBUG: */ printf('[%s:%d]: cacheContent()=%d' . PHP_EOL, __METHOD__, __LINE__, strlen($cacheContent));
215 $fileObject = $this->listCacheFile->openFile('w');
217 // And write whole list
218 $fileObject->fwrite($cacheContent);
221 //* NOISY-DEBUG: */ printf('[%s:%d]: Closing file %s ...' . PHP_EOL, __METHOD__, __LINE__, $fileObject->getPathName());
228 // Skip here if already cached
229 //* NOISY-DEBUG: */ printf('[%s:%d]: this->classesCached=%d' . PHP_EOL, __METHOD__, __LINE__, intval($this->classesCached));
230 if ($this->classesCached === false) {
231 // Generate a full-cache of all classes
232 //* NOISY-DEBUG: */ printf('[%s:%d]: this->flushCache()=%d' . PHP_EOL, __METHOD__, __LINE__, count($this->flushCache));
233 foreach ($this->flushCache as $key => $fileInstance) {
235 //* NOISY-DEBUG: */ printf('[%s:%d]: key=%s,fileInstance[]=%s' . PHP_EOL, __METHOD__, __LINE__, $key, gettype($fileInstance));
236 $fileObject = $fileInstance->openFile('r');
239 // @TODO Add some uglifying code (compress) here
240 //* NOISY-DEBUG: */ printf('[%s:%d]: Adding fileInstance->size=%d bytes ...' . PHP_EOL, __METHOD__, __LINE__, $fileInstance->getSize());
241 $cacheContent .= $fileObject->fread($fileInstance->getSize());
245 //* NOISY-DEBUG: */ printf('[%s:%d]: cacheContent()=%d' . PHP_EOL, __METHOD__, __LINE__, strlen($cacheContent));
246 $fileObject = $this->classCacheFile->openFile('w');
249 $fileObject->fwrite($cacheContent);
252 //* NOISY-DEBUG: */ printf('[%s:%d]: Closing file %s ...' . PHP_EOL, __METHOD__, __LINE__, $fileObject->getPathName());
257 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
261 * Scans for all framework classes, exceptions and interfaces.
265 public static function scanFrameworkClasses () {
266 // Get loader instance
267 //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__);
268 $loaderInstance = self::getSelfInstance();
270 // "Cache" configuration instance and framework base path
271 $frameworkBasePath = self::$configInstance->getConfigEntry('framework_base_path');
274 //* NOISY-DEBUG: */ printf('[%s:%d]: frameworkBasePath=%s,self::$frameworkPaths()=%d,' . PHP_EOL, __METHOD__, __LINE__, $frameworkBasePath, count(self::$frameworkPaths));
275 foreach (self::$frameworkPaths as $shortPath) {
276 // Generate full path from it
277 //* NOISY-DEBUG: */ printf('[%s:%d]: shortPath[%s]=%s' . PHP_EOL, __METHOD__, __LINE__, gettype($shortPath), $shortPath);
278 $realPathName = realpath(sprintf(
286 // Is it not false and accessible?
287 //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName[%s]=%s' . PHP_EOL, __METHOD__, __LINE__, gettype($realPathName), $realPathName);
288 if (is_bool($realPathName)) {
289 // Skip this, it is not accessible
291 } elseif (!is_readable($realPathName)) {
292 // @TODO Throw exception instead of break
296 // Try to load the framework classes
297 $loaderInstance->scanClassPath($realPathName);
301 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
305 * Scans for application's classes, etc.
308 * @throws UnexpectedValueException If a given path isn't one or not readable
310 public static function scanApplicationClasses () {
311 // Get loader instance
312 //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__);
313 $loaderInstance = self::getSelfInstance();
315 // "Cache" application base path
316 $basePath = self::$configInstance->getConfigEntry('application_base_path');
318 // Load all classes for the application
319 //* NOISY-DEBUG: */ printf('[%s:%d]: self::frameworkPaths()=%d,basePath=%s' . PHP_EOL, __METHOD__, __LINE__, count(self::$frameworkPaths), $basePath);
320 foreach (self::$frameworkPaths as $shortPath) {
322 //* NOISY-DEBUG: */ printf('[%s:%d]: shortPath=%s' . PHP_EOL, __METHOD__, __LINE__, $shortPath);
323 $realPathName = realpath(sprintf(
327 FrameworkBootstrap::getDetectedApplicationName(),
332 // Is the path readable?
333 //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName[%s]=%s - AFTER!' . PHP_EOL, __METHOD__, __LINE__, gettype($realPathName), $realPathName);
334 if (!is_string($realPathName)) {
336 //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName[]=%s - SKIPPED!' . PHP_EOL, __METHOD__, __LINE__, gettype($realPathName));
338 } elseif (!is_dir($realPathName)) {
339 // Is not a directory
340 throw new UnexpectedValueException(sprintf('realPathName=%s is not a directory', $realPathName));
341 } elseif (!is_readable($realPathName)) {
343 throw new UnexpectedValueException(sprintf('realPathName=%s cannot be read from', $realPathName));
346 // Try to load the application classes
347 //* NOISY-DEBUG: */ printf('[%s:%d]: Scanning for classes/interfaces at realPathName=%s ...' . PHP_EOL, __METHOD__, __LINE__, $realPathName);
348 $loaderInstance->scanClassPath($realPathName);
352 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
356 * Scans for test classes, etc.
359 * @throws UnexpectedValueException If a given path isn't one or not readable
361 public static function scanTestsClasses () {
362 // Get loader instance
363 //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__);
364 $loaderInstance = self::getSelfInstance();
366 // "Cache" root base path
367 $basePath = self::$configInstance->getConfigEntry('root_base_path');
369 // Load all classes for the application
370 //* NOISY-DEBUG: */ printf('[%s:%d]: self::testPaths()=%d,basePath=%s' . PHP_EOL, __METHOD__, __LINE__, count(self::$testPaths), $basePath);
371 foreach (self::$testPaths as $shortPath) {
372 // Construct path name
373 //* NOISY-DEBUG: */ printf('[%s:%d]: shortPath=%s' . PHP_EOL, __METHOD__, __LINE__, $shortPath);
374 $realPathName = realpath(sprintf(
381 // Is the path readable?
382 //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName[%s]=%s - AFTER!' . PHP_EOL, __METHOD__, __LINE__, gettype($realPathName), $realPathName);
383 if (!is_string($realPathName)) {
385 //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName[]=%s - SKIPPED!' . PHP_EOL, __METHOD__, __LINE__, gettype($realPathName));
387 } elseif (!is_dir($realPathName)) {
388 // Is not a directory
389 throw new UnexpectedValueException(sprintf('realPathName=%s is not a directory', $realPathName));
390 } elseif (!is_readable($realPathName)) {
392 throw new UnexpectedValueException(sprintf('realPathName=%s cannot be read from', $realPathName));
395 // Try to load the application classes
396 //* NOISY-DEBUG: */ printf('[%s:%d]: Scanning for classes/interfaces at realPathName=%s ...' . PHP_EOL, __METHOD__, __LINE__, $realPathName);
397 $loaderInstance->scanClassPath($realPathName);
401 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
405 * Enables or disables strict naming-convention tests on class loading
407 * @param $strictNamingConvention Whether to strictly check naming-convention
410 public static function enableStrictNamingConventionCheck (bool $strictNamingConvention = true) {
411 self::$strictNamingConvention = $strictNamingConvention;
415 * Registeres given relative path where test classes reside. For regular
416 * framework uses, they should not be loaded (and used).
418 * @param $relativePath Relative path to test classes
420 * @throws InvalidArgumentException If a parameter is invalid or path not found
422 public static function registerTestsPath (string $relativePath) {
423 // Validate parameter
424 //* NOISY-DEBUG: */ printf('[%s:%d]: relativePath=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $relativePath);
425 if (empty($relativePath)) {
426 // Should not be empty
427 throw new InvalidArgumentException('Parameter "relativePath" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
430 // Get real path from it
431 $fullQualifiedPath = self::$configInstance->getConfigEntry('root_base_path') . $relativePath;
434 //* NOISY-DEBUG: */ printf('[%s:%d]: fullQualifiedPath=%s' . PHP_EOL, __METHOD__, __LINE__, $fullQualifiedPath);
435 if (!is_dir($fullQualifiedPath)) {
437 throw new InvalidArgumentException(sprintf('fullQualifiedPath=%s cannot be found', $fullQualifiedPath));
438 } elseif (!is_readable($fullQualifiedPath)) {
440 throw new InvalidArgumentException(sprintf('fullQualifiedPath=%s is not readable', $fullQualifiedPath));
444 //* NOISY-DEBUG: */ printf('[%s:%d]: Adding relativePath=%s ...' . PHP_EOL, __METHOD__, __LINE__, $relativePath);
445 self::$testPaths[$relativePath] = $relativePath;
448 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
454 * @param $className Name of the class to load
456 * @throws InvalidArgumentException If the class' name does not contain a namespace: Tld\Domain\Project is AT LEAST recommended!
458 public static function autoLoad (string $className) {
459 // Validate parameter
460 //* NOISY-DEBUG: */ printf('[%s:%d] className=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $className);
461 if (empty($className)) {
462 // Should not be empty
463 throw new InvalidArgumentException('Parameter "className" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
466 // The class name MUST be at least Tld\Domain\Project\Package\SomeFooBar so split at them
467 $classNameParts = explode("\\", $className);
469 // At least 3 parts should be there
470 //* NOISY-DEBUG: */ printf('[%s:%d]: self::strictNamingConvention=%d,classNameParts()=%d' . PHP_EOL, __METHOD__, __LINE__, intval(self::$strictNamingConvention), count($classNameParts));
471 if ((self::$strictNamingConvention === true) && (count($classNameParts) < 5)) {
472 // Namespace scheme is: Tld\Domain\Project\Package[\SubPackage...]
473 throw new InvalidArgumentException(sprintf('Class name "%s" is not conform to naming-convention: Tld\Domain\Project\Package[\SubPackage...]\SomeFooBar', $className));
476 // Try to include this class
477 //* NOISY-DEBUG: */ printf('[%s:%d]: Invoking self->loadClassFile(%s) ...' . PHP_EOL, __METHOD__, __LINE__, $className);
478 self::getSelfInstance()->loadClassFile($className);
481 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
485 * Singleton getter for an instance of this class
487 * @return $selfInstance A singleton instance of this class
489 public static final function getSelfInstance () {
490 // Is the instance there?
491 //* NOISY-DEBUG: */ printf('[%s:%d]: self::selfInstance[]=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, gettype(self::$selfInstance));
492 if (is_null(self::$selfInstance)) {
493 // Get a new one and initialize it
494 //* NOISY-DEBUG: */ printf('[%s:%d]: Initializing class loader ...' . PHP_EOL, __METHOD__, __LINE__);
495 self::$selfInstance = new ClassLoader();
496 self::$selfInstance->initClassLoader();
499 // Return the instance
500 //* NOISY-DEBUG: */ printf('[%s:%d]: self::selfInstance=%s - EXIT!' . PHP_EOL, __METHOD__, __LINE__, get_class(self::$selfInstance));
501 return self::$selfInstance;
505 * Getter for total include counter
507 * @return $total Total loaded include files
509 public final function getTotal () {
514 * Getter for a printable list of included main/interfaces/exceptions
516 * @param $includeList A printable include list
518 public function getPrintableIncludeList () {
521 foreach (array_keys($this->loadedClasses) as $classFile) {
522 $includeList .= basename($classFile) . '<br />' . PHP_EOL;
530 * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix
532 * @param $basePath The relative base path to 'framework_base_path' constant for all classes
533 * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored
535 * @throws InvalidArgumentException If a parameter is invalid
537 protected function scanClassPath (string $basePath, array $ignoreList = [] ) {
538 // Is a list has been restored from cache, don't read it again
539 //* NOISY-DEBUG: */ printf('[%s:%d] basePath=%s,ignoreList()=%d - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $basePath, count($ignoreList));
540 if (empty($basePath)) {
542 throw new InvalidArgumentException('Parameter "basePath" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
543 } elseif ($this->listCached === true) {
545 //* NOISY-DEBUG: */ printf('[%s:%d] this->listCache=true - EXIT!' . PHP_EOL, __METHOD__, __LINE__);
549 // Keep it in class for later usage, but flip index<->value
550 $this->ignoreList = array_flip($ignoreList);
553 * Set base directory which holds all our classes, an absolute path
554 * should be used here so is_dir(), is_file() and so on will always
555 * find the correct files and dirs.
557 $basePath = realpath($basePath);
559 // If the basePath is false it is invalid
560 //* NOISY-DEBUG: */ printf('[%s:%d] basePath[%s]=%s' . PHP_EOL, __METHOD__, __LINE__, gettype($basePath), $basePath);
561 if (!is_string($basePath)) {
562 /* @TODO: Do not exit here. */
563 exit(__METHOD__ . ': Cannot read ' . $basePath . ' !' . PHP_EOL);
566 // Get a new iterator
567 //* NOISY-DEBUG: */ printf('[%s:%d] basePath=%s' . PHP_EOL, __METHOD__, __LINE__, $basePath);
568 $iteratorInstance = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath), RecursiveIteratorIterator::CHILD_FIRST);
571 while ($iteratorInstance->valid()) {
573 $currentEntry = $iteratorInstance->current();
575 // Get filename from iterator which is the class' name (according naming-convention)
576 //* NOISY-DEBUG: */ printf('[%s:%d] currentEntry=%s,currentEntry->size=%d' . PHP_EOL, __METHOD__, __LINE__, $currentEntry->__toString(), $currentEntry->getSize());
577 $fileName = $currentEntry->getFilename();
579 // Current entry must be a file, not smaller than 100 bytes and not on ignore list
580 //* NOISY-DEBUG: */ printf('[%s:%d] fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
581 if (!$currentEntry->isFile() || isset($this->ignoreList[$fileName]) || $currentEntry->getSize() < 100) {
582 // Advance to next entry
583 $iteratorInstance->next();
585 // Skip non-file entries
586 //* NOISY-DEBUG: */ printf('[%s:%d] SKIP: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
590 // Is this file wanted?
591 //* NOISY-DEBUG: */ printf('[%s:%d] FOUND: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
592 if ((substr($fileName, 0, strlen($this->prefix)) == $this->prefix) && (substr($fileName, -strlen($this->suffix), strlen($this->suffix)) == $this->suffix)) {
593 // Add it to the list
594 //* NOISY-DEBUG: */ printf('[%s:%d] ADD: fileName=%s,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry);
595 $this->pendingFiles[$fileName] = $currentEntry;
598 //* NOISY-DEBUG: */ printf('[%s:%d] NOT ADDED: fileName=%s,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry);
601 // Advance to next entry
602 //* NOISY-DEBUG: */ printf('[%s:%d] NEXT: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
603 $iteratorInstance->next();
607 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
611 * Initializes our loader class
615 private function initClassLoader () {
616 // Set suffix and prefix from configuration
617 //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__);
618 $this->suffix = self::$configInstance->getConfigEntry('class_suffix');
619 $this->prefix = self::$configInstance->getConfigEntry('class_prefix');
622 //* NOISY-DEBUG: */ printf('[%s:%d]: this->suffix=%s,this->prefix=%s' . PHP_EOL, __METHOD__, __LINE__, $this->suffix, $this->prefix);
623 self::$selfInstance = $this;
625 // Skip here if no dev-mode
626 //* NOISY-DEBUG: */ printf('[%s:%d]: self::developerModeEnabled=%d' . PHP_EOL, __METHOD__, __LINE__, intval(self::$developerModeEnabled));
627 if (self::$developerModeEnabled) {
628 // Developer mode is enabled
629 //* NOISY-DEBUG: */ printf('[%s:%d]: Developer mode is enabled - EXIT!' . PHP_EOL, __METHOD__, __LINE__);
633 // Init cache instances
634 //* NOISY-DEBUG: */ printf('[%s:%d]: Initializing cache file instances ...' . PHP_EOL, __METHOD__, __LINE__);
635 $this->listCacheFile = new SplFileInfo(self::$configInstance->getConfigEntry('local_database_path') . 'list-' . FrameworkBootstrap::getDetectedApplicationName() . '.cache');
636 $this->classCacheFile = new SplFileInfo(self::$configInstance->getConfigEntry('local_database_path') . 'class-' . FrameworkBootstrap::getDetectedApplicationName() . '.cache');
638 // Is the cache there?
639 //* NOISY-DEBUG: */ printf('[%s:%d]: Checking this->listCacheFile=%s ...' . PHP_EOL, __METHOD__, __LINE__, $this->listCacheFile);
640 if (FrameworkBootstrap::isReadableFile($this->listCacheFile)) {
641 // Load and convert it
642 //* NOISY-DEBUG: */ printf('[%s:%d]: Loading %s ...' . PHP_EOL, __METHOD__, __LINE__, $this->listCacheFile);
643 $this->pendingFiles = json_decode(file_get_contents($this->listCacheFile->getPathname()));
645 // List has been restored from cache!
646 $this->listCached = true;
649 // Does the class cache exist?
650 //* NOISY-DEBUG: */ printf('[%s:%d]: Checking this->classCacheFile=%s ...' . PHP_EOL, __METHOD__, __LINE__, $this->classCacheFile);
651 if (FrameworkBootstrap::isReadableFile($this->classCacheFile)) {
653 //* NOISY-DEBUG: */ printf('[%s:%d]: Invoking FrameworkBootstrap::loadInclude(%s) ...' . PHP_EOL, __METHOD__, __LINE__, $this->classCacheFile);
654 FrameworkBootstrap::loadInclude($this->classCacheFile);
656 // Mark the class cache as loaded
657 $this->classesCached = true;
661 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
665 * Tries to find the given class in our list. It will ignore already loaded
666 * (to the program available) class/interface/trait files. But you SHOULD
667 * save below "expensive" code by pre-checking this->loadedClasses[], if
670 * @param $className The class that shall be loaded
673 private function loadClassFile (string $className) {
674 // The class name should contain at least 2 back-slashes, so split at them
675 //* NOISY-DEBUG: */ printf('[%s:%d]: className=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $className);
676 $classNameParts = explode("\\", $className);
679 //* NOISY-DEBUG: */ printf('[%s:%d]: classNameParts()=%d' . PHP_EOL, __METHOD__, __LINE__, count($classNameParts));
680 $shortClassName = array_pop($classNameParts);
682 // Create a name with prefix and suffix
683 //* NOISY-DEBUG: */ printf('[%s:%d]: this->prefix=%s,shortClassName=%s,this->suffix=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $this->prefix, $shortClassName, $this->suffix);
684 $fileName = sprintf('%s%s%s', $this->prefix, $shortClassName, $this->suffix);
686 // Now look it up in our index
687 //* DEBUG-DIE: */ die(sprintf('[%s:%d]: this->pendingFiles=%s', __METHOD__, __LINE__, print_r($this->pendingFiles, TRUE)));
688 //* NOISY-DEBUG: */ printf('[%s:%d] ISSET: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
689 if ((isset($this->pendingFiles[$fileName])) && (!isset($this->loadedClasses[$this->pendingFiles[$fileName]->getPathname()]))) {
690 // File is found and not loaded so load it only once
691 //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: fileName=%s - START' . PHP_EOL, __METHOD__, __LINE__, $fileName);
692 FrameworkBootstrap::loadInclude($this->pendingFiles[$fileName]);
693 //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: fileName=%s - END' . PHP_EOL, __METHOD__, __LINE__, $fileName);
695 // Count this loaded class/interface/exception
698 // Mark this class as loaded for other purposes than loading it.
699 //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: fileName=%s marked as loaded ...' . PHP_EOL, __METHOD__, __LINE__, $fileName);
700 $this->loadedClasses[$this->pendingFiles[$fileName]->getPathname()] = true;
702 // Developer mode excludes caching (better debugging)
703 //* NOISY-DEBUG: */ printf('[%s:%d] self::developerModeEnabled=%d' . PHP_EOL, __METHOD__, __LINE__, intval(self::$developerModeEnabled));
704 if (!self::$developerModeEnabled) {
705 // Reset cache and mark file for flushing
706 //* NOISY-DEBUG: */ printf('[%s:%d] Setting this->classesCached=false ...' . PHP_EOL, __METHOD__, __LINE__);
707 $this->classesCached = false;
708 $this->flushCache[$fileName] = $this->pendingFiles[$fileName];
711 // Remove it from classes list so it won't be found twice.
712 //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
713 unset($this->pendingFiles[$fileName]);
716 //* NOISY-DEBUG: */ printf('[%s:%d] 404: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
720 //* NOISY-DEBUG: */ printf('[%s:%d] EXIT!' . PHP_EOL, __METHOD__, __LINE__);