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
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • NetteLoader
  • RobotLoader
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  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 @deprecated */
 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 $refreshed = 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 static
 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->refreshed) {
 89:                     $this->getCache()->save($this->getKey(), $this->classes);
 90:                 } else {
 91:                     $this->rebuild();
 92:                 }
 93:             } elseif (!$this->refreshed && 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 static
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:         if ($this->cacheStorage) {
147:             $this->getCache()->save($this->getKey(), Nette\Utils\Callback::closure($this, 'rebuildCallback'));
148:         } else {
149:             $this->rebuildCallback();
150:         }
151:     }
152: 
153: 
154:     /**
155:      * @internal
156:      */
157:     public function rebuildCallback()
158:     {
159:         $this->refreshed = true; // prevents calling rebuild() or updateFile() in tryLoad()
160:         $files = $missing = [];
161:         foreach ($this->classes as $class => $info) {
162:             if (is_array($info)) {
163:                 $files[$info['file']]['time'] = $info['time'];
164:                 $files[$info['file']]['classes'][] = $info['orig'];
165:             } else {
166:                 $missing[$class] = $info;
167:             }
168:         }
169: 
170:         $this->classes = [];
171:         foreach ($this->scanPaths as $path) {
172:             foreach (is_file($path) ? [new SplFileInfo($path)] : $this->createFileIterator($path) as $file) {
173:                 $file = $file->getPathname();
174:                 if (isset($files[$file]) && $files[$file]['time'] == filemtime($file)) {
175:                     $classes = $files[$file]['classes'];
176:                 } else {
177:                     $classes = $this->scanPhp(file_get_contents($file));
178:                 }
179:                 $files[$file] = ['classes' => [], 'time' => filemtime($file)];
180: 
181:                 foreach ($classes as $class) {
182:                     $info = &$this->classes[strtolower($class)];
183:                     if (isset($info['file'])) {
184:                         throw new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
185:                     }
186:                     $info = ['file' => $file, 'time' => filemtime($file), 'orig' => $class];
187:                 }
188:             }
189:         }
190:         $this->classes += $missing;
191:         return $this->classes;
192:     }
193: 
194: 
195:     /**
196:      * Creates an iterator scaning directory for PHP files, subdirectories and 'netterobots.txt' files.
197:      * @return \Iterator
198:      * @throws Nette\IOException if path is not found
199:      */
200:     private function createFileIterator($dir)
201:     {
202:         if (!is_dir($dir)) {
203:             throw new Nette\IOException("File or directory '$dir' not found.");
204:         }
205: 
206:         $ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : preg_split('#[,\s]+#', $this->ignoreDirs);
207:         $disallow = [];
208:         foreach ($ignoreDirs as $item) {
209:             if ($item = realpath($item)) {
210:                 $disallow[$item] = true;
211:             }
212:         }
213: 
214:         $iterator = Nette\Utils\Finder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : preg_split('#[,\s]+#', $this->acceptFiles))
215:             ->filter(function (SplFileInfo $file) use (&$disallow) {
216:                 return !isset($disallow[$file->getPathname()]);
217:             })
218:             ->from($dir)
219:             ->exclude($ignoreDirs)
220:             ->filter($filter = function (SplFileInfo $dir) use (&$disallow) {
221:                 $path = $dir->getPathname();
222:                 if (is_file("$path/netterobots.txt")) {
223:                     foreach (file("$path/netterobots.txt") as $s) {
224:                         if (preg_match('#^(?:disallow\\s*:)?\\s*(\\S+)#i', $s, $matches)) {
225:                             $disallow[$path . str_replace('/', DIRECTORY_SEPARATOR, rtrim('/' . ltrim($matches[1], '/'), '/'))] = true;
226:                         }
227:                     }
228:                 }
229:                 return !isset($disallow[$path]);
230:             });
231: 
232:         $filter(new SplFileInfo($dir));
233:         return $iterator;
234:     }
235: 
236: 
237:     /**
238:      * @return void
239:      */
240:     private function updateFile($file)
241:     {
242:         foreach ($this->classes as $class => $info) {
243:             if (isset($info['file']) && $info['file'] === $file) {
244:                 unset($this->classes[$class]);
245:             }
246:         }
247: 
248:         if (is_file($file)) {
249:             foreach ($this->scanPhp(file_get_contents($file)) as $class) {
250:                 $info = &$this->classes[strtolower($class)];
251:                 if (isset($info['file']) && @filemtime($info['file']) !== $info['time']) { // @ file may not exists
252:                     $this->updateFile($info['file']);
253:                     $info = &$this->classes[strtolower($class)];
254:                 }
255:                 if (isset($info['file'])) {
256:                     throw new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
257:                 }
258:                 $info = ['file' => $file, 'time' => filemtime($file), 'orig' => $class];
259:             }
260:         }
261:     }
262: 
263: 
264:     /**
265:      * Searches classes, interfaces and traits in PHP file.
266:      * @param  string
267:      * @return array
268:      */
269:     private function scanPhp($code)
270:     {
271:         $expected = false;
272:         $namespace = '';
273:         $level = $minLevel = 0;
274:         $classes = [];
275: 
276:         if (preg_match('#//nette' . 'loader=(\S*)#', $code, $matches)) {
277:             foreach (explode(',', $matches[1]) as $name) {
278:                 $classes[] = $name;
279:             }
280:             return $classes;
281:         }
282: 
283:         foreach (@token_get_all($code) as $token) { // @ can be corrupted or can use newer syntax
284:             if (is_array($token)) {
285:                 switch ($token[0]) {
286:                     case T_COMMENT:
287:                     case T_DOC_COMMENT:
288:                     case T_WHITESPACE:
289:                         continue 2;
290: 
291:                     case T_NS_SEPARATOR:
292:                     case T_STRING:
293:                         if ($expected) {
294:                             $name .= $token[1];
295:                         }
296:                         continue 2;
297: 
298:                     case T_NAMESPACE:
299:                     case T_CLASS:
300:                     case T_INTERFACE:
301:                     case T_TRAIT:
302:                         $expected = $token[0];
303:                         $name = '';
304:                         continue 2;
305:                     case T_CURLY_OPEN:
306:                     case T_DOLLAR_OPEN_CURLY_BRACES:
307:                         $level++;
308:                 }
309:             }
310: 
311:             if ($expected) {
312:                 switch ($expected) {
313:                     case T_CLASS:
314:                     case T_INTERFACE:
315:                     case T_TRAIT:
316:                         if ($name && $level === $minLevel) {
317:                             $classes[] = $namespace . $name;
318:                         }
319:                         break;
320: 
321:                     case T_NAMESPACE:
322:                         $namespace = $name ? $name . '\\' : '';
323:                         $minLevel = $token === '{' ? 1 : 0;
324:                 }
325: 
326:                 $expected = null;
327:             }
328: 
329:             if ($token === '{') {
330:                 $level++;
331:             } elseif ($token === '}') {
332:                 $level--;
333:             }
334:         }
335:         return $classes;
336:     }
337: 
338: 
339:     /********************* caching ****************d*g**/
340: 
341: 
342:     /**
343:      * Sets auto-refresh mode.
344:      * @return static
345:      */
346:     public function setAutoRefresh($on = true)
347:     {
348:         $this->autoRebuild = (bool) $on;
349:         return $this;
350:     }
351: 
352: 
353:     /**
354:      * Sets path to temporary directory.
355:      * @return static
356:      */
357:     public function setTempDirectory($dir)
358:     {
359:         if ($dir) {
360:             Nette\Utils\FileSystem::createDir($dir);
361:             $this->cacheStorage = new Nette\Caching\Storages\FileStorage($dir);
362:         } else {
363:             $this->cacheStorage = new Nette\Caching\Storages\DevNullStorage;
364:         }
365:         return $this;
366:     }
367: 
368: 
369:     /**
370:      * @return static
371:      */
372:     public function setCacheStorage(Nette\Caching\IStorage $storage)
373:     {
374:         $this->cacheStorage = $storage;
375:         return $this;
376:     }
377: 
378: 
379:     /**
380:      * @return Nette\Caching\IStorage
381:      */
382:     public function getCacheStorage()
383:     {
384:         return $this->cacheStorage;
385:     }
386: 
387: 
388:     /**
389:      * @return Nette\Caching\Cache
390:      */
391:     protected function getCache()
392:     {
393:         if (!$this->cacheStorage) {
394:             trigger_error('Set path to temporary directory using setTempDirectory().', E_USER_WARNING);
395:             $this->cacheStorage = new Nette\Caching\Storages\DevNullStorage;
396:         }
397:         return new Cache($this->cacheStorage, 'Nette.RobotLoader');
398:     }
399: 
400: 
401:     /**
402:      * @return array
403:      */
404:     protected function getKey()
405:     {
406:         return [$this->ignoreDirs, $this->acceptFiles, $this->scanPaths];
407:     }
408: }
409: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0