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