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

Classes

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