b6094879ebab7c5d1cb0d2dcfb662c2c50d414c9
[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@ship-simu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 Core Developer Team
8  * @license             GNU GPL 3.0 or any newer version
9  * @link                http://www.ship-simu.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.3
26  *  - Constructor is now empty and factory method 'createClassLoader' is created
27  *  - renamed loadClasses to scanClassPath
28  *  - Added initLoader(), $configInstance renamed to $configInstance
29  * 1.2
30  *  - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class
31  * 1.1
32  *  - loadClasses rewritten to fix some notices
33  * 1.0
34  *  - Initial release
35  * ----------------------------------
36  */
37 class ClassLoader {
38         /**
39          * Instance of this class
40          */
41         private static $selfInstance = null;
42
43         /**
44          * Array with all classes
45          */
46         private $classes = array();
47
48         /**
49          * List of loaded classes
50          */
51         private $loadedClasses = array();
52
53         /**
54          * Suffix with extension for all class files
55          */
56         private $prefix = "class_";
57
58         /**
59          * Suffix with extension for all class files
60          */
61         private $suffix = ".php";
62
63         /**
64          * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner
65          * @see scanLocalPath
66          */
67         private $ignoreList = array();
68
69         /**
70          * Debug this class loader? (true = yes, false = no)
71          */
72         private $debug = false;
73
74         /**
75          * Wether the file list is cached or not
76          */
77         private $listCached = false;
78
79         /**
80          * Wethe class content has been cached
81          */
82         private $classesCached = false;
83
84         /**
85          * Filename for the list cache
86          */
87         private $listCacheFQFN = '';
88
89         /**
90          * Cache for class content
91          */
92         private $classCacheFQFN = '';
93
94         /**
95          * Counter for loaded include files
96          */
97         private $total = 0;
98
99         /**
100          * The protected constructor. Please use the factory method below, or use
101          * getInstance() for singleton
102          *
103          * @return      void
104          */
105         protected function __construct () {
106                 // Is Currently empty
107         }
108
109         /**
110          * Our renamed factory method
111          *
112          * @param       $configInstance Configuration class instance
113          * @return      void
114          */
115         public final static function createClassLoader (FrameworkConfiguration $configInstance) {
116                 // Get a new instance
117                 $loaderInstance = new ClassLoader();
118
119                 // Init the instance
120                 $loaderInstance->initLoader($configInstance);
121
122                 // Return the prepared instance
123                 return $loaderInstance;
124         }
125
126         /**
127          * Initializes our loader class
128          *
129          * @param       $configInstance Configuration class instance
130          * @return      void
131          */
132         protected function initLoader (FrameworkConfiguration $configInstance) {
133                 // Set configuration instance
134                 $this->configInstance = $configInstance;
135
136                 // Construct the FQFN for the cache
137                 if (!defined('DEVELOPER')) {
138                         $this->listCacheFQFN  = $this->configInstance->readConfig('local_db_path') . "list-" . $this->configInstance->readConfig('app_name') . ".cache";
139                         $this->classCacheFQFN = $this->configInstance->readConfig('local_db_path') . "class-" . $this->configInstance->readConfig('app_name') . ".cache";
140                 } // END - if
141
142                 // Set suffix and prefix from configuration
143                 $this->suffix = $configInstance->readConfig('class_suffix');
144                 $this->prefix = $configInstance->readConfig('class_prefix');
145
146                 // Set own instance
147                 self::$selfInstance = $this;
148
149                 // Skip here if no dev-mode
150                 if (defined('DEVELOPER')) return;
151
152                 // IS the cache there?
153                 if (file_exists($this->listCacheFQFN)) {
154                         // Get content
155                         $cacheContent = file_get_contents($this->listCacheFQFN);
156
157                         // And convert it
158                         $this->classes = unserialize($cacheContent);
159
160                         // List has been restored from cache!
161                         $this->listCached = true;
162                 } // END - if
163
164                 // Does the class cache exist?
165                 if (file_exists($this->classCacheFQFN)) {
166                         // Then include it
167                         require($this->classCacheFQFN);
168
169                         // Mark the class cache as loaded
170                         $this->classesCached = true;
171                 } // END - if
172         }
173
174         /**
175          * Autoload-function
176          *
177          * @param       $className      Name of the class to load
178          * @return      void
179          */
180         public static function autoLoad ($className) {
181                 // Try to include this class
182                 self::getInstance()->includeClass($className);
183         }
184
185         /**
186          * Getter for an instance of this class
187          *
188          * @return      $selfInstance           An instance of this class
189          */
190         public final static function getInstance () {
191                 // Is the instance there?
192                 if (is_null(self::$selfInstance)) {
193                         // Get a new one
194                         self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getInstance());
195                 } // END - if
196
197                 // Return the instance
198                 return self::$selfInstance;
199         }
200
201         /**
202          * The destructor makes it sure all caches got flushed
203          *
204          * @return      void
205          */
206         public function __destruct () {
207                 // Skip here if dev-mode
208                 if (defined('DEVELOPER')) return;
209
210                 // Skip here if already cached
211                 if ($this->listCached === false) {
212                         // Writes the cache file of our list away
213                         $cacheContent = serialize($this->classes);
214                         file_put_contents($this->listCacheFQFN, $cacheContent);
215                 } // END - if
216
217                 // Skip here if already cached
218                 if ($this->classesCached === false) {
219                         // Generate a full-cache of all classes
220                         $cacheContent = '';
221                         foreach ($this->loadedClasses as $fqfn) {
222                                 // Load the file
223                                 $cacheContent .= file_get_contents($fqfn);
224                         } // END - foreach
225
226                         // And write it away
227                         file_put_contents($this->classCacheFQFN, $cacheContent);
228                 } // END - if
229         }
230
231         /**
232          * Fall-back method. Please replace loadClasses() with scanClassPath() !
233          *
234          * @param       $basePath               The relative base path to 'base_path' constant for all classes
235          * @param       $ignoreList             An optional list (array forced) of directory and file names which shall be ignored
236          * @return      void
237          * @deprecated
238          * @todo        Rewrite your apps to scanClassPath()
239          */
240         public function loadClasses ($basePath, array $ignoreList = array() ) {
241                 // This outputs an ugly message because you need to change to scanClassPath
242                 print __METHOD__." is deprecated. Use scanClassPath() to make this warning go away.<br />\n";
243
244                 // Call our new method
245                 $this->scanClassPath($basePath, $ignoreList);
246         }
247
248         /**
249          * Scans recursively a local path for class files which must have a prefix and a suffix as given by $this->suffix and $this->prefix
250          *
251          * @param       $basePath               The relative base path to 'base_path' constant for all classes
252          * @param       $ignoreList             An optional list (array forced) of directory and file names which shall be ignored
253          * @return      void
254          */
255         public function scanClassPath ($basePath, array $ignoreList = array() ) {
256                 // Is a list has been restored from cache, don't read it again
257                 if ($this->listCached === true) {
258                         // Abort here
259                         return;
260                 }
261
262                 // Directories which our class loader ignores by default while
263                 // deep-scanning the directory structure.
264                 $ignoreList[] = '.';
265                 $ignoreList[] = '..';
266                 $ignoreList[] = '.htaccess';
267                 $ignoreList[] = '.svn';
268
269                 // Keep it in class for later usage
270                 $this->ignoreList = $ignoreList;
271
272                 // Set base directory which holds all our classes, we should use an
273                 // absolute path here so is_dir(), is_file() and so on will always
274                 // find the correct files and dirs.
275                 $basePath2 = realpath($basePath);
276
277                 // If the basePath is false it is invalid
278                 if ($basePath2 === false) {
279                         /* @todo: Do not die here. */
280                         die("Cannot read {$basePath} !");
281                 } else {
282                         // Set base path
283                         $basePath = $basePath2;
284                 }
285
286                 // Get a new iterator
287                 //* DEBUG: */ echo "<strong>Base path: {$basePath}</strong><br />\n";
288                 $iterator = new RecursiveDirectoryIterator($basePath);
289                 $recursive = new RecursiveIteratorIterator($iterator);
290                 foreach ($recursive as $entry) {
291                         // Get filename from iterator
292                         $fileName = $entry->getFileName();
293
294                         // Is this file wanted?
295                         //* DEBUG: */ echo "FOUND:{$fileName}<br />\n";
296                         if ((!in_array($fileName, $this->ignoreList)) && (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) && (substr($fileName, -strlen($this->suffix), strlen($this->suffix)) == $this->suffix)) {
297                                 // Get the FQFN and add it to our class list
298                                 $fqfn = $entry->getRealPath();
299                                 //* DEBUG: */ echo "ADD: {$fileName}<br />\n";
300                                 $this->classes[$fileName] = $fqfn;
301                         } // END - if
302                 } // END - foreach
303         }
304
305         /**
306          * Load extra config files
307          *
308          * @return      void
309          */
310         public function loadExtraConfigs () {
311                 // Backup old prefix
312                 $oldPrefix = $this->prefix;
313
314                 // Set new prefix (temporary!)
315                 $this->prefix = 'config-';
316
317                 // Set base directory
318                 $basePath = $this->configInstance->readConfig('base_path') . 'inc/config/';
319
320                 // Load all classes from the config directory
321                 $this->scanClassPath($basePath);
322
323                 // Include these extra configs now
324                 $this->includeExtraConfigs();
325
326                 // Set the prefix back
327                 $this->prefix = $oldPrefix;
328         }
329
330         /**
331          * Tries to find the given class in our list. This method ignores silently
332          * missing classes or interfaces. So if you use class_exists() this method
333          * does not interrupt your program.
334          *
335          * @param       $className      The class we shall load
336          * @return      void
337          */
338         public function includeClass ($className) {
339                 // Create a name with prefix and suffix
340                 $fileName = $this->prefix . $className . $this->suffix;
341
342                 // Now look it up in our index
343                 if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) {
344                         // File is found and not loaded so load it only once
345                         //* DEBUG: */ echo "LOAD: ".$fileName." - Start<br />\n";
346                         require($this->classes[$fileName]);
347                         //* DEBUG: */ echo "LOAD: ".$fileName." - End<br />\n";
348
349                         // Count this include
350                         $this->total++;
351
352                         // Mark this class as loaded
353                         $this->loadedClasses[] = $this->classes[$fileName];
354
355                         // Remove it from classes list
356                         unset($this->classes[$fileName]);
357
358                         // Developer mode excludes caching (better debugging)
359                         if (!defined('DEVELOPER')) {
360                                 // Reset cache
361                                 $this->classesCached = false;
362                         } // END - if
363                 } // END - if
364         }
365
366         /**
367          * Includes all extra config files
368          *
369          * @return      void
370          */
371         private function includeExtraConfigs () {
372                 // Run through all class names (should not be much)
373                 foreach ($this->classes as $fileName => $fqfn) {
374                         // Is this a config?
375                         if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) {
376                                 // Then include it
377                                 require($fqfn);
378
379                                 // Remove it from the list
380                                 unset($this->classes[$fileName]);
381                         } // END - if
382                 } // END - foreach
383         }
384
385         /**
386          * Getter for total include counter
387          *
388          * @return      $total  Total loaded include files
389          */
390         public final function getTotal () {
391                 return $this->total;
392         }
393
394         /**
395          * Getter for a printable list of included classes/interfaces/exceptions
396          *
397          * @param       $includeList    A printable include list
398          */
399         public function getPrintableIncludeList () {
400                 // Prepare the list
401                 $includeList = '';
402                 foreach ($this->loadedClasses as $classFile) {
403                         $includeList .= basename($classFile)."<br />\n";
404                 } // END - foreach
405
406                 // And return it
407                 return $includeList;
408         }
409 }
410
411 // [EOF]
412 ?>