Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • AutoLoader
  • NetteLoader
  • RobotLoader
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://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:     Nette\Caching\Cache;
 16: 
 17: 
 18: 
 19: /**
 20:  * Nette auto loader is responsible for loading classes and interfaces.
 21:  *
 22:  * @author     David Grudl
 23:  *
 24:  * @property-read array $indexedClasses
 25:  * @property   Nette\Caching\IStorage $cacheStorage
 26:  */
 27: class RobotLoader extends AutoLoader
 28: {
 29:     const RETRY_LIMIT = 3;
 30: 
 31:     /** @var array */
 32:     public $scanDirs = array();
 33: 
 34:     /** @var string|array  comma separated wildcards */
 35:     public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';
 36: 
 37:     /** @var string|array  comma separated wildcards */
 38:     public $acceptFiles = '*.php, *.php5';
 39: 
 40:     /** @var bool */
 41:     public $autoRebuild = TRUE;
 42: 
 43:     /** @var array of lowered-class => [file, mtime, class] or num-of-retry */
 44:     private $classes = array();
 45: 
 46:     /** @var bool */
 47:     private $rebuilt = FALSE;
 48: 
 49:     /** @var array of missing classes in this request */
 50:     private $missing = array();
 51: 
 52:     /** @var Nette\Caching\IStorage */
 53:     private $cacheStorage;
 54: 
 55: 
 56: 
 57:     /**
 58:      */
 59:     public function __construct()
 60:     {
 61:         if (!extension_loaded('tokenizer')) {
 62:             throw new Nette\NotSupportedException("PHP extension Tokenizer is not loaded.");
 63:         }
 64:     }
 65: 
 66: 
 67: 
 68:     /**
 69:      * Register autoloader.
 70:      * @return RobotLoader  provides a fluent interface
 71:      */
 72:     public function register()
 73:     {
 74:         $this->classes = $this->getCache()->load($this->getKey(), new Nette\Callback($this, '_rebuildCallback'));
 75:         parent::register();
 76:         return $this;
 77:     }
 78: 
 79: 
 80: 
 81:     /**
 82:      * Handles autoloading of classes, interfaces or traits.
 83:      * @param  string
 84:      * @return void
 85:      */
 86:     public function tryLoad($type)
 87:     {
 88:         $type = ltrim(strtolower($type), '\\'); // PHP namespace bug #49143
 89: 
 90:         $info = & $this->classes[$type];
 91:         if (isset($this->missing[$type]) || (is_int($info) && $info >= self::RETRY_LIMIT)) {
 92:             return;
 93:         }
 94: 
 95:         if ($this->autoRebuild) {
 96:             if (!is_array($info) || !is_file($info['file'])) {
 97:                 $info = is_int($info) ? $info + 1 : 0;
 98:                 if ($this->rebuilt) {
 99:                     $this->getCache()->save($this->getKey(), $this->classes, array(
100:                         Cache::CONSTS => 'Nette\Framework::REVISION',
101:                     ));
102:                 } else {
103:                     $this->rebuild();
104:                 }
105:             } elseif (!$this->rebuilt && filemtime($info['file']) !== $info['time']) {
106:                 $this->updateFile($info['file']);
107:                 if (!isset($this->classes[$type])) {
108:                     $this->classes[$type] = 0;
109:                 }
110:                 $this->getCache()->save($this->getKey(), $this->classes, array(
111:                     Cache::CONSTS => 'Nette\Framework::REVISION',
112:                 ));
113:             }
114:         }
115: 
116:         if (isset($this->classes[$type]['file'])) {
117:             Nette\Utils\LimitedScope::load($this->classes[$type]['file'], TRUE);
118:             self::$count++;
119:         } else {
120:             $this->missing[$type] = TRUE;
121:         }
122:     }
123: 
124: 
125: 
126:     /**
127:      * Add directory (or directories) to list.
128:      * @param  string|array
129:      * @return RobotLoader  provides a fluent interface
130:      * @throws Nette\DirectoryNotFoundException if path is not found
131:      */
132:     public function addDirectory($path)
133:     {
134:         foreach ((array) $path as $val) {
135:             $real = realpath($val);
136:             if ($real === FALSE) {
137:                 throw new Nette\DirectoryNotFoundException("Directory '$val' not found.");
138:             }
139:             $this->scanDirs[] = $real;
140:         }
141:         return $this;
142:     }
143: 
144: 
145: 
146:     /**
147:      * @return array of class => filename
148:      */
149:     public function getIndexedClasses()
150:     {
151:         $res = array();
152:         foreach ($this->classes as $class => $info) {
153:             if (is_array($info)) {
154:                 $res[$info['orig']] = $info['file'];
155:             }
156:         }
157:         return $res;
158:     }
159: 
160: 
161: 
162:     /**
163:      * Rebuilds class list cache.
164:      * @return void
165:      */
166:     public function rebuild()
167:     {
168:         $this->rebuilt = TRUE; // prevents calling rebuild() or updateFile() in tryLoad()
169:         $this->getCache()->save($this->getKey(), new Nette\Callback($this, '_rebuildCallback'));
170:     }
171: 
172: 
173: 
174:     /**
175:      * @internal
176:      */
177:     public function _rebuildCallback(& $dp)
178:     {
179:         $files = $missing = array();
180:         foreach ($this->classes as $class => $info) {
181:             if (is_array($info)) {
182:                 $files[$info['file']]['time'] = $info['time'];
183:                 $files[$info['file']]['classes'][] = $info['orig'];
184:             } else {
185:                 $missing[$class] = $info;
186:             }
187:         }
188: 
189:         $this->classes = array();
190:         foreach (array_unique($this->scanDirs) as $dir) {
191:             foreach ($this->createFileIterator($dir) as $file) {
192:                 $file = $file->getPathname();
193:                 if (isset($files[$file]) && $files[$file]['time'] == filemtime($file)) {
194:                     $classes = $files[$file]['classes'];
195:                 } else {
196:                     $classes = $this->scanPhp(file_get_contents($file));
197:                 }
198: 
199:                 foreach ($classes as $class) {
200:                     $info = & $this->classes[strtolower($class)];
201:                     if (isset($info['file'])) {
202:                         $e = new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
203:                         {
204:                             throw $e;
205:                         }
206:                     }
207:                     $info = array('file' => $file, 'time' => filemtime($file), 'orig' => $class);
208:                 }
209:             }
210:         }
211: 
212:         $dp = array(
213:             Cache::CONSTS => 'Nette\Framework::REVISION'
214:         );
215:         $this->classes += $missing;
216:         return $this->classes;
217:     }
218: 
219: 
220: 
221:     /**
222:      * Creates an iterator scaning directory for PHP files, subdirectories and 'netterobots.txt' files.
223:      * @return \Iterator
224:      */
225:     private function createFileIterator($dir)
226:     {
227:         if (!is_dir($dir)) {
228:             return new \ArrayIterator(array(new \SplFileInfo($dir)));
229:         }
230: 
231:         $ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : preg_split('#[,\s]+#', $this->ignoreDirs);
232:         $disallow = array();
233:         foreach ($ignoreDirs as $item) {
234:             if ($item = realpath($item)) {
235:                 $disallow[$item] = TRUE;
236:             }
237:         }
238: 
239:         $iterator = Nette\Utils\Finder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : preg_split('#[,\s]+#', $this->acceptFiles))
240:             ->filter(function($file) use (&$disallow){
241:                 return !isset($disallow[$file->getPathname()]);
242:             })
243:             ->from($dir)
244:             ->exclude($ignoreDirs)
245:             ->filter($filter = function($dir) use (&$disallow){
246:                 $path = $dir->getPathname();
247:                 if (is_file("$path/netterobots.txt")) {
248:                     foreach (file("$path/netterobots.txt") as $s) {
249:                     if (preg_match('#^(?:disallow\\s*:)?\\s*(\\S+)#i', $s, $matches)) {
250:                             $disallow[$path . str_replace('/', DIRECTORY_SEPARATOR, rtrim('/' . ltrim($matches[1], '/'), '/'))] = TRUE;
251:                         }
252:                     }
253:                 }
254:                 return !isset($disallow[$path]);
255:             });
256: 
257:         $filter(new \SplFileInfo($dir));
258:         return $iterator;
259:     }
260: 
261: 
262: 
263:     /**
264:      * @return void
265:      */
266:     private function updateFile($file)
267:     {
268:         foreach ($this->classes as $class => $info) {
269:             if (isset($info['file']) && $info['file'] === $file) {
270:                 unset($this->classes[$class]);
271:             }
272:         }
273: 
274:         if (is_file($file)) {
275:             foreach ($this->scanPhp(file_get_contents($file)) as $class) {
276:                 $info = & $this->classes[strtolower($class)];
277:                 if (isset($info['file']) && @filemtime($info['file']) !== $info['time']) { // intentionally ==, file may not exists
278:                     $this->updateFile($info['file']);
279:                     $info = & $this->classes[strtolower($class)];
280:                 }
281:                 if (isset($info['file'])) {
282:                     $e = new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
283:                     {
284:                         throw $e;
285:                     }
286:                 }
287:                 $info = array('file' => $file, 'time' => filemtime($file), 'orig' => $class);
288:             }
289:         }
290:     }
291: 
292: 
293: 
294:     /**
295:      * Searches classes, interfaces and traits in PHP file.
296:      * @param  string
297:      * @return array
298:      */
299:     private function scanPhp($code)
300:     {
301:         $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
302:         $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
303:         $T_TRAIT = PHP_VERSION_ID < 50400 ? -1 : T_TRAIT;
304: 
305:         $expected = FALSE;
306:         $namespace = '';
307:         $level = $minLevel = 0;
308:         $classes = array();
309: 
310:         if (preg_match('#//nette'.'loader=(\S*)#', $code, $matches)) {
311:             foreach (explode(',', $matches[1]) as $name) {
312:                 $classes[] = $name;
313:             }
314:             return $classes;
315:         }
316: 
317:         foreach (@token_get_all($code) as $token) { // intentionally @
318:             if (is_array($token)) {
319:                 switch ($token[0]) {
320:                 case T_COMMENT:
321:                 case T_DOC_COMMENT:
322:                 case T_WHITESPACE:
323:                     continue 2;
324: 
325:                 case $T_NS_SEPARATOR:
326:                 case T_STRING:
327:                     if ($expected) {
328:                         $name .= $token[1];
329:                     }
330:                     continue 2;
331: 
332:                 case $T_NAMESPACE:
333:                 case T_CLASS:
334:                 case T_INTERFACE:
335:                 case $T_TRAIT:
336:                     $expected = $token[0];
337:                     $name = '';
338:                     continue 2;
339:                 case T_CURLY_OPEN:
340:                 case T_DOLLAR_OPEN_CURLY_BRACES:
341:                     $level++;
342:                 }
343:             }
344: 
345:             if ($expected) {
346:                 switch ($expected) {
347:                 case T_CLASS:
348:                 case T_INTERFACE:
349:                 case $T_TRAIT:
350:                     if ($level === $minLevel) {
351:                         $classes[] = $namespace . $name;
352:                     }
353:                     break;
354: 
355:                 case $T_NAMESPACE:
356:                     $namespace = $name ? $name . '\\' : '';
357:                     $minLevel = $token === '{' ? 1 : 0;
358:                 }
359: 
360:                 $expected = NULL;
361:             }
362: 
363:             if ($token === '{') {
364:                 $level++;
365:             } elseif ($token === '}') {
366:                 $level--;
367:             }
368:         }
369:         return $classes;
370:     }
371: 
372: 
373: 
374:     /********************* backend ****************d*g**/
375: 
376: 
377: 
378:     /**
379:      * @return RobotLoader
380:      */
381:     public function setCacheStorage(Nette\Caching\IStorage $storage)
382:     {
383:         $this->cacheStorage = $storage;
384:         return $this;
385:     }
386: 
387: 
388: 
389:     /**
390:      * @return Nette\Caching\IStorage
391:      */
392:     public function getCacheStorage()
393:     {
394:         return $this->cacheStorage;
395:     }
396: 
397: 
398: 
399:     /**
400:      * @return Nette\Caching\Cache
401:      */
402:     protected function getCache()
403:     {
404:         if (!$this->cacheStorage) {
405:             trigger_error('Missing cache storage.', E_USER_WARNING);
406:             $this->cacheStorage = new Nette\Caching\Storages\DevNullStorage;
407:         }
408:         return new Cache($this->cacheStorage, 'Nette.RobotLoader');
409:     }
410: 
411: 
412: 
413:     /**
414:      * @return string
415:      */
416:     protected function getKey()
417:     {
418:         return array($this->ignoreDirs, $this->acceptFiles, $this->scanDirs);
419:     }
420: 
421: }
422: 
Nette Framework 2.0.7 API API documentation generated by ApiGen 2.8.0