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

Classes

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