Namespaces

  • 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
  • Namespace
  • 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:  */
 11: 
 12: namespace Nette\Loaders;
 13: 
 14: use Nette,
 15:     Nette\Caching\Cache;
 16: 
 17: 
 18: /**
 19:  * Nette auto loader is responsible for loading classes and interfaces.
 20:  *
 21:  * @author     David Grudl
 22:  *
 23:  * @property-read array $indexedClasses
 24:  * @property   Nette\Caching\IStorage $cacheStorage
 25:  */
 26: class RobotLoader extends AutoLoader
 27: {
 28:     const RETRY_LIMIT = 3;
 29: 
 30:     /** @var array */
 31:     public $scanDirs = array();
 32: 
 33:     /** @var string|array  comma separated wildcards */
 34:     public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';
 35: 
 36:     /** @var string|array  comma separated wildcards */
 37:     public $acceptFiles = '*.php, *.php5';
 38: 
 39:     /** @var bool */
 40:     public $autoRebuild = TRUE;
 41: 
 42:     /** @var array of lowered-class => [file, mtime, class] or num-of-retry */
 43:     private $classes = array();
 44: 
 45:     /** @var bool */
 46:     private $rebuilt = FALSE;
 47: 
 48:     /** @var array of missing classes in this request */
 49:     private $missing = array();
 50: 
 51:     /** @var Nette\Caching\IStorage */
 52:     private $cacheStorage;
 53: 
 54: 
 55:     public function __construct()
 56:     {
 57:         if (!extension_loaded('tokenizer')) {
 58:             throw new Nette\NotSupportedException("PHP extension Tokenizer is not loaded.");
 59:         }
 60:     }
 61: 
 62: 
 63:     /**
 64:      * Register autoloader.
 65:      * @return self
 66:      */
 67:     public function register()
 68:     {
 69:         $this->classes = $this->getCache()->load($this->getKey(), new Nette\Callback($this, '_rebuildCallback'));
 70:         parent::register();
 71:         return $this;
 72:     }
 73: 
 74: 
 75:     /**
 76:      * Handles autoloading of classes, interfaces or traits.
 77:      * @param  string
 78:      * @return void
 79:      */
 80:     public function tryLoad($type)
 81:     {
 82:         $type = ltrim(strtolower($type), '\\'); // PHP namespace bug #49143
 83: 
 84:         $info = & $this->classes[$type];
 85:         if (isset($this->missing[$type]) || (is_int($info) && $info >= self::RETRY_LIMIT)) {
 86:             return;
 87:         }
 88: 
 89:         if ($this->autoRebuild) {
 90:             if (!is_array($info) || !is_file($info['file'])) {
 91:                 $info = is_int($info) ? $info + 1 : 0;
 92:                 if ($this->rebuilt) {
 93:                     $this->getCache()->save($this->getKey(), $this->classes, array(
 94:                         Cache::CONSTS => 'Nette\Framework::REVISION',
 95:                     ));
 96:                 } else {
 97:                     $this->rebuild();
 98:                 }
 99:             } elseif (!$this->rebuilt && filemtime($info['file']) !== $info['time']) {
100:                 $this->updateFile($info['file']);
101:                 if (!isset($this->classes[$type])) {
102:                     $this->classes[$type] = 0;
103:                 }
104:                 $this->getCache()->save($this->getKey(), $this->classes, array(
105:                     Cache::CONSTS => 'Nette\Framework::REVISION',
106:                 ));
107:             }
108:         }
109: 
110:         if (isset($this->classes[$type]['file'])) {
111:             Nette\Utils\LimitedScope::load($this->classes[$type]['file'], TRUE);
112:             self::$count++;
113:         } else {
114:             $this->missing[$type] = TRUE;
115:         }
116:     }
117: 
118: 
119:     /**
120:      * Add directory (or directories) to list.
121:      * @param  string|array
122:      * @return self
123:      * @throws Nette\DirectoryNotFoundException if path is not found
124:      */
125:     public function addDirectory($path)
126:     {
127:         foreach ((array) $path as $val) {
128:             $real = realpath($val);
129:             if ($real === FALSE) {
130:                 throw new Nette\DirectoryNotFoundException("Directory '$val' not found.");
131:             }
132:             $this->scanDirs[] = $real;
133:         }
134:         return $this;
135:     }
136: 
137: 
138:     /**
139:      * @return array of class => filename
140:      */
141:     public function getIndexedClasses()
142:     {
143:         $res = array();
144:         foreach ($this->classes as $class => $info) {
145:             if (is_array($info)) {
146:                 $res[$info['orig']] = $info['file'];
147:             }
148:         }
149:         return $res;
150:     }
151: 
152: 
153:     /**
154:      * Rebuilds class list cache.
155:      * @return void
156:      */
157:     public function rebuild()
158:     {
159:         $this->rebuilt = TRUE; // prevents calling rebuild() or updateFile() in tryLoad()
160:         $this->getCache()->save($this->getKey(), new Nette\Callback($this, '_rebuildCallback'));
161:     }
162: 
163: 
164:     /**
165:      * @internal
166:      */
167:     public function _rebuildCallback(& $dp)
168:     {
169:         $files = $missing = array();
170:         foreach ($this->classes as $class => $info) {
171:             if (is_array($info)) {
172:                 $files[$info['file']]['time'] = $info['time'];
173:                 $files[$info['file']]['classes'][] = $info['orig'];
174:             } else {
175:                 $missing[$class] = $info;
176:             }
177:         }
178: 
179:         $this->classes = array();
180:         foreach (array_unique($this->scanDirs) as $dir) {
181:             foreach ($this->createFileIterator($dir) as $file) {
182:                 $file = $file->getPathname();
183:                 if (isset($files[$file]) && $files[$file]['time'] == filemtime($file)) {
184:                     $classes = $files[$file]['classes'];
185:                 } else {
186:                     $classes = $this->scanPhp(file_get_contents($file));
187:                 }
188: 
189:                 foreach ($classes as $class) {
190:                     $info = & $this->classes[strtolower($class)];
191:                     if (isset($info['file'])) {
192:                         $e = new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
193:                         {
194:                             throw $e;
195:                         }
196:                     }
197:                     $info = array('file' => $file, 'time' => filemtime($file), 'orig' => $class);
198:                 }
199:             }
200:         }
201: 
202:         $dp = array(
203:             Cache::CONSTS => 'Nette\Framework::REVISION'
204:         );
205:         $this->classes += $missing;
206:         return $this->classes;
207:     }
208: 
209: 
210:     /**
211:      * Creates an iterator scaning directory for PHP files, subdirectories and 'netterobots.txt' files.
212:      * @return \Iterator
213:      */
214:     private function createFileIterator($dir)
215:     {
216:         if (!is_dir($dir)) {
217:             return new \ArrayIterator(array(new \SplFileInfo($dir)));
218:         }
219: 
220:         $ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : preg_split('#[,\s]+#', $this->ignoreDirs);
221:         $disallow = array();
222:         foreach ($ignoreDirs as $item) {
223:             if ($item = realpath($item)) {
224:                 $disallow[$item] = TRUE;
225:             }
226:         }
227: 
228:         $iterator = Nette\Utils\Finder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : preg_split('#[,\s]+#', $this->acceptFiles))
229:             ->filter(function($file) use (& $disallow){
230:                 return !isset($disallow[$file->getPathname()]);
231:             })
232:             ->from($dir)
233:             ->exclude($ignoreDirs)
234:             ->filter($filter = function($dir) use (& $disallow){
235:                 $path = $dir->getPathname();
236:                 if (is_file("$path/netterobots.txt")) {
237:                     foreach (file("$path/netterobots.txt") as $s) {
238:                     if (preg_match('#^(?:disallow\\s*:)?\\s*(\\S+)#i', $s, $matches)) {
239:                             $disallow[$path . str_replace('/', DIRECTORY_SEPARATOR, rtrim('/' . ltrim($matches[1], '/'), '/'))] = TRUE;
240:                         }
241:                     }
242:                 }
243:                 return !isset($disallow[$path]);
244:             });
245: 
246:         $filter(new \SplFileInfo($dir));
247:         return $iterator;
248:     }
249: 
250: 
251:     /**
252:      * @return void
253:      */
254:     private function updateFile($file)
255:     {
256:         foreach ($this->classes as $class => $info) {
257:             if (isset($info['file']) && $info['file'] === $file) {
258:                 unset($this->classes[$class]);
259:             }
260:         }
261: 
262:         if (is_file($file)) {
263:             foreach ($this->scanPhp(file_get_contents($file)) as $class) {
264:                 $info = & $this->classes[strtolower($class)];
265:                 if (isset($info['file']) && @filemtime($info['file']) !== $info['time']) { // intentionally ==, file may not exists
266:                     $this->updateFile($info['file']);
267:                     $info = & $this->classes[strtolower($class)];
268:                 }
269:                 if (isset($info['file'])) {
270:                     $e = new Nette\InvalidStateException("Ambiguous class $class resolution; defined in {$info['file']} and in $file.");
271:                     {
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 ($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 RobotLoader
365:      */
366:     public function setCacheStorage(Nette\Caching\IStorage $storage)
367:     {
368:         $this->cacheStorage = $storage;
369:         return $this;
370:     }
371: 
372: 
373:     /**
374:      * @return Nette\Caching\IStorage
375:      */
376:     public function getCacheStorage()
377:     {
378:         return $this->cacheStorage;
379:     }
380: 
381: 
382:     /**
383:      * @return Nette\Caching\Cache
384:      */
385:     protected function getCache()
386:     {
387:         if (!$this->cacheStorage) {
388:             trigger_error('Missing cache storage.', E_USER_WARNING);
389:             $this->cacheStorage = new Nette\Caching\Storages\DevNullStorage;
390:         }
391:         return new Cache($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.12 API API documentation generated by ApiGen 2.8.0