1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23:
24: class NRobotLoader extends NAutoLoader
25: {
26: const RETRY_LIMIT = 3;
27:
28:
29: public $scanDirs = array();
30:
31:
32: public $ignoreDirs = '.*, *.old, *.bak, *.tmp, temp';
33:
34:
35: public $acceptFiles = '*.php, *.php5';
36:
37:
38: public $autoRebuild = TRUE;
39:
40:
41: private $list = array();
42:
43:
44: private $files;
45:
46:
47: private $rebuilt = FALSE;
48:
49:
50: private $checked = array();
51:
52:
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: 70: 71:
72: public function register()
73: {
74: $this->list = $this->getCache()->load($this->getKey(), callback($this, '_rebuildCallback'));
75: parent::register();
76: return $this;
77: }
78:
79:
80:
81: 82: 83: 84: 85:
86: public function tryLoad($type)
87: {
88: $type = ltrim(strtolower($type), '\\');
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: NCache::CONSTS => 'NFramework::REVISION',
97: ));
98: } else {
99: $this->rebuild();
100: }
101: }
102:
103: if (isset($info[0])) {
104: NLimitedScope::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: NCache::CONSTS => 'NFramework::REVISION',
112: ));
113: } else {
114: $this->rebuild();
115: }
116: }
117: self::$count++;
118: }
119: }
120:
121:
122:
123: 124: 125: 126:
127: public function rebuild()
128: {
129: $this->getCache()->save($this->getKey(), callback($this, '_rebuildCallback'));
130: $this->rebuilt = TRUE;
131: }
132:
133:
134:
135: 136: 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: NCache::CONSTS => 'NFramework::REVISION'
151: );
152: return $this->list;
153: }
154:
155:
156:
157: 158: 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: 175: 176: 177: 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: 195: 196: 197: 198: 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: NDebugger::_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: 224: 225: 226:
227: private function scanDirectory($dir)
228: {
229: if (is_dir($dir)) {
230: $ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : NStrings::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 = NFinder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : NStrings::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 = NStrings::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: 271: 272: 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 = NStrings::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) {
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:
356:
357:
358:
359: 360: 361: 362:
363: public function setCacheStorage(ICacheStorage $storage)
364: {
365: $this->cacheStorage = $storage;
366: return $this;
367: }
368:
369:
370:
371: 372: 373:
374: public function getCacheStorage()
375: {
376: return $this->cacheStorage;
377: }
378:
379:
380:
381: 382: 383:
384: protected function getCache()
385: {
386: if (!$this->cacheStorage) {
387: trigger_error('Missing cache storage.', E_USER_WARNING);
388: $this->cacheStorage = new NDevNullStorage;
389: }
390: return new NCache($this->cacheStorage, 'Nette.RobotLoader');
391: }
392:
393:
394:
395: 396: 397:
398: protected function getKey()
399: {
400: return array($this->ignoreDirs, $this->acceptFiles, $this->scanDirs);
401: }
402:
403: }
404: