Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. DocBlock.php

class DocBlock

Same name in this branch
  1. 11.1.x vendor/phpdocumentor/reflection-docblock/src/DocBlock.php \phpDocumentor\Reflection\DocBlock

This is an abstraction around a PHPUnit-specific docBlock, allowing us to ask meaningful questions about a specific reflection symbol.

@no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit

@internal This class is not covered by the backward compatibility promise for PHPUnit

Hierarchy

  • class \PHPUnit\Metadata\Annotation\Parser\DocBlock

Expanded class hierarchy of DocBlock

7 string references to 'DocBlock'
DebugClassLoader::checkAnnotations in vendor/symfony/error-handler/DebugClassLoader.php
DebugClassLoader::fixReturnStatements in vendor/symfony/error-handler/DebugClassLoader.php
DebugClassLoader::patchMethod in vendor/symfony/error-handler/DebugClassLoader.php
Utility method to add
DebugClassLoader::__construct in vendor/symfony/error-handler/DebugClassLoader.php
FileHeaderSniff::getHeaderLines in vendor/squizlabs/php_codesniffer/src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php
Gather information about the statements inside a possible file header.

... See full list

File

vendor/phpunit/phpunit/src/Metadata/Parser/Annotation/DocBlock.php, line 41

Namespace

PHPUnit\Metadata\Annotation\Parser
View source
final class DocBlock {
    private const REGEX_REQUIRES_VERSION = '/@requires\\s+(?P<name>PHP(?:Unit)?)\\s+(?P<operator>[<>=!]{0,2})\\s*(?P<version>[\\d\\.-]+(dev|(RC|alpha|beta)[\\d\\.])?)[ \\t]*\\r?$/m';
    private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\\s+(?P<name>PHP(?:Unit)?)\\s+(?P<constraint>[\\d\\t \\-.|~^]+)[ \\t]*\\r?$/m';
    private const REGEX_REQUIRES_OS = '/@requires\\s+(?P<name>OS(?:FAMILY)?)\\s+(?P<value>.+?)[ \\t]*\\r?$/m';
    private const REGEX_REQUIRES_SETTING = '/@requires\\s+(?P<name>setting)\\s+(?P<setting>([^ ]+?))\\s*(?P<value>[\\w\\.-]+[\\w\\.]?)?[ \\t]*\\r?$/m';
    private const REGEX_REQUIRES = '/@requires\\s+(?P<name>function|extension)\\s+(?P<value>([^\\s<>=!]+))\\s*(?P<operator>[<>=!]{0,2})\\s*(?P<version>[\\d\\.-]+[\\d\\.]?)?[ \\t]*\\r?$/m';
    private readonly string $docComment;
    
    /**
     * @psalm-var array<string, array<int, string>> pre-parsed annotations indexed by name and occurrence index
     */
    private readonly array $symbolAnnotations;
    
    /**
     * @psalm-var null|(array{
     *   __OFFSET: array<string, int>&array{__FILE: string},
     *   setting?: array<string, string>,
     *   extension_versions?: array<string, array{version: string, operator: string}>
     * }&array<
     *   string,
     *   string|array{version: string, operator: string}|array{constraint: string}|array<int|string, string>
     * >)
     */
    private ?array $parsedRequirements = null;
    private readonly int $startLine;
    private readonly string $fileName;
    
    /**
     * @throws AnnotationsAreNotSupportedForInternalClassesException
     */
    public static function ofClass(ReflectionClass $class) : self {
        if ($class->isInternal()) {
            throw new AnnotationsAreNotSupportedForInternalClassesException($class->getName());
        }
        return new self((string) $class->getDocComment(), self::extractAnnotationsFromReflector($class), $class->getStartLine(), $class->getFileName());
    }
    
    /**
     * @throws AnnotationsAreNotSupportedForInternalClassesException
     */
    public static function ofMethod(ReflectionMethod $method) : self {
        if ($method->getDeclaringClass()
            ->isInternal()) {
            throw new AnnotationsAreNotSupportedForInternalClassesException($method->getDeclaringClass()
                ->getName());
        }
        return new self((string) $method->getDocComment(), self::extractAnnotationsFromReflector($method), $method->getStartLine(), $method->getFileName());
    }
    
    /**
     * Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized.
     *
     * @param array<string, array<int, string>> $symbolAnnotations
     */
    private function __construct(string $docComment, array $symbolAnnotations, int $startLine, string $fileName) {
        $this->docComment = $docComment;
        $this->symbolAnnotations = $symbolAnnotations;
        $this->startLine = $startLine;
        $this->fileName = $fileName;
    }
    
