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 = FALSE;
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]) || ($this->list[$type] !== FALSE && !is_file($this->list[$type][0]))) {
92: $this->list[$type] = FALSE;
93:
94: if ($this->autoRebuild) {
95: if ($this->rebuilt) {
96: $this->getCache()->save($this->getKey(), $this->list);
97: } else {
98: $this->rebuild();
99: }
100: }
101: }
102:
103: if (!empty($this->list[$type])) {
104: LimitedScope::load($this->list[$type][0]);
105: self::$count++;
106: }
107: }
108:
109:
110:
111: 112: 113: 114:
115: public function rebuild()
116: {
117: $this->getCache()->save($this->getKey(), callback($this, '_rebuildCallback'));
118: $this->rebuilt = TRUE;
119: }
120:
121:
122:
123: 124: 125:
126: public function _rebuildCallback()
127: {
128: foreach ($this->list as $pair) {
129: if ($pair) $this->files[$pair[0]] = $pair[1];
130: }
131: foreach (array_unique($this->scanDirs) as $dir) {
132: $this->scanDirectory($dir);
133: }
134: $this->files = NULL;
135: return $this->list;
136: }
137:
138:
139:
140: 141: 142:
143: public function getIndexedClasses()
144: {
145: $res = array();
146: foreach ($this->list as $class => $pair) {
147: if ($pair) $res[$pair[2]] = $pair[0];
148: }
149: return $res;
150: }
151:
152:
153:
154: 155: 156: 157: 158: 159:
160: public function addDirectory($path)
161: {
162: foreach ((array) $path as $val) {
163: $real = realpath($val);
164: if ($real === FALSE) {
165: throw new DirectoryNotFoundException("Directory '$val' not found.");
166: }
167: $this->scanDirs[] = $real;
168: }
169: }
170:
171:
172:
173: 174: 175: 176: 177: 178: 179:
180: private function addClass($class, $file, $time)
181: {
182: $lClass = strtolower($class);
183: if (!empty($this->list[$lClass]) && $this->list[$lClass][0] !== $file && is_file($this->list[$lClass][0])) {
184: $e = new InvalidStateException("Ambiguous class '$class' resolution; defined in $file and in " . $this->list[$lClass][0] . ".");
185: if (PHP_VERSION_ID < 50300) {
186: Debug::_exceptionHandler($e);
187: exit;
188: } else {
189: throw $e;
190: }
191: }
192: $this->list[$lClass] = array($file, $time, $class);
193: }
194:
195:
196:
197: 198: 199: 200: 201:
202: private function scanDirectory($dir)
203: {
204: if (is_dir($dir)) {
205: $disallow = array();
206: $iterator = Finder::findFiles(String::split($this->acceptFiles, '#[,\s]+#'))
207: ->filter(create_function('$file', 'extract(NClosureFix::$vars['.NClosureFix::uses(array('disallow'=>&$disallow)).'], EXTR_REFS);
208: return !isset($disallow[$file->getPathname()]);
209: '))
210: ->from($dir)
211: ->exclude(String::split($this->ignoreDirs, '#[,\s]+#'))
212: ->filter($filter = create_function('$dir', 'extract(NClosureFix::$vars['.NClosureFix::uses(array('disallow'=>&$disallow)).'], EXTR_REFS);
213: $path = $dir->getPathname();
214: if (is_file("$path/netterobots.txt")) {
215: foreach (file("$path/netterobots.txt") as $s) {
216: if ($matches = String::match($s, \'#^disallow\\\\s*:\\\\s*(\\\\S+)#i\')) {
217: $disallow[$path . str_replace(\'/\', DIRECTORY_SEPARATOR, rtrim(\'/\' . ltrim($matches[1], \'/\'), \'/\'))] = TRUE;
218: }
219: }
220: }
221: return !isset($disallow[$path]);
222: '));
223: $filter(new SplFileInfo($dir));
224: } else {
225: $iterator = new ArrayIterator(array(new SplFileInfo($dir)));
226: }
227:
228: foreach ($iterator as $entry) {
229: $path = $entry->getPathname();
230: if (!isset($this->files[$path]) || $this->files[$path] !== $entry->getMTime()) {
231: $this->scanScript($path);
232: }
233: }
234: }
235:
236:
237:
238: 239: 240: 241: 242:
243: private function scanScript($file)
244: {
245: $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
246: $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
247:
248: $expected = FALSE;
249: $namespace = '';
250: $level = $minLevel = 0;
251: $time = filemtime($file);
252: $s = file_get_contents($file);
253:
254: if ($matches = String::match($s, '#//nette'.'loader=(\S*)#')) {
255: foreach (explode(',', $matches[1]) as $name) {
256: $this->addClass($name, $file, $time);
257: }
258: return;
259: }
260:
261: foreach (token_get_all($s) as $token)
262: {
263: if (is_array($token)) {
264: switch ($token[0]) {
265: case T_COMMENT:
266: case T_DOC_COMMENT:
267: case T_WHITESPACE:
268: continue 2;
269:
270: case $T_NS_SEPARATOR:
271: case T_STRING:
272: if ($expected) {
273: $name .= $token[1];
274: }
275: continue 2;
276:
277: case $T_NAMESPACE:
278: case T_CLASS:
279: case T_INTERFACE:
280: $expected = $token[0];
281: $name = '';
282: continue 2;
283: case T_CURLY_OPEN:
284: case T_DOLLAR_OPEN_CURLY_BRACES:
285: $level++;
286: }
287: }
288:
289: if ($expected) {
290: switch ($expected) {
291: case T_CLASS:
292: case T_INTERFACE:
293: if ($level === $minLevel) {
294: $this->addClass($namespace . $name, $file, $time);
295: }
296: break;
297:
298: case $T_NAMESPACE:
299: $namespace = $name ? $name . '\\' : '';
300: $minLevel = $token === '{' ? 1 : 0;
301: }
302:
303: $expected = NULL;
304: }
305:
306: if ($token === '{') {
307: $level++;
308: } elseif ($token === '}') {
309: $level--;
310: }
311: }
312: }
313:
314:
315:
316:
317:
318:
319:
320: 321: 322: 323:
324: public function setCacheStorage(ICacheStorage $storage)
325: {
326: $this->cacheStorage = $storage;
327: return $this;
328: }
329:
330:
331:
332: 333: 334:
335: public function getCacheStorage()
336: {
337: return $this->cacheStorage;
338: }
339:
340:
341:
342: 343: 344:
345: protected function getCache()
346: {
347: if (!$this->cacheStorage) {
348: trigger_error('Missing cache storage.', E_USER_WARNING);
349: $this->cacheStorage = new DummyStorage;
350: }
351: return new Cache($this->cacheStorage, 'Nette.RobotLoader');
352: }
353:
354:
355:
356: 357: 358:
359: protected function getKey()
360: {
361: return md5("v2|$this->ignoreDirs|$this->acceptFiles|" . implode('|', $this->scanDirs));
362: }
363:
364: }
365: