]> git.mxchange.org Git - core.git/blob - framework/loader/class_ClassLoader.php
Continued:
[core.git] / framework / loader / class_ClassLoader.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Loader;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
8
9 // Import SPL stuff
10 use \InvalidArgumentException;
11 use \RecursiveDirectoryIterator;
12 use \RecursiveIteratorIterator;
13 use \SplFileInfo;
14 use \UnexpectedValueException;
15
16 /**
17  * This class loads class include files with a specific prefix and suffix
18  *
19  * @author              Roland Haeder <webmaster@shipsimu.org>
20  * @version             1.7.0
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
24  *
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.
29  *
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.
34  *
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/>.
37  *
38  * ----------------------------------
39  * 1.7.0
40  *  - "Cached" more like config instance and root/application base path for
41  *    shorter call stacks and lesser methods invoked
42  *  - More debug logging
43  * 1.6.0
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
51  * 1.5.0
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.
55  * 1.4.0
56  *  - Some comments improved, other minor improvements
57  * 1.3.0
58  *  - Constructor is now empty and factory method 'createClassLoader' is created
59  *  - renamed loadClasses to scanClassPath
60  *  - Added initLoader()
61  * 1.2.0
62  *  - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class
63  * 1.1.0
64  *  - loadClasses rewritten to fix some notices
65  * 1.0.0
66  *  - Initial release
67  * ----------------------------------
68  */
69 final class ClassLoader {
70         /**
71          * Instance of this class
72          */
73         private static $selfInstance = NULL;
74
75         /**
76          * Instance of a FrameworkConfiguration class
77          */
78         private static $configInstance = NULL;
79
80         /**
81          * Cached configuration entry 'is_developer_mode_enabled'
82          */
83         private static $developerModeEnabled = NULL;
84
85         /**
86          * Array with all valid but pending for loading file names (class,
87          * interfaces, traits must start with below prefix).
88          */
89         private $pendingFiles = [];
90
91         /**
92          * List of loaded classes
93          */
94         private $loadedClasses = [];
95
96         /**
97          * Suffix with extension for all class files
98          */
99         private $prefix = 'class_';
100
101         /**
102          * Suffix with extension for all class files
103          */
104         private $suffix = '.php';
105
106         /**
107          * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner
108          * @see scanLocalPath
109          */
110         private $ignoreList = [];
111
112         /**
113          * Debug this class loader? (true = yes, false = no)
114          */
115         private $debug = false;
116
117         /**
118          * Whether the file list is cached
119          */
120         private $listCached = false;
121
122         /**
123          * Wethe class content has been cached
124          */
125         private $classesCached = false;
126
127         /**
128          * SplFileInfo for the list cache
129          */
130         private $listCacheFile = NULL;
131
132         /**
133          * SplFileInfo for class content
134          */
135         private $classCacheFile = NULL;
136
137         /**
138          * Counter for loaded include files
139          */
140         private $total = 0;
141
142         /**
143          * By default the class loader is strict with naming-convention check
144          */
145         private static $strictNamingConvention = true;
146
147         /**
148          * Framework/application paths for classes, etc.
149          */
150         private static $frameworkPaths = [
151                 'classes',    // Classes
152                 'exceptions', // Exceptions
153                 'interfaces', // Interfaces
154                 'middleware', // The middleware
155                 'traits',     // Traits
156         ];
157
158         /**
159          * Registered paths where test classes can be found. These are all relative
160          * to base_path .
161          */
162         private static $testPaths = [];
163
164         /**
165          * Cached includes that needs to be flushed
166          */
167         private $flushCache = [];
168
169         /**
170          * The protected constructor. Please use the factory method below, or use
171          * getSelfInstance() for singleton
172          *
173          * @return      void
174          */
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();
181
182                         // Cache config entry
183                         self::$developerModeEnabled = self::$configInstance->isEnabled('developer_mode');
184                 }
185
186                 // Trace message
187                 //* NOISY-DEBUG: */ printf('[%s:%d]: self::developerModeEnabled=%d - EXIT!' . PHP_EOL, __METHOD__, __LINE__, intval(self::$developerModeEnabled));
188         }
189
190         /**
191          * The destructor makes it sure all caches got flushed
192          *
193          * @return      void
194          */
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__);
201                         return;
202                 }
203
204                 // Init content
205                 $cacheContent = '';
206
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);
212
213                         // Open cache instance
214                         //* NOISY-DEBUG: */ printf('[%s:%d]: cacheContent()=%d' . PHP_EOL, __METHOD__, __LINE__, strlen($cacheContent));
215                         $fileObject = $this->listCacheFile->openFile('w');
216
217                         // And write whole list
218                         $fileObject->fwrite($cacheContent);
219
220                         // Close it
221                         //* NOISY-DEBUG: */ printf('[%s:%d]: Closing file %s ...' . PHP_EOL, __METHOD__, __LINE__, $fileObject->getPathName());
222                         unset($fileObject);
223                 }
224
225                 // Init content
226                 $cacheContent = '';
227
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) {
234                                 // Open file
235                                 //* NOISY-DEBUG: */ printf('[%s:%d]: key=%s,fileInstance[]=%s' . PHP_EOL, __METHOD__, __LINE__, $key, gettype($fileInstance));
236                                 $fileObject = $fileInstance->openFile('r');
237
238                                 // Load the file
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());
242                         }
243
244                         // Open file
245                         //* NOISY-DEBUG: */ printf('[%s:%d]: cacheContent()=%d' . PHP_EOL, __METHOD__, __LINE__, strlen($cacheContent));
246                         $fileObject = $this->classCacheFile->openFile('w');
247
248                         // And write it away
249                         $fileObject->fwrite($cacheContent);
250
251                         // Close it
252                         //* NOISY-DEBUG: */ printf('[%s:%d]: Closing file %s ...' . PHP_EOL, __METHOD__, __LINE__, $fileObject->getPathName());
253                         unset($fileObject);
254                 }
255
256                 // Trace message
257                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
258         }
259
260         /**
261          * Scans for all framework classes, exceptions and interfaces.
262          *
263          * @return      void
264          */
265         public static function scanFrameworkClasses () {
266                 // Get loader instance
267                 //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__);
268                 $loaderInstance = self::getSelfInstance();
269
270                 // "Cache" configuration instance and framework base path
271                 $frameworkBasePath = self::$configInstance->getConfigEntry('framework_base_path');
272
273                 // Load all classes
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(
279                                 '%smain%s%s%s',
280                                 $frameworkBasePath,
281                                 DIRECTORY_SEPARATOR,
282                                 $shortPath,
283                                 DIRECTORY_SEPARATOR
284                         ));
285
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
290                                 continue;
291                         } elseif (!is_readable($realPathName)) {
292                                 // @TODO Throw exception instead of break
293                                 break;
294                         }
295
296                         // Try to load the framework classes
297                         $loaderInstance->scanClassPath($realPathName);
298                 }
299
300                 // Trace message
301                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
302         }
303
304         /**
305          * Scans for application's classes, etc.
306          *
307          * @return      void
308          * @throws      UnexpectedValueException        If a given path isn't one or not readable
309          */
310         public static function scanApplicationClasses () {
311                 // Get loader instance
312                 //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__);
313                 $loaderInstance = self::getSelfInstance();
314
315                 // "Cache" application base path
316                 $basePath = self::$configInstance->getConfigEntry('application_base_path');
317
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) {
321                         // Create path name
322                         //* NOISY-DEBUG: */ printf('[%s:%d]: shortPath=%s' . PHP_EOL, __METHOD__, __LINE__, $shortPath);
323                         $realPathName = realpath(sprintf(
324                                 '%s%s%s%s%s',
325                                 $basePath,
326                                 DIRECTORY_SEPARATOR,
327                                 FrameworkBootstrap::getDetectedApplicationName(),
328                                 DIRECTORY_SEPARATOR,
329                                 $shortPath
330                         ));
331
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)) {
335                                 // Skip this cone
336                                 //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName[]=%s - SKIPPED!' . PHP_EOL, __METHOD__, __LINE__, gettype($realPathName));
337                                 continue;
338                         } elseif (!is_dir($realPathName)) {
339                                 // Is not a directory
340                                 throw new UnexpectedValueException(sprintf('realPathName=%s is not a directory', $realPathName), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
341                         } elseif (!is_readable($realPathName)) {
342                                 // Not readable
343                                 throw new UnexpectedValueException(sprintf('realPathName=%s cannot be read from', $realPathName), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
344                         }
345
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);
349                 }
350
351                 // Trace message
352                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
353         }
354
355         /**
356          * Enables or disables strict naming-convention tests on class loading
357          *
358          * @param       $strictNamingConvention Whether to strictly check naming-convention
359          * @return      void
360          */
361         public static function enableStrictNamingConventionCheck (bool $strictNamingConvention = true) {
362                 self::$strictNamingConvention = $strictNamingConvention;
363         }
364
365         /**
366          * Autoload-function
367          *
368          * @param       $className      Name of the class to load
369          * @return      void
370          * @throws      InvalidArgumentException        If the class' name does not contain a namespace: Tld\Domain\Project is AT LEAST recommended!
371          */
372         public static function autoLoad (string $className) {
373                 // Validate parameter
374                 //* NOISY-DEBUG: */ printf('[%s:%d] className=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $className);
375                 if (empty($className)) {
376                         // Should not be empty
377                         throw new InvalidArgumentException('Parameter "className" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
378                 }
379
380                 // The class name MUST be at least Tld\Domain\Project\Package\SomeFooBar so split at them
381                 $classNameParts = explode("\\", $className);
382
383                 // At least 3 parts should be there
384                 //* NOISY-DEBUG: */ printf('[%s:%d]: self::strictNamingConvention=%d,classNameParts()=%d' . PHP_EOL, __METHOD__, __LINE__, intval(self::$strictNamingConvention), count($classNameParts));
385                 if ((self::$strictNamingConvention === true) && (count($classNameParts) < 5)) {
386                         // Namespace scheme is: Tld\Domain\Project\Package[\SubPackage...]
387                         throw new InvalidArgumentException(sprintf('Class name "%s" is not conform to naming-convention: Tld\Domain\Project\Package[\SubPackage...]\SomeFooBar', $className));
388                 }
389
390                 // Try to include this class
391                 //* NOISY-DEBUG: */ printf('[%s:%d]: Invoking self->loadClassFile(%s) ...' . PHP_EOL, __METHOD__, __LINE__, $className);
392                 self::getSelfInstance()->loadClassFile($className);
393
394                 // Trace message
395                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
396         }
397
398         /**
399          * Singleton getter for an instance of this class
400          *
401          * @return      $selfInstance   A singleton instance of this class
402          */
403         public static final function getSelfInstance () {
404                 // Is the instance there?
405                 //* NOISY-DEBUG: */ printf('[%s:%d]: self::selfInstance[]=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, gettype(self::$selfInstance));
406                 if (is_null(self::$selfInstance)) {
407                         // Get a new one and initialize it
408                         //* NOISY-DEBUG: */ printf('[%s:%d]: Initializing class loader ...' . PHP_EOL, __METHOD__, __LINE__);
409                         self::$selfInstance = new ClassLoader();
410                         self::$selfInstance->initClassLoader();
411                 }
412
413                 // Return the instance
414                 //* NOISY-DEBUG: */ printf('[%s:%d]: self::selfInstance=%s - EXIT!' . PHP_EOL, __METHOD__, __LINE__, get_class(self::$selfInstance));
415                 return self::$selfInstance;
416         }
417
418         /**
419          * Getter for total include counter
420          *
421          * @return      $total  Total loaded include files
422          */
423         public final function getTotal () {
424                 return $this->total;
425         }
426
427         /**
428          * Getter for a printable list of included main/interfaces/exceptions
429          *
430          * @param       $includeList    A printable include list
431          */
432         public function getPrintableIncludeList () {
433                 // Prepare the list
434                 $includeList = '';
435                 foreach (array_keys($this->loadedClasses) as $classFile) {
436                         $includeList .= basename($classFile) . '<br />' . PHP_EOL;
437                 }
438
439                 // And return it
440                 return $includeList;
441         }
442
443         /**
444          * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix
445          *
446          * @param       $basePath               The relative base path to 'framework_base_path' constant for all classes
447          * @param       $ignoreList             An optional list (array forced) of directory and file names which shall be ignored
448          * @return      void
449          * @throws      InvalidArgumentException If a parameter is invalid
450          */
451         protected function scanClassPath (string $basePath, array $ignoreList = [] ) {
452                 // Is a list has been restored from cache, don't read it again
453                 //* NOISY-DEBUG: */ printf('[%s:%d] basePath=%s,ignoreList()=%d - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $basePath, count($ignoreList));
454                 if (empty($basePath)) {
455                         // Throw IAE
456                         throw new InvalidArgumentException('Parameter "basePath" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
457                 } elseif ($this->listCached === true) {
458                         // Abort here
459                         //* NOISY-DEBUG: */ printf('[%s:%d] this->listCache=true - EXIT!' . PHP_EOL, __METHOD__, __LINE__);
460                         return;
461                 }
462
463                 // Keep it in class for later usage, but flip index<->value
464                 $this->ignoreList = array_flip($ignoreList);
465
466                 /*
467                  * Set base directory which holds all our classes, an absolute path
468                  * should be used here so is_dir(), is_file() and so on will always
469                  * find the correct files and dirs.
470                  */
471                 $basePath = realpath($basePath);
472
473                 // If the basePath is false it is invalid
474                 //* NOISY-DEBUG: */ printf('[%s:%d] basePath[%s]=%s' . PHP_EOL, __METHOD__, __LINE__, gettype($basePath), $basePath);
475                 if (!is_string($basePath)) {
476                         /* @TODO: Do not exit here. */
477                         exit(__METHOD__ . ': Cannot read ' . $basePath . ' !' . PHP_EOL);
478                 }
479
480                 // Get a new iterator
481                 //* NOISY-DEBUG: */ printf('[%s:%d] basePath=%s' . PHP_EOL, __METHOD__, __LINE__, $basePath);
482                 $iteratorInstance = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath), RecursiveIteratorIterator::CHILD_FIRST);
483
484                 // Load all entries
485                 while ($iteratorInstance->valid()) {
486                         // Get current entry
487                         $currentEntry = $iteratorInstance->current();
488
489                         // Get filename from iterator which is the class' name (according naming-convention)
490                         //* NOISY-DEBUG: */ printf('[%s:%d] currentEntry=%s,currentEntry->size=%d' . PHP_EOL, __METHOD__, __LINE__, $currentEntry->__toString(), $currentEntry->getSize());
491                         $fileName = $currentEntry->getFilename();
492
493                         // Current entry must be a file, not smaller than 100 bytes and not on ignore list
494                         //* NOISY-DEBUG: */ printf('[%s:%d] fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
495                         if (!$currentEntry->isFile() || isset($this->ignoreList[$fileName]) || $currentEntry->getSize() < 100) {
496                                 // Advance to next entry
497                                 $iteratorInstance->next();
498
499                                 // Skip non-file entries
500                                 //* NOISY-DEBUG: */ printf('[%s:%d] SKIP: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
501                                 continue;
502                         }
503
504                         // Is this file wanted?
505                         //* NOISY-DEBUG: */ printf('[%s:%d] FOUND: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
506                         if ((substr($fileName, 0, strlen($this->prefix)) == $this->prefix) && (substr($fileName, -strlen($this->suffix), strlen($this->suffix)) == $this->suffix)) {
507                                 // Add it to the list
508                                 //* NOISY-DEBUG: */ printf('[%s:%d] ADD: fileName=%s,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry);
509                                 $this->pendingFiles[$fileName] = $currentEntry;
510                         } else {
511                                 // Not added
512                                 //* NOISY-DEBUG: */ printf('[%s:%d] NOT ADDED: fileName=%s,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry);
513                         }
514
515                         // Advance to next entry
516                         //* NOISY-DEBUG: */ printf('[%s:%d] NEXT: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
517                         $iteratorInstance->next();
518                 }
519
520                 // Trace message
521                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
522         }
523
524         /**
525          * Initializes our loader class
526          *
527          * @return      void
528          */
529         private function initClassLoader () {
530                 // Set suffix and prefix from configuration
531                 //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__);
532                 $this->suffix = self::$configInstance->getConfigEntry('class_suffix');
533                 $this->prefix = self::$configInstance->getConfigEntry('class_prefix');
534
535                 // Set own instance
536                 //* NOISY-DEBUG: */ printf('[%s:%d]: this->suffix=%s,this->prefix=%s' . PHP_EOL, __METHOD__, __LINE__, $this->suffix, $this->prefix);
537                 self::$selfInstance = $this;
538
539                 // Skip here if no dev-mode
540                 //* NOISY-DEBUG: */ printf('[%s:%d]: self::developerModeEnabled=%d' . PHP_EOL, __METHOD__, __LINE__, intval(self::$developerModeEnabled));
541                 if (self::$developerModeEnabled) {
542                         // Developer mode is enabled
543                         //* NOISY-DEBUG: */ printf('[%s:%d]: Developer mode is enabled - EXIT!' . PHP_EOL, __METHOD__, __LINE__);
544                         return;
545                 }
546
547                 // Init cache instances
548                 //* NOISY-DEBUG: */ printf('[%s:%d]: Initializing cache file instances ...' . PHP_EOL, __METHOD__, __LINE__);
549                 $this->listCacheFile  = new SplFileInfo(self::$configInstance->getConfigEntry('local_database_path') . 'list-' . FrameworkBootstrap::getDetectedApplicationName() . '.cache');
550                 $this->classCacheFile = new SplFileInfo(self::$configInstance->getConfigEntry('local_database_path') . 'class-' . FrameworkBootstrap::getDetectedApplicationName() . '.cache');
551
552                 // Is the cache there?
553                 //* NOISY-DEBUG: */ printf('[%s:%d]: Checking this->listCacheFile=%s ...' . PHP_EOL, __METHOD__, __LINE__, $this->listCacheFile);
554                 if (FrameworkBootstrap::isReadableFile($this->listCacheFile)) {
555                         // Load and convert it
556                         //* NOISY-DEBUG: */ printf('[%s:%d]: Loading %s ...' . PHP_EOL, __METHOD__, __LINE__, $this->listCacheFile);
557                         $this->pendingFiles = json_decode(file_get_contents($this->listCacheFile->getPathname()));
558
559                         // List has been restored from cache!
560                         $this->listCached = true;
561                 }
562
563                 // Does the class cache exist?
564                 //* NOISY-DEBUG: */ printf('[%s:%d]: Checking this->classCacheFile=%s ...' . PHP_EOL, __METHOD__, __LINE__, $this->classCacheFile);
565                 if (FrameworkBootstrap::isReadableFile($this->classCacheFile)) {
566                         // Then include it
567                         //* NOISY-DEBUG: */ printf('[%s:%d]: Invoking FrameworkBootstrap::loadInclude(%s) ...' . PHP_EOL, __METHOD__, __LINE__, $this->classCacheFile);
568                         FrameworkBootstrap::loadInclude($this->classCacheFile);
569
570                         // Mark the class cache as loaded
571                         $this->classesCached = true;
572                 }
573
574                 // Trace message
575                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
576         }
577
578         /**
579          * Tries to find the given class in our list. It will ignore already loaded 
580          * (to the program available) class/interface/trait files. But you SHOULD
581          * save below "expensive" code by pre-checking this->loadedClasses[], if
582          * possible.
583          *
584          * @param       $className      The class that shall be loaded
585          * @return      void
586          */
587         private function loadClassFile (string $className) {
588                 // The class name should contain at least 2 back-slashes, so split at them
589                 //* NOISY-DEBUG: */ printf('[%s:%d]: className=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $className);
590                 $classNameParts = explode("\\", $className);
591
592                 // Get last element
593                 //* NOISY-DEBUG: */ printf('[%s:%d]: classNameParts()=%d' . PHP_EOL, __METHOD__, __LINE__, count($classNameParts));
594                 $shortClassName = array_pop($classNameParts);
595
596                 // Create a name with prefix and suffix
597                 //* NOISY-DEBUG: */ printf('[%s:%d]: this->prefix=%s,shortClassName=%s,this->suffix=%s - CALLED!' . PHP_EOL, __METHOD__, __LINE__, $this->prefix, $shortClassName, $this->suffix);
598                 $fileName = sprintf('%s%s%s', $this->prefix, $shortClassName, $this->suffix);
599
600                 // Now look it up in our index
601                 //* DEBUG-DIE: */ die(sprintf('[%s:%d]: this->pendingFiles=%s', __METHOD__, __LINE__, print_r($this->pendingFiles, TRUE)));
602                 //* NOISY-DEBUG: */ printf('[%s:%d] ISSET: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
603                 if ((isset($this->pendingFiles[$fileName])) && (!isset($this->loadedClasses[$this->pendingFiles[$fileName]->getPathname()]))) {
604                         // File is found and not loaded so load it only once
605                         //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: fileName=%s - START' . PHP_EOL, __METHOD__, __LINE__, $fileName);
606                         FrameworkBootstrap::loadInclude($this->pendingFiles[$fileName]);
607                         //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: fileName=%s - END' . PHP_EOL, __METHOD__, __LINE__, $fileName);
608
609                         // Count this loaded class/interface/exception
610                         $this->total++;
611
612                         // Mark this class as loaded for other purposes than loading it.
613                         //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: fileName=%s marked as loaded ...' . PHP_EOL, __METHOD__, __LINE__, $fileName);
614                         $this->loadedClasses[$this->pendingFiles[$fileName]->getPathname()] = true;
615
616                         // Developer mode excludes caching (better debugging)
617                         //* NOISY-DEBUG: */ printf('[%s:%d] self::developerModeEnabled=%d' . PHP_EOL, __METHOD__, __LINE__, intval(self::$developerModeEnabled));
618                         if (!self::$developerModeEnabled) {
619                                 // Reset cache and mark file for flushing
620                                 //* NOISY-DEBUG: */ printf('[%s:%d] Setting this->classesCached=false ...' . PHP_EOL, __METHOD__, __LINE__);
621                                 $this->classesCached = false;
622                                 $this->flushCache[$fileName] = $this->pendingFiles[$fileName];
623                         }
624
625                         // Remove it from classes list so it won't be found twice.
626                         //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
627                         unset($this->pendingFiles[$fileName]);
628                 } else {
629                         // Not found
630                         //* NOISY-DEBUG: */ printf('[%s:%d] 404: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
631                 }
632
633                 // Trace message
634                 //* NOISY-DEBUG: */ printf('[%s:%d] EXIT!' . PHP_EOL, __METHOD__, __LINE__);
635         }
636
637 }