3 * This class loads class include files with a specific prefix and suffix
5 * @author Roland Haeder <webmaster@ship-simu.org>
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
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.
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.
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/>.
24 * ----------------------------------
26 * - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class
28 * - loadClasses rewritten to fix some notices
31 * ----------------------------------
35 * Instance of this class
37 private static $selfInstance = null;
42 private $cfg = array();
45 * Array with all classes
47 private $classes = array();
50 * List of loaded classes
52 private $loadedClasses = array();
55 * Suffix with extension for all class files
57 private $prefix = "class_";
60 * Suffix with extension for all class files
62 private $suffix = ".php";
65 * Length of the suffix. Will be overwritten later.
67 private $suffixLen = 0;
70 * Length of the prefix. Will be overwritten later.
72 private $prefixLen = 0;
75 * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner
78 private $ignoreList = array();
81 * Debug this class loader? (true = yes, false = no)
83 private $debug = false;
86 * Wether the file list is cached or not
88 private $listCached = false;
91 * Wethe class content has been cached
93 private $classesCached = false;
96 * Filename for the list cache
98 private $listCacheFQFN = "";
101 * Cache for class content
103 private $classCacheFQFN = "";
106 * Counter for loaded include files
111 * The *public* constructor
113 * @param $cfgInstance Configuration class instance
116 public function __construct (FrameworkConfiguration $cfgInstance) {
117 // Set configuration instance
118 $this->cfgInstance = $cfgInstance;
120 // Construct the FQFN for the cache
121 if (!defined('DEVELOPER')) {
122 $this->listCacheFQFN = $this->cfgInstance->readConfig('local_db_path') . "list-" . $this->cfgInstance->readConfig('app_name') . ".cache";
123 $this->classCacheFQFN = $this->cfgInstance->readConfig('local_db_path') . "class-" . $this->cfgInstance->readConfig('app_name') . ".cache";
126 // Set suffix and prefix from configuration
127 $this->suffix = $cfgInstance->readConfig('class_suffix');
128 $this->prefix = $cfgInstance->readConfig('class_prefix');
130 // Estimate length of prefix and suffix for substr() function (cache)
131 $this->suffixLen = strlen($this->suffix);
132 $this->prefixLen = strlen($this->prefix);
135 self::$selfInstance = $this;
137 // Skip here if no dev-mode
138 if (defined('DEVELOPER')) return;
140 // IS the cache there?
141 if (file_exists($this->listCacheFQFN)) {
143 $cacheContent = file_get_contents($this->listCacheFQFN);
146 $this->classes = unserialize($cacheContent);
148 // List has been restored from cache!
149 $this->listCached = true;
152 // Does the class cache exist?
153 if (file_exists($this->classCacheFQFN)) {
155 require($this->classCacheFQFN);
157 // Mark the class cache as loaded
158 $this->classesCached = true;
163 * The destructor makes it sure all caches got flushed
167 public function __destruct () {
168 // Skip here if dev-mode
169 if (defined('DEVELOPER')) return;
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);
178 // Skip here if already cached
179 if ($this->classesCached === false) {
180 // Generate a full-cache of all classes
182 foreach ($this->loadedClasses as $fqfn) {
184 $cacheContent .= file_get_contents($fqfn);
188 file_put_contents($this->classCacheFQFN, $cacheContent);
193 * Getter for an instance of this class
195 * @return $selfInstance An instance of this class
197 public final static function getInstance () {
198 // Is the instance there?
199 if (is_null(self::$selfInstance)) {
201 self::$selfInstance = new ClassLoader(FrameworkConfiguration::getInstance());
204 // Return the instance
205 return self::$selfInstance;
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
211 * @param $basePath The relative base path to 'base_path' constant for all classes
212 * @param $ignoreList An optional list (array forced) of directory and file names which shall be ignored
215 public function loadClasses ($basePath, array $ignoreList = array() ) {
216 // Is a list has been restored from cache, don't read it again
217 if ($this->listCached === true) {
222 // Directories which our class loader ignores by default while
223 // deep-scanning the directory structure.
225 $ignoreList[] = "..";
226 $ignoreList[] = ".htaccess";
227 $ignoreList[] = ".svn";
229 // Keep it in class for later usage
230 $this->ignoreList = $ignoreList;
232 // Set base directory which holds all our classes, we should use an
233 // absolute path here so is_dir(), is_file() and so on will always
234 // find the correct files and dirs.
235 $basePath2 = realpath($basePath);
237 // If the basePath is false it is invalid
238 if ($basePath2 === false) {
239 /* @todo: Do not die here. */
240 die("Cannot read {$basePath} !");
243 $basePath = $basePath2;
246 // Get a new iterator
247 //* DEBUG: */ echo "<strong>Base path: {$basePath}</strong><br />\n";
248 $iterator = new RecursiveDirectoryIterator($basePath);
249 $recursive = new RecursiveIteratorIterator($iterator);
250 foreach ($recursive as $entry) {
251 // Get filename from iterator
252 $fileName = $entry->getFileName();
254 // Is this file wanted?
255 //* DEBUG: */ echo "FOUND:{$fileName}<br />\n";
256 if ((!in_array($fileName, $this->ignoreList)) && (substr($fileName, 0, $this->prefixLen) == $this->prefix) && (substr($fileName, -$this->suffixLen, $this->suffixLen) == $this->suffix)) {
257 // Get the FQFN and add it to our class list
258 $fqfn = $entry->getRealPath();
259 //* DEBUG: */ echo "ADD: {$fileName}<br />\n";
260 $this->classes[$fileName] = $fqfn;
266 * Load extra config files
270 public function loadExtraConfigs () {
272 $oldPrefix = $this->prefix;
274 // Set new prefix (temporary!)
275 $this->prefix = "config-";
276 $this->prefixLen = strlen($this->prefix);
278 // Set base directory
279 $basePath = sprintf("%sinc/config/", $this->cfgInstance->readConfig('base_path'));
281 // Load all classes from the config directory
282 $this->loadClasses($basePath);
284 // Include these extra configs now
285 $this->includeExtraConfigs();
287 // Set the prefix back
288 $this->prefix = $oldPrefix;
289 $this->prefixLen = strlen($this->prefix);
294 * Tries to find the given class in our list. This method ignores silently
295 * missing classes or interfaces. So if you use class_exists() this method
296 * does not interrupt your program.
298 * @param $className The class we shall load
301 public function includeClass ($className) {
302 // Create a name with prefix and suffix
303 $fileName = $this->prefix . $className . $this->suffix;
305 // Now look it up in our index
306 if ((isset($this->classes[$fileName])) && (!in_array($this->classes[$fileName], $this->loadedClasses))) {
307 // File is found and not loaded so load it only once
308 //* DEBUG: */ echo "LOAD: ".$fileName." - Start<br />\n";
309 require($this->classes[$fileName]);
310 //* DEBUG: */ echo "LOAD: ".$fileName." - End<br />\n";
312 // Count this include
315 // Mark this class as loaded
316 $this->loadedClasses[] = $this->classes[$fileName];
318 // Remove it from classes list
319 unset($this->classes[$fileName]);
321 // Developer mode excludes caching (better debugging)
322 if (!defined('DEVELOPER')) {
324 $this->classesCached = false;
330 * Includes all extra config files
334 private function includeExtraConfigs () {
335 // Run through all class names (should not be much)
336 foreach ($this->classes as $fileName => $fqfn) {
338 if (substr($fileName, 0, $this->prefixLen) == $this->prefix) {
342 // Remove it from the list
343 unset($this->classes[$fileName]);
349 * Getter for total include counter
351 * @return $total Total loaded include files
353 public final function getTotal () {
358 * Getter for a printable list of included classes/interfaces/exceptions
360 * @param $includeList A printable include list
362 public function getPrintableIncludeList () {
365 foreach ($this->loadedClasses as $classFile) {
366 $includeList .= basename($classFile)."<br />\n";