3 namespace CoreFramework\Loader;
5 // Import framework stuff
6 use CoreFramework\Configuration\FrameworkConfiguration;
7 use CoreFramework\Object\BaseFrameworkSystem;
10 use \RecursiveDirectoryIterator;
11 use \RecursiveIteratorIterator;
14 * This class loads class include files with a specific prefix and suffix
16 * @author Roland Haeder <webmaster@shipsimu.org>
18 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team
19 * @license GNU GPL 3.0 or any newer version
20 * @link http://www.shipsimu.org
22 * This program is free software: you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation, either version 3 of the License, or
25 * (at your option) any later version.
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
32 * You should have received a copy of the GNU General Public License
33 * along with this program. If not, see <http://www.gnu.org/licenses/>.
35 * ----------------------------------
37 * - Some comments improved, other minor improvements
39 * - Constructor is now empty and factory method 'createClassLoader' is created
40 * - renamed loadClasses to scanClassPath
41 * - Added initLoader()
43 * - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class
45 * - loadClasses rewritten to fix some notices
48 * ----------------------------------
52 * Instance of this class
54 private static $selfInstance = NULL;
57 * Array with all classes
59 private $classes = array();
62 * List of loaded classes
64 private $loadedClasses = array();
67 * Suffix with extension for all class files
69 private $prefix = 'class_';
72 * Suffix with extension for all class files
74 private $suffix = '.php';
77 * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner
80 private $ignoreList = array();
83 * Debug this class loader? (TRUE = yes, FALSE = no)
85 private $debug = FALSE;
88 * Whether the file list is cached
90 private $listCached = FALSE;
93 * Wethe class content has been cached
95 private $classesCached = FALSE;
98 * Filename for the list cache
100 private $listCacheFQFN = '';
103 * Cache for class content
105 private $classCacheFQFN = '';
108 * Counter for loaded include files
113 * Framework/application paths for classes, etc.
115 private static $frameworkPaths = array(
116 'exceptions', // Exceptions
117 'interfaces', // Interfaces
118 'classes', // Classes
119 'middleware' // The middleware
124 * The protected constructor. Please use the factory method below, or use
125 * getSelfInstance() for singleton
129 protected function __construct () {
130 // Is currently empty
134 * The destructor makes it sure all caches got flushed
138 public function __destruct () {
139 // Skip here if dev-mode
140 if (defined('DEVELOPER')) {
144 // Skip here if already cached
145 if ($this->listCached === FALSE) {
146 // Writes the cache file of our list away
147 $cacheContent = json_encode($this->classes);
148 file_put_contents($this->listCacheFQFN, $cacheContent);
151 // Skip here if already cached
152 if ($this->classesCached === FALSE) {
153 // Generate a full-cache of all classes
155 foreach ($this->loadedClasses as $fqfn) {
157 $cacheContent .= file_get_contents($fqfn);
161 file_put_contents($this->classCacheFQFN, $cacheContent);
166 * Creates an instance of this class loader for given configuration instance
168 * @param $configInstance Configuration class instance
171 public static final function createClassLoader (FrameworkConfiguration $configInstance) {
172 // Get a new instance
173 $loaderInstance = new ClassLoader();
176 $loaderInstance->initLoader($configInstance);
178 // Return the prepared instance
179 return $loaderInstance;
183 * Scans for all framework classes, exceptions and interfaces.
187 public static function scanFrameworkClasses () {
188 // Cache loader instance
189 $loaderInstance = self::getSelfInstance();
192 foreach (self::$frameworkPaths as $pathName) {
193 // Try to load the framework classes
194 $loaderInstance->scanClassPath('inc/main/' . $pathName . '/');
199 * Scans for application's classes, etc.
203 public static function scanApplicationClasses () {
204 // Get config instance
205 $cfg = FrameworkConfiguration::getSelfInstance();
207 // Load all classes for the application
208 foreach (self::$frameworkPaths as $class) {
210 $path = sprintf('%s/%s/%s', $cfg->getConfigEntry('application_path'), $cfg->getConfigEntry('app_name'), $class);
212 // Is the path readable?
214 // Try to load the application classes
215 ClassLoader::getSelfInstance()->scanClassPath($path);
221 * Initializes our loader class
223 * @param $configInstance Configuration class instance
226 protected function initLoader (FrameworkConfiguration $configInstance) {
227 // Set configuration instance
228 $this->configInstance = $configInstance;
230 // Construct the FQFN for the cache
231 if (!defined('DEVELOPER')) {
232 $this->listCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'list-' . $this->configInstance->getConfigEntry('app_name') . '.cache';
233 $this->classCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'class-' . $this->configInstance->getConfigEntry('app_name') . '.cache';
236 // Set suffix and prefix from configuration
237 $this->suffix = $configInstance->getConfigEntry('class_suffix');
238 $this->prefix = $configInstance->getConfigEntry('class_prefix');
241 self::$selfInstance = $this;
243 // Skip here if no dev-mode
244 if (defined('DEVELOPER')) {
248 // IS the cache there?
249 if (BaseFrameworkSystem::isReadableFile($this->listCacheFQFN)) {
251 $cacheContent = file_get_contents($this->listCacheFQFN);
254 $this->classes = json_decode($cacheContent);
256 // List has been restored from cache!
257 $this->listCached = TRUE;
260 // Does the class cache exist?
261 if (BaseFrameworkSystem::isReadableFile($this->listCacheFQFN)) {
263 require($this->classCacheFQFN);
265 // Mark the class cache as loaded
266 $this->classesCached = TRUE;
273 * @param $className Name of the class to load
276 public static function autoLoad ($className) {
277 // Try to include this class
278 //* NOISY-DEBUG: */ printf('[%s:%d] className=%s' . PHP_EOL, __METHOD__, __LINE__, $className);
279 self::getSelfInstance()->includeClass($className);
283 * Singleton getter for an instance of this class
285 * @return $selfInstance A singleton instance of this class
287 public static final function getSelfInstance () {
288 // Is the instance there?
289 if (is_null(self::$selfInstance)) {
291 self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getSelfInstance());
294 // Return the instance
295 return self::$selfInstance;
299 * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix
301 * @param $basePath The relative base path to 'base_path' constant for all classes
302 * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored
305 public function scanClassPath ($basePath, array $ignoreList = array() ) {
306 // Is a list has been restored from cache, don't read it again
307 if ($this->listCached === TRUE) {
312 // Keep it in class for later usage
313 $this->ignoreList = $ignoreList;
316 * Ignore .htaccess by default as it is for protection of directories
319 array_push($ignoreList, '.htaccess');
322 * Set base directory which holds all our classes, an absolute path
323 * should be used here so is_dir(), is_file() and so on will always
324 * find the correct files and dirs.
326 $basePath2 = realpath($basePath);
328 // If the basePath is FALSE it is invalid
329 if ($basePath2 === FALSE) {
330 /* @TODO: Do not exit here. */
331 exit(__METHOD__ . ': Cannot read ' . $basePath . ' !' . PHP_EOL);
334 $basePath = $basePath2;
337 // Get a new iterator
338 //* NOISY-DEBUG: */ printf('[%s:%d] basePath=%s' . PHP_EOL, __METHOD__, __LINE__, $basePath);
339 $iteratorInstance = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath), RecursiveIteratorIterator::CHILD_FIRST);
342 while ($iteratorInstance->valid()) {
344 $currentEntry = $iteratorInstance->current();
346 // Get filename from iterator
347 $fileName = $currentEntry->getFileName();
349 // Get the "FQFN" (path and file name)
350 $fqfn = $currentEntry->getRealPath();
352 // Current entry must be a file, not smaller than 100 bytes and not on ignore list
353 if ((!$currentEntry->isFile()) || (in_array($fileName, $this->ignoreList)) || (filesize($fqfn) < 100)) {
354 // Advance to next entry
355 $iteratorInstance->next();
357 // Skip non-file entries
358 //* NOISY-DEBUG: */ printf('[%s:%d] SKIP: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
362 // Is this file wanted?
363 //* NOISY-DEBUG: */ printf('[%s:%d] FOUND: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
364 if ((substr($fileName, 0, strlen($this->prefix)) == $this->prefix) && (substr($fileName, -strlen($this->suffix), strlen($this->suffix)) == $this->suffix)) {
365 // Add it to the list
366 //* NOISY-DEBUG: */ printf('[%s:%d] ADD: %s,fqfn=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $fqfn);
367 $this->classes[$fileName] = $fqfn;
370 //* NOISY-DEBUG: */ printf('[%s:%d] NOT ADDED: %s,fqfn=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $fqfn);
373 // Advance to next entry
374 //* NOISY-DEBUG: */ printf('[%s:%d] NEXT: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
375 $iteratorInstance->next();
380 * Load extra config files
384 public function loadExtraConfigs () {
386 $oldPrefix = $this->prefix;
388 // Set new prefix (temporary!)
389 $this->prefix = 'config-';
391 // Set base directory
392 $basePath = $this->configInstance->getConfigEntry('base_path') . 'inc/config/';
394 // Load all classes from the config directory
395 $this->scanClassPath($basePath);
397 // Include these extra configs now
398 $this->includeExtraConfigs();
400 // Set back the old prefix
401 $this->prefix = $oldPrefix;
405 * Tries to find the given class in our list. This method ignores silently
406 * missing classes or interfaces. So if you use class_exists() this method
407 * does not interrupt your program.
409 * @param $className The class that shall be loaded
412 public function includeClass ($className) {
413 // Create a name with prefix and suffix
414 $fileName = $this->prefix . $className . $this->suffix;
416 // Now look it up in our index
417 //* NOISY-DEBUG: */ printf('[%s:%d] ISSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
418 if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) {
419 // File is found and not loaded so load it only once
420 //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - START' . PHP_EOL, __METHOD__, __LINE__, $fileName);
421 require($this->classes[$fileName]);
422 //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - END' . PHP_EOL, __METHOD__, __LINE__, $fileName);
424 // Count this loaded class/interface/exception
427 // Mark this class as loaded for other purposes than loading it.
428 array_push($this->loadedClasses, $this->classes[$fileName]);
430 // Remove it from classes list so it won't be found twice.
431 //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
432 unset($this->classes[$fileName]);
434 // Developer mode excludes caching (better debugging)
435 if (!defined('DEVELOPER')) {
437 //* NOISY-DEBUG: */ printf('[%s:%d] classesCached=FALSE' . PHP_EOL, __METHOD__, __LINE__);
438 $this->classesCached = FALSE;
442 //* NOISY-DEBUG: */ printf('[%s:%d] 404: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
447 * Includes all extra config files
451 private function includeExtraConfigs () {
452 // Run through all class names (should not be much)
453 foreach ($this->classes as $fileName => $fqfn) {
455 if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) {
459 // Remove it from the list
460 //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
461 unset($this->classes[$fileName]);
467 * Getter for total include counter
469 * @return $total Total loaded include files
471 public final function getTotal () {
476 * Getter for a printable list of included main/interfaces/exceptions
478 * @param $includeList A printable include list
480 public function getPrintableIncludeList () {
483 foreach ($this->loadedClasses as $classFile) {
484 $includeList .= basename($classFile) . '<br />' . PHP_EOL;