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