3 * This class loads class include files with a specific prefix and suffix
5 * @author Roland Haeder <webmaster@ship-simu.org>
7 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Core Developer Team
8 * @license GNU GPL 3.0 or any newer version
9 * @link http://www.ship-simu.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 * Initializes our loader class
163 * @param $configInstance Configuration class instance
166 protected function initLoader (FrameworkConfiguration $configInstance) {
167 // Set configuration instance
168 $this->configInstance = $configInstance;
170 // Construct the FQFN for the cache
171 if (!defined('DEVELOPER')) {
172 $this->listCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'list-' . $this->configInstance->getConfigEntry('app_name') . '.cache';
173 $this->classCacheFQFN = $this->configInstance->getConfigEntry('local_db_path') . 'class-' . $this->configInstance->getConfigEntry('app_name') . '.cache';
176 // Set suffix and prefix from configuration
177 $this->suffix = $configInstance->getConfigEntry('class_suffix');
178 $this->prefix = $configInstance->getConfigEntry('class_prefix');
181 self::$selfInstance = $this;
183 // Skip here if no dev-mode
184 if (defined('DEVELOPER')) {
188 // IS the cache there?
189 if (file_exists($this->listCacheFQFN)) {
191 $cacheContent = file_get_contents($this->listCacheFQFN);
194 $this->classes = unserialize($cacheContent);
196 // List has been restored from cache!
197 $this->listCached = true;
200 // Does the class cache exist?
201 if (file_exists($this->classCacheFQFN)) {
203 require($this->classCacheFQFN);
205 // Mark the class cache as loaded
206 $this->classesCached = true;
213 * @param $className Name of the class to load
216 public static function autoLoad ($className) {
217 // Try to include this class
218 self::getSelfInstance()->includeClass($className);
222 * Singleton getter for an instance of this class
224 * @return $selfInstance A singleton instance of this class
226 public static final function getSelfInstance () {
227 // Is the instance there?
228 if (is_null(self::$selfInstance)) {
230 self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getSelfInstance());
233 // Return the instance
234 return self::$selfInstance;
238 * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix
240 * @param $basePath The relative base path to 'base_path' constant for all classes
241 * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored
244 public function scanClassPath ($basePath, array $ignoreList = array() ) {
245 // Is a list has been restored from cache, don't read it again
246 if ($this->listCached === true) {
252 * Directories which this class loader ignores by default while
253 * scanning the whole directory structure starting from given base
257 $ignoreList[] = '..';
258 $ignoreList[] = '.htaccess';
259 $ignoreList[] = '.svn';
261 // Keep it in class for later usage
262 $this->ignoreList = $ignoreList;
265 * Set base directory which holds all our classes, we should use an
266 * absolute path here so is_dir(), is_file() and so on will always
267 * find the correct files and dirs.
269 $basePath2 = realpath($basePath);
271 // If the basePath is false it is invalid
272 if ($basePath2 === false) {
273 /* @todo: Do not die here. */
274 exit(__METHOD__ . ':Cannot read ' . $basePath . ' !');
277 $basePath = $basePath2;
280 // Get a new iterator
281 //* DEBUG: */ echo "<strong>Base path: {$basePath}</strong><br />\n";
282 $iterator = new RecursiveDirectoryIterator($basePath);
283 $recursive = new RecursiveIteratorIterator($iterator);
284 foreach ($recursive as $entry) {
285 // Get filename from iterator
286 $fileName = $entry->getFileName();
288 // Get the FQFN and add it to our class list
289 $fqfn = $entry->getRealPath();
291 // Is this file wanted?
292 //* DEBUG: */ echo "FOUND:{$fileName}<br />\n";
293 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)) {
294 //* DEBUG: */ echo "ADD: {$fileName}<br />\n";
295 // Add it to the list
296 $this->classes[$fileName] = $fqfn;
302 * Load extra config files
306 public function loadExtraConfigs () {
308 $oldPrefix = $this->prefix;
310 // Set new prefix (temporary!)
311 $this->prefix = 'config-';
313 // Set base directory
314 $basePath = $this->configInstance->getConfigEntry('base_path') . 'inc/config/';
316 // Load all classes from the config directory
317 $this->scanClassPath($basePath);
319 // Include these extra configs now
320 $this->includeExtraConfigs();
322 // Set back the old prefix
323 $this->prefix = $oldPrefix;
327 * Tries to find the given class in our list. This method ignores silently
328 * missing classes or interfaces. So if you use class_exists() this method
329 * does not interrupt your program.
331 * @param $className The class we shall load
334 public function includeClass ($className) {
335 // Create a name with prefix and suffix
336 $fileName = $this->prefix . $className . $this->suffix;
338 // Now look it up in our index
339 if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) {
340 // File is found and not loaded so load it only once
341 //* DEBUG: */ echo "LOAD: ".$fileName." - Start<br />\n";
342 require($this->classes[$fileName]);
343 //* DEBUG: */ echo "LOAD: ".$fileName." - End<br />\n";
345 // Count this include
348 // Mark this class as loaded
349 $this->loadedClasses[] = $this->classes[$fileName];
351 // Remove it from classes list
352 unset($this->classes[$fileName]);
354 // Developer mode excludes caching (better debugging)
355 if (!defined('DEVELOPER')) {
357 $this->classesCached = false;
363 * Includes all extra config files
367 private function includeExtraConfigs () {
368 // Run through all class names (should not be much)
369 foreach ($this->classes as $fileName => $fqfn) {
371 if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) {
375 // Remove it from the list
376 unset($this->classes[$fileName]);
382 * Getter for total include counter
384 * @return $total Total loaded include files
386 public final function getTotal () {
391 * Getter for a printable list of included classes/interfaces/exceptions
393 * @param $includeList A printable include list
395 public function getPrintableIncludeList () {
398 foreach ($this->loadedClasses as $classFile) {
399 $includeList .= basename($classFile)."<br />\n";