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 - 2013 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 self::getSelfInstance()->includeClass($className);
271 * Singleton getter for an instance of this class
273 * @return $selfInstance A singleton instance of this class
275 public static final function getSelfInstance () {
276 // Is the instance there?
277 if (is_null(self::$selfInstance)) {
279 self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getSelfInstance());
282 // Return the instance
283 return self::$selfInstance;
287 * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix
289 * @param $basePath The relative base path to 'base_path' constant for all classes
290 * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored
293 public function scanClassPath ($basePath, array $ignoreList = array() ) {
294 // Is a list has been restored from cache, don't read it again
295 if ($this->listCached === TRUE) {
301 * Directories which this class loader ignores by default while
302 * scanning the whole directory structure starting from given base
305 array_push($ignoreList, '.');
306 array_push($ignoreList, '..');
307 array_push($ignoreList, '.htaccess');
309 // Keep it in class for later usage
310 $this->ignoreList = $ignoreList;
313 * Set base directory which holds all our classes, we should use an
314 * absolute path here so is_dir(), is_file() and so on will always
315 * find the correct files and dirs.
317 $basePath2 = realpath($basePath);
319 // If the basePath is FALSE it is invalid
320 if ($basePath2 === FALSE) {
321 /* @todo: Do not die here. */
322 exit(__METHOD__ . ':Cannot read ' . $basePath . ' !' . PHP_EOL);
325 $basePath = $basePath2;
328 // Get a new iterator
329 //* DEBUG: */ echo "<strong>Base path: {$basePath}</strong><br />\n";
330 $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath));
332 foreach ($iterator as $entry) {
333 // Get filename from iterator
334 $fileName = $entry->getFileName();
336 // Get the FQFN and add it to our class list
337 $fqfn = $entry->getRealPath();
339 // Is this file wanted?
340 //* DEBUG: */ echo "FOUND:{$fileName}<br />\n";
341 if ((!in_array($fileName, $this->ignoreList)) && (filesize($fqfn) > 100) && (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) && (substr($fileName, -strlen($this->suffix), strlen($this->suffix)) == $this->suffix)) {
342 //* DEBUG: */ echo "ADD: {$fileName}<br />\n";
343 // Add it to the list
344 $this->classes[$fileName] = $fqfn;
350 * Load extra config files
354 public function loadExtraConfigs () {
356 $oldPrefix = $this->prefix;
358 // Set new prefix (temporary!)
359 $this->prefix = 'config-';
361 // Set base directory
362 $basePath = $this->configInstance->getConfigEntry('base_path') . 'inc/config/';
364 // Load all classes from the config directory
365 $this->scanClassPath($basePath);
367 // Include these extra configs now
368 $this->includeExtraConfigs();
370 // Set back the old prefix
371 $this->prefix = $oldPrefix;
375 * Tries to find the given class in our list. This method ignores silently
376 * missing classes or interfaces. So if you use class_exists() this method
377 * does not interrupt your program.
379 * @param $className The class we shall load
382 public function includeClass ($className) {
383 // Create a name with prefix and suffix
384 $fileName = $this->prefix . $className . $this->suffix;
386 // Now look it up in our index
387 if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) {
388 // File is found and not loaded so load it only once
389 //* DEBUG: */ echo "LOAD: ".$fileName." - Start<br />\n";
390 require($this->classes[$fileName]);
391 //* DEBUG: */ echo "LOAD: ".$fileName." - End<br />\n";
393 // Count this include
396 // Mark this class as loaded
397 array_push($this->loadedClasses, $this->classes[$fileName]);
399 // Remove it from classes list
400 unset($this->classes[$fileName]);
402 // Developer mode excludes caching (better debugging)
403 if (!defined('DEVELOPER')) {
405 $this->classesCached = FALSE;
411 * Includes all extra config files
415 private function includeExtraConfigs () {
416 // Run through all class names (should not be much)
417 foreach ($this->classes as $fileName => $fqfn) {
419 if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) {
423 // Remove it from the list
424 unset($this->classes[$fileName]);
430 * Getter for total include counter
432 * @return $total Total loaded include files
434 public final function getTotal () {
439 * Getter for a printable list of included classes/interfaces/exceptions
441 * @param $includeList A printable include list
443 public function getPrintableIncludeList () {
446 foreach ($this->loadedClasses as $classFile) {
447 $includeList .= basename($classFile) . '<br />' . PHP_EOL;