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