2011-10-27 23:45:12 +02:00
|
|
|
<?php
|
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Dev;
|
|
|
|
|
2022-10-20 23:08:31 +02:00
|
|
|
use BadMethodCallException;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Control\Director;
|
2017-10-26 23:49:38 +02:00
|
|
|
use SilverStripe\Core\Environment;
|
2022-11-15 06:20:54 +01:00
|
|
|
use SilverStripe\Core\Injector\InjectionCreator;
|
2022-10-20 23:08:31 +02:00
|
|
|
use SilverStripe\Core\Injector\InjectorLoader;
|
2017-03-14 03:20:51 +01:00
|
|
|
use SilverStripe\Core\Manifest\Module;
|
2016-08-19 00:51:35 +02:00
|
|
|
|
2011-10-27 23:45:12 +02:00
|
|
|
/**
|
|
|
|
* Handles raising an notice when accessing a deprecated method
|
|
|
|
*
|
|
|
|
* A pattern used in SilverStripe when deprecating a method is to add something like
|
|
|
|
* user_error('This method is deprecated', E_USER_NOTICE);
|
|
|
|
* to the method
|
|
|
|
*
|
|
|
|
* However sometimes we want to mark that a method will be deprecated in some future version and shouldn't be used in
|
|
|
|
* new code, but not forbid in the current version - for instance when that method is still heavily used in framework
|
|
|
|
* or cms.
|
|
|
|
*
|
|
|
|
* This class abstracts the above pattern and adds a way to do that.
|
|
|
|
*
|
2022-11-15 06:20:54 +01:00
|
|
|
* Each call to notice passes a version that the notice will be valid from.
|
2011-10-27 23:45:12 +02:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class Deprecation
|
|
|
|
{
|
|
|
|
const SCOPE_METHOD = 1;
|
|
|
|
const SCOPE_CLASS = 2;
|
|
|
|
const SCOPE_GLOBAL = 4;
|
2022-10-31 07:00:59 +01:00
|
|
|
const SCOPE_CONFIG = 8;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string
|
2022-10-20 02:14:58 +02:00
|
|
|
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
protected static $version;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Override whether deprecation is enabled. If null, then fallback to
|
|
|
|
* SS_DEPRECATION_ENABLED, and then true if not defined.
|
|
|
|
*
|
|
|
|
* Deprecation is only available on dev.
|
|
|
|
*
|
|
|
|
* Must be configured outside of the config API, as deprecation API
|
|
|
|
* must be available before this to avoid infinite loops.
|
|
|
|
*
|
|
|
|
* @var boolean|null
|
2022-11-15 06:20:54 +01:00
|
|
|
* @deprecated 4.12.0 Use $currentlyEnabled instead
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
protected static $enabled = null;
|
|
|
|
|
2022-11-15 06:20:54 +01:00
|
|
|
/**
|
|
|
|
* @var array
|
|
|
|
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
|
|
|
|
*/
|
|
|
|
protected static $module_version_overrides = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array
|
|
|
|
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
|
|
|
|
*/
|
|
|
|
public static $notice_level = E_USER_DEPRECATED;
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
2022-10-20 02:14:58 +02:00
|
|
|
* Must be configured outside of the config API, as deprecation API
|
|
|
|
* must be available before this to avoid infinite loops.
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
2022-10-20 02:14:58 +02:00
|
|
|
* This will be overriden by the SS_DEPRECATION_ENABLED environment if present
|
2022-10-31 07:00:59 +01:00
|
|
|
*
|
|
|
|
* @internal - Marked as internal so this and other private static's are not treated as config
|
2022-10-20 02:14:58 +02:00
|
|
|
*/
|
2022-11-15 06:20:54 +01:00
|
|
|
private static bool $currentlyEnabled = false;
|
2022-10-20 02:14:58 +02:00
|
|
|
|
|
|
|
/**
|
2022-11-15 06:20:54 +01:00
|
|
|
* @internal
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2022-11-15 06:20:54 +01:00
|
|
|
private static bool $insideNotice = false;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2022-10-31 07:00:59 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-11-15 06:20:54 +01:00
|
|
|
private static bool $insideWithNoReplacement = false;
|
2022-10-13 02:37:29 +02:00
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
2022-11-15 06:20:54 +01:00
|
|
|
* Buffer of user_errors to be raised
|
|
|
|
*
|
|
|
|
* @internal
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
2022-11-15 06:20:54 +01:00
|
|
|
private static array $userErrorMessageBuffer = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
private static bool $haveSetShutdownFunction = false;
|
2022-10-20 02:14:58 +02:00
|
|
|
|
2022-10-31 07:00:59 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-11-15 06:20:54 +01:00
|
|
|
private static bool $showNoReplacementNotices = false;
|
2022-10-31 07:00:59 +01:00
|
|
|
|
2022-11-15 06:20:54 +01:00
|
|
|
public static function enable(bool $showNoReplacementNotices = false): void
|
2022-10-20 02:14:58 +02:00
|
|
|
{
|
2022-11-15 06:20:54 +01:00
|
|
|
static::$currentlyEnabled = true;
|
|
|
|
static::$showNoReplacementNotices = $showNoReplacementNotices;
|
2022-10-20 02:14:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function disable(): void
|
|
|
|
{
|
2022-11-15 06:20:54 +01:00
|
|
|
static::$currentlyEnabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to wrap deprecated methods and deprecated config get()/set() that will be removed
|
|
|
|
* in the next major version with no replacement. This is done to surpress deprecation notices
|
|
|
|
* by for calls from the vendor dir to deprecated code that projects have no ability to change
|
|
|
|
*
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public static function withNoReplacement(callable $func)
|
|
|
|
{
|
|
|
|
if (self::$insideWithNoReplacement) {
|
|
|
|
return $func();
|
|
|
|
}
|
|
|
|
self::$insideWithNoReplacement = true;
|
|
|
|
try {
|
|
|
|
return $func();
|
|
|
|
} finally {
|
|
|
|
self::$insideWithNoReplacement = false;
|
|
|
|
}
|
2022-10-20 02:14:58 +02:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
/**
|
2022-10-20 02:14:58 +02:00
|
|
|
* This method is no longer used
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param $ver string -
|
|
|
|
* A php standard version string, see http://php.net/manual/en/function.version-compare.php for details.
|
|
|
|
* @param null $forModule string -
|
|
|
|
* 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
|
|
|
|
* @return void
|
2022-10-20 02:14:58 +02:00
|
|
|
* @deprecated 4.12.0 Use enable() instead
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
public static function notification_version($ver, $forModule = null)
|
|
|
|
{
|
2022-10-20 02:14:58 +02:00
|
|
|
static::notice('4.12.0', 'Use enable() instead');
|
|
|
|
// noop
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-10-20 02:14:58 +02:00
|
|
|
* This method is no longer used
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
|
|
|
* @param array $backtrace A backtrace as returned from debug_backtrace
|
2017-03-14 03:20:51 +01:00
|
|
|
* @return Module The module being called
|
2022-10-20 02:14:58 +02:00
|
|
|
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
protected static function get_calling_module_from_trace($backtrace)
|
|
|
|
{
|
2022-10-20 02:14:58 +02:00
|
|
|
static::notice('4.12.0', 'Will be removed without equivalent functionality to replace it');
|
|
|
|
// noop
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a backtrace, get the method name from the immediate parent caller (the caller of #notice)
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param $backtrace array - a backtrace as returned from debug_backtrace
|
|
|
|
* @param $level - 1 (default) will return immediate caller, 2 will return caller's caller, etc.
|
|
|
|
* @return string - the name of the method
|
|
|
|
*/
|
|
|
|
protected static function get_called_method_from_trace($backtrace, $level = 1)
|
|
|
|
{
|
|
|
|
$level = (int)$level;
|
|
|
|
if (!$level) {
|
|
|
|
$level = 1;
|
|
|
|
}
|
2022-11-15 06:20:54 +01:00
|
|
|
$newLevel = $level;
|
|
|
|
// handle call_user_func
|
|
|
|
if ($level === 4 && strpos($backtrace[2]['function'] ?? '', 'call_user_func') !== false) {
|
|
|
|
$newLevel = 5;
|
|
|
|
} elseif (strpos($backtrace[$level]['function'] ?? '', 'call_user_func') !== false) {
|
|
|
|
$newLevel = $level + 1;
|
|
|
|
}
|
|
|
|
// handle InjectionCreator
|
|
|
|
if ($level == 4 && ($backtrace[$newLevel]['class'] ?? '') === InjectionCreator::class) {
|
|
|
|
$newLevel = $newLevel + 4;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
2022-11-15 06:20:54 +01:00
|
|
|
$called = $backtrace[$newLevel] ?? [];
|
|
|
|
return ($called['class'] ?? '') . ($called['type'] ?? '') . ($called['function'] ?? '');
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-10-20 02:14:58 +02:00
|
|
|
* This method is no longer used
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
|
|
|
* @return bool
|
2022-10-20 02:14:58 +02:00
|
|
|
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
public static function get_enabled()
|
|
|
|
{
|
2022-10-20 02:14:58 +02:00
|
|
|
static::notice('4.12.0', 'Will be removed without equivalent functionality to replace it');
|
|
|
|
// noop
|
|
|
|
}
|
|
|
|
|
2022-11-15 06:20:54 +01:00
|
|
|
public static function isEnabled(): bool
|
2022-10-20 02:14:58 +02:00
|
|
|
{
|
2016-11-29 00:31:16 +01:00
|
|
|
if (!Director::isDev()) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-11-15 06:20:54 +01:00
|
|
|
return static::$currentlyEnabled || Environment::getEnv('SS_DEPRECATION_ENABLED');
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-10-20 02:14:58 +02:00
|
|
|
* This method is no longer used
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
|
|
|
* @param bool $enabled
|
2022-10-20 02:14:58 +02:00
|
|
|
* @deprecated 4.12.0 Use enable() instead
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
public static function set_enabled($enabled)
|
|
|
|
{
|
2022-10-20 02:14:58 +02:00
|
|
|
static::notice('4.12.0', 'Use enable() instead');
|
|
|
|
// noop
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2022-11-15 06:20:54 +01:00
|
|
|
public static function outputNotices(): void
|
|
|
|
{
|
|
|
|
if (!self::isEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// using a while loop with array_shift() to ensure that self::$userErrorMessageBuffer will have
|
|
|
|
// have values removed from it before calling user_error()
|
|
|
|
while (count(self::$userErrorMessageBuffer)) {
|
|
|
|
$arr = array_shift(self::$userErrorMessageBuffer);
|
|
|
|
$message = $arr['message'];
|
|
|
|
$calledInsideWithNoReplacement = $arr['calledInsideWithNoReplacement'];
|
|
|
|
if ($calledInsideWithNoReplacement && !self::$showNoReplacementNotices) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
user_error($message, E_USER_DEPRECATED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-29 00:31:16 +01:00
|
|
|
/**
|
|
|
|
* Raise a notice indicating the method is deprecated if the version passed as the second argument is greater
|
|
|
|
* than or equal to the check version set via ::notification_version
|
|
|
|
*
|
|
|
|
* @param string $atVersion The version at which this notice should start being raised
|
|
|
|
* @param string $string The notice to raise
|
|
|
|
* @param int $scope Notice relates to the method or class context its called in.
|
|
|
|
*/
|
|
|
|
public static function notice($atVersion, $string = '', $scope = Deprecation::SCOPE_METHOD)
|
|
|
|
{
|
2022-11-15 06:20:54 +01:00
|
|
|
if (static::$insideNotice) {
|
2016-11-29 00:31:16 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-11-15 06:20:54 +01:00
|
|
|
static::$insideNotice = true;
|
2022-10-13 02:37:29 +02:00
|
|
|
// try block needs to wrap all code in case anything inside the try block
|
|
|
|
// calls something else that calls Deprecation::notice()
|
|
|
|
try {
|
2022-10-31 07:00:59 +01:00
|
|
|
if ($scope === self::SCOPE_CONFIG) {
|
2022-11-15 06:20:54 +01:00
|
|
|
// Deprecated config set via yaml will only be shown in the browser when using ?flush=1
|
|
|
|
// It will not show in CLI when running dev/build flush=1
|
|
|
|
self::$userErrorMessageBuffer[] = [
|
|
|
|
'message' => $string,
|
|
|
|
'calledInsideWithNoReplacement' => self::$insideWithNoReplacement
|
|
|
|
];
|
2022-10-20 02:14:58 +02:00
|
|
|
} else {
|
2022-11-15 06:20:54 +01:00
|
|
|
if (!self::isEnabled()) {
|
|
|
|
// Do not add to self::$userErrorMessageBuffer, as the backtrace is too expensive
|
2022-10-31 07:00:59 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2022-11-15 06:20:54 +01:00
|
|
|
$caller = self::get_called_method_from_trace($backtrace, 1);
|
2022-10-31 07:00:59 +01:00
|
|
|
} elseif ($scope == Deprecation::SCOPE_CLASS) {
|
|
|
|
$backtrace = debug_backtrace(0);
|
|
|
|
$caller = isset($backtrace[1]['class']) ? $backtrace[1]['class'] : '(unknown)';
|
|
|
|
} else {
|
|
|
|
$caller = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (substr($string, -1) != '.') {
|
|
|
|
$string .= ".";
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2022-11-15 06:20:54 +01:00
|
|
|
$level = self::$insideWithNoReplacement ? 4 : 2;
|
|
|
|
$string .= " Called from " . self::get_called_method_from_trace($backtrace, $level) . '.';
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2022-10-31 07:00:59 +01:00
|
|
|
if ($caller) {
|
2022-11-15 06:20:54 +01:00
|
|
|
$string = $caller . ' is deprecated.' . ($string ? ' ' . $string : '');
|
2022-10-31 07:00:59 +01:00
|
|
|
}
|
2022-11-15 06:20:54 +01:00
|
|
|
self::$userErrorMessageBuffer[] = [
|
|
|
|
'message' => $string,
|
|
|
|
'calledInsideWithNoReplacement' => self::$insideWithNoReplacement
|
|
|
|
];
|
|
|
|
}
|
|
|
|
if (!self::$haveSetShutdownFunction && self::isEnabled()) {
|
|
|
|
// Use a shutdown function rather than immediately calling user_error() so that notices
|
|
|
|
// do not interfere with setting session varibles i.e. headers already sent error
|
|
|
|
// it also means the deprecation notices appear below all phpunit output in CI
|
|
|
|
// which is far nicer than having it spliced between phpunit output
|
|
|
|
register_shutdown_function(function () {
|
|
|
|
self::outputNotices();
|
|
|
|
});
|
|
|
|
self::$haveSetShutdownFunction = true;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
2022-10-20 23:08:31 +02:00
|
|
|
} catch (BadMethodCallException $e) {
|
|
|
|
if ($e->getMessage() === InjectorLoader::NO_MANIFESTS_AVAILABLE) {
|
|
|
|
// noop
|
|
|
|
// this can happen when calling Deprecation::notice() before manifests are available, i.e.
|
|
|
|
// some of the code involved in creating the manifests calls Deprecation::notice()
|
|
|
|
} else {
|
|
|
|
throw $e;
|
|
|
|
}
|
2022-10-13 02:37:29 +02:00
|
|
|
} finally {
|
2022-11-15 06:20:54 +01:00
|
|
|
static::$insideNotice = false;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-10-20 02:14:58 +02:00
|
|
|
* This method is no longer used
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
|
|
|
* @return array Opaque array that should only be used to pass to {@see Deprecation::restore_settings()}
|
2022-10-20 02:14:58 +02:00
|
|
|
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
public static function dump_settings()
|
|
|
|
{
|
2022-10-20 02:14:58 +02:00
|
|
|
static::notice('4.12.0', 'Will be removed without equivalent functionality to replace it');
|
|
|
|
// noop
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-10-20 02:14:58 +02:00
|
|
|
* This method is no longer used
|
2016-11-29 00:31:16 +01:00
|
|
|
*
|
|
|
|
* @param $settings array An array as returned by {@see Deprecation::dump_settings()}
|
2022-10-20 02:14:58 +02:00
|
|
|
* @deprecated 4.12.0 Will be removed without equivalent functionality to replace it
|
2016-11-29 00:31:16 +01:00
|
|
|
*/
|
|
|
|
public static function restore_settings($settings)
|
|
|
|
{
|
2022-10-20 02:14:58 +02:00
|
|
|
static::notice('4.12.0', 'Will be removed without equivalent functionality to replace it');
|
|
|
|
// noop
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
2012-03-24 04:04:52 +01:00
|
|
|
}
|