Merge pull request #10554 from creative-commoners/pulls/4/deprecation-api

FIX Ensure Deprecation works with 1.x branches
This commit is contained in:
Guy Sartorelli 2022-10-20 14:18:22 +13:00 committed by GitHub
commit 421b706a38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 201 deletions

View File

@ -4,9 +4,7 @@ namespace SilverStripe\Dev;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Core\Environment; use SilverStripe\Core\Environment;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Manifest\Module; use SilverStripe\Core\Manifest\Module;
use SilverStripe\Core\Manifest\ModuleLoader;
/** /**
* Handles raising an notice when accessing a deprecated method * Handles raising an notice when accessing a deprecated method
@ -37,14 +35,13 @@ use SilverStripe\Core\Manifest\ModuleLoader;
*/ */
class Deprecation class Deprecation
{ {
const SCOPE_METHOD = 1; const SCOPE_METHOD = 1;
const SCOPE_CLASS = 2; const SCOPE_CLASS = 2;
const SCOPE_GLOBAL = 4; const SCOPE_GLOBAL = 4;
/** /**
*
* @var string * @var string
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
*/ */
protected static $version; protected static $version;
@ -58,26 +55,47 @@ class Deprecation
* must be available before this to avoid infinite loops. * must be available before this to avoid infinite loops.
* *
* @var boolean|null * @var boolean|null
* @deprecated 4.12.0 Use $is_enabled instead
*/ */
protected static $enabled = null; protected static $enabled = null;
/** /**
* Must be configured outside of the config API, as deprecation API
* must be available before this to avoid infinite loops.
* *
* This will be overriden by the SS_DEPRECATION_ENABLED environment if present
*/
protected static bool $is_enabled = false;
/**
* @var array * @var array
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
*/ */
protected static $module_version_overrides = []; protected static $module_version_overrides = [];
protected static bool $inside_notice = false; protected static bool $inside_notice = false;
/** /**
* @var int - the notice level to raise on a deprecation notice. Defaults to E_USER_DEPRECATED if that exists, * Switched out by unit-testing to E_USER_NOTICE because E_USER_DEPRECATED is not
* E_USER_NOTICE if not * caught by $this->expectDeprecated() by default
* https://github.com/laminas/laminas-di/pull/30#issuecomment-927585210
*
* @var int
*/ */
public static $notice_level = null; public static $notice_level = E_USER_DEPRECATED;
public static function enable(): void
{
static::$is_enabled = true;
}
public static function disable(): void
{
static::$is_enabled = false;
}
/** /**
* Set the version that is used to check against the version passed to notice. If the ::notice version is * This method is no longer used
* greater than or equal to this version, a message will be raised
* *
* @static * @static
* @param $ver string - * @param $ver string -
@ -86,32 +104,25 @@ class Deprecation
* The name of a module. The passed version will be used as the check value for * The name of a module. The passed version will be used as the check value for
* calls directly from this module rather than the global value * calls directly from this module rather than the global value
* @return void * @return void
* @deprecated 4.12.0 Use enable() instead
*/ */
public static function notification_version($ver, $forModule = null) public static function notification_version($ver, $forModule = null)
{ {
if ($forModule) { static::notice('4.12.0', 'Use enable() instead');
self::$module_version_overrides[$forModule] = $ver; // noop
} else {
self::$version = $ver;
}
} }
/** /**
* Given a backtrace, get the module name from the caller two removed (the caller of the method that called * This method is no longer used
* #notice)
* *
* @param array $backtrace A backtrace as returned from debug_backtrace * @param array $backtrace A backtrace as returned from debug_backtrace
* @return Module The module being called * @return Module The module being called
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
*/ */
protected static function get_calling_module_from_trace($backtrace) protected static function get_calling_module_from_trace($backtrace)
{ {
if (!isset($backtrace[1]['file'])) { static::notice('4.12.0', 'Will be removed without equivalent functionality to replace it');
return null; // noop
}
$callingfile = realpath($backtrace[1]['file'] ?? '');
return ModuleLoader::inst()->getManifest()->getModuleByPath($callingfile);
} }
/** /**
@ -137,30 +148,38 @@ class Deprecation
} }
/** /**
* Determine if deprecation notices should be displayed * This method is no longer used
* *
* @return bool * @return bool
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
*/ */
public static function get_enabled() public static function get_enabled()
{ {
// Deprecation is only available on dev static::notice('4.12.0', 'Will be removed without equivalent functionality to replace it');
// noop
}
private static function get_is_enabled(): bool
{
if (!Director::isDev()) { if (!Director::isDev()) {
return false; return false;
} }
if (isset(self::$enabled)) { if (Environment::getEnv('SS_DEPRECATION_ENABLED')) {
return self::$enabled; return true;
} }
return Environment::getEnv('SS_DEPRECATION_ENABLED') ?: true; return static::$is_enabled;
} }
/** /**
* Toggle on or off deprecation notices. Will be ignored in live. * This method is no longer used
* *
* @param bool $enabled * @param bool $enabled
* @deprecated 4.12.0 Use enable() instead
*/ */
public static function set_enabled($enabled) public static function set_enabled($enabled)
{ {
self::$enabled = $enabled; static::notice('4.12.0', 'Use enable() instead');
// noop
} }
/** /**
@ -180,71 +199,40 @@ class Deprecation
// try block needs to wrap all code in case anything inside the try block // try block needs to wrap all code in case anything inside the try block
// calls something else that calls Deprecation::notice() // calls something else that calls Deprecation::notice()
try { try {
if (!static::get_enabled()) { if (!self::get_is_enabled()) {
return; return;
} }
$checkVersion = self::$version;
// Getting a backtrace is slow, so we only do it if we need it
$backtrace = null;
// If you pass #.#, assume #.#.0 // If you pass #.#, assume #.#.0
if (preg_match('/^[0-9]+\.[0-9]+$/', $atVersion ?? '')) { if (preg_match('/^[0-9]+\.[0-9]+$/', $atVersion ?? '')) {
$atVersion .= '.0'; $atVersion .= '.0';
} }
if (preg_match('/^[0-9]+\.[0-9]+$/', $checkVersion ?? '')) {
$checkVersion .= '.0'; // Getting a backtrace is slow, so we only do it if we need it
$backtrace = null;
// Get the calling scope
if ($scope == Deprecation::SCOPE_METHOD) {
$backtrace = debug_backtrace(0);
$caller = self::get_called_method_from_trace($backtrace);
} elseif ($scope == Deprecation::SCOPE_CLASS) {
$backtrace = debug_backtrace(0);
$caller = isset($backtrace[1]['class']) ? $backtrace[1]['class'] : '(unknown)';
} else {
$caller = false;
} }
if (self::$module_version_overrides) { // Then raise the notice
$module = self::get_calling_module_from_trace($backtrace = debug_backtrace(0)); if (substr($string, -1) != '.') {
if ($module) { $string .= ".";
if (($name = $module->getComposerName())
&& isset(self::$module_version_overrides[$name])
) {
$checkVersion = self::$module_version_overrides[$name];
} elseif (($name = $module->getShortName())
&& isset(self::$module_version_overrides[$name])
) {
$checkVersion = self::$module_version_overrides[$name];
}
}
} }
// Check the version against the notice version $string .= " Called from " . self::get_called_method_from_trace($backtrace, 2) . '.';
if ($checkVersion && version_compare($checkVersion ?? '', $atVersion ?? '', '>=')) {
// Get the calling scope
if ($scope == Deprecation::SCOPE_METHOD) {
if (!$backtrace) {
$backtrace = debug_backtrace(0);
}
$caller = self::get_called_method_from_trace($backtrace);
} elseif ($scope == Deprecation::SCOPE_CLASS) {
if (!$backtrace) {
$backtrace = debug_backtrace(0);
}
$caller = isset($backtrace[1]['class']) ? $backtrace[1]['class'] : '(unknown)';
} else {
$caller = false;
}
// Get the level to raise the notice as if ($caller) {
$level = self::$notice_level; user_error($caller . ' is deprecated.' . ($string ? ' ' . $string : ''), self::$notice_level);
if (!$level) { } else {
$level = E_USER_DEPRECATED; user_error($string, self::$notice_level);
}
// Then raise the notice
if (substr($string ?? '', -1) != '.') {
$string .= ".";
}
$string .= " Called from " . self::get_called_method_from_trace($backtrace, 2) . '.';
if ($caller) {
user_error($caller . ' is deprecated.' . ($string ? ' ' . $string : ''), $level ?? 0);
} else {
user_error($string ?? '', $level ?? 0);
}
} }
} finally { } finally {
static::$inside_notice = false; static::$inside_notice = false;
@ -252,30 +240,26 @@ class Deprecation
} }
/** /**
* Method for when testing. Dump all the current version settings to a variable for later passing to restore * This method is no longer used
* *
* @return array Opaque array that should only be used to pass to {@see Deprecation::restore_settings()} * @return array Opaque array that should only be used to pass to {@see Deprecation::restore_settings()}
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
*/ */
public static function dump_settings() public static function dump_settings()
{ {
return [ static::notice('4.12.0', 'Will be removed without equivalent functionality to replace it');
'level' => self::$notice_level, // noop
'version' => self::$version,
'moduleVersions' => self::$module_version_overrides,
'enabled' => self::$enabled,
];
} }
/** /**
* Method for when testing. Restore all the current version settings from a variable * This method is no longer used
* *
* @param $settings array An array as returned by {@see Deprecation::dump_settings()} * @param $settings array An array as returned by {@see Deprecation::dump_settings()}
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
*/ */
public static function restore_settings($settings) public static function restore_settings($settings)
{ {
self::$notice_level = $settings['level']; static::notice('4.12.0', 'Will be removed without equivalent functionality to replace it');
self::$version = $settings['version']; // noop
self::$module_version_overrides = $settings['moduleVersions'];
self::$enabled = $settings['enabled'];
} }
} }

