Skip to content

Commit 47aa5a7

Browse files
[ErrorHandler] improve DebugClassLoader patching logic
1 parent 153e081 commit 47aa5a7

File tree

1 file changed

+71
-26
lines changed

1 file changed

+71
-26
lines changed

src/Symfony/Component/ErrorHandler/DebugClassLoader.php

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class DebugClassLoader
6767
private $classLoader;
6868
private $isFinder;
6969
private $loaded = [];
70-
private $compatPatch;
70+
private $patchTypes;
7171
private static $caseCheck;
7272
private static $checkedClasses = [];
7373
private static $final = [];
@@ -85,7 +85,7 @@ public function __construct(callable $classLoader)
8585
{
8686
$this->classLoader = $classLoader;
8787
$this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile');
88-
$this->compatPatch = getenv('SYMFONY_PATCH_TYPE_DECLARATIONS_COMPAT') ?: null;
88+
parse_str(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes);
8989

9090
if (!isset(self::$caseCheck)) {
9191
$file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
@@ -160,13 +160,22 @@ public static function disable(): void
160160
spl_autoload_unregister($function);
161161
}
162162

163+
$loader = null;
164+
163165
foreach ($functions as $function) {
164166
if (\is_array($function) && $function[0] instanceof self) {
167+
$loader = $function[0];
165168
$function = $function[0]->getClassLoader();
166169
}
167170

168171
spl_autoload_register($function);
169172
}
173+
174+
if (null !== $loader) {
175+
foreach (array_merge(get_declared_interfaces(), get_declared_traits(), get_declared_classes()) as $class) {
176+
$loader->checkClass($class);
177+
}
178+
}
170179
}
171180

172181
public function findFile(string $class): ?string
@@ -348,7 +357,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
348357
if (trait_exists($class)) {
349358
$file = $refl->getFileName();
350359

351-
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
360+
foreach ($refl->getMethods() as $method) {
352361
if ($method->getFileName() === $file) {
353362
self::$methodTraits[$file][$method->getStartLine()] = $class;
354363
}
@@ -370,7 +379,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
370379
}
371380
}
372381

373-
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
382+
foreach ($refl->getMethods() as $method) {
374383
if ($method->class !== $class) {
375384
continue;
376385
}
@@ -413,18 +422,24 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
413422
}
414423
}
415424

