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 - 2009 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 * - Constructor is now empty and factory method 'createClassLoader' is created
27 * - renamed loadClasses to scanClassPath
28 * - Added initLoader(), $cfgInstance renamed to $configInstance
30 * - ClassLoader rewritten to PHP SPL's own RecursiveIteratorIterator class
32 * - loadClasses rewritten to fix some notices
35 * ----------------------------------
39 * Instance of this class
41 private static $selfInstance = null;
46 private $cfg = array();
49 * Array with all classes
51 private $classes = array();
54 * List of loaded classes
56 private $loadedClasses = array();
59 * Suffix with extension for all class files
61 private $prefix = "class_";
64 * Suffix with extension for all class files
66 private $suffix = ".php";
69 * A list for directory names (no leading/trailing slashes!) which not be scanned by the path scanner
72 private $ignoreList = array();
75 * Debug this class loader? (true = yes, false = no)
77 private $debug = false;
80 * Wether the file list is cached or not
82 private $listCached = false;
85 * Wethe class content has been cached
87 private $classesCached = false;
90 * Filename for the list cache
92 private $listCacheFQFN = "";
95 * Cache for class content
97 private $classCacheFQFN = "";
100 * Counter for loaded include files
105 * The protected constructor. Please use the factory method below, or use
106 * getInstance() for singleton
110 protected function __construct () {
111 // Is Currently empty
115 * Our renamed factory method
117 * @param $configInstance Configuration class instance
120 public final static function createClassLoader (FrameworkConfiguration $configInstance) {
121 // Get a new instance
122 $loaderInstance = new ClassLoader();
125 $loaderInstance->initLoader($configInstance);
127 // Return the prepared instance
128 return $loaderInstance;
132 * Initializes our loader class
134 * @param $configInstance Configuration class instance
137 protected function initLoader (FrameworkConfiguration $configInstance) {
138 // Set configuration instance
139 $this->cfgInstance = $configInstance;
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";
147 // Set suffix and prefix from configuration
148 $this->suffix = $configInstance->readConfig('class_suffix');
149 $this->prefix = $configInstance->readConfig('class_prefix');
152 self::$selfInstance = $this;
154 // Skip here if no dev-mode
155 if (defined('DEVELOPER')) return;
157 // IS the cache there?
158 if (file_exists($this->listCacheFQFN)) {
160 $cacheContent = file_get_contents($this->listCacheFQFN);
163 $this->classes = unserialize($cacheContent);
165 // List has been restored from cache!
166 $this->listCached = true;
169 // Does the class cache exist?
170 if (file_exists($this->classCacheFQFN)) {
172 require($this->classCacheFQFN);
174 // Mark the class cache as loaded
175 $this->classesCached = true;
182 * @param $className Name of the class to load
185 public static function autoLoad ($className) {
186 // Try to include this class
187 self::getInstance()->includeClass($className);
191 * Getter for an instance of this class
193 * @return $selfInstance An instance of this class
195 public final static function getInstance () {
196 // Is the instance there?
197 if (is_null(self::$selfInstance)) {
199 self::$selfInstance = ClassLoader::createClassLoader(FrameworkConfiguration::getInstance());
202 // Return the instance
203 return self::$selfInstance;
207 * The destructor makes it sure all caches got flushed
211 public function __destruct () {
212 // Skip here if dev-mode
213 if (defined('DEVELOPER')) return;
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);
222 // Skip here if already cached
223 if ($this->classesCached === false) {
224 // Generate a full-cache of all classes
226 foreach ($this->loadedClasses as $fqfn) {
228 $cacheContent .= file_get_contents($fqfn);
232 file_put_contents($this->classCacheFQFN, $cacheContent);
237 * Fall-back method. Please replace loadClasses() with scanClassPath() !
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
243 * @todo Rewrite your apps to scanClassPath()
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";
249 // Call our new method
250 $this->scanClassPath($basePath, $ignoreList);
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
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
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) {
267 // Directories which our class loader ignores by default while
268 // deep-scanning the directory structure.
270 $ignoreList[] = "..";
271 $ignoreList[] = ".htaccess";
272 $ignoreList[] = ".svn";
274 // Keep it in class for later usage
275 $this->ignoreList = $ignoreList;
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);
282 // If the basePath is false it is invalid
283 if ($basePath2 === false) {
284 /* @todo: Do not die here. */
285 die("Cannot read {$basePath} !");
288 $basePath = $basePath2;
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();
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;
311 * Load extra config files
315 public function loadExtraConfigs () {
317 $oldPrefix = $this->prefix;
319 // Set new prefix (temporary!)
320 $this->prefix = "config-";
322 // Set base directory
323 $basePath = sprintf("%sinc/config/", $this->cfgInstance->readConfig('base_path'));
325 // Load all classes from the config directory
326 $this->scanClassPath($basePath);
328 // Include these extra configs now
329 $this->includeExtraConfigs();
331 // Set the prefix back
332 $this->prefix = $oldPrefix;
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.
340 * @param $className The class we shall load
343 public function includeClass ($className) {
344 // Create a name with prefix and suffix
345 $fileName = $this->prefix . $className . $this->suffix;
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";
354 // Count this include
357 // Mark this class as loaded
358 $this->loadedClasses[] = $this->classes[$fileName];
360 // Remove it from classes list
361 unset($this->classes[$fileName]);
363 // Developer mode excludes caching (better debugging)
364 if (!defined('DEVELOPER')) {
366 $this->classesCached = false;
372 * Includes all extra config files
376 private function includeExtraConfigs () {
377 // Run through all class names (should not be much)
378 foreach ($this->classes as $fileName => $fqfn) {
380 if (substr($fileName, 0, strlen($this->prefix)) == $this->prefix) {
384 // Remove it from the list
385 unset($this->classes[$fileName]);
391 * Getter for total include counter
393 * @return $total Total loaded include files
395 public final function getTotal () {
400 * Getter for a printable list of included classes/interfaces/exceptions
402 * @param $includeList A printable include list
404 public function getPrintableIncludeList () {
407 foreach ($this->loadedClasses as $classFile) {
408 $includeList .= basename($classFile)."<br />\n";