Check if path can be loadded before attempting it.
[core.git] / inc / loader / class_ClassLoader.php
1 <?php
2 /**
3  * This class loads class include files with a specific prefix and suffix
4  *
5  * @author              Roland Haeder <webmaster@shipsimu.org>
6  * @version             0.0.0
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
10  *
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.
15  *
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.
20  *
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/>.
23  *
24  * ----------------------------------
25  * 1.4
26  *  - Some comments improved, other minor improvements
27  * 1.3
28  *  - Constructor is now empty and factory method 'createClassLoader' is created
29  *  - renamed loadClasses to scanClassPath
30  *  - Added initLoader()
31  * 1.2
32  *  - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class
33  * 1.1
34  *  - loadClasses rewritten to fix some notices
35  * 1.0
36  *  - Initial release
37  * ----------------------------------
38  */
39 class ClassLoader {
40         /**
41          * Instance of this class
42          */
43         private static $selfInstance = NULL;
44
45         /**
46          * Array with all classes
47          */
48         private $classes = array();
49
50         /**
51          * List of loaded classes
52          */
53         private $loadedClasses = array();
54
55         /**
56          * Suffix with extension for all class files
57          */
58         private $prefix = 'class_';
59
60         /**
61          * Suffix with extension for all class files
62          */
63         private $suffix = '.php';
64
65         /**
66          * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner
67          * @see scanLocalPath
68          */
69         private $ignoreList = array();
70
71         /**
72          * Debug this class loader? (TRUE = yes, FALSE = no)
73          */
74         private $debug = FALSE;
75
76         /**
77          * Whether the file list is cached
78          */
79         private $listCached = FALSE;
80
81         /**
82          * Wethe class content has been cached
83          */
84         private $classesCached = FALSE;
85
86         /**
87          * Filename for the list cache
88          */
89         private $listCacheFQFN = '';
90
91         /**
92          * Cache for class content
93          */
94         private $classCacheFQFN = '';
95
96         /**
97          * Counter for loaded include files
98          */
99         private $total = 0;
100
101         /**
102          * Framework/application paths for classes, etc.
103          */
104         private static $frameworkPaths = array(
105                 'exceptions', // Exceptions
106                 'interfaces', // Interfaces
107                 'main',       // General main classes
108                 'middleware'  // The middleware
109         );
110
111
112         /**
113          * The protected constructor. Please use the factory method below, or use
114          * getSelfInstance() for singleton
115          *
116          * @return      void
117          */
118         protected function __construct () {
119                 // Is currently empty
120         }
121
122         /**
123          * The destructor makes it sure all caches got flushed
124          *
125          * @return      void
126          */
127         public function __destruct () {
128                 // Skip here if dev-mode
129                 if (defined('DEVELOPER')) {
130                         return;
131                 } // END - if
132
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);
138                 } // END - if
139
140                 // Skip here if already cached
141                 if ($this->classesCached === FALSE) {
142                         // Generate a full-cache of all classes
143                         $cacheContent = '';
144                         foreach ($this->loadedClasses as $fqfn) {
145                                 // Load the file
146                                 $cacheContent .= file_get_contents($fqfn);
147                         } // END - foreach
148
149                         // And write it away
150                         file_put_contents($this->classCacheFQFN, $cacheContent);
151                 } // END - if
152         }
153
154         /**
155          * Creates an instance of this class loader for given configuration instance
156          *
157          * @param       $configInstance         Configuration class instance
158          * @return      void
159          */
160         public static final function createClassLoader (FrameworkConfiguration $configInstance) {
161                 // Get a new instance
162                 $loaderInstance = new ClassLoader();
163
164                 // Init the instance
165                 $loaderInstance->initLoader($configInstance);
166
167                 // Return the prepared instance
168                 return $loaderInstance;
169         }
170
171         /**
172          * Scans for all framework classes, exceptions and interfaces.
173          *
174          * @return      void
175          */
176         public static function scanFrameworkClasses () {
177                 // Cache loader instance
178                 $loaderInstance = self::getSelfInstance();
179
180                 // Load all classes
181                 foreach (self::$frameworkPaths as $className) {
182                         // Try to load the framework classes
183                         $loaderInstance->scanClassPath('inc/classes/' . $className . '/');
184                 } // END - foreach
185         }
186
187         /**
188          * Scans for application's classes, etc.
189          *
190          * @return      void
191          */
192         public static function scanApplicationClasses () {
193                 // Get config instance
194                 $cfg = FrameworkConfiguration::getSelfInstance();
195
196                 // Load all classes for the application
197                 foreach (self::$frameworkPaths as $class) {
198                         // Create path name
199                         $path = sprintf('%s/%s/%s', $cfg->getConfigEntry('application_path'), $cfg->getConfigEntry('app_name'), $class);
200
201                         // Is the path readable?
202                         if (is_dir($path)) {
203                                 // Try to load the application classes
204                                 ClassLoader::getSelfInstance()->scanClassPath($path);
205                         } // END - if
206                 } // END - foreach
207         }
208
209         /**
210          * Initializes our loader class
211          *
212          * @param       $configInstance Configuration class instance
213          * @return      void
214          */
215         protected function initLoader (FrameworkConfiguration $configInstance) {
216                 // Set configuration instance
217                 $this->configInstance = $configInstance;
218
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';
223                 } // END - if
224
225                 // Set suffix and prefix from configuration
226                 $this->suffix = $configInstance->getConfigEntry('class_suffix');
227                 $this->prefix = $configInstance->getConfigEntry('class_prefix');
228
229                 // Set own instance
230                 self::$selfInstance = $this;
231
232                 // Skip here if no dev-mode
233                 if (defined('DEVELOPER')) {
234                         return;
235                 } // END - if
236
237                 // IS the cache there?
238                 if (file_exists($this->listCacheFQFN)) {
239                         // Get content
240                         $cacheContent = file_get_contents($this->listCacheFQFN);
241
242                         // And convert it
243                         $this->classes = unserialize($cacheContent);
244
245                         // List has been restored from cache!
246                         $this->listCached = TRUE;
247                 } // END - if
248
249                 // Does the class cache exist?
250                 if (file_exists($this->classCacheFQFN)) {
251                         // Then include it
252                         require($this->classCacheFQFN);
253
254                         // Mark the class cache as loaded
255                         $this->classesCached = TRUE;
256                 } // END - if
257         }
258
259         /**
260          * Autoload-function
261          *
262          * @param       $className      Name of the class to load
263          * @return      void
264          */
265         public static function autoLoad ($className) {
266                 // Try to include this class
267                 self::getSelfInstance()->includeClass($className);
268         }
269
270         /**
271          * Singleton getter for an instance of this class
272          *
273          * @return      $selfInstance   A singleton instance of this class
274          */
275         public static final function getSelfInstance () {
276                 // Is the instance there?
277                 if (is_null(self::$selfInstance)) {
278                         // Get a new one
279                         self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getSelfInstance());
280                 } // END - if
281
282                 // Return the instance
283                 return self::$selfInstance;
284         }
285
286         /**
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
288          *
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
291          * @return      void
292          */
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) {
296                         // Abort here
297                         return;
298                 } // END - if
299
300                 /*
301                  * Directories which this class loader ignores by default while
302                  * scanning the whole directory structure starting from given base
303                  * path.
304                  */
305                 array_push($ignoreList, '.');
306                 array_push($ignoreList, '..');
307                 array_push($ignoreList, '.htaccess');
308                 array_push($ignoreList, '.svn');
309
310                 // Keep it in class for later usage
311                 $this->ignoreList = $ignoreList;
312
313                 /*
314                  * Set base directory which holds all our classes, we should use an
315                  * absolute path here so is_dir(), is_file() and so on will always
316                  * find the correct files and dirs.
317                  */
318                 $basePath2 = realpath($basePath);
319
320                 // If the basePath is FALSE it is invalid
321                 if ($basePath2 === FALSE) {
322                         /* @todo: Do not die here. */
323                         exit(__METHOD__ . ':Cannot read ' . $basePath . ' !');
324                 } else {
325                         // Set base path
326                         $basePath = $basePath2;
327                 }
328
329                 // Get a new iterator
330                 //* DEBUG: */ echo "<strong>Base path: {$basePath}</strong><br />\n";
331                 $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basePath));
332
333                 foreach ($iterator as $entry) {
334                         // Get filename from iterator
335                         $fileName = $entry->getFileName();
336
337                         // Get the FQFN and add it to our class list
338                         $fqfn = $entry->getRealPath();
339
340                         // Is this file wanted?
341                         //* DEBUG: */ echo "FOUND:{$fileName}<br />\n";
342                         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)) {
343                                 //* DEBUG: */ echo "ADD: {$fileName}<br />\n";
344                                 // Add it to the list
345                                 $this->classes[$fileName] = $fqfn;
346                         } // END - if
347                 } // END - foreach
348         }
349
350         /**
351          * Load extra config files
352          *
353          * @return      void
354          */
355         public function loadExtraConfigs () {
356                 // Backup old prefix
357                 $oldPrefix = $this->prefix;
358
359                 // Set new prefix (temporary!)
360                 $this->prefix = 'config-';
361
362                 // Set base directory
363                 $basePath = $this->configInstance->getConfigEntry('base_path') . 'inc/config/';
364
365                 // Load all classes from the config directory
366                 $this->scanClassPath($basePath);
367
368                 // Include these extra configs now
369                 $this->includeExtraConfigs();
370
371                 // Set back the old prefix
372                 $this->prefix = $oldPrefix;
373         }
374
375         /**
376          * Tries to find the given class in our list. This method ignores silently
377          * missing classes or interfaces. So if you use class_exists() this method
378          * does not interrupt your program.
379          *
380          * @param       $className      The class we shall load
381          * @return      void
382          */
383         public function includeClass ($className) {
384                 // Create a name with prefix and suffix
385                 $fileName = $this->prefix . $className . $this->suffix;
386
387                 // Now look it up in our index
388                 if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) {
389                         // File is found and not loaded so load it only once
390                         //* DEBUG: */ echo "LOAD: ".$fileName." - Start<br />\n";
391                         require($this->classes[$fileName]);
392                         //* DEBUG: */ echo "LOAD: ".$fileName." - End<br />\n";
393
394                         // Count this include
395                         $this->total++;
396
397                         // Mark this class as loaded
398                         array_push($this->loadedClasses, $this->classes[$fileName]);
399
400                         // Remove it from classes list
401                         unset($this->classes[$fileName]);
402
403                         // Developer mode excludes caching (better debugging)
404                         if (!defined('DEVELOPER')) {
405                                 // Reset cache
406                                 $this->classesCached = FALSE;
407                         } // END - if
408                 } // END - if
409         }
410
411         /**
412          * Includes all extra config files
413          *
414          * @return      void
415          */
416         private function includeExtraConfigs () {
417                 // Run through all class names (should not be much)
418                 foreach ($this->classes as $fileName => $fqfn) {
419                         // Is this a config?
420                         if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) {
421                                 // Then include it
422                                 require($fqfn);
423
424                                 // Remove it from the list
425                                 unset($this->classes[$fileName]);
426                         } // END - if
427                 } // END - foreach
428         }
429
430         /**
431          * Getter for total include counter
432          *
433          * @return      $total  Total loaded include files
434          */
435         public final function getTotal () {
436                 return $this->total;
437         }
438
439         /**
440          * Getter for a printable list of included classes/interfaces/exceptions
441          *
442          * @param       $includeList    A printable include list
443          */
444         public function getPrintableIncludeList () {
445                 // Prepare the list
446                 $includeList = '';
447                 foreach ($this->loadedClasses as $classFile) {
448                         $includeList .= basename($classFile) . '<br />' . PHP_EOL;
449                 } // END - foreach
450
451                 // And return it
452                 return $includeList;
453         }
454 }
455
456 // [EOF]
457 ?>