. namespace core; /** * Deprecation utility. * * @package core * @copyright 2024 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class deprecation { /** * Get the attribute from a reference. * * The reference can be: * - a string, in which case it will be checked for a function, class, method, property, constant, or enum. * - an array * - an instantiated object, in which case the object will be checked for a class, method, property, or constant. * * @param array|string|object $reference A reference to a potentially deprecated thing. * @return null|deprecated */ public static function from(array|string|object $reference): ?deprecated { if (is_string($reference)) { if (str_contains($reference, '::')) { // The reference is a string but it looks to be in the format `object::item`. return self::from(explode('::', $reference)); } if (class_exists($reference)) { // The reference looks to be a class name. return self::from([$reference]); } if (function_exists($reference)) { // The reference looks to be a global function. $ref = new \ReflectionFunction($reference); if ($attributes = $ref->getAttributes(deprecated::class)) { return $attributes[0]->newInstance(); } } return null; } if (is_object($reference)) { // The reference is an object. Normalise and check again. return self::from([$reference]); } if (is_array($reference) && count($reference)) { if (is_object($reference[0])) { $rc = new \ReflectionObject($reference[0]); if ($rc->isEnum() && $reference[0]->name) { // Enums can be passed via ::from([enum::NAME]). // In this case they will have a 'name', which must exist. return self::from_reflected_object($rc, $reference[0]->name); } return self::from_reflected_object($rc, $reference[1] ?? null); } if (is_string($reference[0]) && class_exists($reference[0])) { $rc = new \ReflectionClass($reference[0]); return self::from_reflected_object($rc, $reference[1] ?? null); } // The reference is an array, but it's not an object or a class that currently exists. return null; } } /** * Check if a reference is deprecated. * * @param array|string|object $reference * @return bool */ public static function is_deprecated(array|string|object $reference): bool { return self::from($reference) !== null; } /** * Emit a deprecation notice if the reference is deprecated. * * @param array|string|object $reference */ public static function emit_deprecation_if_present(array|string|object $reference): void { if ($attribute = self::from($reference)) { self::emit_deprecation_notice($attribute); } } /** * Fetch a deprecation attribute from a reflected object. * * @param \ReflectionClass $rc The reflected object * @param null|string $name The name of the thing to check for deprecation * @return null|deprecated */ protected static function from_reflected_object( \ReflectionClass $rc, ?string $name, ): ?deprecated { if ($name === null) { // No name specified. This may be a deprecated class. if ($attributes = $rc->getAttributes(deprecated::class)) { return $attributes[0]->newInstance(); } return null; } if ($rc->hasConstant($name)) { // This class has a constant with the specified name. // Note: This also applies to enums. $ref = $rc->getReflectionConstant($name); if ($attributes = $ref->getAttributes(deprecated::class)) { return $attributes[0]->newInstance(); } } if ($rc->hasMethod($name)) { // This class has a method with the specified name. $ref = $rc->getMethod($name); if ($attributes = $ref->getAttributes(deprecated::class)) { return $attributes[0]->newInstance(); } } if ($rc->hasProperty($name)) { // This class has a property with the specified name. $ref = $rc->getProperty($name); if ($attributes = $ref->getAttributes(deprecated::class)) { return $attributes[0]->newInstance(); } } return null; } /** * Get a string describing the deprecation. * * @param deprecated $attribute * @return string */ public static function get_deprecation_string(deprecated $attribute): string { $output = "Deprecation: {$attribute->descriptor} has been deprecated"; if ($attribute->since) { $output .= " since {$attribute->since}"; } $output .= "."; if ($attribute->reason) { $output .= " {$attribute->reason}."; } if ($attribute->replacement) { $output .= " Use {$attribute->replacement} instead."; } if ($attribute->mdl) { $output .= " See {$attribute->mdl} for more information."; } return $output; } /** * Emit the relevant deprecation notice. * * @param deprecated $attribute */ public static function emit_deprecation_notice(deprecated $attribute): void { if (!$attribute->emit) { return; } $message = self::get_deprecation_string($attribute); if ($attribute->final) { throw new \coding_exception($message); } debugging($message, DEBUG_DEVELOPER); } }