Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • none

Classes

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