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