    /**
     * @psalm-return array{
     *   __OFFSET: array<string, int>&array{__FILE: string},
     *   setting?: array<string, string>,
     *   extension_versions?: array<string, array{version: string, operator: string}>
     * }&array<
     *   string,
     *   string|array{version: string, operator: string}|array{constraint: string}|array<int|string, string>
     * >
     *
     * @throws InvalidVersionRequirementException
     */
    public function requirements() : array {
        if ($this->parsedRequirements !== null) {
            return $this->parsedRequirements;
        }
        $offset = $this->startLine;
        $requires = [];
        $recordedSettings = [];
        $extensionVersions = [];
        $recordedOffsets = [
            '__FILE' => realpath($this->fileName),
        ];
        // Trim docblock markers, split it into lines and rewind offset to start of docblock
        $lines = preg_replace([
            '#^/\\*{2}#',
            '#\\*/$#',
        ], '', preg_split('/\\r\\n|\\r|\\n/', $this->docComment));
        $offset -= count($lines);
        foreach ($lines as $line) {
            if (preg_match(self::REGEX_REQUIRES_OS, $line, $matches)) {
                $requires[$matches['name']] = $matches['value'];
                $recordedOffsets[$matches['name']] = $offset;
            }
            if (preg_match(self::REGEX_REQUIRES_VERSION, $line, $matches)) {
                $requires[$matches['name']] = [
                    'version' => $matches['version'],
                    'operator' => $matches['operator'],
                ];
                $recordedOffsets[$matches['name']] = $offset;
            }
            if (preg_match(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $line, $matches)) {
                if (!empty($requires[$matches['name']])) {
                    $offset++;
                    continue;
                }
                try {
                    $versionConstraintParser = new VersionConstraintParser();
                    $requires[$matches['name'] . '_constraint'] = [
                        'constraint' => $versionConstraintParser->parse(trim($matches['constraint'])),
                    ];
                    $recordedOffsets[$matches['name'] . '_constraint'] = $offset;
                } catch (PharIoVersionException $e) {
                    throw new InvalidVersionRequirementException($e->getMessage(), $e->getCode(), $e);
                }
            }
            if (preg_match(self::REGEX_REQUIRES_SETTING, $line, $matches)) {
                $recordedSettings[$matches['setting']] = $matches['value'];
                $recordedOffsets['__SETTING_' . $matches['setting']] = $offset;
            }
            if (preg_match(self::REGEX_REQUIRES, $line, $matches)) {
                $name = $matches['name'] . 's';
                if (!isset($requires[$name])) {
                    $requires[$name] = [];
                }
                $requires[$name][] = $matches['value'];
                $recordedOffsets[$matches['name'] . '_' . $matches['value']] = $offset;
                if ($name === 'extensions' && !empty($matches['version'])) {
                    $extensionVersions[$matches['value']] = [
                        'version' => $matches['version'],
                        'operator' => $matches['operator'],
                    ];
                }
            }
            $offset++;
        }
        return $this->parsedRequirements = array_merge($requires, [
            '__OFFSET' => $recordedOffsets,
        ], array_filter([
            'setting' => $recordedSettings,
            'extension_versions' => $extensionVersions,
        ]));
    }
    public function symbolAnnotations() : array {
        return $this->symbolAnnotations;
    }
    
    /**
     * @psalm-return array<string, array<int, string>>
     */
    private static function parseDocBlock(string $docBlock) : array {
        // Strip away the docblock header and footer to ease parsing of one line annotations
        $docBlock = substr($docBlock, 3, -2);
        $annotations = [];
        if (preg_match_all('/@(?P<name>[A-Za-z_-]+)(?:[ \\t]+(?P<value>.*?))?[ \\t]*\\r?$/m', $docBlock, $matches)) {
            $numMatches = count($matches[0]);
            for ($i = 0; $i < $numMatches; $i++) {
                $annotations[$matches['name'][$i]][] = $matches['value'][$i];
            }
        }
        return $annotations;
    }
    private static function extractAnnotationsFromReflector(ReflectionClass|ReflectionFunctionAbstract $reflector) : array {
        $annotations = [];
        if ($reflector instanceof ReflectionClass) {
            $annotations = array_merge($annotations, ...array_map(static fn(ReflectionClass $trait): array => self::parseDocBlock((string) $trait->getDocComment()), array_values($reflector->getTraits())));
        }
        return array_merge($annotations, self::parseDocBlock((string) $reflector->getDocComment()));
    }

}

Members

Title Sort descending Modifiers Object type Summary
DocBlock::$docComment private property
DocBlock::$fileName private property
DocBlock::$parsedRequirements private property @psalm-var null|(array{
__OFFSET: array&lt;string, int&gt;&amp;array{__FILE: string},
setting?: array&lt;string, string&gt;,
extension_versions?: array&lt;string, array{version: string, operator:…
DocBlock::$startLine private property
DocBlock::$symbolAnnotations private property @psalm-var array&lt;string, array&lt;int, string&gt;&gt; pre-parsed annotations indexed by name and occurrence index
DocBlock::extractAnnotationsFromReflector private static function
DocBlock::ofClass public static function
DocBlock::ofMethod public static function
DocBlock::parseDocBlock private static function @psalm-return array&lt;string, array&lt;int, string&gt;&gt;
DocBlock::REGEX_REQUIRES private constant
DocBlock::REGEX_REQUIRES_OS private constant
DocBlock::REGEX_REQUIRES_SETTING private constant
DocBlock::REGEX_REQUIRES_VERSION private constant
DocBlock::REGEX_REQUIRES_VERSION_CONSTRAINT private constant
DocBlock::requirements public function @psalm-return array{
__OFFSET: array&lt;string, int&gt;&amp;array{__FILE: string},
setting?: array&lt;string, string&gt;,
extension_versions?: array&lt;string, array{version: string, operator: string}&gt;
}&amp;array&lt;
string,
string|array{version:…
DocBlock::symbolAnnotations public function
DocBlock::__construct private function Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized.
RSS feed
Powered by Drupal