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

Breadcrumb

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

function DebugClassLoader::checkAnnotations

1 call to DebugClassLoader::checkAnnotations()
DebugClassLoader::checkClass in vendor/symfony/error-handler/DebugClassLoader.php

File

vendor/symfony/error-handler/DebugClassLoader.php, line 362

Class

DebugClassLoader
Autoloader checking if the class is really defined in the file found.

Namespace

Symfony\Component\ErrorHandler

Code

public function checkAnnotations(\ReflectionClass $refl, string $class) : array {
    if ('Symfony\\Bridge\\PhpUnit\\Legacy\\SymfonyTestsListenerForV7' === $class || 'Symfony\\Bridge\\PhpUnit\\Legacy\\SymfonyTestsListenerForV6' === $class) {
        return [];
    }
    $deprecations = [];
    $className = str_contains($class, "@anonymous\x00") ? ((get_parent_class($class) ?: key(class_implements($class))) ?: 'class') . '@anonymous' : $class;
    // Don't trigger deprecations for classes in the same vendor
    if ($class !== $className) {
        $vendor = preg_match('/^namespace ([^;\\\\\\s]++)[;\\\\]/m', @file_get_contents($refl->getFileName()), $vendor) ? $vendor[1] . '\\' : '';
        $vendorLen = \strlen($vendor);
    }
    elseif (2 > ($vendorLen = 1 + (strpos($class, '\\') ?: strpos($class, '_')))) {
        $vendorLen = 0;
        $vendor = '';
    }
    else {
        $vendor = str_replace('_', '\\', substr($class, 0, $vendorLen));
    }
    $parent = get_parent_class($class) ?: null;
    self::$returnTypes[$class] = [];
    $classIsTemplate = false;
    // Detect annotations on the class
    if ($doc = $this->parsePhpDoc($refl)) {
        $classIsTemplate = isset($doc['template']) || isset($doc['template-covariant']);
        foreach ([
            'final',
            'deprecated',
            'internal',
        ] as $annotation) {
            if (null !== ($description = $doc[$annotation][0] ?? null)) {
                self::${$annotation}[$class] = '' !== $description ? ' ' . $description . (preg_match('/[.!]$/', $description) ? '' : '.') : '.';
            }
        }
        if ($refl->isInterface() && isset($doc['method'])) {
            foreach ($doc['method'] as $name => [
                $static,
                $returnType,
                $signature,
                $description,
            ]) {
                self::$method[$class][] = [
                    $class,
                    $static,
                    $returnType,
                    $name . $signature,
                    $description,
                ];
                if ('' !== $returnType) {
                    $this->setReturnType($returnType, $refl->name, $name, $refl->getFileName(), $parent);
                }
            }
        }
    }
    $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
    if ($parent) {
        $parentAndOwnInterfaces[$parent] = $parent;
        if (!isset(self::$checkedClasses[$parent])) {
            $this->checkClass($parent);
        }
        if (isset(self::$final[$parent])) {
            $deprecations[] = \sprintf('The "%s" class is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $className);
        }
    }
    // Detect if the parent is annotated
    foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) {
        if (!isset(self::$checkedClasses[$use])) {
            $this->checkClass($use);
        }
        if (isset(self::$deprecated[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) && !isset(self::$deprecated[$class])) {
            $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
            $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
            $deprecations[] = \sprintf('The "%s" %s %s "%s" that is deprecated%s', $className, $type, $verb, $use, self::$deprecated[$use]);
        }
        if (isset(self::$internal[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen)) {
            $deprecations[] = \sprintf('The "%s" %s is considered internal%s It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $className);
        }
        if (isset(self::$method[$use])) {
            if ($refl->isAbstract()) {
                if (isset(self::$method[$class])) {
                    self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]);
                }
                else {
                    self::$method[$class] = self::$method[$use];
                }
            }
            elseif (!$refl->isInterface()) {
                if (!strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) && str_starts_with($className, 'Symfony\\') && (!class_exists(InstalledVersions::class) || 'symfony/symfony' !== InstalledVersions::getRootPackage()['name'])) {
                    // skip "same vendor" @method deprecations for Symfony\* classes unless symfony/symfony is being tested
                    continue;
                }
                $hasCall = $refl->hasMethod('__call');
                $hasStaticCall = $refl->hasMethod('__callStatic');
                foreach (self::$method[$use] as [
                    $interface,
                    $static,
                    $returnType,
                    $name,
                    $description,
                ]) {
                    if ($static ? $hasStaticCall : $hasCall) {
                        continue;
                    }
                    $realName = substr($name, 0, strpos($name, '('));
                    if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))
                        ->isPublic() || $static && !$methodRefl->isStatic() || !$static && $methodRefl->isStatic()) {
                        $deprecations[] = \sprintf('Class "%s" should implement method "%s::%s%s"%s', $className, ($static ? 'static ' : '') . $interface, $name, $returnType ? ': ' . $returnType : '', null === $description ? '.' : ': ' . $description);
                    }
                }
            }
        }
    }
    if (trait_exists($class)) {
        $file = $refl->getFileName();
        foreach ($refl->getMethods() as $method) {
            if ($method->getFileName() === $file) {
                self::$methodTraits[$file][$method->getStartLine()] = $class;
            }
        }
        return $deprecations;
    }
    // Inherit @final, @internal, @param and @return annotations for methods
    self::$finalMethods[$class] = [];
    self::$internalMethods[$class] = [];
    self::$annotatedParameters[$class] = [];
    self::$finalProperties[$class] = [];
    self::$finalConstants[$class] = [];
    foreach ($parentAndOwnInterfaces as $use) {
        foreach ([
            'finalMethods',
            'internalMethods',
            'annotatedParameters',
            'returnTypes',
            'finalProperties',
            'finalConstants',
        ] as $property) {
            if (isset(self::${$property}[$use])) {
                self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
            }
        }
        if (null !== (TentativeTypes::RETURN_TYPES[$use] ?? null)) {
            foreach (TentativeTypes::RETURN_TYPES[$use] as $method => $returnType) {
                $returnType = explode('|', $returnType);
                foreach ($returnType as $i => $t) {
                    if ('?' !== $t && !isset(self::BUILTIN_RETURN_TYPES[$t])) {
                        $returnType[$i] = '\\' . $t;
                    }
                }
                $returnType = implode('|', $returnType);
                self::$returnTypes[$class] += [
                    $method => [
                        $returnType,
                        str_starts_with($returnType, '?') ? substr($returnType, 1) . '|null' : $returnType,
                        $use,
                        '',
                    ],
                ];
            }
        }
    }
    foreach ($refl->getMethods() as $method) {
        if ($method->class !== $class) {
            continue;
        }
        if (null === ($ns = self::$methodTraits[$method->getFileName()][$method->getStartLine()] ?? null)) {
            $ns = $vendor;
            $len = $vendorLen;
        }
        elseif (2 > ($len = 1 + (strpos($ns, '\\') ?: strpos($ns, '_')))) {
            $len = 0;
            $ns = '';
        }
        else {
            $ns = str_replace('_', '\\', substr($ns, 0, $len));
        }
        if ($parent && isset(self::$finalMethods[$parent][$method->name])) {
            [
                $declaringClass,
                $message,
            ] = self::$finalMethods[$parent][$method->name];
            $deprecations[] = \sprintf('The "%s::%s()" method is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className);
        }
        if (isset(self::$internalMethods[$class][$method->name])) {
            [
                $declaringClass,
                $message,
            ] = self::$internalMethods[$class][$method->name];
            if (strncmp($ns, $declaringClass, $len)) {
                $deprecations[] = \sprintf('The "%s::%s()" method is considered internal%s It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className);
            }
        }
        // To read method annotations
        $doc = $this->parsePhpDoc($method);
        if (($classIsTemplate || isset($doc['template']) || isset($doc['template-covariant'])) && $method->hasReturnType()) {
            unset($doc['return']);
        }
        if (isset(self::$annotatedParameters[$class][$method->name])) {
            $definedParameters = [];
            foreach ($method->getParameters() as $parameter) {
                $definedParameters[$parameter->name] = true;
            }
            foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) {
                if (!isset($definedParameters[$parameterName]) && !isset($doc['param'][$parameterName])) {
                    $deprecations[] = \sprintf($deprecation, $className);
                }
            }
        }
        $forcePatchTypes = $this->patchTypes['force'];
        if ($canAddReturnType = null !== $forcePatchTypes && !str_contains($method->getFileName(), \DIRECTORY_SEPARATOR . 'vendor' . \DIRECTORY_SEPARATOR)) {
            $this->patchTypes['force'] = $forcePatchTypes ?: 'docblock';
            $canAddReturnType = 2 === (int) $forcePatchTypes || false !== stripos($method->getFileName(), \DIRECTORY_SEPARATOR . 'Tests' . \DIRECTORY_SEPARATOR) || $refl->isFinal() || $method->isFinal() || $method->isPrivate() || '.' === (self::$internal[$class] ?? null) && !$refl->isAbstract() || '.' === (self::$final[$class] ?? null) || '' === ($doc['final'][0] ?? null) || '' === ($doc['internal'][0] ?? null);
        }
        if (null !== ($returnType = self::$returnTypes[$class][$method->name] ?? null) && 'docblock' === $this->patchTypes['force'] && !$method->hasReturnType() && isset(TentativeTypes::RETURN_TYPES[$returnType[2]][$method->name])) {
            $this->patchReturnTypeWillChange($method);
        }
        if (null !== ($returnType ??= self::MAGIC_METHODS[$method->name] ?? null) && !$method->hasReturnType() && !isset($doc['return'])) {
            [
                $normalizedType,
                $returnType,
                $declaringClass,
                $declaringFile,
            ] = \is_string($returnType) ? [
                $returnType,
                $returnType,
                '',
                '',
            ] : $returnType;
            if ($canAddReturnType && 'docblock' !== $this->patchTypes['force']) {
                $this->patchMethod($method, $returnType, $declaringFile, $normalizedType);
            }
            if (!isset($doc['deprecated']) && strncmp($ns, $declaringClass, $len)) {
                if ('docblock' === $this->patchTypes['force']) {
                    $this->patchMethod($method, $returnType, $declaringFile, $normalizedType);
                }
                elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) {
                    $deprecations[] = \sprintf('Method "%s::%s()" might add "%s" as a native return type declaration in the future. Do the same in %s "%s" now to avoid errors or add an explicit @return annotation to suppress this message.', $declaringClass, $method->name, $normalizedType, interface_exists($declaringClass) ? 'implementation' : 'child class', $className);
                }
            }
        }
        if (!$doc) {
            $this->patchTypes['force'] = $forcePatchTypes;
            continue;
        }
        if (isset($doc['return'])) {
            $this->setReturnType($doc['return'] ?? self::MAGIC_METHODS[$method->name], $method->class, $method->name, $method->getFileName(), $parent, $method->getReturnType());
            if (isset(self::$returnTypes[$class][$method->name][0]) && $canAddReturnType) {
                $this->fixReturnStatements($method, self::$returnTypes[$class][$method->name][0]);
            }
            if ($method->isPrivate()) {
                unset(self::$returnTypes[$class][$method->name]);
            }
        }
        $this->patchTypes['force'] = $forcePatchTypes;
        if ($method->isPrivate()) {
            continue;
        }
        $finalOrInternal = false;
        foreach ([
            'final',
            'internal',
        ] as $annotation) {
            if (null !== ($description = $doc[$annotation][0] ?? null)) {
                self::${$annotation . 'Methods'}[$class][$method->name] = [
                    $class,
                    '' !== $description ? ' ' . $description . (preg_match('/[[:punct:]]$/', $description) ? '' : '.') : '.',
                ];
                $finalOrInternal = true;
            }
        }
        if ($finalOrInternal || $method->isConstructor() || !isset($doc['param']) || StatelessInvocation::class === $class) {
            continue;
        }
        if (!isset(self::$annotatedParameters[$class][$method->name])) {
            $definedParameters = [];
            foreach ($method->getParameters() as $parameter) {
                $definedParameters[$parameter->name] = true;
            }
        }
        foreach ($doc['param'] as $parameterName => $parameterType) {
            if (!isset($definedParameters[$parameterName])) {
                self::$annotatedParameters[$class][$method->name][$parameterName] = \sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its %s "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType . ' ' : '', $parameterName, interface_exists($className) ? 'interface' : 'parent class', $className);
            }
        }
    }
    $finals = isset(self::$final[$class]) || $refl->isFinal() ? [] : [
        'finalConstants' => $refl->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC | \ReflectionClassConstant::IS_PROTECTED),
        'finalProperties' => $refl->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED),
    ];
    foreach ($finals as $type => $reflectors) {
        foreach ($reflectors as $r) {
            if ($r->class !== $class) {
                continue;
            }
            $doc = $this->parsePhpDoc($r);
            foreach ($parentAndOwnInterfaces as $use) {
                if (isset(self::${$type}[$use][$r->name]) && !isset($doc['deprecated']) && ('finalConstants' === $type || substr($use, 0, strrpos($use, '\\')) !== substr($use, 0, strrpos($class, '\\')))) {
                    $msg = 'finalConstants' === $type ? '%s" constant' : '$%s" property';
                    $deprecations[] = \sprintf('The "%s::' . $msg . ' is considered final. You should not override it in "%s".', self::${$type}[$use][$r->name], $r->name, $class);
                }
            }
            if (isset($doc['final']) || 'finalProperties' === $type && str_starts_with($class, 'Symfony\\') && !$r->hasType()) {
                self::${$type}[$class][$r->name] = $class;
            }
        }
    }
    return $deprecations;
}
RSS feed
Powered by Drupal