Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

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