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