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