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
      • 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
  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
 19: {
 20:     use Nette\SmartObject;
 21: 
 22:     const RETRY_LIMIT = 3;
 23: 
 24:     /** @var string|array  comma separated wildcards */
 25:     public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';
 26: 
 27:     /** @var string|array  comma separated wildcards */
 28:     public $acceptFiles = '*.php, *.php5';
 29: 
 30:     /** @var bool */
 31:     public $autoRebuild = TRUE;
 32: 
 33:     /** @var array */
 34:     private $scanPaths = [];
 35: 
 36:     /** @var array of lowered-class => [file, time, orig] or num-of-retry */
 37:     private $classes = [];
 38: 
 39:     /** @var bool */
 40:     private $rebuilt = FALSE;
 41: 
 42:     /** @var array of missing classes in this request */
 43:     private $missing = [];
 44: 
 45:     /** @var Nette\Caching\IStorage */
 46:     private $cacheStorage;
 47: 
 48: 
 49:     public function __construct()
 50:     {
 51:         if (!extension_loaded('tokenizer')) {
 52:             throw new Nette\NotSupportedException('PHP extension Tokenizer is not loaded.');
 53:         }
 54:     }
 55: 
 56: 
 57:     /**
 58:      * Register autoloader.
 59:      * @param  bool  prepend autoloader?
 60:      * @return self
 61:      */
 62:     public function register($prepend = FALSE)
 63:     {
 64:         $this->classes = $this->getCache()->load($this->getKey(), [$this, 'rebuildCallback']);
 65:         spl_autoload_register([$this, 'tryLoad'], TRUE, (bool) $prepend);
 66:         return $this;
 67:     }
 68: 
 69: 
 70:     /**
 71:      * Handles autoloading of classes, interfaces or traits.
 72:      * @param  string
 73:      * @return void
 74:      */
 75:     public function tryLoad($type)
 76:     {
 77:         $type = $orig = ltrim($type, '\\'); // PHP namespace bug #49143
 78:         $type = strtolower($type);
 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);
 90:                 } else {
 91:                     $this->rebuild();
 92:                 }
 93:             } elseif (!$this->rebuilt && filemtime($info['file']) !== $info['time']) {
 94:                 $this->updateFile($info['file']);
 95:                 if (!isset($this->classes[$type])) {
 96:                     $this->classes[$type] = 0;
 97:                 }
 98:                 $this->getCache()->save($this->getKey(), $this->classes);
 99:             }
