Proyectos de Subversion Moodle

Rev

| 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
    /**
141
     * Fetch a referenced deprecation attribute from a reflected object.
142
     *
143
     * @param \ReflectionClass $rc The reflected object
144
     * @param null|string $name The name of the thing to check for deprecation
145
     * @return null|deprecated_with_reference
146
     */
147
    protected static function from_reflected_object(
148
        \ReflectionClass $rc,
149
        ?string $name,
150
    ): ?deprecated_with_reference {
151
        // Check if the class itself is deprecated first.
152
        $classattribute = self::get_attribute($rc, $rc->name);
153
        if ($classattribute || $name === null) {
154
            return $classattribute;
155
        }
156
 
157
        // Check for any deprecated interfaces.
158
        foreach ($rc->getInterfaces() as $interface) {
159
            if ($attribute = self::get_attribute($interface, $interface->name)) {
160
                return $attribute;
161
            }
162
        }
163
 
164
        // And any deprecated traits.
165
        foreach ($rc->getTraits() as $trait) {
166
            if ($attribute = self::get_attribute($trait, $trait->name)) {
167
                return $attribute;
168
            }
169
        }
170
 
171
        if ($rc->hasConstant($name)) {
172
            // This class has a constant with the specified name.
173
            // Note: This also applies to enums.
174
            return self::get_attribute(
175
                $rc->getReflectionConstant($name),
176
                "{$rc->name}::{$name}",
177
            );
178
        }
179
 
180
        if ($rc->hasMethod($name)) {
181
            // This class has a method with the specified name.
182
            return self::get_attribute(
183
                $rc->getMethod($name),
184
                "{$rc->name}::{$name}",
185
            );
186
        }
187
 
188
        if ($rc->hasProperty($name)) {
189
            // This class has a property with the specified name.
190
            return self::get_attribute(
191
                $rc->getProperty($name),
192
                "{$rc->name}::{$name}",
193
            );
194
        }
195
 
196
        return null;
197
    }
198
 
199
    /**
200
     * Get a string describing the deprecation.
201
     *
202
     * @param deprecated $attribute
203
     * @param string $owner
204
     * @return string
205
     */
206
    public static function get_deprecation_string(
207
        deprecated $attribute,
208
    ): string {
209
        $output = "Deprecation:";
210
 
211
        if ($attribute instanceof deprecated_with_reference) {
212
            $output .= " {$attribute->owner}";
213
        }
214
        $output .= " has been deprecated";
215
 
216
        if ($attribute->since) {
217
            $output .= " since {$attribute->since}";
218
        }
219
 
220
        $output .= ".";
221
 
222
        if ($attribute->reason) {
223
            $output .= " {$attribute->reason}.";
224
        }
225
 
226
        if ($attribute->replacement) {
227
            $output .= " Use {$attribute->replacement} instead.";
228
        }
229
 
230
        if ($attribute->mdl) {
231
            $output .= " See {$attribute->mdl} for more information.";
232
        }
233
 
234
        return $output;
235
    }
236
 
237
    /**
238
     * Emit the relevant deprecation notice.
239
     *
240
     * @param deprecated $attribute
241
     */
242
    protected static function emit_deprecation_notice(
243
        deprecated $attribute,
244
    ): void {
245
        if (!$attribute->emit) {
246
            return;
247
        }
248
 
249
        $message = self::get_deprecation_string($attribute);
250
 
251
        if ($attribute->final) {
252
            throw new \coding_exception($message);
253
        }
254
 
255
        debugging($message, DEBUG_DEVELOPER);
256
    }
257
}