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