100:         }
101: 
102:         if (isset($this->classes[$type]['file'])) {
103:             if ($this->classes[$type]['orig'] !== $orig) {
104:                 trigger_error("Case mismatch on class name '$orig', correct name is '{$this->classes[$type]['orig']}'.", E_USER_WARNING);
105:             }
106:             call_user_func(function ($file) { require $file; }, $this->classes[$type]['file']);
107:         } else {
108:             $this->missing[$type] = TRUE;
109:         }
110:     }
111: 
112: 
113:     /**
114:      * Add path or paths to list.
115:      * @param  string|string[]  absolute path
116:      * @return self
117:      */
118:     public function addDirectory($path)
119:     {
120:         $this->scanPaths = array_merge($this->scanPaths, (array) $path);
121:         return $this;
122:     }
123: 
124: 
125:     /**
126:      * @return array of class => filename
127:      */
128:     public function getIndexedClasses()
129:     {
130:         $res = [];
131:         foreach ($this->classes as $info) {
132:             if (is_array($info)) {
133:                 $res[$info['orig']] = $info['file'];
134:             }
135:         }
136:         return $res;
137:     }
138: 
139: 
140:     /**
141:      * Rebuilds class list cache.
142:      * @return void
143:      */
144:     public function rebuild()
145:     {
146:         $this->rebuilt = TRUE; // prevents calling rebuild() or updateFile() in tryLoad()
147:         $this->getCache()->save($this->getKey(), Nette\Utils\Callback::closure($this, 'rebuildCallback'));
148:     }
149: 
150: 
151:     /**
152:      * @internal
153:      */
154:     public function rebuildCallback()
155:     {
156:         $files = $missing = [];
157:         foreach ($this->classes as $class => $info) {
158:             if (is_array($info)) {
159:                 $files[$info['file']]['time'] = $info['time'];
160:                 $files[$info['file']]['classes'][] = $info['orig'];
161:             } else {
162:                 $missing[$class] = $info;
163:             }
164:         }
165: 
166:         $this->classes = [];
167:         foreach ($this->scanPaths as $path) {
168:             foreach (is_file($path) ? [new SplFileInfo($path)] : $this->createFileIterator($path) as $file) {
169:                 $file = $file->getPathname();
170:                 if (isset($files[$file]) && $files[$file]['time'] == filemtime($file)) {
171:                     $classes = $files[$file]['classes'];
172:                 } else {
173:                     $classes = $this->scanPhp(file_get_contents($file));
174:                 }
175:                 $files[$file] = ['classes' => [], 'time' => filemtime($file)];
176: 
177:                 foreach ($classes as $class) {
178:                     $info = & $this->classes[strtolower($class)];
179:                     if (isset($info['file'])) {
180:                         throw new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
181:                     }
182:                     $info = ['file' => $file, 'time' => filemtime($file), 'orig' => $class];
183:                 }
184:             }
185:         }
186:         $this->classes += $missing;
187:         return $this->classes;
188:     }
189: 
190: 
191:     /**
192:      * Creates an iterator scaning directory for PHP files, subdirectories and 'netterobots.txt' files.
193:      * @return \Iterator
194:      * @throws Nette\IOException if path is not found
195:      */
196:     private function createFileIterator($dir)
197:     {
198:         if (!is_dir($dir)) {
199:             throw new Nette\IOException("File or directory '$dir' not found.");
200:         }
201: 
202:         $ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : preg_split('#[,\s]+#', $this->ignoreDirs);
203:         $disallow = [];
204:         foreach ($ignoreDirs as $item) {
205:             if ($item = realpath($item)) {
206:                 $disallow[$item] = TRUE;
207:             }
208:         }
209: 
210:         $iterator = Nette\Utils\Finder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : preg_split('#[,\s]+#', $this->acceptFiles))
211:             ->filter(function (SplFileInfo $file) use (& $disallow) {
212:                 return !isset($disallow[$file->getPathname()]);
213:             })
214:             ->from($dir)
215:             ->exclude($ignoreDirs)
216:             ->filter($filter = function (SplFileInfo $dir) use (& $disallow) {
217:                 $path = $dir->getPathname();
218:                 if (is_file("$path/netterobots.txt")) {
219:                     foreach (file("$path/netterobots.txt") as $s) {
220:                         if (preg_match('#^(?:disallow\\s*:)?\\s*(\\S+)#i', $s, $matches)) {
221:                             $disallow[$path . str_replace('/', DIRECTORY_SEPARATOR, rtrim('/' . ltrim($matches[1], '/'), '/'))] = TRUE;
222:                         }
223:                     }
224:                 }
225:                 return !isset($disallow[$path]);
226:             });
227: 
228:         $filter(new SplFileInfo($dir));
229:         return $iterator;
230:     }
231: 
232: 
233:     /**
234:      * @return void
235:      */
236:     private function updateFile($file)
237:     {
238:         foreach ($this->classes as $class => $info) {
239:             if (isset($info['file']) && $info['file'] === $file) {
240:                 unset($this->classes[$class]);
241:             }
242:         }
243: 
244:         if (is_file($file)) {
245:             foreach ($this->scanPhp(file_get_contents($file)) as $class) {
246:                 $info = & $this->classes[strtolower($class)];
247:                 if (isset($info['file']) && @filemtime($info['file']) !== $info['time']) { // @ file may not exists
248:                     $this->updateFile($info['file']);
249:                     $info = & $this->classes[strtolower($class)];
250:                 }
251:                 if (isset($info['file'])) {
252:                     throw new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
253:                 }
254:                 $info = ['file' => $file, 'time' => filemtime($file), 'orig' => $class];
255:             }
256:         }
257:     }
258: 
259: 
260:     /**
261:      * Searches classes, interfaces and traits in PHP file.
262:      * @param  string
263:      * @return array
264:      */
265:     private function scanPhp($code)
266:     {
267:         $expected = FALSE;
268:         $namespace = '';
269:         $level = $minLevel = 0;
270:         $classes = [];
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) { // @ can be corrupted or can use newer syntax
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 self
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 [$this->ignoreDirs, $this->acceptFiles, $this->scanPaths];
376:     }
377: 
378: }
379: 
Nette 2.4-20160930 API API documentation generated by ApiGen 2.8.0