Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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