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