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

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