425+
if ($canAddReturnType = '' !== ($this->patchTypes['class-prefix'] ?? '') && 0 === strpos($class, $this->patchTypes['class-prefix'])) {
426+
$canAddReturnType = false !== strpos($refl->getFileName(), \DIRECTORY_SEPARATOR.'Tests'.\DIRECTORY_SEPARATOR)
427+
|| $refl->isFinal()
428+
|| $method->isFinal()
429+
|| $method->isPrivate()
430+
|| ('' === (self::$internal[$class] ?? null) && !$refl->isAbstract())
431+
|| '' === (self::$final[$class] ?? null)
432+
|| preg_match('/@(final|internal)$/m', $doc);
433+
}
434+
416435
if (isset(self::$returnTypes[$class][$method->name]) && !$method->hasReturnType() && !($doc && preg_match('/\n\s+\* @return +(\S+)/', $doc))) {
417436
list($normalizedType, $returnType, $declaringClass, $declaringFile) = self::$returnTypes[$class][$method->name];
418437

419-
if (null !== $this->compatPatch && 0 === strpos($class, $this->compatPatch)) {
420-
self::fixReturnStatements($method, $normalizedType);
438+
if ($canAddReturnType) {
439+
$this->patchMethod($method, $returnType, $declaringFile, $normalizedType);
421440
}
422441

423442
if (strncmp($ns, $declaringClass, $len)) {
424-
if (null !== $this->compatPatch && 0 === strpos($class, $this->compatPatch)) {
425-
self::patchMethod($method, $returnType, $declaringFile);
426-
}
427-
428443
$deprecations[] = sprintf('Method "%s::%s()" will return "%s" as of its next major version. Doing the same in child class "%s" will be required when upgrading.', $declaringClass, $method->name, $normalizedType, $class);
429444
}
430445
}
@@ -436,11 +451,19 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
436451
if (!$method->hasReturnType() && false !== strpos($doc, '@return') && preg_match('/\n\s+\* @return +(\S+)/', $doc, $matches)) {
437452
$this->setReturnType($matches[1], $method, $parent);
438453

439-
if (null !== $this->compatPatch && 0 === strpos($class, $this->compatPatch)) {
440-
self::fixReturnStatements($method, self::$returnTypes[$class][$method->name][0] ?? '?');
454+
if (isset(self::$returnTypes[$class][$method->name][0]) && $canAddReturnType) {
455+
$this->fixReturnStatements($method, self::$returnTypes[$class][$method->name][0]);
456+
}
457+
458+
if ($method->isPrivate()) {
459+
unset(self::$returnTypes[$class][$method->name]);
441460
}
442461
}
443462

463+
if ($method->isPrivate()) {
464+
continue;
465+
}
466+
444467
$finalOrInternal = false;
445468

446469
foreach (['final', 'internal'] as $annotation) {
@@ -630,7 +653,7 @@ private function setReturnType(string $types, \ReflectionMethod $method, ?string
630653
} elseif ('null' === $normalizedType) {
631654
$normalizedType = $t;
632655
$returnType = $t;
633-
} elseif ($n !== $normalizedType) {
656+
} elseif ($n !== $normalizedType || !preg_match('/^\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $n)) {
634657
// ignore multi-types return declarations
635658
return;
636659
}
@@ -680,7 +703,7 @@ private function normalizeType(string $type, string $class, ?string $parent): st
680703
/**
681704
* Utility method to add @return annotations to the Symfony code-base where it triggers a self-deprecations.
682705
*/
683-
private static function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile)
706+
private function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile, string $normalizedType)
684707
{
685708
static $patchedMethods = [];
686709
static $useStatements = [];
@@ -690,8 +713,10 @@ private static function patchMethod(\ReflectionMethod $method, string $returnTyp
690713
}
691714

692715
$patchedMethods[$file][$startLine] = true;
693-
$patchedMethods[$file][0] = $patchedMethods[$file][0] ?? 0;
694-
$startLine += $patchedMethods[$file][0] - 2;
716+
$fileOffset = $patchedMethods[$file][0] ?? 0;
717+
$startLine += $fileOffset - 2;
718+
$nullable = '?' === $normalizedType[0] ? '?' : '';
719+
$normalizedType = ltrim($normalizedType, '?');
695720
$returnType = explode('|', $returnType);
696721
$code = file($file);
697722

@@ -737,32 +762,42 @@ private static function patchMethod(\ReflectionMethod $method, string $returnTyp
737762
if (!isset($useMap[$alias])) {
738763
$useStatements[$file][2][$alias] = $type;
739764
$code[$useOffset] = "use $type;\n".$code[$useOffset];
740-
++$patchedMethods[$file][0];
765+
++$fileOffset;
741766
} elseif ($useMap[$alias] !== $type) {
742767
$alias .= 'FIXME';
743768
$useStatements[$file][2][$alias] = $type;
744769
$code[$useOffset] = "use $type as $alias;\n".$code[$useOffset];
745-
++$patchedMethods[$file][0];
770+
++$fileOffset;
746771
}
747772

748773
$returnType[$i] = null !== $format ? sprintf($format, $alias) : $alias;
774+
775+
if (!isset(self::SPECIAL_RETURN_TYPES[$normalizedType]) && !isset(self::SPECIAL_RETURN_TYPES[$returnType[$i]])) {
776+
$normalizedType = $returnType[$i];
777+
}
749778
}
750779

751-
$returnType = implode('|', $returnType);
780+
if ('object' === $normalizedType && ($this->patchTypes['keep-compat-with-php71'] ?? false)) {
781+
$returnType = implode('|', $returnType);
752782

753-
if ($method->getDocComment()) {
754-
$code[$startLine] = " * @return $returnType\n".$code[$startLine];
755-
} else {
756-
$code[$startLine] .= <<<EOTXT
783+
if ($method->getDocComment()) {
784+
$code[$startLine] = " * @return $returnType\n".$code[$startLine];
785+
} else {
786+
$code[$startLine] .= <<<EOTXT
757787
/**
758788
* @return $returnType
759789
*/
760790
761791
EOTXT;
792+
}
793+
794+
$fileOffset += substr_count($code[$startLine], "\n") - 1;
762795
}
763796

764-
$patchedMethods[$file][0] += substr_count($code[$startLine], "\n") - 1;
797+
$patchedMethods[$file][0] = $fileOffset;
765798
file_put_contents($file, $code);
799+
800+
$this->fixReturnStatements($method, $nullable.$normalizedType, $patchedMethods[$file][0]);
766801
}
767802

768803
private static function getUseStatements(string $file): array
@@ -808,15 +843,25 @@ private static function getUseStatements(string $file): array
808843
return [$namespace, $useOffset, $useMap];
809844
}
810845

811-
private static function fixReturnStatements(\ReflectionMethod $method, string $returnType)
846+
private function fixReturnStatements(\ReflectionMethod $method, string $returnType, int $i = 0)
812847
{
848+
if (($this->patchTypes['keep-compat-with-php71'] ?? false) && 'object' === ltrim($returnType, '?')) {
849+
return;
850+
}
851+
813852
if (!file_exists($file = $method->getFileName())) {
814853
return;
815854
}
816855

817856
$fixedCode = $code = file($file);
857+
$i += $method->getStartLine();
858+
859+
if ('?' !== $returnType) {
860+
$fixedCode[$i - 1] = preg_replace('/\)(;?\n)/', "): $returnType\\1", $code[$i - 1]);
861+
}
862+
818863
$end = $method->getEndLine();
819-
for ($i = $method->getStartLine(); $i < $end; ++$i) {
864+
for (; $i < $end; ++$i) {
820865
if ('void' === $returnType) {
821866
$fixedCode[$i] = str_replace(' return null;', ' return;', $code[$i]);
822867
} elseif ('mixed' === $returnType || '?' === $returnType[0]) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy