Packages

  • 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

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