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