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