1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15:
16: namespace TokenReflection;
17:
18: use TokenReflection\Broker, TokenReflection\Exception;
19: use RecursiveDirectoryIterator, RecursiveIteratorIterator;
20:
21:
22: define('NATIVE_TRAITS', defined('T_TRAIT'));
23: if (!NATIVE_TRAITS) {
24: define('T_TRAIT', -1);
25: define('T_TRAIT_C', -2);
26: define('T_INSTEADOF', -3);
27: define('T_CALLABLE', -4);
28: }
29:
30: 31: 32: 33: 34:
35: class Broker
36: {
37: 38: 39: 40: 41:
42: const OPTION_SAVE_TOKEN_STREAM = 0x0001;
43:
44: 45: 46: 47: 48: 49: 50:
51: const OPTION_PARSE_FUNCTION_BODY = 0x0002;
52:
53: 54: 55: 56: 57:
58: const OPTION_DEFAULT = 0x0003;
59:
60: 61: 62: 63: 64:
65: const CACHE_NAMESPACE = 'namespace';
66:
67: 68: 69: 70: 71:
72: const CACHE_CLASS = 'class';
73:
74: 75: 76: 77: 78:
79: const CACHE_CONSTANT = 'constant';
80:
81: 82: 83: 84: 85:
86: const CACHE_FUNCTION = 'function';
87:
88: 89: 90: 91: 92:
93: private $backend;
94:
95: 96: 97: 98: 99:
100: private $cache;
101:
102: 103: 104: 105: 106:
107: private $options;
108:
109: 110: 111: 112: 113: 114:
115: public function __construct(Broker\Backend $backend, $options = self::OPTION_DEFAULT)
116: {
117: $this->cache = array(
118: self::CACHE_NAMESPACE => array(),
119: self::CACHE_CLASS => array(),
120: self::CACHE_CONSTANT => array(),
121: self::CACHE_FUNCTION => array()
122: );
123:
124: $this->options = $options;
125:
126: $this->backend = $backend
127: ->setBroker($this)
128: ->setStoringTokenStreams((bool) ($options & self::OPTION_SAVE_TOKEN_STREAM));
129: }
130:
131: 132: 133: 134: 135:
136: public function getOptions()
137: {
138: return $this->options;
139: }
140:
141: 142: 143: 144: 145: 146:
147: public function isOptionSet($option)
148: {
149: return (bool) ($this->options & $option);
150: }
151:
152: 153: 154: 155: 156: 157: 158: 159:
160: public function processString($source, $fileName, $returnReflectionFile = false)
161: {
162: if ($this->backend->isFileProcessed($fileName)) {
163: $tokens = $this->backend->getFileTokens($fileName);
164: } else {
165: $tokens = new Stream\StringStream($source, $fileName);
166: }
167:
168: $reflectionFile = new ReflectionFile($tokens, $this);
169: if (!$this->backend->isFileProcessed($fileName)) {
170: $this->backend->addFile($tokens, $reflectionFile);
171:
172:
173: foreach ($this->cache as $type => $cached) {
174: if (!empty($cached)) {
175: $this->cache[$type] = array_filter($cached, function(IReflection $reflection) {
176: return $reflection->isTokenized();
177: });
178: }
179: }
180: }
181:
182: return $returnReflectionFile ? $reflectionFile : true;
183: }
184:
185: 186: 187: 188: 189: 190: 191: 192:
193: public function processFile($fileName, $returnReflectionFile = false)
194: {
195: try {
196: if ($this->backend->isFileProcessed($fileName)) {
197: $tokens = $this->backend->getFileTokens($fileName);
198: } else {
199: $tokens = new Stream\FileStream($fileName);
200: }
201:
202: $reflectionFile = new ReflectionFile($tokens, $this);
203: if (!$this->backend->isFileProcessed($fileName)) {
204: $this->backend->addFile($tokens, $reflectionFile);
205:
206:
207: foreach ($this->cache as $type => $cached) {
208: if (!empty($cached)) {
209: $this->cache[$type] = array_filter($cached, function(IReflection $reflection) {
210: return $reflection->isTokenized();
211: });
212: }
213: }
214: }
215:
216: return $returnReflectionFile ? $reflectionFile : true;
217: } catch (Exception\ParseException $e) {
218: throw $e;
219: } catch (Exception\StreamException $e) {
220: throw new Exception\BrokerException($this, 'Could not process the file.', 0, $e);
221: }
222: }
223:
224: 225: 226: 227: 228: 229: 230: 231: 232: 233:
234: public function processPhar($fileName, $returnReflectionFile = false)
235: {
236: if (!is_file($fileName)) {
237: throw new Exception\BrokerException($this, 'File does not exist.', Exception\BrokerException::DOES_NOT_EXIST);
238: }
239:
240: if (!extension_loaded('Phar')) {
241: throw new Exception\BrokerException($this, 'The PHAR PHP extension is not loaded.', Exception\BrokerException::PHP_EXT_MISSING);
242: }
243:
244: try {
245: $result = array();
246: foreach (new RecursiveIteratorIterator(new \Phar($fileName)) as $entry) {
247: if ($entry->isFile()) {
248: $result[$entry->getPathName()] = $this->processFile($entry->getPathName(), $returnReflectionFile);
249: }
250: }
251:
252: return $returnReflectionFile ? $result : true;
253: } catch (Exception\ParseException $e) {
254: throw $e;
255: } catch (Exception\StreamException $e) {
256: throw new Exception\BrokerException($this, 'Could not process the archive.', 0, $e);
257: }
258: }
259:
260: 261: 262: 263: 264: 265: 266: 267: 268: 269:
270: public function processDirectory($path, $filters = array(), $returnReflectionFile = false)
271: {
272: $realPath = realpath($path);
273: if (!is_dir($realPath)) {
274: throw new Exception\BrokerException($this, 'File does not exist.', Exception\BrokerException::DOES_NOT_EXIST);
275: }
276:
277: try {
278: $result = array();
279: foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($realPath)) as $entry) {
280: if ($entry->isFile()) {
281: $process = empty($filters);
282: if (!$process) {
283: foreach ((array) $filters as $filter) {
284: $whitelisting = '!' !== $filter{0};
285: if (fnmatch($whitelisting ? $filter : substr($filter, 1), $entry->getPathName(), FNM_NOESCAPE)) {
286: $process = $whitelisting;
287: }
288: }
289: }
290:
291: if ($process) {
292: $result[$entry->getPathName()] = $this->processFile($entry->getPathName(), $returnReflectionFile);
293: }
294: }
295: }
296:
297: return $returnReflectionFile ? $result : true;
298: } catch (Exception\ParseException $e) {
299: throw $e;
300: } catch (Exception\StreamException $e) {
301: throw new Exception\BrokerException($this, 'Could not process the directory.', 0, $e);
302: }
303: }
304:
305: 306: 307: 308: 309: 310: 311: 312:
313: public function process($path, $returnReflectionFile = false)
314: {
315: if (is_dir($path)) {
316: return $this->processDirectory($path, array(), $returnReflectionFile);
317: } elseif (is_file($path)) {
318: if (preg_match('~\\.phar(?:$|\\.)~i', $path)) {
319: return $this->processPhar($path, $returnReflectionFile);
320: }
321:
322: return $this->processFile($path, $returnReflectionFile);
323: } else {
324: throw new Exception\BrokerException($this, 'The given directory/file does not exist.', Exception\BrokerException::DOES_NOT_EXIST);
325: }
326: }
327:
328: 329: 330: 331: 332: 333:
334: public function hasNamespace($namespaceName)
335: {
336: return isset($this->cache[self::CACHE_NAMESPACE][$namespaceName]) || $this->backend->hasNamespace($namespaceName);
337: }
338:
339: 340: 341: 342: 343: 344:
345: public function getNamespace($namespaceName)
346: {
347: $namespaceName = ltrim($namespaceName, '\\');
348:
349: if (isset($this->cache[self::CACHE_NAMESPACE][$namespaceName])) {
350: return $this->cache[self::CACHE_NAMESPACE][$namespaceName];
351: }
352:
353: $namespace = $this->backend->getNamespace($namespaceName);
354: if (null !== $namespace) {
355: $this->cache[self::CACHE_NAMESPACE][$namespaceName] = $namespace;
356: }
357:
358: return $namespace;
359: }
360:
361: 362: 363: 364: 365: 366:
367: public function hasClass($className)
368: {
369: return isset($this->cache[self::CACHE_CLASS][$className]) || $this->backend->hasClass($className);
370: }
371:
372: 373: 374: 375: 376: 377:
378: public function getClass($className)
379: {
380: $className = ltrim($className, '\\');
381:
382: if (isset($this->cache[self::CACHE_CLASS][$className])) {
383: return $this->cache[self::CACHE_CLASS][$className];
384: }
385:
386: $this->cache[self::CACHE_CLASS][$className] = $this->backend->getClass($className);
387: return $this->cache[self::CACHE_CLASS][$className];
388: }
389:
390: 391: 392: 393: 394: 395:
396: public function getClasses($types = Broker\Backend::TOKENIZED_CLASSES)
397: {
398: return $this->backend->getClasses($types);
399: }
400:
401: 402: 403: 404: 405: 406:
407: public function hasConstant($constantName)
408: {
409: return isset($this->cache[self::CACHE_CONSTANT][$constantName]) || $this->backend->hasConstant($constantName);
410: }
411:
412: 413: 414: 415: 416: 417:
418: public function getConstant($constantName)
419: {
420: $constantName = ltrim($constantName, '\\');
421:
422: if (isset($this->cache[self::CACHE_CONSTANT][$constantName])) {
423: return $this->cache[self::CACHE_CONSTANT][$constantName];
424: }
425:
426: if ($constant = $this->backend->getConstant($constantName)) {
427: $this->cache[self::CACHE_CONSTANT][$constantName] = $constant;
428: }
429:
430: return $constant;
431: }
432:
433: 434: 435: 436: 437:
438: public function getConstants()
439: {
440: return $this->backend->getConstants();
441: }
442:
443: 444: 445: 446: 447: 448:
449: public function hasFunction($functionName)
450: {
451: return isset($this->cache[self::CACHE_FUNCTION][$functionName]) || $this->backend->hasFunction($functionName);
452: }
453:
454: 455: 456: 457: 458: 459:
460: public function getFunction($functionName)
461: {
462: $functionName = ltrim($functionName, '\\');
463:
464: if (isset($this->cache[self::CACHE_FUNCTION][$functionName])) {
465: return $this->cache[self::CACHE_FUNCTION][$functionName];
466: }
467:
468: if ($function = $this->backend->getFunction($functionName)) {
469: $this->cache[self::CACHE_FUNCTION][$functionName] = $function;
470: }
471:
472: return $function;
473: }
474:
475: 476: 477: 478: 479:
480: public function getFunctions()
481: {
482: return $this->backend->getFunctions();
483: }
484:
485: 486: 487: 488: 489: 490:
491: public function hasFile($fileName)
492: {
493: return $this->backend->hasFile($fileName);
494: }
495:
496: 497: 498: 499: 500: 501:
502: public function getFile($fileName)
503: {
504: return $this->backend->getFile($fileName);
505: }
506:
507: 508: 509: 510: 511:
512: public function getFiles()
513: {
514: return $this->backend->getFiles();
515: }
516:
517: 518: 519: 520: 521: 522:
523: public function getFileTokens($fileName)
524: {
525: return $this->backend->getFileTokens($fileName);
526: }
527:
528: 529: 530: 531: 532: 533:
534: public static function getRealPath($path)
535: {
536: if (0 === strpos($path, 'phar://')) {
537: return is_file($path) || is_dir($path) ? $path : false;
538: } else {
539: return realpath($path);
540: }
541: }
542: }
543: