Verzeichnisstruktur phpBB-3.3.16


Veröffentlicht
27.04.2026

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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

ClassScanner.php

Zuletzt modifiziert: 01.05.2026, 11:27 - Dateigröße: 40.11 KiB


 0001  <?php
 0002  /**
 0003   * Zend Framework (http://framework.zend.com/)
 0004   *
 0005   * @link      http://github.com/zendframework/zf2 for the canonical source repository
 0006   * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
 0007   * @license   http://framework.zend.com/license/new-bsd New BSD License
 0008   */
 0009   
 0010  namespace Zend\Code\Scanner;
 0011   
 0012  use ReflectionClass;
 0013  use Zend\Code\Annotation;
 0014  use Zend\Code\Exception;
 0015  use Zend\Code\NameInformation;
 0016   
 0017  use function array_key_exists;
 0018  use function array_merge;
 0019  use function array_search;
 0020  use function array_slice;
 0021  use function array_values;
 0022  use function define;
 0023  use function defined;
 0024  use function explode;
 0025  use function in_array;
 0026  use function is_array;
 0027  use function is_int;
 0028  use function is_object;
 0029  use function is_string;
 0030  use function ltrim;
 0031  use function sprintf;
 0032  use function substr_count;
 0033  use function trigger_error;
 0034   
 0035  class ClassScanner implements ScannerInterface
 0036  {
 0037      /**
 0038       * @var bool
 0039       */
 0040      protected $isScanned = false;
 0041   
 0042      /**
 0043       * @var string
 0044       */
 0045      protected $docComment;
 0046   
 0047      /**
 0048       * @var string
 0049       */
 0050      protected $name;
 0051   
 0052      /**
 0053       * @var string
 0054       */
 0055      protected $shortName;
 0056   
 0057      /**
 0058       * @var int
 0059       */
 0060      protected $lineStart;
 0061   
 0062      /**
 0063       * @var int
 0064       */
 0065      protected $lineEnd;
 0066   
 0067      /**
 0068       * @var bool
 0069       */
 0070      protected $isTrait = false;
 0071   
 0072      /**
 0073       * @var bool
 0074       */
 0075      protected $isFinal = false;
 0076   
 0077      /**
 0078       * @var bool
 0079       */
 0080      protected $isAbstract = false;
 0081   
 0082      /**
 0083       * @var bool
 0084       */
 0085      protected $isInterface = false;
 0086   
 0087      /**
 0088       * @var string
 0089       */
 0090      protected $parentClass;
 0091   
 0092      /**
 0093       * @var string
 0094       */
 0095      protected $shortParentClass;
 0096   
 0097      /**
 0098       * @var array
 0099       */
 0100      protected $interfaces = [];
 0101   
 0102      /**
 0103       * @var array
 0104       */
 0105      protected $shortInterfaces = [];
 0106   
 0107      /**
 0108       * @var array
 0109       */
 0110      protected $tokens = [];
 0111   
 0112      /**
 0113       * @var NameInformation
 0114       */
 0115      protected $nameInformation;
 0116   
 0117      /**
 0118       * @var array
 0119       */
 0120      protected $infos = [];
 0121   
 0122      /**
 0123       * @var array
 0124       */
 0125      protected $traits = [];
 0126   
 0127      /**
 0128       * @var array
 0129       */
 0130      protected $methods = [];
 0131   
 0132      /**
 0133       * @param  array $classTokens
 0134       * @param  NameInformation|null $nameInformation
 0135       * @return ClassScanner
 0136       */
 0137      public function __construct(array $classTokens, NameInformation $nameInformation = null)
 0138      {
 0139          $this->tokens          = $classTokens;
 0140          $this->nameInformation = $nameInformation;
 0141      }
 0142   
 0143      /**
 0144       * Get annotations
 0145       *
 0146       * @param  Annotation\AnnotationManager $annotationManager
 0147       * @return Annotation\AnnotationCollection
 0148       */
 0149      public function getAnnotations(Annotation\AnnotationManager $annotationManager)
 0150      {
 0151          if (($docComment = $this->getDocComment()) == '') {
 0152              return false;
 0153          }
 0154   
 0155          return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation);
 0156      }
 0157   
 0158      /**
 0159       * Return documentation comment
 0160       *
 0161       * @return null|string
 0162       */
 0163      public function getDocComment()
 0164      {
 0165          $this->scan();
 0166   
 0167          return $this->docComment;
 0168      }
 0169   
 0170      /**
 0171       * Return documentation block
 0172       *
 0173       * @return false|DocBlockScanner
 0174       */
 0175      public function getDocBlock()
 0176      {
 0177          if (! $docComment = $this->getDocComment()) {
 0178              return false;
 0179          }
 0180   
 0181          return new DocBlockScanner($docComment);
 0182      }
 0183   
 0184      /**
 0185       * Return a name of class
 0186       *
 0187       * @return null|string
 0188       */
 0189      public function getName()
 0190      {
 0191          $this->scan();
 0192          return $this->name;
 0193      }
 0194   
 0195      /**
 0196       * Return short name of class
 0197       *
 0198       * @return null|string
 0199       */
 0200      public function getShortName()
 0201      {
 0202          $this->scan();
 0203          return $this->shortName;
 0204      }
 0205   
 0206      /**
 0207       * Return number of first line
 0208       *
 0209       * @return int|null
 0210       */
 0211      public function getLineStart()
 0212      {
 0213          $this->scan();
 0214          return $this->lineStart;
 0215      }
 0216   
 0217      /**
 0218       * Return number of last line
 0219       *
 0220       * @return int|null
 0221       */
 0222      public function getLineEnd()
 0223      {
 0224          $this->scan();
 0225          return $this->lineEnd;
 0226      }
 0227   
 0228      /**
 0229       * Verify if class is final
 0230       *
 0231       * @return bool
 0232       */
 0233      public function isFinal()
 0234      {
 0235          $this->scan();
 0236          return $this->isFinal;
 0237      }
 0238   
 0239      /**
 0240       * Verify if class is a trait
 0241       *
 0242       * @return bool
 0243       */
 0244      public function isTrait()
 0245      {
 0246          $this->scan();
 0247          return $this->isTrait;
 0248      }
 0249   
 0250      /**
 0251       * Verify if class is instantiable
 0252       *
 0253       * @return bool
 0254       */
 0255      public function isInstantiable()
 0256      {
 0257          $this->scan();
 0258          return ! $this->isAbstract && ! $this->isInterface && ! $this->isTrait;
 0259      }
 0260   
 0261      /**
 0262       * Verify if class is an abstract class
 0263       *
 0264       * @return bool
 0265       */
 0266      public function isAbstract()
 0267      {
 0268          $this->scan();
 0269          return $this->isAbstract;
 0270      }
 0271   
 0272      /**
 0273       * Verify if class is an interface
 0274       *
 0275       * @return bool
 0276       */
 0277      public function isInterface()
 0278      {
 0279          $this->scan();
 0280          return $this->isInterface;
 0281      }
 0282   
 0283      /**
 0284       * Verify if class has parent
 0285       *
 0286       * @return bool
 0287       */
 0288      public function hasParentClass()
 0289      {
 0290          $this->scan();
 0291          return $this->parentClass !== null;
 0292      }
 0293   
 0294      /**
 0295       * Return a name of parent class
 0296       *
 0297       * @return null|string
 0298       */
 0299      public function getParentClass()
 0300      {
 0301          $this->scan();
 0302          return $this->parentClass;
 0303      }
 0304   
 0305      /**
 0306       * Return a list of interface names
 0307       *
 0308       * @return array
 0309       */
 0310      public function getInterfaces()
 0311      {
 0312          $this->scan();
 0313          return $this->interfaces;
 0314      }
 0315   
 0316      /**
 0317       * Return a list of constant names
 0318       *
 0319       * @return array
 0320       */
 0321      public function getConstantNames()
 0322      {
 0323          $this->scan();
 0324   
 0325          $return = [];
 0326          foreach ($this->infos as $info) {
 0327              if ($info['type'] != 'constant') {
 0328                  continue;
 0329              }
 0330   
 0331              $return[] = $info['name'];
 0332          }
 0333   
 0334          return $return;
 0335      }
 0336   
 0337      /**
 0338       * Return a list of constants
 0339       *
 0340       * @param  bool $namesOnly Set false to return instances of ConstantScanner
 0341       * @return array|ConstantScanner[]
 0342       */
 0343      public function getConstants($namesOnly = true)
 0344      {
 0345          if (true === $namesOnly) {
 0346              trigger_error('Use method getConstantNames() instead', E_USER_DEPRECATED);
 0347              return $this->getConstantNames();
 0348          }
 0349   
 0350          $this->scan();
 0351   
 0352          $return = [];
 0353          foreach ($this->infos as $info) {
 0354              if ($info['type'] != 'constant') {
 0355                  continue;
 0356              }
 0357   
 0358              $return[] = $this->getConstant($info['name']);
 0359          }
 0360   
 0361          return $return;
 0362      }
 0363   
 0364      /**
 0365       * Return a single constant by given name or index of info
 0366       *
 0367       * @param  string|int $constantNameOrInfoIndex
 0368       * @throws Exception\InvalidArgumentException
 0369       * @return bool|ConstantScanner
 0370       */
 0371      public function getConstant($constantNameOrInfoIndex)
 0372      {
 0373          $this->scan();
 0374   
 0375          if (is_int($constantNameOrInfoIndex)) {
 0376              $info = $this->infos[$constantNameOrInfoIndex];
 0377              if ($info['type'] != 'constant') {
 0378                  throw new Exception\InvalidArgumentException('Index of info offset is not about a constant');
 0379              }
 0380          } elseif (is_string($constantNameOrInfoIndex)) {
 0381              $constantFound = false;
 0382              foreach ($this->infos as $info) {
 0383                  if ($info['type'] === 'constant' && $info['name'] === $constantNameOrInfoIndex) {
 0384                      $constantFound = true;
 0385                      break;
 0386                  }
 0387              }
 0388              if (! $constantFound) {
 0389                  return false;
 0390              }
 0391          } else {
 0392              throw new Exception\InvalidArgumentException(
 0393                  'Invalid constant name of info index type.  Must be of type int or string'
 0394              );
 0395          }
 0396          if (! isset($info)) {
 0397              return false;
 0398          }
 0399          $p = new ConstantScanner(
 0400              array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1),
 0401              $this->nameInformation
 0402          );
 0403          $p->setClass($this->name);
 0404          $p->setScannerClass($this);
 0405          return $p;
 0406      }
 0407   
 0408      /**
 0409       * Verify if class has constant
 0410       *
 0411       * @param  string $name
 0412       * @return bool
 0413       */
 0414      public function hasConstant($name)
 0415      {
 0416          $this->scan();
 0417   
 0418          foreach ($this->infos as $info) {
 0419              if ($info['type'] === 'constant' && $info['name'] === $name) {
 0420                  return true;
 0421              }
 0422          }
 0423   
 0424          return false;
 0425      }
 0426   
 0427      /**
 0428       * Return a list of property names
 0429       *
 0430       * @return array
 0431       */
 0432      public function getPropertyNames()
 0433      {
 0434          $this->scan();
 0435   
 0436          $return = [];
 0437          foreach ($this->infos as $info) {
 0438              if ($info['type'] != 'property') {
 0439                  continue;
 0440              }
 0441   
 0442              $return[] = $info['name'];
 0443          }
 0444   
 0445          return $return;
 0446      }
 0447   
 0448      /**
 0449       * Return a list of properties
 0450       *
 0451       * @return PropertyScanner[]
 0452       */
 0453      public function getProperties()
 0454      {
 0455          $this->scan();
 0456   
 0457          $return = [];
 0458          foreach ($this->infos as $info) {
 0459              if ($info['type'] != 'property') {
 0460                  continue;
 0461              }
 0462   
 0463              $return[] = $this->getProperty($info['name']);
 0464          }
 0465   
 0466          return $return;
 0467      }
 0468   
 0469      /**
 0470       * Return a single property by given name or index of info
 0471       *
 0472       * @param  string|int $propertyNameOrInfoIndex
 0473       * @throws Exception\InvalidArgumentException
 0474       * @return bool|PropertyScanner
 0475       */
 0476      public function getProperty($propertyNameOrInfoIndex)
 0477      {
 0478          $this->scan();
 0479   
 0480          if (is_int($propertyNameOrInfoIndex)) {
 0481              $info = $this->infos[$propertyNameOrInfoIndex];
 0482              if ($info['type'] != 'property') {
 0483                  throw new Exception\InvalidArgumentException('Index of info offset is not about a property');
 0484              }
 0485          } elseif (is_string($propertyNameOrInfoIndex)) {
 0486              $propertyFound = false;
 0487              foreach ($this->infos as $info) {
 0488                  if ($info['type'] === 'property' && $info['name'] === $propertyNameOrInfoIndex) {
 0489                      $propertyFound = true;
 0490                      break;
 0491                  }
 0492              }
 0493              if (! $propertyFound) {
 0494                  return false;
 0495              }
 0496          } else {
 0497              throw new Exception\InvalidArgumentException(
 0498                  'Invalid property name of info index type.  Must be of type int or string'
 0499              );
 0500          }
 0501          if (! isset($info)) {
 0502              return false;
 0503          }
 0504          $p = new PropertyScanner(
 0505              array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1),
 0506              $this->nameInformation
 0507          );
 0508          $p->setClass($this->name);
 0509          $p->setScannerClass($this);
 0510          return $p;
 0511      }
 0512   
 0513      /**
 0514       * Verify if class has property
 0515       *
 0516       * @param  string $name
 0517       * @return bool
 0518       */
 0519      public function hasProperty($name)
 0520      {
 0521          $this->scan();
 0522   
 0523          foreach ($this->infos as $info) {
 0524              if ($info['type'] === 'property' && $info['name'] === $name) {
 0525                  return true;
 0526              }
 0527          }
 0528   
 0529          return false;
 0530      }
 0531   
 0532      /**
 0533       * Retrieve any traits used by the class.
 0534       *
 0535       * @return ClassScanner[]
 0536       */
 0537      public function getTraits()
 0538      {
 0539          if (! empty($this->traits)) {
 0540              return $this->traits;
 0541          }
 0542   
 0543          // get list of trait names
 0544          $traitNames = $this->getTraitNames();
 0545          foreach ($traitNames as $traitName) {
 0546              $r = new ReflectionClass($traitName);
 0547              if (! $r->isTrait()) {
 0548                  throw new Exception\RuntimeException(sprintf(
 0549                      'Non-trait class detected as a trait: %s',
 0550                      $traitName
 0551                  ));
 0552              }
 0553              $fileName = $r->getFileName();
 0554   
 0555              $file = new FileScanner($fileName);
 0556              $this->traits[] = $file->getClass($traitName);
 0557          }
 0558   
 0559          return $this->traits;
 0560      }
 0561   
 0562      /**
 0563       * Retrieve a list of trait names used by this class.
 0564       *
 0565       * @return array
 0566       */
 0567      public function getTraitNames()
 0568      {
 0569          $this->scan();
 0570   
 0571          $return = [];
 0572          foreach ($this->infos as $info) {
 0573              if ($info['type'] !== 'use') {
 0574                  continue;
 0575              }
 0576   
 0577              if (is_array($info['use_statements'])) {
 0578                  foreach ($info['use_statements'] as $trait) {
 0579                      $traitName = $trait;
 0580                      if ($this->nameInformation instanceof NameInformation) {
 0581                          $traitName = $this->nameInformation->resolveName($traitName);
 0582                      }
 0583                      $return[] = $traitName;
 0584                  }
 0585              }
 0586          }
 0587   
 0588          return $return;
 0589      }
 0590   
 0591      /**
 0592       * Retrieve a list of aliased traits used by the class.
 0593       *
 0594       * @return array
 0595       */
 0596      public function getTraitAliases()
 0597      {
 0598          $this->scan();
 0599   
 0600          $return = [];
 0601          foreach ($this->infos as $info) {
 0602              if ($info['type'] !== 'use') {
 0603                  continue;
 0604              }
 0605   
 0606              if (is_array($info['aliases'])) {
 0607                  foreach ($info['aliases'] as $alias) {
 0608                      if (null === $alias
 0609                          || (! empty($alias['type']) && $alias['type'] !== 'as')
 0610                      ) {
 0611                          continue;
 0612                      }
 0613   
 0614                      // attempt to get fqcn
 0615                      list($trait, $method) = explode('::', $alias['original']);
 0616                      if ($this->nameInformation instanceof NameInformation) {
 0617                          $trait = $this->nameInformation->resolveName($trait);
 0618                      }
 0619   
 0620                      $return[$alias['alias']] = $trait . '::' . $method;
 0621                  }
 0622              }
 0623          }
 0624   
 0625          return $return;
 0626      }
 0627   
 0628      /**
 0629       * Retrieve visibility for a given alias.
 0630       *
 0631       * @param mixed $aliasName
 0632       * @return string
 0633       */
 0634      protected function getVisibilityForAlias($aliasName)
 0635      {
 0636          $this->scan();
 0637   
 0638          $return = null;
 0639          foreach ($this->infos as $info) {
 0640              if ($info['type'] !== 'use') {
 0641                  continue;
 0642              }
 0643   
 0644              if (is_array($info['aliases'])) {
 0645                  foreach ($info['aliases'] as $alias) {
 0646                      if (null === $alias
 0647                          && (! empty($alias['type']) && $alias['type'] !== 'as')
 0648                      ) {
 0649                          continue;
 0650                      }
 0651   
 0652                      if ($alias['alias'] === $aliasName) {
 0653                          $return = $alias['visibility'];
 0654                          break 2;
 0655                      }
 0656                  }
 0657              }
 0658          }
 0659   
 0660          return $return;
 0661      }
 0662   
 0663      /**
 0664       * Return an array of key = trait to keep, value = trait::method to ignore
 0665       *
 0666       * @return array
 0667       */
 0668      protected function getBlockedTraitMethods()
 0669      {
 0670          $this->scan();
 0671   
 0672          $return = [];
 0673          foreach ($this->infos as $info) {
 0674              if ($info['type'] !== 'use') {
 0675                  continue;
 0676              }
 0677   
 0678              if (is_array($info['aliases'])) {
 0679                  foreach ($info['aliases'] as $alias) {
 0680                      if (null === $alias
 0681                          || (! empty($alias['type']) && $alias['type'] !== 'insteadof')
 0682                      ) {
 0683                          continue;
 0684                      }
 0685   
 0686                      // attempt to get fqcn
 0687                      list($trait, $method) = explode('::', $alias['original']);
 0688                      if ($this->nameInformation instanceof NameInformation) {
 0689                          $trait = $this->nameInformation->resolveName($alias['alias']);
 0690                      }
 0691   
 0692                      $return[] = $trait . '::' . $method;
 0693                  }
 0694              }
 0695          }
 0696   
 0697          return $return;
 0698      }
 0699   
 0700      /**
 0701       * Return a list of method names
 0702       *
 0703       * @return array
 0704       */
 0705      public function getMethodNames()
 0706      {
 0707          $this->scan();
 0708   
 0709          $methods = $this->getMethods();
 0710          $return = [];
 0711          foreach ($methods as $method) {
 0712              $return[] = $method->getName();
 0713          }
 0714   
 0715          return $return;
 0716      }
 0717   
 0718      /**
 0719       * Return a list of methods
 0720       *
 0721       * @return MethodScanner[]
 0722       */
 0723      public function getMethods()
 0724      {
 0725          $this->scan();
 0726   
 0727          if (! empty($this->methods)) {
 0728              return $this->methods;
 0729          }
 0730   
 0731          foreach ($this->infos as $info) {
 0732              if ($info['type'] !== 'method' && $info['type'] !== 'use') {
 0733                  continue;
 0734              }
 0735   
 0736              // Merge in trait methods
 0737              if ($info['type'] === 'use') {
 0738                  $traitMethods = [];
 0739                  $traits       = $this->getTraits();
 0740                  $insteadof    = $this->getBlockedTraitMethods();
 0741                  $aliases      = $this->getTraitAliases();
 0742   
 0743                  foreach ($traits as $trait) {
 0744                      $tempMethods = $trait->getMethods();
 0745                      foreach ($tempMethods as $tempMethod) {
 0746                          $methodFullName = $trait->getName() . '::' . $tempMethod->getName();
 0747                          $methodAlias = array_search($methodFullName, $aliases);
 0748   
 0749                          if (false !== $methodAlias) {
 0750                              // trait::method is aliased
 0751                              // clone the tempMethod as we need to change
 0752                              // the name and possibly the visibility of the
 0753                              // scanned method.
 0754                              //
 0755                              // @todo setName and setVisibility were added to
 0756                              // MethodScanner to accomplish this, may not be the
 0757                              // best option, could use ReflectionClass instead?
 0758                              $newMethod = clone $tempMethod;
 0759                              $newMethod->setName($methodAlias);
 0760   
 0761                              // if visibility exists, change it on the MethodScanner
 0762                              $visibility = $this->getVisibilityForAlias($methodAlias);
 0763                              if (null !== $visibility) {
 0764                                  $newMethod->setVisibility($visibility);
 0765                              }
 0766                              $traitMethods[$methodAlias] = $newMethod;
 0767                          } elseif (in_array($methodFullName, $insteadof)) {
 0768                              // ignore overridden methods
 0769                              continue;
 0770                          } else {
 0771                              if (array_key_exists($tempMethod->getName(), $traitMethods)) {
 0772                                  throw new Exception\RuntimeException(sprintf(
 0773                                      'Trait method %s has not been applied because there are'
 0774                                      . ' collisions with other trait methods see: (insteadof OR as)',
 0775                                      $tempMethod->getName()
 0776                                  ));
 0777                              }
 0778   
 0779                              $traitMethods[$tempMethod->getName()] = $tempMethod;
 0780                          }
 0781                      }
 0782                  }
 0783   
 0784                  $this->methods = array_merge($this->methods, array_values($traitMethods));
 0785                  continue;
 0786              }
 0787   
 0788              $m = new MethodScanner(
 0789                  array_slice(
 0790                      $this->tokens,
 0791                      $info['tokenStart'],
 0792                      $info['tokenEnd'] - $info['tokenStart'] + 1
 0793                  ),
 0794                  $this->nameInformation
 0795              );
 0796              $m->setClass($this->name);
 0797              $m->setScannerClass($this);
 0798   
 0799              $this->methods[] = $m;
 0800          }
 0801   
 0802          return $this->methods;
 0803      }
 0804   
 0805      /**
 0806       * Return a single method by given name or index of info
 0807       *
 0808       * @param  string|int $methodNameOrInfoIndex
 0809       * @throws Exception\InvalidArgumentException
 0810       * @return MethodScanner
 0811       */
 0812      public function getMethod($methodNameOrInfoIndex)
 0813      {
 0814          $this->scan();
 0815   
 0816          if (is_int($methodNameOrInfoIndex)) {
 0817              $info = $this->infos[$methodNameOrInfoIndex];
 0818              if ($info['type'] != 'method') {
 0819                  throw new Exception\InvalidArgumentException('Index of info offset is not about a method');
 0820              }
 0821              $methodNameOrInfoIndex = $info['name'];
 0822          }
 0823   
 0824          $returnMethod = false;
 0825          $methods = $this->getMethods();
 0826          foreach ($methods as $method) {
 0827              if ($method->getName() === $methodNameOrInfoIndex) {
 0828                  $returnMethod = $method;
 0829                  break;
 0830              }
 0831          }
 0832   
 0833          return $returnMethod;
 0834      }
 0835   
 0836      /**
 0837       * Verify if class has method by given name
 0838       *
 0839       * @param  string $name
 0840       * @return bool
 0841       */
 0842      public function hasMethod($name)
 0843      {
 0844          $this->scan();
 0845   
 0846          return is_object($this->getMethod($name));
 0847      }
 0848   
 0849      public static function export()
 0850      {
 0851          // @todo
 0852      }
 0853   
 0854      public function __toString()
 0855      {
 0856          // @todo
 0857      }
 0858   
 0859      /**
 0860       * Scan tokens
 0861       *
 0862       * @return void
 0863       * @throws Exception\RuntimeException
 0864       */
 0865      protected function scan()
 0866      {
 0867          if ($this->isScanned) {
 0868              return;
 0869          }
 0870   
 0871          if (! $this->tokens) {
 0872              throw new Exception\RuntimeException('No tokens were provided');
 0873          }
 0874   
 0875          /**
 0876           * Variables & Setup
 0877           */
 0878          $tokens       = &$this->tokens; // localize
 0879          $infos        = &$this->infos; // localize
 0880          $tokenIndex   = null;
 0881          $token        = null;
 0882          $tokenType    = null;
 0883          $tokenContent = null;
 0884          $tokenLine    = null;
 0885          $namespace    = null;
 0886          $infoIndex    = 0;
 0887          $braceCount   = 0;
 0888   
 0889          /*
 0890           * MACRO creation
 0891           */
 0892          $MACRO_TOKEN_ADVANCE = function () use (
 0893              &$tokens,
 0894              &$tokenIndex,
 0895              &$token,
 0896              &$tokenType,
 0897              &$tokenContent,
 0898              &$tokenLine
 0899          ) {
 0900              static $lastTokenArray = null;
 0901              $tokenIndex = $tokenIndex === null ? 0 : $tokenIndex + 1;
 0902              if (! isset($tokens[$tokenIndex])) {
 0903                  $token        = false;
 0904                  $tokenContent = false;
 0905                  $tokenType    = false;
 0906                  $tokenLine    = false;
 0907   
 0908                  return false;
 0909              }
 0910              $token = $tokens[$tokenIndex];
 0911   
 0912              if (is_string($token)) {
 0913                  $tokenType    = null;
 0914                  $tokenContent = $token;
 0915                  $tokenLine   += substr_count(
 0916                      $lastTokenArray[1] ?? '',
 0917                      "\n"
 0918                  ); // adjust token line by last known newline count
 0919              } else {
 0920                  $lastTokenArray = $token;
 0921                  [$tokenType, $tokenContent, $tokenLine] = $token;
 0922              }
 0923   
 0924              return $tokenIndex;
 0925          };
 0926          $MACRO_INFO_ADVANCE  = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
 0927              $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
 0928              $infos[$infoIndex]['lineEnd']  = $tokenLine;
 0929              $infoIndex++;
 0930   
 0931              return $infoIndex;
 0932          };
 0933   
 0934          /**
 0935           * START FINITE STATE MACHINE FOR SCANNING TOKENS
 0936           */
 0937          // Initialize token
 0938          $MACRO_TOKEN_ADVANCE();
 0939   
 0940          SCANNER_TOP:
 0941   
 0942          switch ($tokenType) {
 0943              case T_DOC_COMMENT:
 0944                  $this->docComment = $tokenContent;
 0945                  goto SCANNER_CONTINUE;
 0946                  //goto no break needed
 0947   
 0948              case T_FINAL:
 0949              case T_ABSTRACT:
 0950              case T_CLASS:
 0951              case T_INTERFACE:
 0952              case T_TRAIT:
 0953                  // CLASS INFORMATION
 0954   
 0955                  $classContext        = null;
 0956                  $classInterfaceIndex = 0;
 0957   
 0958                  SCANNER_CLASS_INFO_TOP:
 0959   
 0960                  if (is_string($tokens[$tokenIndex + 1]) && $tokens[$tokenIndex + 1] === '{') {
 0961                      goto SCANNER_CLASS_INFO_END;
 0962                  }
 0963   
 0964                  $this->lineStart = $tokenLine;
 0965   
 0966                  switch ($tokenType) {
 0967                      case T_FINAL:
 0968                          $this->isFinal = true;
 0969                          goto SCANNER_CLASS_INFO_CONTINUE;
 0970                          // goto no break needed
 0971   
 0972                      case T_ABSTRACT:
 0973                          $this->isAbstract = true;
 0974                          goto SCANNER_CLASS_INFO_CONTINUE;
 0975                          // goto no break needed
 0976   
 0977                      case T_TRAIT:
 0978                          $this->isTrait = true;
 0979                          $this->shortName = $tokens[$tokenIndex + 2][1];
 0980                          if ($this->nameInformation && $this->nameInformation->hasNamespace()) {
 0981                              $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName;
 0982                          } else {
 0983                              $this->name = $this->shortName;
 0984                          }
 0985                          goto SCANNER_CLASS_INFO_CONTINUE;
 0986                          // goto no break needed
 0987   
 0988                      case T_INTERFACE:
 0989                          $this->isInterface = true;
 0990                          // fall-through
 0991                      case T_CLASS:
 0992                          $this->shortName = $tokens[$tokenIndex + 2][1];
 0993                          if ($this->nameInformation && $this->nameInformation->hasNamespace()) {
 0994                              $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName;
 0995                          } else {
 0996                              $this->name = $this->shortName;
 0997                          }
 0998                          goto SCANNER_CLASS_INFO_CONTINUE;
 0999                          // goto no break needed
 1000   
 1001                      case T_NS_SEPARATOR:
 1002                      case T_STRING:
 1003                          switch ($classContext) {
 1004                              case T_EXTENDS:
 1005                                  if ($this->isInterface) {
 1006                                      $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent;
 1007                                  } else {
 1008                                      $this->shortParentClass .= $tokenContent;
 1009                                  }
 1010                                  break;
 1011                              case T_IMPLEMENTS:
 1012                                  $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent;
 1013                                  break;
 1014                          }
 1015                          goto SCANNER_CLASS_INFO_CONTINUE;
 1016                          // goto no break needed
 1017   
 1018                      case T_EXTENDS:
 1019                      case T_IMPLEMENTS:
 1020                          $classContext = $tokenType;
 1021                          if (($this->isInterface && $classContext === T_EXTENDS) || $classContext === T_IMPLEMENTS) {
 1022                              $this->shortInterfaces[$classInterfaceIndex] = '';
 1023                          } elseif (! $this->isInterface && $classContext === T_EXTENDS) {
 1024                              $this->shortParentClass = '';
 1025                          }
 1026                          goto SCANNER_CLASS_INFO_CONTINUE;
 1027                          // goto no break needed
 1028   
 1029                      case null:
 1030                          if (($classContext == T_IMPLEMENTS && $tokenContent == ',')
 1031                              || ($classContext == T_EXTENDS && $tokenContent == ',' && $this->isInterface)
 1032                          ) {
 1033                              $classInterfaceIndex++;
 1034                              $this->shortInterfaces[$classInterfaceIndex] = '';
 1035                          }
 1036                  }
 1037   
 1038                  SCANNER_CLASS_INFO_CONTINUE:
 1039   
 1040                  if ($MACRO_TOKEN_ADVANCE() === false) {
 1041                      goto SCANNER_END;
 1042                  }
 1043                  goto SCANNER_CLASS_INFO_TOP;
 1044   
 1045                  SCANNER_CLASS_INFO_END:
 1046   
 1047                  goto SCANNER_CONTINUE;
 1048          }
 1049   
 1050          if ($tokenType === null && $tokenContent === '{' && $braceCount === 0) {
 1051              $braceCount++;
 1052              if ($MACRO_TOKEN_ADVANCE() === false) {
 1053                  goto SCANNER_END;
 1054              }
 1055   
 1056              SCANNER_CLASS_BODY_TOP:
 1057   
 1058              if ($braceCount === 0) {
 1059                  goto SCANNER_CLASS_BODY_END;
 1060              }
 1061   
 1062              switch ($tokenType) {
 1063                  case T_CONST:
 1064                      $infos[$infoIndex] = [
 1065                          'type'          => 'constant',
 1066                          'tokenStart'    => $tokenIndex,
 1067                          'tokenEnd'      => null,
 1068                          'lineStart'     => $tokenLine,
 1069                          'lineEnd'       => null,
 1070                          'name'          => null,
 1071                          'value'         => null,
 1072                      ];
 1073   
 1074                      SCANNER_CLASS_BODY_CONST_TOP:
 1075   
 1076                      if ($tokenContent === ';') {
 1077                          goto SCANNER_CLASS_BODY_CONST_END;
 1078                      }
 1079   
 1080                      if ($tokenType === T_STRING && null === $infos[$infoIndex]['name']) {
 1081                          $infos[$infoIndex]['name'] = $tokenContent;
 1082                      }
 1083   
 1084                      SCANNER_CLASS_BODY_CONST_CONTINUE:
 1085   
 1086                      if ($MACRO_TOKEN_ADVANCE() === false) {
 1087                          goto SCANNER_END;
 1088                      }
 1089                      goto SCANNER_CLASS_BODY_CONST_TOP;
 1090   
 1091                      SCANNER_CLASS_BODY_CONST_END:
 1092   
 1093                      $MACRO_INFO_ADVANCE();
 1094                      goto SCANNER_CLASS_BODY_CONTINUE;
 1095                      // goto no break needed
 1096   
 1097                  case T_USE:
 1098                      // ensure php backwards compatibility
 1099                      if (! defined('T_INSTEADOF')) {
 1100                          define('T_INSTEADOF', 24000);
 1101                      }
 1102   
 1103                      $infos[$infoIndex] = [
 1104                          'type'           => 'use',
 1105                          'tokenStart'     => $tokenIndex,
 1106                          'tokenEnd'       => null,
 1107                          'lineStart'      => $tokens[$tokenIndex][2],
 1108                          'lineEnd'        => null,
 1109                          'name'           => $namespace,
 1110                          'use_statements' => [0 => null],
 1111                          'aliases'        => [0 => null],
 1112                      ];
 1113   
 1114                      $isOriginalName = [T_STRING, T_DOUBLE_COLON];
 1115                      $isAlias        = [T_STRING];
 1116                      $isVisibility   = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC];
 1117                      $isAliasType    = [T_AS, T_INSTEADOF];
 1118                      $isValidAlias   = array_merge($isOriginalName, $isAlias, $isVisibility, $isAliasType);
 1119   
 1120                      $useStatementIndex   = 0;
 1121                      $aliasStatementIndex = 0;
 1122                      $useAliasContext     = false;
 1123                      $useAsContext        = false;
 1124   
 1125                      // start processing with next token
 1126                      if ($MACRO_TOKEN_ADVANCE() === false) {
 1127                          goto SCANNER_END;
 1128                      }
 1129   
 1130                      SCANNER_USE_TOP:
 1131   
 1132                      if ($tokenType === null) {
 1133                          if ($tokenContent === '{') {
 1134                              $useStatementIndex = 0;
 1135                              $useAliasContext   = true;
 1136                              $infos[$infoIndex]['aliases'][$useStatementIndex] = [
 1137                                  'original'   => null,
 1138                                  'alias'      => null,
 1139                                  'visibility' => null,
 1140                                  'type'       => 'as',
 1141                              ];
 1142                          } elseif ($tokenContent === '}') {
 1143                              $useAliasContext = false;
 1144                              goto SCANNER_USE_END;
 1145                          } elseif ($tokenContent === ';') {
 1146                              if ($useAliasContext === true) {
 1147                                  $useStatementIndex++;
 1148                                  $useAsContext = false;
 1149                              }
 1150                              // only end if we aren't inside braces
 1151                              if (false === $useAliasContext) {
 1152                                  goto SCANNER_USE_END;
 1153                              }
 1154                          } elseif ($tokenContent === ',') {
 1155                              $useStatementIndex++;
 1156                              $infos[$infoIndex]['use_statements'][$useStatementIndex] = '';
 1157                          }
 1158                      }
 1159   
 1160                      // ANALYZE
 1161                      if ($tokenType !== null) {
 1162                          // use context
 1163                          if (false === $useAliasContext) {
 1164                              if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) {
 1165                                  $infos[$infoIndex]['use_statements'][$useStatementIndex] .= $tokenContent;
 1166                              }
 1167                          } else {
 1168                              if (in_array($tokenType, $isValidAlias)
 1169                                  && empty($infos[$infoIndex]['aliases'][$useStatementIndex])
 1170                              ) {
 1171                                  $infos[$infoIndex]['aliases'][$useStatementIndex] = [
 1172                                      'original'   => null,
 1173                                      'visibility' => null,
 1174                                      'alias'      => null,
 1175                                      'type'       => null,
 1176                                  ];
 1177                              }
 1178   
 1179                              if ($tokenType == T_AS || $tokenType == T_INSTEADOF) {
 1180                                  $useAsContext = true;
 1181                                  $infos[$infoIndex]['aliases'][$useStatementIndex]['type'] = $tokenType == T_INSTEADOF
 1182                                      ? 'insteadof'
 1183                                      : 'as';
 1184                                  goto SCANNER_USE_CONTINUE;
 1185                              }
 1186   
 1187                              // in alias context
 1188                              if ($useAsContext === true && in_array($tokenType, $isAlias)) {
 1189                                  $infos[$infoIndex]['aliases'][$useStatementIndex]['alias'] = $tokenContent;
 1190                              } elseif (in_array($tokenType, $isOriginalName)) {
 1191                                  $infos[$infoIndex]['aliases'][$useStatementIndex]['original'] .= $tokenContent;
 1192                              } elseif (in_array($tokenType, $isVisibility)) {
 1193                                  //add whitespace (will trim later)
 1194                                  $infos[$infoIndex]['aliases'][$useStatementIndex]['visibility'] = $tokenType;
 1195                              }
 1196                          }
 1197                      }
 1198   
 1199                      SCANNER_USE_CONTINUE:
 1200   
 1201                      if ($MACRO_TOKEN_ADVANCE() === false) {
 1202                          goto SCANNER_END;
 1203                      }
 1204                      goto SCANNER_USE_TOP;
 1205   
 1206                      SCANNER_USE_END:
 1207   
 1208                      $MACRO_INFO_ADVANCE();
 1209                      goto SCANNER_CLASS_BODY_CONTINUE;
 1210                      // goto no break needed
 1211   
 1212                  case T_DOC_COMMENT:
 1213                  case T_PUBLIC:
 1214                  case T_PROTECTED:
 1215                  case T_PRIVATE:
 1216                  case T_ABSTRACT:
 1217                  case T_FINAL:
 1218                  case T_VAR:
 1219                  case T_FUNCTION:
 1220                      $infos[$infoIndex] = [
 1221                          'type'        => null,
 1222                          'tokenStart'  => $tokenIndex,
 1223                          'tokenEnd'    => null,
 1224                          'lineStart'   => $tokenLine,
 1225                          'lineEnd'     => null,
 1226                          'name'        => null,
 1227                      ];
 1228   
 1229                      $memberContext     = null;
 1230                      $methodBodyStarted = false;
 1231   
 1232                      SCANNER_CLASS_BODY_MEMBER_TOP:
 1233   
 1234                      if ($memberContext === 'method') {
 1235                          switch ($tokenContent) {
 1236                              case '{':
 1237                                  $methodBodyStarted = true;
 1238                                  $braceCount++;
 1239                                  goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
 1240                                  // goto no break needed
 1241   
 1242                              case '}':
 1243                                  $braceCount--;
 1244                                  goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
 1245                                  // goto no break needed
 1246   
 1247                              case ';':
 1248                                  $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
 1249                                  goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
 1250                                  // goto no break needed
 1251                          }
 1252                      }
 1253   
 1254                      if ($memberContext !== null) {
 1255                          if (($memberContext === 'property' && $tokenContent === ';')
 1256                              || ($memberContext === 'method' && $methodBodyStarted && $braceCount === 1)
 1257                              || ($memberContext === 'method' && $this->isInterface && $tokenContent === ';')
 1258                          ) {
 1259                              goto SCANNER_CLASS_BODY_MEMBER_END;
 1260                          }
 1261                      }
 1262   
 1263                      switch ($tokenType) {
 1264                          case T_CONST:
 1265                              $memberContext             = 'constant';
 1266                              $infos[$infoIndex]['type'] = 'constant';
 1267                              goto SCANNER_CLASS_BODY_CONST_CONTINUE;
 1268                              //goto no break needed
 1269   
 1270                          case T_VARIABLE:
 1271                              if ($memberContext === null) {
 1272                                  $memberContext             = 'property';
 1273                                  $infos[$infoIndex]['type'] = 'property';
 1274                                  $infos[$infoIndex]['name'] = ltrim($tokenContent, '$');
 1275                              }
 1276                              goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
 1277                              // goto no break needed
 1278   
 1279                          case T_FUNCTION:
 1280                              $memberContext             = 'method';
 1281                              $infos[$infoIndex]['type'] = 'method';
 1282                              goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
 1283                              // goto no break needed
 1284   
 1285                          case T_STRING:
 1286                              if ($memberContext === 'method' && null === $infos[$infoIndex]['name']) {
 1287                                  $infos[$infoIndex]['name'] = $tokenContent;
 1288                              }
 1289                              goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
 1290                              // goto no break needed
 1291                      }
 1292   
 1293                      SCANNER_CLASS_BODY_MEMBER_CONTINUE:
 1294   
 1295                      if ($MACRO_TOKEN_ADVANCE() === false) {
 1296                          goto SCANNER_END;
 1297                      }
 1298                      goto SCANNER_CLASS_BODY_MEMBER_TOP;
 1299   
 1300                      SCANNER_CLASS_BODY_MEMBER_END:
 1301   
 1302                      $memberContext = null;
 1303                      $MACRO_INFO_ADVANCE();
 1304                      goto SCANNER_CLASS_BODY_CONTINUE;
 1305                      // goto no break needed
 1306   
 1307                  case null: // no type, is a string
 1308                      switch ($tokenContent) {
 1309                          case '{':
 1310                              $braceCount++;
 1311                              goto SCANNER_CLASS_BODY_CONTINUE;
 1312                              // goto no break needed
 1313   
 1314                          case '}':
 1315                              $braceCount--;
 1316                              goto SCANNER_CLASS_BODY_CONTINUE;
 1317                              // goto no break needed
 1318                      }
 1319              }
 1320   
 1321              SCANNER_CLASS_BODY_CONTINUE:
 1322   
 1323              if ($braceCount === 0 || $MACRO_TOKEN_ADVANCE() === false) {
 1324                  goto SCANNER_CONTINUE;
 1325              }
 1326              goto SCANNER_CLASS_BODY_TOP;
 1327   
 1328              SCANNER_CLASS_BODY_END:
 1329   
 1330              goto SCANNER_CONTINUE;
 1331          }
 1332   
 1333          SCANNER_CONTINUE:
 1334   
 1335          if ($tokenContent === '}') {
 1336              $this->lineEnd = $tokenLine;
 1337          }
 1338   
 1339          if ($MACRO_TOKEN_ADVANCE() === false) {
 1340              goto SCANNER_END;
 1341          }
 1342          goto SCANNER_TOP;
 1343   
 1344          SCANNER_END:
 1345   
 1346          // process short names
 1347          if ($this->nameInformation) {
 1348              if ($this->shortParentClass) {
 1349                  $this->parentClass = $this->nameInformation->resolveName($this->shortParentClass);
 1350              }
 1351              if ($this->shortInterfaces) {
 1352                  foreach ($this->shortInterfaces as $siIndex => $si) {
 1353                      $this->interfaces[$siIndex] = $this->nameInformation->resolveName($si);
 1354                  }
 1355              }
 1356          } else {
 1357              $this->parentClass = $this->shortParentClass;
 1358              $this->interfaces  = $this->shortInterfaces;
 1359          }
 1360   
 1361          $this->isScanned = true;
 1362      }
 1363  }
 1364