xref: /dokuwiki/inc/Debug/DebugHelper.php (revision 177d6836e2f75d0e404be9c566e61725852a1e07)
1<?php
2
3
4namespace dokuwiki\Debug;
5
6use dokuwiki\Extension\Event;
7use dokuwiki\Extension\EventHandler;
8use dokuwiki\Logger;
9
10class DebugHelper
11{
12    protected const INFO_DEPRECATION_LOG_EVENT = 'INFO_DEPRECATION_LOG';
13
14    /**
15     * Check if deprecation messages shall be handled
16     *
17     * This is either because its logging is not disabled or a deprecation handler was registered
18     *
19     * @return bool
20     */
21    public static function isEnabled()
22    {
23        /** @var EventHandler $EVENT_HANDLER */
24        global $EVENT_HANDLER;
25        if (
26            !Logger::getInstance(Logger::LOG_DEPRECATED)->isLogging() &&
27            (!$EVENT_HANDLER instanceof EventHandler || !$EVENT_HANDLER->hasHandlerForEvent('INFO_DEPRECATION_LOG'))
28        ) {
29            // avoid any work if no one cares
30            return false;
31        }
32        return true;
33    }
34
35    /**
36     * Log accesses to deprecated fucntions to the debug log
37     *
38     * @param string $alternative (optional) The function or method that should be used instead
39     * @param int $callerOffset (optional) How far the deprecated method is removed from this one
40     * @param string $thing (optional) The deprecated thing, defaults to the calling method
41     * @triggers \dokuwiki\Debug::INFO_DEPRECATION_LOG_EVENT
42     */
43    public static function dbgDeprecatedFunction($alternative = '', $callerOffset = 1, $thing = '')
44    {
45        if (!self::isEnabled()) return;
46
47        $backtrace = debug_backtrace();
48        for ($i = 0; $i < $callerOffset; ++$i) {
49            if (count($backtrace) > 1) array_shift($backtrace);
50        }
51
52        [$self, $call] = $backtrace;
53
54        self::triggerDeprecationEvent(
55            $backtrace,
56            $alternative,
57            self::formatCall($self),
58            self::formatCall($call),
59            $self['file'] ?? $call['file'] ?? '',
60            $self['line'] ?? $call['line'] ?? 0
61        );
62    }
63
64    /**
65     * Format the given backtrace info into a proper function/method call string
66     * @param array $call
67     * @return string
68     */
69    protected static function formatCall($call)
70    {
71        $thing = '';
72        if (!empty($call['class'])) {
73            $thing .= $call['class'] . '::';
74        }
75        $thing .= $call['function'] . '()';
76        return trim($thing, ':');
77    }
78
79    /**
80     * This marks logs a deprecation warning for a property that should no longer be used
81     *
82     * This is usually called withing a magic getter or setter.
83     * For logging deprecated functions or methods see dbgDeprecatedFunction()
84     *
85     * @param string $class The class with the deprecated property
86     * @param string $propertyName The name of the deprecated property
87     *
88     * @triggers \dokuwiki\Debug::INFO_DEPRECATION_LOG_EVENT
89     */
90    public static function dbgDeprecatedProperty($class, $propertyName)
91    {
92        if (!self::isEnabled()) return;
93
94        $backtrace = debug_backtrace();
95        array_shift($backtrace);
96        $call = $backtrace[1];
97        $caller = trim($call['class'] . '::' . $call['function'] . '()', ':');
98        $qualifiedName = $class . '::$' . $propertyName;
99        self::triggerDeprecationEvent(
100            $backtrace,
101            '',
102            $qualifiedName,
103            $caller,
104            $backtrace[0]['file'],
105            $backtrace[0]['line']
106        );
107    }
108
109    /**
110     * Trigger a custom deprecation event
111     *
112     * Usually dbgDeprecatedFunction() or dbgDeprecatedProperty() should be used instead.
113     * This method is intended only for those situation where they are not applicable.
114     *
115     * @param string $alternative
116     * @param string $deprecatedThing
117     * @param string $caller
118     * @param string $file
119     * @param int $line
120     * @param int $callerOffset How many lines should be removed from the beginning of the backtrace
121     */
122    public static function dbgCustomDeprecationEvent(
123        $alternative,
124        $deprecatedThing,
125        $caller,
126        $file,
127        $line,
128        $callerOffset = 1
129    ) {
130        if (!self::isEnabled()) return;
131
132        $backtrace = array_slice(debug_backtrace(), $callerOffset);
133
134        self::triggerDeprecationEvent(
135            $backtrace,
136            $alternative,
137            $deprecatedThing,
138            $caller,
139            $file,
140            $line
141        );
142
143    }
144
145    /**
146     * @param array $backtrace
147     * @param string $alternative
148     * @param string $deprecatedThing
149     * @param string $caller
150     * @param string $file
151     * @param int $line
152     */
153    private static function triggerDeprecationEvent(
154        array $backtrace,
155        $alternative,
156        $deprecatedThing,
157        $caller,
158        $file,
159        $line
160    ) {
161        $data = [
162            'trace' => $backtrace,
163            'alternative' => $alternative,
164            'called' => $deprecatedThing,
165            'caller' => $caller,
166            'file' => $file,
167            'line' => $line,
168        ];
169        $event = new Event(self::INFO_DEPRECATION_LOG_EVENT, $data);
170        if ($event->advise_before()) {
171            $msg = $event->data['called'] . ' is deprecated. It was called from ';
172            $msg .= $event->data['caller'] . ' in ' . $event->data['file'] . ':' . $event->data['line'];
173            if ($event->data['alternative']) {
174                $msg .= ' ' . $event->data['alternative'] . ' should be used instead!';
175            }
176            Logger::getInstance(Logger::LOG_DEPRECATED)->log($msg);
177        }
178        $event->advise_after();
179    }
180}
181