3 * This class loads class include files with a specific prefix and suffix
5 * @author Roland Haeder <webmaster@shipsimu.org>
7 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Core Developer Team
8 * @license GNU GPL 3.0 or any newer version
9 * @link http://www.shipsimu.org
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 * ----------------------------------
26 * - Some comments improved, other minor improvements
28 * - Constructor is now empty and factory method 'createClassLoader' is created
29 * - renamed loadClasses to scanClassPath
30 * - Added initLoader()
32 * - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class
34 * - loadClasses rewritten to fix some notices
37 * ----------------------------------
41 * Instance of this class
43 private static $selfInstance = NULL;
46 * Array with all classes
48 private $classes = array();
51 * List of loaded classes
53 private $loadedClasses = array();
56 * Suffix with extension for all class files
58 private $prefix = 'class_';
61 * Suffix with extension for all class files
63 private $suffix = '.php';
66 * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner
69 private $ignoreList = array();
72 * Debug this class loader? (TRUE = yes, FALSE = no)
74 private $debug = FALSE;
77 * Whether the file list is cached
79 private $listCached = FALSE;
82 * Wethe class content has been cached
84 private $classesCached = FALSE;
87 * Filename for the list cache
89 private $listCacheFQFN = '';
92 * Cache for class content
94 private $classCacheFQFN = '';
97 * Counter for loaded include files
102 * Framework/application paths for classes, etc.
104 private static $frameworkPaths = array(
105 'exceptions', // Exceptions
106 'interfaces', // Interfaces
107 'main', // General main classes
108 'middleware' // The middleware
113 * The protected constructor. Please use the factory method below, or use
114 * getSelfInstance() for singleton
118 protected function __construct () {
119 // Is currently empty
123 * The destructor makes it sure all caches got flushed
127 public function __destruct () {
128 // Skip here if dev-mode
129 if (defined('DEVELOPER')) {
133 // Skip here if already cached
134 if ($this->listCached === FALSE) {
135 // Writes the cache file of our list away
136 $cacheContent = serialize($this->classes);
137 file_put_contents($this->listCacheFQFN, $cacheContent);
140 // Skip here if already cached
141 if ($this->classesCached === FALSE) {
142 // Generate a full-cache of all classes
144 foreach ($this->loadedClasses as $fqfn) {
146 $cacheContent .= file_get_contents($fqfn);
150 file_put_contents($this->classCacheFQFN, $cacheContent);
155 * Creates an instance of this class loader for given configuration instance
157 * @param $configInstance Configuration class instance
160 public static final function createClassLoader (FrameworkConfiguration $configInstance) {
161 // Get a new instance
162 $loaderInstance = new ClassLoader();
165 $loaderInstance->initLoader($configInstance);
167 // Return the prepared instance
168 return $loaderInstance;
172 * Scans for all framework classes, exceptions and interfaces.
176 public static function scanFrameworkClasses () {
177 // Cache loader instance
178 $loaderInstance = self::getSelfInstance();
181 foreach (self::$frameworkPaths as $className) {
182 // Try to load the framework classes
183 $loaderInstance->scanClassPath('inc/classes/' . $className . '/');
188 * Scans for application's classes, etc.
192 public static function scanApplicationClasses () {
193 // Get config instance
194 $cfg = FrameworkConfiguration::getSelfInstance();
196 // Load all classes for the application
197 foreach (self::$frameworkPaths as $class) {
199 $path = sprintf('%s/%s/%s', $cfg->getConfigEntry('application_path'), $cfg->getConfigEntry('app_name'), $class);
201 // Is the path readable?
203 // Try to load the application classes
204 ClassLoader::getSelfInstance()->scanClassPath($path);
210 * Initializes our loader class
212 * @param $configInstance Configuration class instance
215 protected function initLoader (FrameworkConfiguration $configInstance) {
216 // Set configuration instance
217 $this->configInstance = $configInstance;
219 // Construct the FQFN for the cache
220 if (!defined('DEVELOPER')) {
221 $this->listCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'list-' . $this->configInstance->getConfigEntry('app_name') . '.cache';
222 $this->classCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'class-' . $this->configInstance->getConfigEntry('app_name') . '.cache';
225 // Set suffix and prefix from configuration
226 $this->suffix = $configInstance->getConfigEntry('class_suffix');
227 $this->prefix = $configInstance->getConfigEntry('class_prefix');
230 self::$selfInstance = $this;
232 // Skip here if no dev-mode
233 if (defined('DEVELOPER')) {
237 // IS the cache there?
238 if (file_exists($this->listCacheFQFN)) {
240 $cacheContent = file_get_contents($this->listCacheFQFN);
243 $this->classes = unserialize($cacheContent);
245 // List has been restored from cache!
246 $this->listCached = TRUE;
249 // Does the class cache exist?
250 if (file_exists($this->classCacheFQFN)) {
252 require($this->classCacheFQFN);
254 // Mark the class cache as loaded
255 $this->classesCached = TRUE;
262 * @param $className Name of the class to load
265 public static function autoLoad ($className) {
266 // Try to include this class
267 //* NOISY-DEBUG: */ printf('[%s:%d] className=%s' . PHP_EOL, __METHOD__, __LINE__, $className);
268 self::getSelfInstance()->includeClass($className);
272 * Singleton getter for an instance of this class
274 * @return $selfInstance A singleton instance of this class
276 public static final function getSelfInstance () {
277 // Is the instance there?
278 if (is_null(self::$selfInstance)) {
280 self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getSelfInstance());
283 // Return the instance
284 return self::$selfInstance;
288 * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix
290 * @param $basePath The relative base path to 'base_path' constant for all classes
291 * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored
294 public function scanClassPath ($basePath, array $ignoreList = array() ) {
295 // Is a list has been restored from cache, don't read it again
296 if ($this->listCached === TRUE) {
301 // Keep it in class for later usage
302 $this->ignoreList = $ignoreList;
305 * Ignore .htaccess by default as it is for protection of directories
308 array_push($ignoreList, '.htaccess');
311 * Set base directory which holds all our classes, an absolute path
312 * should be used here so is_dir(), is_file() and so on will always
313 * find the correct files and dirs.
315 $basePath2 = realpath($basePath);
317 // If the basePath is FALSE it is invalid
318 if ($basePath2 === FALSE) {
319 /* @TODO: Do not exit here. */
320 exit(__METHOD__ . ': Cannot read ' . $basePath . ' !' . PHP_EOL);
323 $basePath = $basePath2;
326 // Get a new iterator
327 //* NOISY-DEBUG: */ printf('[%s:%d] basePath=%s' . PHP_EOL, __METHOD__, __LINE__, $basePath);
328 $iteratorInstance = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath), RecursiveIteratorIterator::CHILD_FIRST);
331 while ($iteratorInstance->valid()) {
333 $currentEntry = $iteratorInstance->current();
335 // Get filename from iterator
336 $fileName = $currentEntry->getFileName();
338 // Get the "FQFN" (path and file name)
339 $fqfn = $currentEntry->getRealPath();
341 // Current entry must be a file, not smaller than 100 bytes and not on ignore list
342 if ((!$currentEntry->isFile()) || (in_array($fileName, $this->ignoreList)) || (filesize($fqfn) < 100)) {
343 // Advance to next entry
344 $iteratorInstance->next();
346 // Skip non-file entries
347 //* NOISY-DEBUG: */ printf('[%s:%d] SKIP: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
351 // Is this file wanted?
352 //* NOISY-DEBUG: */ printf('[%s:%d] FOUND: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
353 if ((substr($fileName, 0, strlen($this->prefix)) == $this->prefix) && (substr($fileName, -strlen($this->suffix), strlen($this->suffix)) == $this->suffix)) {
354 // Add it to the list
355 //* NOISY-DEBUG: */ printf('[%s:%d] ADD: %s,fqfn=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $fqfn);
356 $this->classes[$fileName] = $fqfn;
359 //* NOISY-DEBUG: */ printf('[%s:%d] NOT ADDED: %s,fqfn=%s' . PHP_EOL, __METHOD__, __LINE__, $fileName, $fqfn);
362 // Advance to next entry
363 //* NOISY-DEBUG: */ printf('[%s:%d] NEXT: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
364 $iteratorInstance->next();
369 * Load extra config files
373 public function loadExtraConfigs () {
375 $oldPrefix = $this->prefix;
377 // Set new prefix (temporary!)
378 $this->prefix = 'config-';
380 // Set base directory
381 $basePath = $this->configInstance->getConfigEntry('base_path') . 'inc/config/';
383 // Load all classes from the config directory
384 $this->scanClassPath($basePath);
386 // Include these extra configs now
387 $this->includeExtraConfigs();
389 // Set back the old prefix
390 $this->prefix = $oldPrefix;
394 * Tries to find the given class in our list. This method ignores silently
395 * missing classes or interfaces. So if you use class_exists() this method
396 * does not interrupt your program.
398 * @param $className The class that shall be loaded
401 public function includeClass ($className) {
402 // Create a name with prefix and suffix
403 $fileName = $this->prefix . $className . $this->suffix;
405 // Now look it up in our index
406 //* NOISY-DEBUG: */ printf('[%s:%d] ISSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
407 if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) {
408 // File is found and not loaded so load it only once
409 //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - START' . PHP_EOL, __METHOD__, __LINE__, $fileName);
410 require($this->classes[$fileName]);
411 //* NOISY-DEBUG: */ printf('[%s:%d] LOAD: %s - END' . PHP_EOL, __METHOD__, __LINE__, $fileName);
413 // Count this loaded class/interface/exception
416 // Mark this class as loaded for other purposes than loading it.
417 array_push($this->loadedClasses, $this->classes[$fileName]);
419 // Remove it from classes list so it won't be found twice.
420 //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
421 unset($this->classes[$fileName]);
423 // Developer mode excludes caching (better debugging)
424 if (!defined('DEVELOPER')) {
426 //* NOISY-DEBUG: */ printf('[%s:%d] classesCached=FALSE' . PHP_EOL, __METHOD__, __LINE__);
427 $this->classesCached = FALSE;
431 //* NOISY-DEBUG: */ printf('[%s:%d] 404: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
436 * Includes all extra config files
440 private function includeExtraConfigs () {
441 // Run through all class names (should not be much)
442 foreach ($this->classes as $fileName => $fqfn) {
444 if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) {
448 // Remove it from the list
449 //* NOISY-DEBUG: */ printf('[%s:%d] UNSET: %s' . PHP_EOL, __METHOD__, __LINE__, $fileName);
450 unset($this->classes[$fileName]);
456 * Getter for total include counter
458 * @return $total Total loaded include files
460 public final function getTotal () {
465 * Getter for a printable list of included classes/interfaces/exceptions
467 * @param $includeList A printable include list
469 public function getPrintableIncludeList () {
472 foreach ($this->loadedClasses as $classFile) {
473 $includeList .= basename($classFile) . '<br />' . PHP_EOL;