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.
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: 02.04.2025, 15:03 - 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