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 * The protected constructor. Please use the factory method below, or use
103 * getSelfInstance() for singleton
107 protected function __construct () {
108 // Is currently empty
112 * The destructor makes it sure all caches got flushed
116 public function __destruct () {
117 // Skip here if dev-mode
118 if (defined('DEVELOPER')) {
122 // Skip here if already cached
123 if ($this->listCached === FALSE) {
124 // Writes the cache file of our list away
125 $cacheContent = serialize($this->classes);
126 file_put_contents($this->listCacheFQFN, $cacheContent);
129 // Skip here if already cached
130 if ($this->classesCached === FALSE) {
131 // Generate a full-cache of all classes
133 foreach ($this->loadedClasses as $fqfn) {
135 $cacheContent .= file_get_contents($fqfn);
139 file_put_contents($this->classCacheFQFN, $cacheContent);
144 * Creates an instance of this class loader for given configuration instance
146 * @param $configInstance Configuration class instance
149 public static final function createClassLoader (FrameworkConfiguration $configInstance) {
150 // Get a new instance
151 $loaderInstance = new ClassLoader();
154 $loaderInstance->initLoader($configInstance);
156 // Return the prepared instance
157 return $loaderInstance;
161 * Scans for all framework classes, exceptions and interfaces.
165 public static function scanFrameworkClasses () {
166 // Lower framework classes
167 $lowerClasses = array(
168 'exceptions', // Exceptions
169 'interfaces', // Interfaces
170 'main', // General main classes
171 'middleware' // The middleware
174 // Cache loader instance
175 $loaderInstance = self::getSelfInstance();
178 foreach ($lowerClasses as $className) {
179 // Try to load the framework classes
180 $loaderInstance->scanClassPath('inc/classes/' . $className . '/');
185 * Initializes our loader class
187 * @param $configInstance Configuration class instance
190 protected function initLoader (FrameworkConfiguration $configInstance) {
191 // Set configuration instance
192 $this->configInstance = $configInstance;
194 // Construct the FQFN for the cache
195 if (!defined('DEVELOPER')) {
196 $this->listCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'list-' . $this->configInstance->getConfigEntry('app_name') . '.cache';
197 $this->classCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'class-' . $this->configInstance->getConfigEntry('app_name') . '.cache';
200 // Set suffix and prefix from configuration
201 $this->suffix = $configInstance->getConfigEntry('class_suffix');
202 $this->prefix = $configInstance->getConfigEntry('class_prefix');
205 self::$selfInstance = $this;
207 // Skip here if no dev-mode
208 if (defined('DEVELOPER')) {
212 // IS the cache there?
213 if (file_exists($this->listCacheFQFN)) {
215 $cacheContent = file_get_contents($this->listCacheFQFN);
218 $this->classes = unserialize($cacheContent);
220 // List has been restored from cache!
221 $this->listCached = TRUE;
224 // Does the class cache exist?
225 if (file_exists($this->classCacheFQFN)) {
227 require($this->classCacheFQFN);
229 // Mark the class cache as loaded
230 $this->classesCached = TRUE;
237 * @param $className Name of the class to load
240 public static function autoLoad ($className) {
241 // Try to include this class
242 self::getSelfInstance()->includeClass($className);
246 * Singleton getter for an instance of this class
248 * @return $selfInstance A singleton instance of this class
250 public static final function getSelfInstance () {
251 // Is the instance there?
252 if (is_null(self::$selfInstance)) {
254 self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getSelfInstance());
257 // Return the instance
258 return self::$selfInstance;
262 * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix
264 * @param $basePath The relative base path to 'base_path' constant for all classes
265 * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored
268 public function scanClassPath ($basePath, array $ignoreList = array() ) {
269 // Is a list has been restored from cache, don't read it again
270 if ($this->listCached === TRUE) {
276 * Directories which this class loader ignores by default while
277 * scanning the whole directory structure starting from given base
280 array_push($ignoreList, '.');
281 array_push($ignoreList, '..');
282 array_push($ignoreList, '.htaccess');
283 array_push($ignoreList, '.svn');
285 // Keep it in class for later usage
286 $this->ignoreList = $ignoreList;
289 * Set base directory which holds all our classes, we should use an
290 * absolute path here so is_dir(), is_file() and so on will always
291 * find the correct files and dirs.
293 $basePath2 = realpath($basePath);
295 // If the basePath is FALSE it is invalid
296 if ($basePath2 === FALSE) {
297 /* @todo: Do not die here. */
298 exit(__METHOD__ . ':Cannot read ' . $basePath . ' !');
301 $basePath = $basePath2;
304 // Get a new iterator
305 //* DEBUG: */ echo "<strong>Base path: {$basePath}</strong><br />\n";
306 $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath));
308 foreach ($iterator as $entry) {
309 // Get filename from iterator
310 $fileName = $entry->getFileName();
312 // Get the FQFN and add it to our class list
313 $fqfn = $entry->getRealPath();
315 // Is this file wanted?
316 //* DEBUG: */ echo "FOUND:{$fileName}<br />\n";
317 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)) {
318 //* DEBUG: */ echo "ADD: {$fileName}<br />\n";
319 // Add it to the list
320 $this->classes[$fileName] = $fqfn;
326 * Load extra config files
330 public function loadExtraConfigs () {
332 $oldPrefix = $this->prefix;
334 // Set new prefix (temporary!)
335 $this->prefix = 'config-';
337 // Set base directory
338 $basePath = $this->configInstance->getConfigEntry('base_path') . 'inc/config/';
340 // Load all classes from the config directory
341 $this->scanClassPath($basePath);
343 // Include these extra configs now
344 $this->includeExtraConfigs();
346 // Set back the old prefix
347 $this->prefix = $oldPrefix;
351 * Tries to find the given class in our list. This method ignores silently
352 * missing classes or interfaces. So if you use class_exists() this method
353 * does not interrupt your program.
355 * @param $className The class we shall load
358 public function includeClass ($className) {
359 // Create a name with prefix and suffix
360 $fileName = $this->prefix . $className . $this->suffix;
362 // Now look it up in our index
363 if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) {
364 // File is found and not loaded so load it only once
365 //* DEBUG: */ echo "LOAD: ".$fileName." - Start<br />\n";
366 require($this->classes[$fileName]);
367 //* DEBUG: */ echo "LOAD: ".$fileName." - End<br />\n";
369 // Count this include
372 // Mark this class as loaded
373 array_push($this->loadedClasses, $this->classes[$fileName]);
375 // Remove it from classes list
376 unset($this->classes[$fileName]);
378 // Developer mode excludes caching (better debugging)
379 if (!defined('DEVELOPER')) {
381 $this->classesCached = FALSE;
387 * Includes all extra config files
391 private function includeExtraConfigs () {
392 // Run through all class names (should not be much)
393 foreach ($this->classes as $fileName => $fqfn) {
395 if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) {
399 // Remove it from the list
400 unset($this->classes[$fileName]);
406 * Getter for total include counter
408 * @return $total Total loaded include files
410 public final function getTotal () {
415 * Getter for a printable list of included classes/interfaces/exceptions
417 * @param $includeList A printable include list
419 public function getPrintableIncludeList () {
422 foreach ($this->loadedClasses as $classFile) {
423 $includeList .= basename($classFile) . '<br />' . PHP_EOL;