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
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • 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 (http://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:     SplFileInfo;
 13: 
 14: 
 15: /**
 16:  * Nette auto loader is responsible for loading classes and interfaces.
 17:  *
 18:  * @author     David Grudl
 19:  *
 20:  * @property-read array $indexedClasses
 21:  * @property   Nette\Caching\IStorage $cacheStorage
 22:  */
 23: class RobotLoader extends Nette\Object
 24: {
 25:     const RETRY_LIMIT = 3;
 26: 
 27:     /** @var string|array  comma separated wildcards */
 28:     public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';
 29: 
 30:     /** @var string|array  comma separated wildcards */
 31:     public $acceptFiles = '*.php, *.php5';
 32: 
 33:     /** @var bool */
 34:     public $autoRebuild = TRUE;
 35: 
 36:     /** @var array */
 37:     private $scanPaths = array();
 38: 
 39:     /** @var array of lowered-class => [file, time, orig] or num-of-retry */
 40:     private $classes = array();
 41: 
 42:     /** @var bool */
 43:     private $rebuilt = FALSE;
 44: 
 45:     /** @var array of missing classes in this request */
 46:     private $missing = array();
 47: 
 48:     /** @var Nette\Caching\IStorage */
 49:     private $cacheStorage;
 50: 
 51: 
 52:     public function __construct()
 53:     {
 54:         if (!extension_loaded('tokenizer')) {
 55:             throw new Nette\NotSupportedException("PHP extension Tokenizer is not loaded.");
 56:         }
 57:     }
 58: 
 59: 
 60:     /**
 61:      * Register autoloader.
 62:      * @param  bool  prepend autoloader?
 63:      * @return self
 64:      */
 65:     public function register($prepend = FALSE)
 66:     {
 67:         $this->classes = $this->getCache()->load($this->getKey(), array($this, 'rebuildCallback'));
 68:         spl_autoload_register(array($this, 'tryLoad'), TRUE, (bool) $prepend);
 69:         return $this;
 70:     }
 71: 
 72: 
 73:     /**
 74:      * Handles autoloading of classes, interfaces or traits.
 75:      * @param  string
 76:      * @return void
 77:      */
 78:     public function tryLoad($type)
 79:     {
 80:         $type = $orig = ltrim($type, '\\'); // PHP namespace bug #49143
 81:         $type = strtolower($type);
 82: 
 83:         $info = & $this->classes[$type];
 84:         if (isset($this->missing[$type]) || (is_int($info) && $info >= self::RETRY_LIMIT)) {
 85:             return;
 86:         }
 87: 
 88:         if ($this->autoRebuild) {
 89:             if (!is_array($info) || !is_file($info['file'])) {
 90:                 $info = is_int($info) ? $info + 1 : 0;
 91:                 if ($this->rebuilt) {
 92:                     $this->getCache()->save($this->getKey(), $this->classes);
 93:                 } else {
 94:                     $this->rebuild();
 95:                 }
 96:             } elseif (!$this->rebuilt && filemtime($info['file']) !== $info['time']) {
 97:                 $this->updateFile($info['file']);
 98:                 if (!isset($this->classes[$type])) {
 99:                     $this->classes[$type] = 0;
100:                 }
101:                 $this->getCache()->save($this->getKey(), $this->classes);
102:             }
103:         }
104: 
105:         if (isset($this->classes[$type]['file'])) {
106:             if ($this->classes[$type]['orig'] !== $orig) {
107:                 trigger_error("Case mismatch on class name '$orig', correct name is '{$this->classes[$type]['orig']}'.", E_USER_WARNING);
108:             }
109:             call_user_func(function($file) { require $file; }, $this->classes[$type]['file']);
110:         } else {
111:             $this->missing[$type] = TRUE;
112:         }
113:     }
114: 
115: 
116:     /**
117:      * Add path or paths to list.
118:      * @param  string|string[]  absolute path
119:      * @return self
120:      */
121:     public function addDirectory($path)
122:     {
123:         $this->scanPaths = array_merge($this->scanPaths, (array) $path);
124:         return $this;
125:     }
126: 
127: 
128:     /**
129:      * @return array of class => filename
130:      */
131:     public function getIndexedClasses()
132:     {
133:         $res = array();
134:         foreach ($this->classes as $info) {
135:             if (is_array($info)) {
136:                 $res[$info['orig']] = $info['file'];
137:             }
138:         }
139:         return $res;
140:     }
141: 
142: 
143:     /**
144:      * Rebuilds class list cache.
145:      * @return void
146:      */
147:     public function rebuild()
148:     {
149:         $this->rebuilt = TRUE; // prevents calling rebuild() or updateFile() in tryLoad()
150:         $this->getCache()->save($this->getKey(), Nette\Utils\Callback::closure($this, 'rebuildCallback'));
151:     }
152: 
153: 
154:     /**
155:      * @internal
156:      */
157:     public function rebuildCallback()
158:     {
159:         $files = $missing = array();
160:         foreach ($this->classes as $class => $info) {
161:             if (is_array($info)) {
162:                 $files[$info['file']]['time'] = $info['time'];
163:                 $files[$info['file']]['classes'][] = $info['orig'];
164:             } else {
165:                 $missing[$class] = $info;
166:             }
167:         }
168: 
169:         $this->classes = array();
170:         foreach ($this->scanPaths as $path) {
171:             foreach (is_file($path) ? array(new SplFileInfo($path)) : $this->createFileIterator($path) as $file) {
172:                 $file = $file->getPathname();
173:                 if (isset($files[$file]) && $files[$file]['time'] == filemtime($file)) {
174:                     $classes = $files[$file]['classes'];
175:                 } else {
176:                     $classes = $this->scanPhp(file_get_contents($file));
177:                 }
178:                 $files[$file] = array('classes' => array(), 'time' => filemtime($file));
179: 
180:                 foreach ($classes as $class) {
181:                     $info = & $this->classes[strtolower($class)];
182:                     if (isset($info['file'])) {
183:                         throw new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
184:                     }
185:                     $info = array('file' => $file, 'time' => filemtime($file), 'orig' => $class);
186:                 }
187:             }
188:         }
189:         $this->classes += $missing;
190:         return $this->classes;
191:     }
192: 
193: 
194:     /**
195:      * Creates an iterator scaning directory for PHP files, subdirectories and 'netterobots.txt' files.
196:      * @return \Iterator
197:      * @throws Nette\IOException if path is not found
198:      */
199:     private function createFileIterator($dir)
200:     {
201:         if (!is_dir($dir)) {
202:             throw new Nette\IOException("File or directory '$dir' not found.");
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(SplFileInfo $file) use (& $disallow) {
215:                 return !isset($disallow[$file->getPathname()]);
216:             })
217:             ->from($dir)
218:             ->exclude($ignoreDirs)
219:             ->filter($filter = function(SplFileInfo $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->scanPaths);
381:     }
382: 
383: }
384: 
Nette 2.3.1 API API documentation generated by ApiGen 2.8.0