Namespaces

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

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