$bodies Open class bodies: [brace depth, name]. */ $bodies = []; $pending = null; foreach ($tokens as $i => $token) { $id = is_array($token) ? $token[0] : null; $text = is_array($token) ? $token[1] : $token; if ($id !== null && in_array($id, self::TYPES, true) && self::previousId($tokens, $i) !== T_DOUBLE_COLON) { $pending = self::nextName($tokens, $i) ?? ''; continue; } if ($text === '{' || $id === T_CURLY_OPEN || $id === T_DOLLAR_OPEN_CURLY_BRACES) { ++$depth; if ($pending !== null && $text === '{') { $bodies[] = [$depth, $pending]; $pending = null; } continue; } if ($text === '}') { if ($bodies !== [] && $bodies[count($bodies) - 1][0] === $depth) { array_pop($bodies); } --$depth; continue; } if ($id !== T_FUNCTION || self::previousId($tokens, $i) === T_USE) { continue; } $name = self::nextName($tokens, $i); if ($name === null || strcasecmp($name, $function) !== 0) { continue; } $body = $bodies === [] ? null : $bodies[count($bodies) - 1]; $matches = $wanted === null ? $body === null : $body !== null && $body[0] === $depth && strcasecmp($body[1], $wanted) === 0; if ($matches) { return self::capture($tokens, $i); } } return null; } /** @param array $tokens */ private static function capture(array $tokens, int $start): ?self { $startLine = (int) $tokens[$start][2]; $parts = []; $verbatim = ''; $depth = 0; $opened = false; $count = count($tokens); for ($i = $start; $i < $count; ++$i) { $token = $tokens[$i]; $id = is_array($token) ? $token[0] : null; $text = is_array($token) ? $token[1] : $token; $verbatim .= $text; if ($id === null || ! in_array($id, self::IGNORED, true)) { $parts[] = $text; } if ($text === '{' || $id === T_CURLY_OPEN || $id === T_DOLLAR_OPEN_CURLY_BRACES) { ++$depth; $opened = true; } elseif ($text === '}') { --$depth; if ($opened && $depth === 0) { return new self( hash('sha256', implode(' ', $parts)), $verbatim, $startLine, $startLine + substr_count($verbatim, "\n") ); } } elseif ($text === ';' && ! $opened) { return null; } } return null; } /** The next name after $i (skipping whitespace, comments and "&"), null when something else follows. */ private static function nextName(array $tokens, int $i): ?string { $count = count($tokens); for ($j = $i + 1; $j < $count; ++$j) { $token = $tokens[$j]; if (is_array($token) && in_array($token[0], self::IGNORED, true)) { continue; } if ($token === '&' || (is_array($token) && $token[1] === '&')) { continue; } return is_array($token) && $token[0] === T_STRING ? $token[1] : null; } return null; } /** Token ID of the previous significant token, null for a single-character token or none. */ private static function previousId(array $tokens, int $i): ?int { for ($j = $i - 1; $j >= 0; --$j) { $token = $tokens[$j]; if (is_array($token) && in_array($token[0], self::IGNORED, true)) { continue; } return is_array($token) ? $token[0] : null; } return null; } public static function shortName(string $class): string { $position = strrpos($class, '\\'); return $position === false ? $class : substr($class, $position + 1); } }