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\ErrorHandlerCode
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;
}