View File

@ -8,103 +8,34 @@ use SilverStripe\Dev\Tests\DeprecationTest\TestDeprecation;
class DeprecationTest extends SapphireTest class DeprecationTest extends SapphireTest
{ {
static $originalVersionInfo;
protected function setUp(): void
{
parent::setUp();
self::$originalVersionInfo = Deprecation::dump_settings();
Deprecation::$notice_level = E_USER_NOTICE;
Deprecation::set_enabled(true);
}
protected function tearDown(): void protected function tearDown(): void
{ {
Deprecation::restore_settings(self::$originalVersionInfo); Deprecation::$notice_level = E_USER_DEPRECATED;
Deprecation::disable();
parent::tearDown(); parent::tearDown();
} }
public function testLesserVersionTriggersNoNotice() public function testNotice()
{
Deprecation::notification_version('1.0.0');
$this->assertNull(Deprecation::notice('2.0', 'Deprecation test failed'));
}
public function testEqualVersionTriggersNotice()
{ {
$message = implode(' ', [
'SilverStripe\Dev\Tests\DeprecationTest->testNotice is deprecated.',
'My message.',
'Called from PHPUnit\Framework\TestCase->runTest.'
]);
$this->expectError(); $this->expectError();
Deprecation::notification_version('2.0.0'); $this->expectErrorMessage($message);
Deprecation::notice('2.0.0', 'Deprecation test passed'); Deprecation::$notice_level = E_USER_NOTICE;
Deprecation::enable();
Deprecation::notice('1.2.3', 'My message');
} }
public function testBetaVersionDoesntTriggerNoticeWhenDeprecationDoesntSpecifyReleasenum() /**
* @doesNotPerformAssertions
*/
public function testDisabled()
{ {
Deprecation::notification_version('2.0.0-beta1'); Deprecation::$notice_level = E_USER_NOTICE;
$this->assertNull(Deprecation::notice('2.0', 'Deprecation test failed')); // test that no error error is raised because by default Deprecation is disabled
$this->assertNull(Deprecation::notice('2.0.0', 'Deprecation test failed')); Deprecation::notice('4.5.6', 'My message');
}
public function testGreaterVersionTriggersNotice()
{
$this->expectError();
Deprecation::notification_version('3.0.0');
Deprecation::notice('2.0', 'Deprecation test passed');
}
public function testNonMatchingModuleNotifcationVersionDoesntAffectNotice()
{
Deprecation::notification_version('1.0.0');
Deprecation::notification_version('3.0.0', 'my-unrelated-module');
$this->callThatOriginatesFromFramework();
}
public function testMatchingModuleNotifcationVersionAffectsNotice()
{
$this->expectError();
Deprecation::notification_version('1.0.0');
Deprecation::notification_version('3.0.0', 'silverstripe/framework');
$this->callThatOriginatesFromFramework();
}
public function testMethodNameCalculation()
{
$this->assertEquals(
TestDeprecation::get_method(),
static::class . '->testMethodNameCalculation'
);
}
public function testScopeMethod()
{
$this->expectError();
$this->expectErrorMessage('DeprecationTest->testScopeMethod is deprecated. Method scope');
Deprecation::notification_version('2.0.0');
Deprecation::notice('2.0.0', 'Method scope', Deprecation::SCOPE_METHOD);
}
public function testScopeClass()
{
$this->expectError();
$this->expectErrorMessage('DeprecationTest is deprecated. Class scope');
Deprecation::notification_version('2.0.0');
Deprecation::notice('2.0.0', 'Class scope', Deprecation::SCOPE_CLASS);
}
public function testScopeGlobal()
{
$this->expectError();
$this->expectErrorMessage('Global scope');
Deprecation::notification_version('2.0.0');
Deprecation::notice('2.0.0', 'Global scope', Deprecation::SCOPE_GLOBAL);
}
protected function callThatOriginatesFromFramework()
{
$module = TestDeprecation::get_module();
$this->assertNotNull($module);
$this->assertEquals('silverstripe/framework', $module->getName());
$this->assertNull(Deprecation::notice('2.0', 'Deprecation test passed'));
} }
} }

View File

@ -1,18 +0,0 @@
<?php
namespace SilverStripe\Dev\Tests\DeprecationTest;
use SilverStripe\Dev\Deprecation;
class TestDeprecation extends Deprecation
{
public static function get_module()
{
return self::get_calling_module_from_trace(debug_backtrace(0));
}
public static function get_method()
{
return self::get_called_method_from_trace(debug_backtrace(0));
}
}