Zend\Code\Scanner\ClassScanner::scan PHP Method

scan() protected method

Scan tokens
protected scan ( ) : void
return void
    protected function scan()
    {
        if ($this->isScanned) {
            return;
        }
        if (!$this->tokens) {
            throw new Exception\RuntimeException('No tokens were provided');
        }
        /**
         * Variables & Setup
         */
        $tokens =& $this->tokens;
        // localize
        $infos =& $this->infos;
        // localize
        $tokenIndex = null;
        $token = null;
        $tokenType = null;
        $tokenContent = null;
        $tokenLine = null;
        $namespace = null;
        $infoIndex = 0;
        $braceCount = 0;
        /*
         * MACRO creation
         */
        $MACRO_TOKEN_ADVANCE = function () use(&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) {
            static $lastTokenArray = null;
            $tokenIndex = $tokenIndex === null ? 0 : $tokenIndex + 1;
            if (!isset($tokens[$tokenIndex])) {
                $token = false;
                $tokenContent = false;
                $tokenType = false;
                $tokenLine = false;
                return false;
            }
            $token = $tokens[$tokenIndex];
            if (is_string($token)) {
                $tokenType = null;
                $tokenContent = $token;
                $tokenLine = $tokenLine + substr_count($lastTokenArray[1], "\n");
                // adjust token line by last known newline count
            } else {
                $lastTokenArray = $token;
                list($tokenType, $tokenContent, $tokenLine) = $token;
            }
            return $tokenIndex;
        };
        $MACRO_INFO_ADVANCE = function () use(&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
            $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
            $infos[$infoIndex]['lineEnd'] = $tokenLine;
            $infoIndex++;
            return $infoIndex;
        };
        /**
         * START FINITE STATE MACHINE FOR SCANNING TOKENS
         */
        // Initialize token
        $MACRO_TOKEN_ADVANCE();
        SCANNER_TOP:
        switch ($tokenType) {
            case T_DOC_COMMENT:
                $this->docComment = $tokenContent;
                goto SCANNER_CONTINUE;
                //goto no break needed
            //goto no break needed
            case T_FINAL:
            case T_ABSTRACT:
            case T_CLASS:
            case T_INTERFACE:
            case T_TRAIT:
                // CLASS INFORMATION
                $classContext = null;
                $classInterfaceIndex = 0;
                SCANNER_CLASS_INFO_TOP:
                if (is_string($tokens[$tokenIndex + 1]) && $tokens[$tokenIndex + 1] === '{') {
                    goto SCANNER_CLASS_INFO_END;
                }
                $this->lineStart = $tokenLine;
                switch ($tokenType) {
                    case T_FINAL:
                        $this->isFinal = true;
                        goto SCANNER_CLASS_INFO_CONTINUE;
                        // goto no break needed
                    // goto no break needed
                    case T_ABSTRACT:
                        $this->isAbstract = true;
                        goto SCANNER_CLASS_INFO_CONTINUE;
                        // goto no break needed
                    // goto no break needed
                    case T_TRAIT:
                        $this->isTrait = true;
                        $this->shortName = $tokens[$tokenIndex + 2][1];
                        if ($this->nameInformation && $this->nameInformation->hasNamespace()) {
                            $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName;
                        } else {
                            $this->name = $this->shortName;
                        }
                        goto SCANNER_CLASS_INFO_CONTINUE;
                        // goto no break needed
                    // goto no break needed
                    case T_INTERFACE:
                        $this->isInterface = true;
                        // fall-through
                    // fall-through
                    case T_CLASS:
                        $this->shortName = $tokens[$tokenIndex + 2][1];
                        if ($this->nameInformation && $this->nameInformation->hasNamespace()) {
                            $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName;
                        } else {
                            $this->name = $this->shortName;
                        }
                        goto SCANNER_CLASS_INFO_CONTINUE;
                        // goto no break needed
                    // goto no break needed
                    case T_NS_SEPARATOR:
                    case T_STRING:
                        switch ($classContext) {
                            case T_EXTENDS:
                                if ($this->isInterface) {
                                    $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent;
                                } else {
                                    $this->shortParentClass .= $tokenContent;
                                }
                                break;
                            case T_IMPLEMENTS:
                                $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent;
                                break;
                        }
                        goto SCANNER_CLASS_INFO_CONTINUE;
                        // goto no break needed
                    // goto no break needed
                    case T_EXTENDS:
                    case T_IMPLEMENTS:
                        $classContext = $tokenType;
                        if ($this->isInterface && $classContext === T_EXTENDS || $classContext === T_IMPLEMENTS) {
                            $this->shortInterfaces[$classInterfaceIndex] = '';
                        } elseif (!$this->isInterface && $classContext === T_EXTENDS) {
                            $this->shortParentClass = '';
                        }
                        goto SCANNER_CLASS_INFO_CONTINUE;
                        // goto no break needed
                    // goto no break needed
                    case null:
                        if ($classContext == T_IMPLEMENTS && $tokenContent == ',' || $classContext == T_EXTENDS && $tokenContent == ',' && $this->isInterface) {
                            $classInterfaceIndex++;
                            $this->shortInterfaces[$classInterfaceIndex] = '';
                        }
                }
                SCANNER_CLASS_INFO_CONTINUE:
                if ($MACRO_TOKEN_ADVANCE() === false) {
                    goto SCANNER_END;
                }
                goto SCANNER_CLASS_INFO_TOP;
                SCANNER_CLASS_INFO_END:
                goto SCANNER_CONTINUE;
        }
        if ($tokenType === null && $tokenContent === '{' && $braceCount === 0) {
            $braceCount++;
            if ($MACRO_TOKEN_ADVANCE() === false) {
                goto SCANNER_END;
            }
            SCANNER_CLASS_BODY_TOP:
            if ($braceCount === 0) {
                goto SCANNER_CLASS_BODY_END;
            }
            switch ($tokenType) {
                case T_CONST:
                    $infos[$infoIndex] = ['type' => 'constant', 'tokenStart' => $tokenIndex, 'tokenEnd' => null, 'lineStart' => $tokenLine, 'lineEnd' => null, 'name' => null, 'value' => null];
                    SCANNER_CLASS_BODY_CONST_TOP:
                    if ($tokenContent === ';') {
                        goto SCANNER_CLASS_BODY_CONST_END;
                    }
                    if ($tokenType === T_STRING && null === $infos[$infoIndex]['name']) {
                        $infos[$infoIndex]['name'] = $tokenContent;
                    }
                    SCANNER_CLASS_BODY_CONST_CONTINUE:
                    if ($MACRO_TOKEN_ADVANCE() === false) {
                        goto SCANNER_END;
                    }
                    goto SCANNER_CLASS_BODY_CONST_TOP;
                    SCANNER_CLASS_BODY_CONST_END:
                    $MACRO_INFO_ADVANCE();
                    goto SCANNER_CLASS_BODY_CONTINUE;
                    // goto no break needed
                // goto no break needed
                case T_USE:
                    // ensure php backwards compatibility
                    if (!defined('T_INSTEADOF')) {
                        define('T_INSTEADOF', 24000);
                    }
                    $infos[$infoIndex] = ['type' => 'use', 'tokenStart' => $tokenIndex, 'tokenEnd' => null, 'lineStart' => $tokens[$tokenIndex][2], 'lineEnd' => null, 'name' => $namespace, 'use_statements' => [0 => null], 'aliases' => [0 => null]];
                    $isOriginalName = [T_STRING, T_DOUBLE_COLON];
                    $isAlias = [T_STRING];
                    $isVisibility = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC];
                    $isAliasType = [T_AS, T_INSTEADOF];
                    $isValidAlias = array_merge($isOriginalName, $isAlias, $isVisibility, $isAliasType);
                    $useStatementIndex = 0;
                    $aliasStatementIndex = 0;
                    $useAliasContext = false;
                    $useAsContext = false;
                    // start processing with next token
                    if ($MACRO_TOKEN_ADVANCE() === false) {
                        goto SCANNER_END;
                    }
                    SCANNER_USE_TOP:
                    if ($tokenType === null) {
                        if ($tokenContent === "{") {
                            $useStatementIndex = 0;
                            $useAliasContext = true;
                            $infos[$infoIndex]['aliases'][$useStatementIndex] = ['original' => null, 'alias' => null, 'visibility' => null, 'type' => 'as'];
                        } elseif ($tokenContent === "}") {
                            $useAliasContext = false;
                            goto SCANNER_USE_END;
                        } elseif ($tokenContent === ';') {
                            if ($useAliasContext === true) {
                                $useStatementIndex++;
                                $useAsContext = false;
                            }
                            // only end if we aren't inside braces
                            if (false === $useAliasContext) {
                                goto SCANNER_USE_END;
                            }
                        } elseif ($tokenContent === ',') {
                            $useStatementIndex++;
                            $infos[$infoIndex]['use_statements'][$useStatementIndex] = '';
                        }
                    }
                    // ANALYZE
                    if ($tokenType !== null) {
                        // use context
                        if (false === $useAliasContext) {
                            if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) {
                                $infos[$infoIndex]['use_statements'][$useStatementIndex] .= $tokenContent;
                            }
                        } else {
                            if (in_array($tokenType, $isValidAlias) && empty($infos[$infoIndex]['aliases'][$useStatementIndex])) {
                                $infos[$infoIndex]['aliases'][$useStatementIndex] = ['original' => null, 'visibility' => null, 'alias' => null, 'type' => null];
                            }
                            if ($tokenType == T_AS || $tokenType == T_INSTEADOF) {
                                $useAsContext = true;
                                $infos[$infoIndex]['aliases'][$useStatementIndex]['type'] = $tokenType == T_INSTEADOF ? 'insteadof' : 'as';
                                goto SCANNER_USE_CONTINUE;
                            }
                            // in alias context
                            if ($useAsContext === true && in_array($tokenType, $isAlias)) {
                                $infos[$infoIndex]['aliases'][$useStatementIndex]['alias'] = $tokenContent;
                            } elseif (in_array($tokenType, $isOriginalName)) {
                                $infos[$infoIndex]['aliases'][$useStatementIndex]['original'] .= $tokenContent;
                            } elseif (in_array($tokenType, $isVisibility)) {
                                //add whitespace (will trim later)
                                $infos[$infoIndex]['aliases'][$useStatementIndex]['visibility'] = $tokenType;
                            }
                        }
                    }
                    SCANNER_USE_CONTINUE:
                    if ($MACRO_TOKEN_ADVANCE() === false) {
                        goto SCANNER_END;
                    }
                    goto SCANNER_USE_TOP;
                    SCANNER_USE_END:
                    $MACRO_INFO_ADVANCE();
                    goto SCANNER_CLASS_BODY_CONTINUE;
                    // goto no break needed
                // goto no break needed
                case T_DOC_COMMENT:
                case T_PUBLIC:
                case T_PROTECTED:
                case T_PRIVATE:
                case T_ABSTRACT:
                case T_FINAL:
                case T_VAR:
                case T_FUNCTION:
                    $infos[$infoIndex] = ['type' => null, 'tokenStart' => $tokenIndex, 'tokenEnd' => null, 'lineStart' => $tokenLine, 'lineEnd' => null, 'name' => null];
                    $memberContext = null;
                    $methodBodyStarted = false;
                    SCANNER_CLASS_BODY_MEMBER_TOP:
                    if ($memberContext === 'method') {
                        switch ($tokenContent) {
                            case '{':
                                $methodBodyStarted = true;
                                $braceCount++;
                                goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
                                // goto no break needed
                            // goto no break needed
                            case '}':
                                $braceCount--;
                                goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
                                // goto no break needed
                            // goto no break needed
                            case ';':
                                $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
                                goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
                                // goto no break needed
                        }
                    }
                    if ($memberContext !== null) {
                        if ($memberContext === 'property' && $tokenContent === ';' || $memberContext === 'method' && $methodBodyStarted && $braceCount === 1 || $memberContext === 'method' && $this->isInterface && $tokenContent === ';') {
                            goto SCANNER_CLASS_BODY_MEMBER_END;
                        }
                    }
                    switch ($tokenType) {
                        case T_CONST:
                            $memberContext = 'constant';
                            $infos[$infoIndex]['type'] = 'constant';
                            goto SCANNER_CLASS_BODY_CONST_CONTINUE;
                            //goto no break needed
                        //goto no break needed
                        case T_VARIABLE:
                            if ($memberContext === null) {
                                $memberContext = 'property';
                                $infos[$infoIndex]['type'] = 'property';
                                $infos[$infoIndex]['name'] = ltrim($tokenContent, '$');
                            }
                            goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
                            // goto no break needed
                        // goto no break needed
                        case T_FUNCTION:
                            $memberContext = 'method';
                            $infos[$infoIndex]['type'] = 'method';
                            goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
                            // goto no break needed
                        // goto no break needed
                        case T_STRING:
                            if ($memberContext === 'method' && null === $infos[$infoIndex]['name']) {
                                $infos[$infoIndex]['name'] = $tokenContent;
                            }
                            goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
                            // goto no break needed
                    }
                    SCANNER_CLASS_BODY_MEMBER_CONTINUE:
                    if ($MACRO_TOKEN_ADVANCE() === false) {
                        goto SCANNER_END;
                    }
                    goto SCANNER_CLASS_BODY_MEMBER_TOP;
                    SCANNER_CLASS_BODY_MEMBER_END:
                    $memberContext = null;
                    $MACRO_INFO_ADVANCE();
                    goto SCANNER_CLASS_BODY_CONTINUE;
                    // goto no break needed
                // goto no break needed
                case null:
                    // no type, is a string
                    switch ($tokenContent) {
                        case '{':
                            $braceCount++;
                            goto SCANNER_CLASS_BODY_CONTINUE;
                            // goto no break needed
                        // goto no break needed
                        case '}':
                            $braceCount--;
                            goto SCANNER_CLASS_BODY_CONTINUE;
                            // goto no break needed
                    }
            }
            SCANNER_CLASS_BODY_CONTINUE:
            if ($braceCount === 0 || $MACRO_TOKEN_ADVANCE() === false) {
                goto SCANNER_CONTINUE;
            }
            goto SCANNER_CLASS_BODY_TOP;
            SCANNER_CLASS_BODY_END:
            goto SCANNER_CONTINUE;
        }
        SCANNER_CONTINUE:
        if ($tokenContent === '}') {
            $this->lineEnd = $tokenLine;
        }
        if ($MACRO_TOKEN_ADVANCE() === false) {
            goto SCANNER_END;
        }
        goto SCANNER_TOP;
        SCANNER_END:
        // process short names
        if ($this->nameInformation) {
            if ($this->shortParentClass) {
                $this->parentClass = $this->nameInformation->resolveName($this->shortParentClass);
            }
            if ($this->shortInterfaces) {
                foreach ($this->shortInterfaces as $siIndex => $si) {
                    $this->interfaces[$siIndex] = $this->nameInformation->resolveName($si);
                }
            }
        } else {
            $this->parentClass = $this->shortParentClass;
            $this->interfaces = $this->shortInterfaces;
        }
        $this->isScanned = true;
        return;
    }