diff --git a/src/Symfony/Component/ErrorHandler/Resources/bin/patch-type-declarations b/src/Symfony/Component/ErrorHandler/Resources/bin/patch-type-declarations new file mode 100755 index 0000000000000..f8f51b473a6cd --- /dev/null +++ b/src/Symfony/Component/ErrorHandler/Resources/bin/patch-type-declarations @@ -0,0 +1,90 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\in_array('-h', $argv) || \in_array('--help', $argv)) { + echo implode(PHP_EOL, [ + ' Patches type declarations based on "@return" PHPDoc and triggers deprecations for', + ' incompatible method declarations.', + '', + ' This assists you to make your package compatible with Symfony 6, but it can be used', + ' for any class/package.', + '', + ' Available configuration via environment variables:', + ' SYMFONY_PATCH_TYPE_DECLARATIONS', + ' An url-encoded string to change the behavior of the script. Available parameters:', + ' - "force": any value enables deprecation notices - can be any of:', + ' - "phpdoc" to patch only docblock annotations', + ' - "2" to add all possible return types', + ' - "1" to add return types but only to tests/final/internal/private methods', + ' - "php": the target version of PHP - e.g. "7.1" doesn\'t generate "object" types', + ' - "deprecations": "1" to trigger a deprecation notice when a child class misses a', + ' return type while the parent declares an "@return" annotation', + '', + ' SYMFONY_PATCH_TYPE_EXCLUDE', + ' A regex matched against the full path to the class - any match will be excluded', + '', + ' Example: "SYMFONY_PATCH_TYPE_DECLARATIONS=php=7.4 ./patch-type-declarations"', + ]); + exit; +} + +if (false === getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) { + putenv('SYMFONY_PATCH_TYPE_DECLARATIONS=force=2'); + echo 'No SYMFONY_PATCH_TYPE_DECLARATIONS env var set, patching type declarations in all methods (run the command with "-h" for more information).'.PHP_EOL; +} + +if (is_file($autoload = __DIR__.'/../../../autoload.php')) { + // noop +} elseif (is_file($autoload = __DIR__.'/../../../../../autoload.php')) { + // noop +} else { + echo PHP_EOL.' /!\ Cannot find the Composer autoloader, did you forget to run "composer install"?'.PHP_EOL; + exit(1); +} + +if (is_file($phpunitAutoload = dirname($autoload).'/bin/.phpunit/phpunit/vendor/autoload.php')) { + require $phpunitAutoload; +} + +$loader = require $autoload; + +Symfony\Component\ErrorHandler\DebugClassLoader::enable(); + +$deprecations = []; +set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations) { + if (\E_USER_DEPRECATED !== $type) { + return; + } + + [,,,,, $class,] = explode('"', $msg); + $deprecations[$class][] = $msg; +}); + +$exclude = getenv('SYMFONY_PATCH_TYPE_EXCLUDE') ?: null; +foreach ($loader->getClassMap() as $class => $file) { + if ($exclude && preg_match($exclude, realpath($file))) { + continue; + } + + class_exists($class); +} + +Symfony\Component\ErrorHandler\DebugClassLoader::checkClasses(); + +foreach ($deprecations as $class => $classDeprecations) { + echo $class.' ('.\count($classDeprecations).')'.PHP_EOL; + echo implode(PHP_EOL, $classDeprecations).PHP_EOL.PHP_EOL; +} + +if ($deprecations && false !== strpos(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?? '', 'force')) { + echo 'These deprecations might be fixed by the patch script, run this again to check for type deprecations.'.PHP_EOL; +} diff --git a/src/Symfony/Component/ErrorHandler/composer.json b/src/Symfony/Component/ErrorHandler/composer.json index 2aba72f8b78e5..f228d00f93e27 100644 --- a/src/Symfony/Component/ErrorHandler/composer.json +++ b/src/Symfony/Component/ErrorHandler/composer.json @@ -31,5 +31,8 @@ "/Tests/" ] }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], "minimum-stability": "dev" }