Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
TokenArrayScanner.php
001 <?php
002 /**
003 * Zend Framework (http://framework.zend.com/)
004 *
005 * @link http://github.com/zendframework/zf2 for the canonical source repository
006 * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
007 * @license http://framework.zend.com/license/new-bsd New BSD License
008 */
009
010 namespace Zend\Code\Scanner;
011
012 use Zend\Code\Annotation\AnnotationManager;
013 use Zend\Code\Exception;
014 use Zend\Code\NameInformation;
015
016 use function array_shift;
017 use function array_slice;
018 use function in_array;
019 use function is_array;
020 use function is_int;
021 use function is_string;
022
023 class TokenArrayScanner implements ScannerInterface
024 {
025 /**
026 * @var bool
027 */
028 protected $isScanned = false;
029
030 /**
031 * @var array
032 */
033 protected $tokens = [];
034
035 /**
036 * @var null
037 */
038 protected $docComment;
039
040 /**
041 * @var NameInformation
042 */
043 protected $nameInformation;
044
045 /**
046 * @var array
047 */
048 protected $infos = [];
049
050 /**
051 * @var AnnotationManager
052 */
053 protected $annotationManager;
054
055 /**
056 * @param null|array $tokens
057 * @param null|AnnotationManager $annotationManager
058 */
059 public function __construct($tokens, AnnotationManager $annotationManager = null)
060 {
061 $this->tokens = $tokens;
062 $this->annotationManager = $annotationManager;
063 }
064
065 /**
066 * @return AnnotationManager
067 */
068 public function getAnnotationManager()
069 {
070 return $this->annotationManager;
071 }
072
073 /**
074 * Get doc comment
075 *
076 * @todo Assignment of $this->docComment should probably be done in scan()
077 * and then $this->getDocComment() just retrieves it.
078 *
079 * @return string|null
080 */
081 public function getDocComment()
082 {
083 foreach ($this->tokens as $token) {
084 $type = $token[0];
085 $value = $token[1];
086 if (($type == T_OPEN_TAG) || ($type == T_WHITESPACE)) {
087 continue;
088 } elseif ($type == T_DOC_COMMENT) {
089 $this->docComment = $value;
090
091 return $this->docComment;
092 } else {
093 // Only whitespace is allowed before file docblocks
094 return;
095 }
096 }
097 }
098
099 /**
100 * @return array
101 */
102 public function getNamespaces()
103 {
104 $this->scan();
105
106 $namespaces = [];
107 foreach ($this->infos as $info) {
108 if ($info['type'] == 'namespace') {
109 $namespaces[] = $info['namespace'];
110 }
111 }
112
113 return $namespaces;
114 }
115
116 /**
117 * @param null|string $namespace
118 * @return array|null
119 */
120 public function getUses($namespace = null)
121 {
122 $this->scan();
123
124 return $this->getUsesNoScan($namespace);
125 }
126
127 /**
128 * @return void
129 */
130 public function getIncludes()
131 {
132 $this->scan();
133 // @todo Implement getIncludes() in TokenArrayScanner
134 }
135
136 /**
137 * @return array
138 */
139 public function getClassNames()
140 {
141 $this->scan();
142
143 $return = [];
144 foreach ($this->infos as $info) {
145 if ($info['type'] != 'class') {
146 continue;
147 }
148
149 $return[] = $info['name'];
150 }
151
152 return $return;
153 }
154
155 /**
156 * @return ClassScanner[]
157 */
158 public function getClasses()
159 {
160 $this->scan();
161
162 $return = [];
163 foreach ($this->infos as $info) {
164 if ($info['type'] != 'class') {
165 continue;
166 }
167
168 $return[] = $this->getClass($info['name']);
169 }
170
171 return $return;
172 }
173
174 /**
175 * Return the class object from this scanner
176 *
177 * @param string|int $name
178 * @throws Exception\InvalidArgumentException
179 * @return ClassScanner|false
180 */
181 public function getClass($name)
182 {
183 $this->scan();
184
185 if (is_int($name)) {
186 $info = $this->infos[$name];
187 if ($info['type'] != 'class') {
188 throw new Exception\InvalidArgumentException('Index of info offset is not about a class');
189 }
190 } elseif (is_string($name)) {
191 $classFound = false;
192 foreach ($this->infos as $info) {
193 if ($info['type'] === 'class' && $info['name'] === $name) {
194 $classFound = true;
195 break;
196 }
197 }
198
199 if (! $classFound) {
200 return false;
201 }
202 }
203
204 return new ClassScanner(
205 array_slice(
206 $this->tokens,
207 $info['tokenStart'],
208 $info['tokenEnd'] - $info['tokenStart'] + 1
209 ), // zero indexed array
210 new NameInformation($info['namespace'], $info['uses'])
211 );
212 }
213
214 /**
215 * @param string $className
216 * @return bool|null|NameInformation
217 */
218 public function getClassNameInformation($className)
219 {
220 $this->scan();
221
222 $classFound = false;
223 foreach ($this->infos as $info) {
224 if ($info['type'] === 'class' && $info['name'] === $className) {
225 $classFound = true;
226 break;
227 }
228 }
229
230 if (! $classFound) {
231 return false;
232 }
233
234 if (! isset($info)) {
235 return;
236 }
237
238 return new NameInformation($info['namespace'], $info['uses']);
239 }
240
241 /**
242 * @return array
243 */
244 public function getFunctionNames()
245 {
246 $this->scan();
247 $functionNames = [];
248 foreach ($this->infos as $info) {
249 if ($info['type'] == 'function') {
250 $functionNames[] = $info['name'];
251 }
252 }
253
254 return $functionNames;
255 }
256
257 /**
258 * @return array
259 */
260 public function getFunctions()
261 {
262 $this->scan();
263
264 $functions = [];
265 // foreach ($this->infos as $info) {
266 // if ($info['type'] == 'function') {
267 // // @todo $functions[] = new FunctionScanner($info['name']);
268 // }
269 // }
270
271 return $functions;
272 }
273
274 /**
275 * Export
276 *
277 * @param mixed $tokens
278 */
279 public static function export($tokens)
280 {
281 // @todo
282 }
283
284 public function __toString()
285 {
286 // @todo
287 }
288
289 /**
290 * Scan
291 *
292 * @todo: $this->docComment should be assigned for valid docblock during
293 * the scan instead of $this->getDocComment() (starting with
294 * T_DOC_COMMENT case)
295 *
296 * @throws Exception\RuntimeException
297 */
298 protected function scan()
299 {
300 if ($this->isScanned) {
301 return;
302 }
303
304 if (! $this->tokens) {
305 throw new Exception\RuntimeException('No tokens were provided');
306 }
307
308 /**
309 * Variables & Setup
310 */
311 $tokens = &$this->tokens; // localize
312 $infos = &$this->infos; // localize
313 $tokenIndex = null;
314 $token = null;
315 $tokenType = null;
316 $tokenContent = null;
317 $tokenLine = null;
318 $namespace = null;
319 $docCommentIndex = false;
320 $infoIndex = 0;
321
322 /*
323 * MACRO creation
324 */
325 $MACRO_TOKEN_ADVANCE = function () use (
326 &$tokens,
327 &$tokenIndex,
328 &$token,
329 &$tokenType,
330 &$tokenContent,
331 &$tokenLine
332 ) {
333 $tokenIndex = $tokenIndex === null ? 0 : $tokenIndex + 1;
334 if (! isset($tokens[$tokenIndex])) {
335 $token = false;
336 $tokenContent = false;
337 $tokenType = false;
338 $tokenLine = false;
339
340 return false;
341 }
342 if (is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"') {
343 do {
344 $tokenIndex++;
345 } while (! (is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"'));
346 }
347 $token = $tokens[$tokenIndex];
348 if (is_array($token)) {
349 list($tokenType, $tokenContent, $tokenLine) = $token;
350 } else {
351 $tokenType = null;
352 $tokenContent = $token;
353 }
354
355 return $tokenIndex;
356 };
357 $MACRO_TOKEN_LOGICAL_START_INDEX = function () use (&$tokenIndex, &$docCommentIndex) {
358 return $docCommentIndex === false ? $tokenIndex : $docCommentIndex;
359 };
360 $MACRO_DOC_COMMENT_START = function () use (&$tokenIndex, &$docCommentIndex) {
361 $docCommentIndex = $tokenIndex;
362
363 return $docCommentIndex;
364 };
365 $MACRO_DOC_COMMENT_VALIDATE = function () use (&$tokenType, &$docCommentIndex) {
366 static $validTrailingTokens = null;
367 if ($validTrailingTokens === null) {
368 $validTrailingTokens = [T_WHITESPACE, T_FINAL, T_ABSTRACT, T_INTERFACE, T_CLASS, T_FUNCTION];
369 }
370 if ($docCommentIndex !== false && ! in_array($tokenType, $validTrailingTokens)) {
371 $docCommentIndex = false;
372 }
373
374 return $docCommentIndex;
375 };
376 $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
377 $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
378 $infos[$infoIndex]['lineEnd'] = $tokenLine;
379 $infoIndex++;
380
381 return $infoIndex;
382 };
383
384 /**
385 * START FINITE STATE MACHINE FOR SCANNING TOKENS
386 */
387 // Initialize token
388 $MACRO_TOKEN_ADVANCE();
389
390 SCANNER_TOP:
391
392 if ($token === false) {
393 goto SCANNER_END;
394 }
395
396 // Validate current doc comment index
397 $MACRO_DOC_COMMENT_VALIDATE();
398
399 switch ($tokenType) {
400 case T_DOC_COMMENT:
401 $MACRO_DOC_COMMENT_START();
402 goto SCANNER_CONTINUE;
403 // goto no break needed
404
405 case T_NAMESPACE:
406 $infos[$infoIndex] = [
407 'type' => 'namespace',
408 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
409 'tokenEnd' => null,
410 'lineStart' => $token[2],
411 'lineEnd' => null,
412 'namespace' => null,
413 ];
414
415 // start processing with next token
416 if ($MACRO_TOKEN_ADVANCE() === false) {
417 goto SCANNER_END;
418 }
419
420 SCANNER_NAMESPACE_TOP:
421
422 if (($tokenType === null && $tokenContent === ';') || $tokenContent === '{') {
423 goto SCANNER_NAMESPACE_END;
424 }
425
426 if ($tokenType === T_WHITESPACE) {
427 goto SCANNER_NAMESPACE_CONTINUE;
428 }
429
430 if ($tokenType === T_NS_SEPARATOR || $tokenType === T_STRING) {
431 $infos[$infoIndex]['namespace'] .= $tokenContent;
432 }
433
434 SCANNER_NAMESPACE_CONTINUE:
435
436 if ($MACRO_TOKEN_ADVANCE() === false) {
437 goto SCANNER_END;
438 }
439 goto SCANNER_NAMESPACE_TOP;
440
441 SCANNER_NAMESPACE_END:
442
443 $namespace = $infos[$infoIndex]['namespace'];
444
445 $MACRO_INFO_ADVANCE();
446 goto SCANNER_CONTINUE;
447 // goto no break needed
448
449 case T_USE:
450 $infos[$infoIndex] = [
451 'type' => 'use',
452 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
453 'tokenEnd' => null,
454 'lineStart' => $tokens[$tokenIndex][2],
455 'lineEnd' => null,
456 'namespace' => $namespace,
457 'statements' => [
458 0 => [
459 'use' => null,
460 'as' => null,
461 ],
462 ],
463 ];
464
465 $useStatementIndex = 0;
466 $useAsContext = false;
467
468 // start processing with next token
469 if ($MACRO_TOKEN_ADVANCE() === false) {
470 goto SCANNER_END;
471 }
472
473 SCANNER_USE_TOP:
474
475 if ($tokenType === null) {
476 if ($tokenContent === ';') {
477 goto SCANNER_USE_END;
478 } elseif ($tokenContent === ',') {
479 $useAsContext = false;
480 $useStatementIndex++;
481 $infos[$infoIndex]['statements'][$useStatementIndex] = [
482 'use' => null,
483 'as' => null,
484 ];
485 }
486 }
487
488 // ANALYZE
489 if ($tokenType !== null) {
490 if ($tokenType == T_AS) {
491 $useAsContext = true;
492 goto SCANNER_USE_CONTINUE;
493 }
494
495 if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) {
496 if ($useAsContext == false) {
497 $infos[$infoIndex]['statements'][$useStatementIndex]['use'] .= $tokenContent;
498 } else {
499 $infos[$infoIndex]['statements'][$useStatementIndex]['as'] = $tokenContent;
500 }
501 }
502 }
503
504 SCANNER_USE_CONTINUE:
505
506 if ($MACRO_TOKEN_ADVANCE() === false) {
507 goto SCANNER_END;
508 }
509 goto SCANNER_USE_TOP;
510
511 SCANNER_USE_END:
512
513 $MACRO_INFO_ADVANCE();
514 goto SCANNER_CONTINUE;
515 // goto no break needed
516
517 case T_INCLUDE:
518 case T_INCLUDE_ONCE:
519 case T_REQUIRE:
520 case T_REQUIRE_ONCE:
521 // Static for performance
522 static $includeTypes = [
523 T_INCLUDE => 'include',
524 T_INCLUDE_ONCE => 'include_once',
525 T_REQUIRE => 'require',
526 T_REQUIRE_ONCE => 'require_once',
527 ];
528
529 $infos[$infoIndex] = [
530 'type' => 'include',
531 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
532 'tokenEnd' => null,
533 'lineStart' => $tokens[$tokenIndex][2],
534 'lineEnd' => null,
535 'includeType' => $includeTypes[$tokens[$tokenIndex][0]],
536 'path' => '',
537 ];
538
539 // start processing with next token
540 if ($MACRO_TOKEN_ADVANCE() === false) {
541 goto SCANNER_END;
542 }
543
544 SCANNER_INCLUDE_TOP:
545
546 if ($tokenType === null && $tokenContent === ';') {
547 goto SCANNER_INCLUDE_END;
548 }
549
550 $infos[$infoIndex]['path'] .= $tokenContent;
551
552 SCANNER_INCLUDE_CONTINUE:
553
554 if ($MACRO_TOKEN_ADVANCE() === false) {
555 goto SCANNER_END;
556 }
557 goto SCANNER_INCLUDE_TOP;
558
559 SCANNER_INCLUDE_END:
560
561 $MACRO_INFO_ADVANCE();
562 goto SCANNER_CONTINUE;
563 // goto no break needed
564
565 case T_FUNCTION:
566 case T_FINAL:
567 case T_ABSTRACT:
568 case T_CLASS:
569 case T_INTERFACE:
570 case T_TRAIT:
571 $infos[$infoIndex] = [
572 'type' => $tokenType === T_FUNCTION ? 'function' : 'class',
573 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(),
574 'tokenEnd' => null,
575 'lineStart' => $tokens[$tokenIndex][2],
576 'lineEnd' => null,
577 'namespace' => $namespace,
578 'uses' => $this->getUsesNoScan($namespace),
579 'name' => null,
580 'shortName' => null,
581 ];
582
583 $classBraceCount = 0;
584
585 // start processing with current token
586
587 SCANNER_CLASS_TOP:
588
589 // process the name
590 if ($infos[$infoIndex]['shortName'] == ''
591 && (($tokenType === T_CLASS
592 || $tokenType === T_INTERFACE
593 || $tokenType === T_TRAIT)
594 && $infos[$infoIndex]['type'] === 'class'
595 || ($tokenType === T_FUNCTION && $infos[$infoIndex]['type'] === 'function'))
596 ) {
597 $infos[$infoIndex]['shortName'] = is_array($tokens[$tokenIndex + 2])
598 ? $tokens[$tokenIndex + 2][1]
599 : $tokens[$tokenIndex + 2];
600 $infos[$infoIndex]['name'] = ($namespace !== null
601 ? $namespace . '\\'
602 : '') . $infos[$infoIndex]['shortName'];
603 }
604
605 if ($tokenType === null) {
606 if ($tokenContent == '{') {
607 $classBraceCount++;
608 }
609 if ($tokenContent == '}') {
610 $classBraceCount--;
611 if ($classBraceCount === 0) {
612 goto SCANNER_CLASS_END;
613 }
614 }
615 }
616
617 SCANNER_CLASS_CONTINUE:
618
619 if ($MACRO_TOKEN_ADVANCE() === false) {
620 goto SCANNER_END;
621 }
622 goto SCANNER_CLASS_TOP;
623
624 SCANNER_CLASS_END:
625
626 $MACRO_INFO_ADVANCE();
627 goto SCANNER_CONTINUE;
628 // goto no break needed
629 }
630
631 SCANNER_CONTINUE:
632
633 if ($MACRO_TOKEN_ADVANCE() === false) {
634 goto SCANNER_END;
635 }
636 goto SCANNER_TOP;
637
638 SCANNER_END:
639
640 /**
641 * END FINITE STATE MACHINE FOR SCANNING TOKENS
642 */
643 $this->isScanned = true;
644 }
645
646 /**
647 * Check for namespace
648 *
649 * @param string $namespace
650 * @return bool
651 */
652 public function hasNamespace($namespace)
653 {
654 $this->scan();
655
656 foreach ($this->infos as $info) {
657 if ($info['type'] == 'namespace' && $info['namespace'] == $namespace) {
658 return true;
659 }
660 }
661 return false;
662 }
663
664 /**
665 * @param string $namespace
666 * @return null|array
667 * @throws Exception\InvalidArgumentException
668 */
669 protected function getUsesNoScan($namespace)
670 {
671 $namespaces = [];
672 foreach ($this->infos as $info) {
673 if ($info['type'] == 'namespace') {
674 $namespaces[] = $info['namespace'];
675 }
676 }
677
678 if ($namespace === null) {
679 $namespace = array_shift($namespaces);
680 } elseif (! is_string($namespace)) {
681 throw new Exception\InvalidArgumentException('Invalid namespace provided');
682 } elseif (! in_array($namespace, $namespaces)) {
683 return;
684 }
685
686 $uses = [];
687 foreach ($this->infos as $info) {
688 if ($info['type'] !== 'use') {
689 continue;
690 }
691 foreach ($info['statements'] as $statement) {
692 if ($info['namespace'] == $namespace) {
693 $uses[] = $statement;
694 }
695 }
696 }
697
698 return $uses;
699 }
700 }
701