Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core;
18
 
19
use core\attribute\deprecated;
20
use core\attribute\deprecated_with_reference;
21
 
22
/**
23
 * Deprecation utility.
24
 *
25
 * @package    core
26
 * @copyright  2024 Andrew Lyons <andrew@nicols.co.uk>
27
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28
 */
29
class deprecation {
30
    /**
31
     * Get the attribute from a reference.
32
     *
33
     * The reference can be:
34
     * - a string, in which case it will be checked for a function, class, method, property, constant, or enum.
35
     * - an array
36
     * - an instantiated object, in which case the object will be checked for a class, method, property, or constant.
37
     *
38
     * @param array|string|object $reference A reference to a potentially deprecated thing.
39
     * @return null|deprecated
40
     */
41
    public static function from(array|string|object $reference): ?deprecated {
42
        if (is_string($reference)) {
43
            if (str_contains($reference, '::')) {
44
                // The reference is a string but it looks to be in the format `object::item`.
45
                return self::from(explode('::', $reference));
46
            }
47
 
48
            if (class_exists($reference) || interface_exists($reference) || trait_exists($reference)) {
49
                // The reference looks to be a class name.
50
                return self::from([$reference]);
51
            }
52
 
53
            if (function_exists($reference)) {
54
                // The reference looks to be a global function.
55
                return self::get_attribute(new \ReflectionFunction($reference), $reference);
56
            }
57
 
58
            return null;
59
        }
60
 
61
        if (is_object($reference)) {
62
            // The reference is an object. Normalise and check again.
63
            return self::from([$reference]);
64
        }
65
 
66
        if (is_array($reference) && count($reference)) {
67
            if (is_object($reference[0])) {
68
                $rc = new \ReflectionObject($reference[0]);
69
 
70
                if ($rc->isEnum() && $reference[0]->name) {
71
                    // Enums can be passed via ::from([enum::NAME]).
72
                    // In this case they will have a 'name', which must exist.
73
                    return self::from_reflected_object($rc, $reference[0]->name);
74
                }
75
                return self::from_reflected_object($rc, $reference[1] ?? null);
76
            }
77
 
78
            if (is_string($reference[0])) {
79
                if (class_exists($reference[0]) || interface_exists($reference[0]) || trait_exists($reference[0])) {
80
                    $rc = new \ReflectionClass($reference[0]);
81
                    return self::from_reflected_object($rc, $reference[1] ?? null);
82
                }
83
            }
84
 
85
            // The reference is an array, but it's not an object or a class that currently exists.
86
            return null;
87
        }
88
 
89
        // The reference is none of the above.
90
        return null;
91
    }
92
 
93
    /**
94
     * Get a deprecation attribute from a reflector.
95
     *
96
     * @param \Reflector $ref The reflector
97
     * @param string $owner A descriptor of the owner of the thing that is deprecated
98
     * @return null|deprecated_with_reference
99
     */
100
    protected static function get_attribute(
101
        \Reflector $ref,
102
        string $owner,
103
    ): ?deprecated_with_reference {
104
        if ($attributes = $ref->getAttributes(deprecated::class)) {
105
            $attribute = $attributes[0]->newInstance();
106
            return new deprecated_with_reference(
107
                owner: $owner,
108
                replacement: $attribute->replacement,
109
                since: $attribute->since,
110
                reason: $attribute->reason,
111
                mdl: $attribute->mdl,
112
                final: $attribute->final,
113
                emit: $attribute->emit,
114
            );
115
        }
116
        return null;
117
    }
118
 
119
    /**
120
     * Check if a reference is deprecated.
121
     *
122
     * @param array|string|object $reference
123
     * @return bool
124
     */
125
    public static function is_deprecated(array|string|object $reference): bool {
126
        return self::from($reference) !== null;
127
    }
128
 
129
    /**
130
     * Emit a deprecation notice if the reference is deprecated.
131
     *
132
     * @param array|string|object $reference
133
     */
134
    public static function emit_deprecation_if_present(array|string|object $reference): void {
135
        if ($attribute = self::from($reference)) {
136
            self::emit_deprecation_notice($attribute);
137
        }
138
    }
139
 
140
    /**
1441 ariadna 141
     * Emit a deprecation notice for a reference.
142
     *
143
     * This will emit a deprecation notice if the reference is deprecated.
144
     * If the reference is not deprecated, the function will emit debugging information.
145
     *
146
     * @param array|string|object $reference
147
     */
148
    public static function emit_deprecation(array|string|object $reference): void {
149
        if ($attribute = self::from($reference)) {
150
            self::emit_deprecation_notice($attribute);
151
        } else {
152
            // If the reference is not deprecated, we should not emit a notice.
153
            // This is to prevent false positives in tests.
154
            debugging(
155
                "Deprecation notice requested but object is not deprecated.",
156
                DEBUG_DEVELOPER,
157
            );
158
        }
159
    }
160
 
161
    /**
1 efrain 162
     * Fetch a referenced deprecation attribute from a reflected object.
163
     *
164
     * @param \ReflectionClass $rc The reflected object
165
     * @param null|string $name The name of the thing to check for deprecation
166
     * @return null|deprecated_with_reference
167
     */
168
    protected static function from_reflected_object(
169
        \ReflectionClass $rc,
170
        ?string $name,
171
    ): ?deprecated_with_reference {
172
        // Check if the class itself is deprecated first.
173
        $classattribute = self::get_attribute($rc, $rc->name);
174
        if ($classattribute || $name === null) {
175
            return $classattribute;
176
        }
177
 
178
        // Check for any deprecated interfaces.
179
        foreach ($rc->getInterfaces() as $interface) {
180
            if ($attribute = self::get_attribute($interface, $interface->name)) {
181
                return $attribute;
182
            }
183
        }
184
 
185
        // And any deprecated traits.
186
        foreach ($rc->getTraits() as $trait) {
187
            if ($attribute = self::get_attribute($trait, $trait->name)) {
188
                return $attribute;
189
            }
190
        }
191
 
192
        if ($rc->hasConstant($name)) {
193
            // This class has a constant with the specified name.
194
            // Note: This also applies to enums.
195
            return self::get_attribute(
196
                $rc->getReflectionConstant($name),
197
                "{$rc->name}::{$name}",
198
            );
199
        }
200
 
201
        if ($rc->hasMethod($name)) {
202
            // This class has a method with the specified name.
203
            return self::get_attribute(
204
                $rc->getMethod($name),
205
                "{$rc->name}::{$name}",
206
            );
207
        }
208
 
209
        if ($rc->hasProperty($name)) {
210
            // This class has a property with the specified name.
211
            return self::get_attribute(
212
                $rc->getProperty($name),
213
                "{$rc->name}::{$name}",
214
            );
215
        }
216
 
217
        return null;
218
    }
219
 
220
    /**
221
     * Get a string describing the deprecation.
222
     *
223
     * @param deprecated $attribute
224
     * @param string $owner
225
     * @return string
226
     */
227
    public static function get_deprecation_string(
228
        deprecated $attribute,
229
    ): string {
230
        $output = "Deprecation:";
231
 
232
        if ($attribute instanceof deprecated_with_reference) {
233
            $output .= " {$attribute->owner}";
234
        }
235
        $output .= " has been deprecated";
236
 
237
        if ($attribute->since) {
238
            $output .= " since {$attribute->since}";
239
        }
240
 
241
        $output .= ".";
242
 
243
        if ($attribute->reason) {
244
            $output .= " {$attribute->reason}.";
245
        }
246
 
247
        if ($attribute->replacement) {
248
            $output .= " Use {$attribute->replacement} instead.";
249
        }
250
 
251
        if ($attribute->mdl) {
252
            $output .= " See {$attribute->mdl} for more information.";
253
        }
254
 
255
        return $output;
256
    }
257
 
258
    /**
259
     * Emit the relevant deprecation notice.
260
     *
261
     * @param deprecated $attribute
262
     */
263
    protected static function emit_deprecation_notice(
264
        deprecated $attribute,
265
    ): void {
266
        if (!$attribute->emit) {
267
            return;
268
        }
269
 
270
        $message = self::get_deprecation_string($attribute);
271
 
272
        if ($attribute->final) {
273
            throw new \coding_exception($message);
274
        }
275
 
276
        debugging($message, DEBUG_DEVELOPER);
277
    }
278
}