Namespaces

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

  • AutoLoader
  • LimitedScope
  • NetteLoader
  • RobotLoader
  • Overview
  • Namespace
  • Class
  • Tree
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  */
 11: 
 12: namespace Nette\Loaders;
 13: 
 14: use Nette;
 15: 
 16: 
 17: 
 18: /**
 19:  * Nette auto loader is responsible for loading classes and interfaces.
 20:  *
 21:  * @author     David Grudl
 22:  */
 23: class RobotLoader extends AutoLoader
 24: {
 25:     /** @var array */
 26:     public $scanDirs;
 27: 
 28:     /** @var string  comma separated wildcards */
 29:     public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';
 30: 
 31:     /** @var string  comma separated wildcards */
 32:     public $acceptFiles = '*.php, *.php5';
 33: 
 34:     /** @var bool */
 35:     public $autoRebuild;
 36: 
 37:     /** @var array */
 38:     private $list = array();
 39: 
 40:     /** @var array */
 41:     private $files;
 42: 
 43:     /** @var bool */
 44:     private $rebuilt = FALSE;
 45: 
 46:     /** @var string */
 47:     private $acceptMask;
 48: 
 49:     /** @var string */
 50:     private $ignoreMask;
 51: 
 52: 
 53: 
 54:     /**
 55:      */
 56:     public function __construct()
 57:     {
 58:         if (!extension_loaded('tokenizer')) {
 59:             throw new \Exception("PHP extension Tokenizer is not loaded.");
 60:         }
 61:     }
 62: 
 63: 
 64: 
 65:     /**
 66:      * Register autoloader.
 67:      * @return void
 68:      */
 69:     public function register()
 70:     {
 71:         $cache = $this->getCache();
 72:         $key = $this->getKey();
 73:         if (isset($cache[$key])) {
 74:             $this->list = $cache[$key];
 75:         } else {
 76:             $this->rebuild();
 77:         }
 78: 
 79:         if (isset($this->list[strtolower(__CLASS__)]) && class_exists('Nette\Loaders\NetteLoader', FALSE)) {
 80:             NetteLoader::getInstance()->unregister();
 81:         }
 82: 
 83:         parent::register();
 84:     }
 85: 
 86: 
 87: 
 88:     /**
 89:      * Handles autoloading of classes or interfaces.
 90:      * @param  string
 91:      * @return void
 92:      */
 93:     public function tryLoad($type)
 94:     {
 95:         $type = ltrim(strtolower($type), '\\'); // PHP namespace bug #49143
 96:         if (isset($this->list[$type])) {
 97:             if ($this->list[$type] !== FALSE) {
 98:                 LimitedScope::load($this->list[$type][0]);
 99:                 self::$count++;
100:             }
101: 
102:         } else {
103:             $this->list[$type] = FALSE;
104: 
105:             if ($this->autoRebuild === NULL) {
106:                 $this->autoRebuild = !$this->isProduction();
107:             }
108: 
109:             if ($this->autoRebuild) {
110:                 if ($this->rebuilt) {
111:                     $this->getCache()->save($this->getKey(), $this->list);
112:                 } else {
113:                     $this->rebuild();
114:                 }
115:             }
116: 
117:             if ($this->list[$type] !== FALSE) {
118:                 LimitedScope::load($this->list[$type][0], TRUE);
119:                 self::$count++;
120:             }
121:         }
122:     }
123: 
124: 
125: 
126:     /**
127:      * Rebuilds class list cache.
128:      * @return void
129:      */
130:     public function rebuild()
131:     {
132:         $this->getCache()->save($this->getKey(), callback($this, '_rebuildCallback'));
133:         $this->rebuilt = TRUE;
134:     }
135: 
136: 
137: 
138:     /**
139:      * @internal
140:      */
141:     public function _rebuildCallback()
142:     {
143:         $this->acceptMask = self::wildcards2re($this->acceptFiles);
144:         $this->ignoreMask = self::wildcards2re($this->ignoreDirs);
145:         foreach ($this->list as $pair) {
146:             if ($pair) $this->files[$pair[0]] = $pair[1];
147:         }
148:         foreach (array_unique($this->scanDirs) as $dir) {
149:             $this->scanDirectory($dir);
150:         }
151:         $this->files = NULL;
152:         return $this->list;
153:     }
154: 
155: 
156: 
157:     /**
158:      * @return array of class => filename
159:      */
160:     public function getIndexedClasses()
161:     {
162:         $res = array();
163:         foreach ($this->list as $class => $pair) {
164:             if ($pair) $res[$class] = $pair[0];
165:         }
166:         return $res;
167:     }
168: 
169: 
170: 
171:     /**
172:      * Add directory (or directories) to list.
173:      * @param  string|array
174:      * @return void
175:      * @throws \DirectoryNotFoundException if path is not found
176:      */
177:     public function addDirectory($path)
178:     {
179:         foreach ((array) $path as $val) {
180:             $real = realpath($val);
181:             if ($real === FALSE) {
182:                 throw new \DirectoryNotFoundException("Directory '$val' not found.");
183:             }
184:             $this->scanDirs[] = $real;
185:         }
186:     }
187: 
188: 
189: 
190:     /**
191:      * Add class and file name to the list.
192:      * @param  string
193:      * @param  string
194:      * @param  int
195:      * @return void
196:      */
197:     private function addClass($class, $file, $time)
198:     {
199:         $class = strtolower($class);
200:         if (!empty($this->list[$class]) && $this->list[$class][0] !== $file) {
201:             spl_autoload_call($class); // hack: enables exceptions
202:             throw new \InvalidStateException("Ambiguous class '$class' resolution; defined in $file and in " . $this->list[$class][0] . ".");
203:         }
204:         $this->list[$class] = array($file, $time);
205:     }
206: 
207: 
208: 
209:     /**
210:      * Scan a directory for PHP files, subdirectories and 'netterobots.txt' file.
211:      * @param  string
212:      * @return void
213:      */
214:     private function scanDirectory($dir)
215:     {
216:         if (is_file($dir)) {
217:             if (!isset($this->files[$dir]) || $this->files[$dir] !== filemtime($dir)) {
218:                 $this->scanScript($dir);
219:             }
220:             return;
221:         }
222: 
223:         $iterator = dir($dir);
224:         if (!$iterator) return;
225: 
226:         $disallow = array();
227:         if (is_file($dir . '/netterobots.txt')) {
228:             foreach (file($dir . '/netterobots.txt') as $s) {
229:                 if (preg_match('#^disallow\\s*:\\s*(\\S+)#i', $s, $m)) {
230:                     $disallow[trim($m[1], '/')] = TRUE;
231:                 }
232:             }
233:             if (isset($disallow[''])) return;
234:         }
235: 
236:         while (FALSE !== ($entry = $iterator->read())) {
237:             if ($entry == '.' || $entry == '..' || isset($disallow[$entry])) continue;
238: 
239:             $path = $dir . DIRECTORY_SEPARATOR . $entry;
240: 
241:             // process subdirectories
242:             if (is_dir($path)) {
243:                 // check ignore mask
244:                 if (!preg_match($this->ignoreMask, $entry)) {
245:                     $this->scanDirectory($path);
246:                 }
247:                 continue;
248:             }
249: 
250:             if (is_file($path) && preg_match($this->acceptMask, $entry)) {
251:                 if (!isset($this->files[$path]) || $this->files[$path] !== filemtime($path)) {
252:                     $this->scanScript($path);
253:                 }
254:             }
255:         }
256: 
257:         $iterator->close();
258:     }
259: 
260: 
261: 
262:     /**
263:      * Analyse PHP file.
264:      * @param  string
265:      * @return void
266:      */
267:     private function scanScript($file)
268:     {
269:         $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
270:         $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
271: 
272:         $expected = FALSE;
273:         $namespace = '';
274:         $level = $minLevel = 0;
275:         $time = filemtime($file);
276:         $s = file_get_contents($file);
277: 
278:         if (preg_match('#//nette'.'loader=(\S*)#', $s, $matches)) {
279:             foreach (explode(',', $matches[1]) as $name) {
280:                 $this->addClass($name, $file, $time);
281:             }
282:             return;
283:         }
284: 
285:         foreach (token_get_all($s) as $token)
286:         {
287:             if (is_array($token)) {
288:                 switch ($token[0]) {
289:                 case T_COMMENT:
290:                 case T_DOC_COMMENT:
291:                 case T_WHITESPACE:
292:                     continue 2;
293: 
294:                 case $T_NS_SEPARATOR:
295:                 case T_STRING:
296:                     if ($expected) {
297:                         $name .= $token[1];
298:                     }
299:                     continue 2;
300: 
301:                 case $T_NAMESPACE:
302:                 case T_CLASS:
303:                 case T_INTERFACE:
304:                     $expected = $token[0];
305:                     $name = '';
306:                     continue 2;
307:                 case T_CURLY_OPEN:
308:                 case T_DOLLAR_OPEN_CURLY_BRACES:
309:                     $level++;
310:                 }
311:             }
312: 
313:             if ($expected) {
314:                 switch ($expected) {
315:                 case T_CLASS:
316:                 case T_INTERFACE:
317:                     if ($level === $minLevel) {
318:                         $this->addClass($namespace . $name, $file, $time);
319:                     }
320:                     break;
321: 
322:                 case $T_NAMESPACE:
323:                     $namespace = $name ? $name . '\\' : '';
324:                     $minLevel = $token === '{' ? 1 : 0;
325:                 }
326: 
327:                 $expected = NULL;
328:             }
329: 
330:             if ($token === '{') {
331:                 $level++;
332:             } elseif ($token === '}') {
333:                 $level--;
334:             }
335:         }
336:     }
337: 
338: 
339: 
340:     /**
341:      * Converts comma separated wildcards to regular expression.
342:      * @param  string
343:      * @return string
344:      */
345:     private static function wildcards2re($wildcards)
346:     {
347:         $mask = array();
348:         foreach (explode(',', $wildcards) as $wildcard) {
349:             $wildcard = trim($wildcard);
350:             $wildcard = addcslashes($wildcard, '.\\+[^]$(){}=!><|:#');
351:             $wildcard = strtr($wildcard, array('*' => '.*', '?' => '.'));
352:             $mask[] = $wildcard;
353:         }
354:         return '#^(' . implode('|', $mask) . ')$#i';
355:     }
356: 
357: 
358: 
359:     /********************* backend ****************d*g**/
360: 
361: 
362: 
363:     /**
364:      * @return Nette\Caching\Cache
365:      */
366:     protected function getCache()
367:     {
368:         return Nette\Environment::getCache('Nette.RobotLoader');
369:     }
370: 
371: 
372: 
373:     /**
374:      * @return string
375:      */
376:     protected function getKey()
377:     {
378:         return md5("v2|$this->ignoreDirs|$this->acceptFiles|" . implode('|', $this->scanDirs));
379:     }
380: 
381: 
382: 
383:     /**
384:      * @return bool
385:      */
386:     protected function isProduction()
387:     {
388:         return Nette\Environment::isProduction();
389:     }
390: 
391: }
392: 
Nette Framework 0.9.7 API documentation generated by ApiGen 2.3.0