]> 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          * Scans for test classes, etc.
357          *
358          * @return      void
359          * @throws      UnexpectedValueException        If a given path isn't one or not readable
360          */
361         public static function scanTestsClasses () {
362                 // Get loader instance
363                 //* NOISY-DEBUG: */ printf('[%s:%d]: CALLED!' . PHP_EOL, __METHOD__, __LINE__);
364                 $loaderInstance = self::getSelfInstance();
365
366                 // "Cache" root base path
367                 $basePath = self::$configInstance->getConfigEntry('root_base_path');
368
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(
375                                 '%s%s%s',
376                                 $basePath,
377                                 DIRECTORY_SEPARATOR,
378                                 $shortPath
379                         ));
380
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)) {
384                                 // Skip this cone
385                                 //* NOISY-DEBUG: */ printf('[%s:%d]: realPathName[]=%s - SKIPPED!' . PHP_EOL, __METHOD__, __LINE__, gettype($realPathName));
386                                 continue;
387                         } elseif (!is_dir($realPathName)) {
388                                 // Is not a directory
389                                 throw new UnexpectedValueException(sprintf('realPathName=%s is not a directory', $realPathName), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
390                         } elseif (!is_readable($realPathName)) {
391                                 // Not readable
392                                 throw new UnexpectedValueException(sprintf('realPathName=%s cannot be read from', $realPathName), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
393                         }
394
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);
398                 }
399
400                 // Trace message
401                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
402         }
403
404         /**
405          * Enables or disables strict naming-convention tests on class loading
406          *
407          * @param       $strictNamingConvention Whether to strictly check naming-convention
408          * @return      void
409          */
410         public static function enableStrictNamingConventionCheck (bool $strictNamingConvention = true) {
411                 self::$strictNamingConvention = $strictNamingConvention;
412         }
413
414         /**
415          * Registeres given relative path where test classes reside. For regular
416          * framework uses, they should not be loaded (and used).
417          *
418          * @param       $relativePath   Relative path to test classes
419          * @return      void
420          * @throws      InvalidArgumentException        If a parameter is invalid or path not found
421          */
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);
428                 }
429
430                 // Get real path from it
431                 $fullQualifiedPath = self::$configInstance->getConfigEntry('root_base_path') . $relativePath;
432
433                 // Is it there?
434                 //* NOISY-DEBUG: */ printf('[%s:%d]: fullQualifiedPath=%s' . PHP_EOL, __METHOD__, __LINE__, $fullQualifiedPath);
435                 if (!is_dir($fullQualifiedPath)) {
436                         // Not there
437                         throw new InvalidArgumentException(sprintf('fullQualifiedPath=%s cannot be found', $fullQualifiedPath));
438                 } elseif (!is_readable($fullQualifiedPath)) {
439                         // Not readable
440                         throw new InvalidArgumentException(sprintf('fullQualifiedPath=%s is not readable', $fullQualifiedPath));
441                 }
442
443                 // "Register" it
444                 //* NOISY-DEBUG: */ printf('[%s:%d]: Adding relativePath=%s ...' . PHP_EOL, __METHOD__, __LINE__, $relativePath);
445                 self::$testPaths[$relativePath] = $relativePath;
446
447                 // Trace message
448                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
449         }
450
451         /**
452          * Autoload-function
453          *
454          * @param       $className      Name of the class to load
455          * @return      void
456          * @throws      InvalidArgumentException        If the class' name does not contain a namespace: Tld\Domain\Project is AT LEAST recommended!
457          */
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);
464                 }
465
466                 // The class name MUST be at least Tld\Domain\Project\Package\SomeFooBar so split at them
467                 $classNameParts = explode("\\", $className);
468
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));
474                 }
475
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);
479
480                 // Trace message
481                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
482         }
483
484         /**
485          * Singleton getter for an instance of this class
486          *
487          * @return      $selfInstance   A singleton instance of this class
488          */
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();
497                 }
498
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;
502         }
503
504         /**
505          * Getter for total include counter
506          *
507          * @return      $total  Total loaded include files
508          */
509         public final function getTotal () {
510                 return $this->total;
511         }
512
513         /**
514          * Getter for a printable list of included main/interfaces/exceptions
515          *
516          * @param       $includeList    A printable include list
517          */
518         public function getPrintableIncludeList () {
519                 // Prepare the list
520                 $includeList = '';
521                 foreach (array_keys($this->loadedClasses) as $classFile) {
522                         $includeList .= basename($classFile) . '<br />' . PHP_EOL;
523                 }
524
525                 // And return it
526                 return $includeList;
527         }
528
529         /**
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
531          *
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
534          * @return      void
535          * @throws      InvalidArgumentException If a parameter is invalid
536          */
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)) {
541                         // Throw IAE
542                         throw new InvalidArgumentException('Parameter "basePath" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
543                 } elseif ($this->listCached === true) {
544                         // Abort here
545                         //* NOISY-DEBUG: */ printf('[%s:%d] this->listCache=true - EXIT!' . PHP_EOL, __METHOD__, __LINE__);
546                         return;
547                 }
548
549                 // Keep it in class for later usage, but flip index<->value
550                 $this->ignoreList = array_flip($ignoreList);
551
552                 /*
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.
556                  */
557                 $basePath = realpath($basePath);
558
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);
564                 }
565
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);
569
570                 // Load all entries
571                 while ($iteratorInstance->valid()) {
572                         // Get current entry
573                         $currentEntry = $iteratorInstance->current();
574
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();
578
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();
584
585                                 // Skip non-file entries
586                                 //* NOISY-DEBUG: */ printf('[%s:%d] SKIP: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
587                                 continue;
588                         }
589
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;
596                         } else {
597                                 // Not added
598                                 //* NOISY-DEBUG: */ printf('[%s:%d] NOT ADDED: fileName=%s,currentEntry=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $currentEntry);
599                         }
600
601                         // Advance to next entry
602                         //* NOISY-DEBUG: */ printf('[%s:%d] NEXT: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
603                         $iteratorInstance->next();
604                 }
605
606                 // Trace message
607                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
608         }
609
610         /**
611          * Initializes our loader class
612          *
613          * @return      void
614          */
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');
620
621                 // Set own instance
622                 //* NOISY-DEBUG: */ printf('[%s:%d]: this->suffix=%s,this->prefix=%s' . PHP_EOL, __METHOD__, __LINE__, $this->suffix, $this->prefix);
623                 self::$selfInstance = $this;
624
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__);
630                         return;
631                 }
632
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');
637
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()));
644
645                         // List has been restored from cache!
646                         $this->listCached = true;
647                 }
648
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)) {
652                         // Then include it
653                         //* NOISY-DEBUG: */ printf('[%s:%d]: Invoking FrameworkBootstrap::loadInclude(%s) ...' . PHP_EOL, __METHOD__, __LINE__, $this->classCacheFile);
654                         FrameworkBootstrap::loadInclude($this->classCacheFile);
655
656                         // Mark the class cache as loaded
657                         $this->classesCached = true;
658                 }
659
660                 // Trace message
661                 //* NOISY-DEBUG: */ printf('[%s:%d]: EXIT!' . PHP_EOL, __METHOD__, __LINE__);
662         }
663
664         /**
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
668          * possible.
669          *
670          * @param       $className      The class that shall be loaded
671          * @return      void
672          */
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);
677
678                 // Get last element
679                 //* NOISY-DEBUG: */ printf('[%s:%d]: classNameParts()=%d' . PHP_EOL, __METHOD__, __LINE__, count($classNameParts));
680                 $shortClassName = array_pop($classNameParts);
681
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);
685
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);
694
695                         // Count this loaded class/interface/exception
696                         $this->total++;
697
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;
701
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];
709                         }
710
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]);
714                 } else {
715                         // Not found
716                         //* NOISY-DEBUG: */ printf('[%s:%d] 404: fileName=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
717                 }
718
719                 // Trace message
720                 //* NOISY-DEBUG: */ printf('[%s:%d] EXIT!' . PHP_EOL, __METHOD__, __LINE__);
721         }
722
723 }