Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • AutoLoader
  • NetteLoader
  • RobotLoader
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  */
 11: 
 12: namespace Nette\Loaders;
 13: 
 14: use Nette,
 15:     Nette\Caching\Cache;
 16: 
 17: 
 18: 
 19: /**
 20:  * Nette auto loader is responsible for loading classes and interfaces.
 21:  *
 22:  * @author     David Grudl
 23:  *
 24:  * @property-read array $indexedClasses
 25:  * @property   Nette\Caching\IStorage $cacheStorage
 26:  */
 27: class RobotLoader extends AutoLoader
 28: {
 29:     const RETRY_LIMIT = 3;
 30: 
 31:     /** @var array */
 32:     public $scanDirs = array();
 33: 
 34:     /** @var string|array  comma separated wildcards */
 35:     public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';
 36: 
 37:     /** @var string|array  comma separated wildcards */
 38:     public $acceptFiles = '*.php, *.php5';
 39: 
 40:     /** @var bool */
 41:     public $autoRebuild = TRUE;
 42: 
 43:     /** @var array of lowered-class => [file, mtime, class] or num-of-retry */
 44:     private $classes = array();
 45: 
 46:     /** @var bool */
 47:     private $rebuilt = FALSE;
 48: 
 49:     /** @var array of missing classes in this request */
 50:     private $missing = array();
 51: 
 52:     /** @var Nette\Caching\IStorage */
 53:     private $cacheStorage;
 54: 
 55: 
 56: 
 57:     public function __construct()
 58:     {
 59:         if (!extension_loaded('tokenizer')) {
 60:             throw new Nette\NotSupportedException("PHP extension Tokenizer is not loaded.");
 61:         }
 62:     }
 63: 
 64: 
 65: 
 66:     /**
 67:      * Register autoloader.
 68:      * @return RobotLoader  provides a fluent interface
 69:      */
 70:     public function register()
 71:     {
 72:         $this->classes = $this->getCache()->load($this->getKey(), new Nette\Callback($this, '_rebuildCallback'));
 73:         parent::register();
 74:         return $this;
 75:     }
 76: 
 77: 
 78: 
 79:     /**
 80:      * Handles autoloading of classes, interfaces or traits.
 81:      * @param  string
 82:      * @return void
 83:      */
 84:     public function tryLoad($type)
 85:     {
 86:         $type = ltrim(strtolower($type), '\\'); // PHP namespace bug #49143
 87: 
 88:         $info = & $this->classes[$type];
 89:         if (isset($this->missing[$type]) || (is_int($info) && $info >= self::RETRY_LIMIT)) {
 90:             return;
 91:         }
 92: 
 93:         if ($this->autoRebuild) {
 94:             if (!is_array($info) || !is_file($info['file'])) {
 95:                 $info = is_int($info) ? $info + 1 : 0;
 96:                 if ($this->rebuilt) {
 97:                     $this->getCache()->save($this->getKey(), $this->classes, array(
 98:                         Cache::CONSTS => 'Nette\Framework::REVISION',
 99:                     ));
100:                 } else {
101:                     $this->rebuild();
102:                 }
103:             } elseif (!$this->rebuilt && filemtime($info['file']) !== $info['time']) {
104:                 $this->updateFile($info['file']);
105:                 if (!isset($this->classes[$type])) {
106:                     $this->classes[$type] = 0;
107:                 }
108:                 $this->getCache()->save($this->getKey(), $this->classes, array(
109:                     Cache::CONSTS => 'Nette\Framework::REVISION',
110:                 ));
111:             }
112:         }
113: 
114:         if (isset($this->classes[$type]['file'])) {
115:             Nette\Utils\LimitedScope::load($this->classes[$type]['file'], TRUE);
116:             self::$count++;
117:         } else {
118:             $this->missing[$type] = TRUE;
119:         }
120:     }
121: 
122: 
123: 
124:     /**
125:      * Add directory (or directories) to list.
126:      * @param  string|array
127:      * @return RobotLoader  provides a fluent interface
128:      * @throws Nette\DirectoryNotFoundException if path is not found
129:      */
130:     public function addDirectory($path)
131:     {
132:         foreach ((array) $path as $val) {
133:             $real = realpath($val);
134:             if ($real === FALSE) {
135:                 throw new Nette\DirectoryNotFoundException("Directory '$val' not found.");
136:             }
137:             $this->scanDirs[] = $real;
138:         }
139:         return $this;
140:     }
141: 
142: 
143: 
144:     /**
145:      * @return array of class => filename
146:      */
147:     public function getIndexedClasses()
148:     {
149:         $res = array();
150:         foreach ($this->classes as $class => $info) {
151:             if (is_array($info)) {
152:                 $res[$info['orig']] = $info['file'];
153:             }
154:         }
155:         return $res;
156:     }
157: 
158: 
159: 
160:     /**
161:      * Rebuilds class list cache.
162:      * @return void
163:      */
164:     public function rebuild()
165:     {
166:         $this->rebuilt = TRUE; // prevents calling rebuild() or updateFile() in tryLoad()
167:         $this->getCache()->save($this->getKey(), new Nette\Callback($this, '_rebuildCallback'));
168:     }
169: 
170: 
171: 
172:     /**
173:      * @internal
174:      */
175:     public function _rebuildCallback(& $dp)
176:     {
177:         $files = $missing = array();
178:         foreach ($this->classes as $class => $info) {
179:             if (is_array($info)) {
180:                 $files[$info['file']]['time'] = $info['time'];
181:                 $files[$info['file']]['classes'][] = $info['orig'];
182:             } else {
183:                 $missing[$class] = $info;
184:             }
185:         }
186: 
187:         $this->classes = array();
188:         foreach (array_unique($this->scanDirs) as $dir) {
189:             foreach ($this->createFileIterator($dir) as $file) {
190:                 $file = $file->getPathname();
191:                 if (isset($files[$file]) && $files[$file]['time'] == filemtime($file)) {
192:                     $classes = $files[$file]['classes'];
193:                 } else {
194:                     $classes = $this->scanPhp(file_get_contents($file));
195:                 }
196: 
197:                 foreach ($classes as $class) {
198:                     $info = & $this->classes[strtolower($class)];
199:                     if (isset($info['file'])) {
200:                         $e = new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
201:                         {
202:                             throw $e;
203:                         }
204:                     }
205:                     $info = array('file' => $file, 'time' => filemtime($file), 'orig' => $class);
206:                 }
207:             }
208:         }
209: 
210:         $dp = array(
211:             Cache::CONSTS => 'Nette\Framework::REVISION'
212:         );
213:         $this->classes += $missing;
214:         return $this->classes;
215:     }
216: 
217: 
218: 
219:     /**
220:      * Creates an iterator scaning directory for PHP files, subdirectories and 'netterobots.txt' files.
221:      * @return \Iterator
222:      */
223:     private function createFileIterator($dir)
224:     {
225:         if (!is_dir($dir)) {
226:             return new \ArrayIterator(array(new \SplFileInfo($dir)));
227:         }
228: 
229:         $ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : preg_split('#[,\s]+#', $this->ignoreDirs);
230:         $disallow = array();
231:         foreach ($ignoreDirs as $item) {
232:             if ($item = realpath($item)) {
233:                 $disallow[$item] = TRUE;
234:             }
235:         }
236: 
237:         $iterator = Nette\Utils\Finder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : preg_split('#[,\s]+#', $this->acceptFiles))
238:             ->filter(function($file) use (&$disallow){
239:                 return !isset($disallow[$file->getPathname()]);
240:             })
241:             ->from($dir)
242:             ->exclude($ignoreDirs)
243:             ->filter($filter = function($dir) use (&$disallow){
244:                 $path = $dir->getPathname();
245:                 if (is_file("$path/netterobots.txt")) {
246:                     foreach (file("$path/netterobots.txt") as $s) {
247:                     if (preg_match('#^(?:disallow\\s*:)?\\s*(\\S+)#i', $s, $matches)) {
248:                             $disallow[$path . str_replace('/', DIRECTORY_SEPARATOR, rtrim('/' . ltrim($matches[1], '/'), '/'))] = TRUE;
249:                         }
250:                     }
251:                 }
252:                 return !isset($disallow[$path]);
253:             });
254: 
255:         $filter(new \SplFileInfo($dir));
256:         return $iterator;
257:     }
258: 
259: 
260: 
261:     /**
262:      * @return void
263:      */
264:     private function updateFile($file)
265:     {
266:         foreach ($this->classes as $class => $info) {
267:             if (isset($info['file']) && $info['file'] === $file) {
268:                 unset($this->classes[$class]);
269:             }
270:         }
271: 
272:         if (is_file($file)) {
273:             foreach ($this->scanPhp(file_get_contents($file)) as $class) {
274:                 $info = & $this->classes[strtolower($class)];
275:                 if (isset($info['file']) && @filemtime($info['file']) !== $info['time']) { // intentionally ==, file may not exists
276:                     $this->updateFile($info['file']);
277:                     $info = & $this->classes[strtolower($class)];
278:                 }
279:                 if (isset($info['file'])) {
280:                     $e = new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
281:                     {
282:                         throw $e;
283:                     }
284:                 }
285:                 $info = array('file' => $file, 'time' => filemtime($file), 'orig' => $class);
286:             }
287:         }
288:     }
289: 
290: 
291: 
292:     /**
293:      * Searches classes, interfaces and traits in PHP file.
294:      * @param  string
295:      * @return array
296:      */
297:     private function scanPhp($code)
298:     {
299:         $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
300:         $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
301:         $T_TRAIT = PHP_VERSION_ID < 50400 ? -1 : T_TRAIT;
302: 
303:         $expected = FALSE;
304:         $namespace = '';
305:         $level = $minLevel = 0;
306:         $classes = array();
307: 
308:         if (preg_match('#//nette'.'loader=(\S*)#', $code, $matches)) {
309:             foreach (explode(',', $matches[1]) as $name) {
310:                 $classes[] = $name;
311:             }
312:             return $classes;
313:         }
314: 
315:         foreach (@token_get_all($code) as $token) { // intentionally @
316:             if (is_array($token)) {
317:                 switch ($token[0]) {
318:                 case T_COMMENT:
319:                 case T_DOC_COMMENT:
320:                 case T_WHITESPACE:
321:                     continue 2;
322: 
323:                 case $T_NS_SEPARATOR:
324:                 case T_STRING:
325:                     if ($expected) {
326:                         $name .= $token[1];
327:                     }
328:                     continue 2;
329: 
330:                 case $T_NAMESPACE:
331:                 case T_CLASS:
332:                 case T_INTERFACE:
333:                 case $T_TRAIT:
334:                     $expected = $token[0];
335:                     $name = '';
336:                     continue 2;
337:                 case T_CURLY_OPEN:
338:                 case T_DOLLAR_OPEN_CURLY_BRACES:
339:                     $level++;
340:                 }
341:             }
342: 
343:             if ($expected) {
344:                 switch ($expected) {
345:                 case T_CLASS:
346:                 case T_INTERFACE:
347:                 case $T_TRAIT:
348:                     if ($level === $minLevel) {
349:                         $classes[] = $namespace . $name;
350:                     }
351:                     break;
352: 
353:                 case $T_NAMESPACE:
354:                     $namespace = $name ? $name . '\\' : '';
355:                     $minLevel = $token === '{' ? 1 : 0;
356:                 }
357: 
358:                 $expected = NULL;
359:             }
360: 
361:             if ($token === '{') {
362:                 $level++;
363:             } elseif ($token === '}') {
364:                 $level--;
365:             }
366:         }
367:         return $classes;
368:     }
369: 
370: 
371: 
372:     /********************* backend ****************d*g**/
373: 
374: 
375: 
376:     /**
377:      * @return RobotLoader
378:      */
379:     public function setCacheStorage(Nette\Caching\IStorage $storage)
380:     {
381:         $this->cacheStorage = $storage;
382:         return $this;
383:     }
384: 
385: 
386: 
387:     /**
388:      * @return Nette\Caching\IStorage
389:      */
390:     public function getCacheStorage()
391:     {
392:         return $this->cacheStorage;
393:     }
394: 
395: 
396: 
397:     /**
398:      * @return Nette\Caching\Cache
399:      */
400:     protected function getCache()
401:     {
402:         if (!$this->cacheStorage) {
403:             trigger_error('Missing cache storage.', E_USER_WARNING);
404:             $this->cacheStorage = new Nette\Caching\Storages\DevNullStorage;
405:         }
406:         return new Cache($this->cacheStorage, 'Nette.RobotLoader');
407:     }
408: 
409: 
410: 
411:     /**
412:      * @return string
413:      */
414:     protected function getKey()
415:     {
416:         return array($this->ignoreDirs, $this->acceptFiles, $this->scanDirs);
417:     }
418: 
419: }
420: 
Nette Framework 2.0.10 API API documentation generated by ApiGen 2.8.0