Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace Doctrine\Deprecations;
6
 
7
use Psr\Log\LoggerInterface;
8
 
9
use function array_key_exists;
10
use function array_reduce;
11
use function debug_backtrace;
12
use function sprintf;
13
use function strpos;
14
use function strrpos;
15
use function substr;
16
use function trigger_error;
17
 
18
use const DEBUG_BACKTRACE_IGNORE_ARGS;
19
use const DIRECTORY_SEPARATOR;
20
use const E_USER_DEPRECATED;
21
 
22
/**
23
 * Manages Deprecation logging in different ways.
24
 *
25
 * By default triggered exceptions are not logged.
26
 *
27
 * To enable different deprecation logging mechanisms you can call the
28
 * following methods:
29
 *
30
 *  - Minimal collection of deprecations via getTriggeredDeprecations()
31
 *    \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
32
 *
33
 *  - Uses @trigger_error with E_USER_DEPRECATED
34
 *    \Doctrine\Deprecations\Deprecation::enableWithTriggerError();
35
 *
36
 *  - Sends deprecation messages via a PSR-3 logger
37
 *    \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
38
 *
39
 * Packages that trigger deprecations should use the `trigger()` or
40
 * `triggerIfCalledFromOutside()` methods.
41
 */
42
class Deprecation
43
{
44
    private const TYPE_NONE               = 0;
45
    private const TYPE_TRACK_DEPRECATIONS = 1;
46
    private const TYPE_TRIGGER_ERROR      = 2;
47
    private const TYPE_PSR_LOGGER         = 4;
48
 
49
    /** @var int */
50
    private static $type = self::TYPE_NONE;
51
 
52
    /** @var LoggerInterface|null */
53
    private static $logger;
54
 
55
    /** @var array<string,bool> */
56
    private static $ignoredPackages = [];
57
 
58
    /** @var array<string,int> */
59
    private static $ignoredLinks = [];
60
 
61
    /** @var bool */
62
    private static $deduplication = true;
63
 
64
    /**
65
     * Trigger a deprecation for the given package and identfier.
66
     *
67
     * The link should point to a Github issue or Wiki entry detailing the
68
     * deprecation. It is additionally used to de-duplicate the trigger of the
69
     * same deprecation during a request.
70
     *
71
     * @param mixed $args
72
     */
73
    public static function trigger(string $package, string $link, string $message, ...$args): void
74
    {
75
        if (self::$type === self::TYPE_NONE) {
76
            return;
77
        }
78
 
79
        if (array_key_exists($link, self::$ignoredLinks)) {
80
            self::$ignoredLinks[$link]++;
81
        } else {
82
            self::$ignoredLinks[$link] = 1;
83
        }
84
 
85
        if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
86
            return;
87
        }
88
 
89
        if (isset(self::$ignoredPackages[$package])) {
90
            return;
91
        }
92
 
93
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
94
 
95
        $message = sprintf($message, ...$args);
96
 
97
        self::delegateTriggerToBackend($message, $backtrace, $link, $package);
98
    }
99
 
100
    /**
101
     * Trigger a deprecation for the given package and identifier when called from outside.
102
     *
103
     * "Outside" means we assume that $package is currently installed as a
104
     * dependency and the caller is not a file in that package. When $package
105
     * is installed as a root package then deprecations triggered from the
106
     * tests folder are also considered "outside".
107
     *
108
     * This deprecation method assumes that you are using Composer to install
109
     * the dependency and are using the default /vendor/ folder and not a
110
     * Composer plugin to change the install location. The assumption is also
111
     * that $package is the exact composer packge name.
112
     *
113
     * Compared to {@link trigger()} this method causes some overhead when
114
     * deprecation tracking is enabled even during deduplication, because it
115
     * needs to call {@link debug_backtrace()}
116
     *
117
     * @param mixed $args
118
     */
119
    public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void
120
    {
121
        if (self::$type === self::TYPE_NONE) {
122
            return;
123
        }
124
 
125
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
126
 
127
        // first check that the caller is not from a tests folder, in which case we always let deprecations pass
128
        if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) {
129
            $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR;
130
 
131
            if (strpos($backtrace[0]['file'], $path) === false) {
132
                return;
133
            }
134
 
135
            if (strpos($backtrace[1]['file'], $path) !== false) {
136
                return;
137
            }
138
        }
139
 
140
        if (array_key_exists($link, self::$ignoredLinks)) {
141
            self::$ignoredLinks[$link]++;
142
        } else {
143
            self::$ignoredLinks[$link] = 1;
144
        }
145
 
146
        if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) {
147
            return;
148
        }
149
 
150
        if (isset(self::$ignoredPackages[$package])) {
151
            return;
152
        }
153
 
154
        $message = sprintf($message, ...$args);
155
 
156
        self::delegateTriggerToBackend($message, $backtrace, $link, $package);
157
    }
158
 
159
    /**
160
     * @param array<mixed> $backtrace
161
     */
162
    private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void
163
    {
164
        if ((self::$type & self::TYPE_PSR_LOGGER) > 0) {
165
            $context = [
166
                'file' => $backtrace[0]['file'],
167
                'line' => $backtrace[0]['line'],
168
                'package' => $package,
169
                'link' => $link,
170
            ];
171
 
172
            self::$logger->notice($message, $context);
173
        }
174
 
175
        if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) {
176
            return;
177
        }
178
 
179
        $message .= sprintf(
180
            ' (%s:%d called by %s:%d, %s, package %s)',
181
            self::basename($backtrace[0]['file']),
182
            $backtrace[0]['line'],
183
            self::basename($backtrace[1]['file']),
184
            $backtrace[1]['line'],
185
            $link,
186
            $package
187
        );
188
 
189
        @trigger_error($message, E_USER_DEPRECATED);
190
    }
191
 
192
    /**
193
     * A non-local-aware version of PHPs basename function.
194
     */
195
    private static function basename(string $filename): string
196
    {
197
        $pos = strrpos($filename, DIRECTORY_SEPARATOR);
198
 
199
        if ($pos === false) {
200
            return $filename;
201
        }
202
 
203
        return substr($filename, $pos + 1);
204
    }
205
 
206
    public static function enableTrackingDeprecations(): void
207
    {
208
        self::$type |= self::TYPE_TRACK_DEPRECATIONS;
209
    }
210
 
211
    public static function enableWithTriggerError(): void
212
    {
213
        self::$type |= self::TYPE_TRIGGER_ERROR;
214
    }
215
 
216
    public static function enableWithPsrLogger(LoggerInterface $logger): void
217
    {
218
        self::$type  |= self::TYPE_PSR_LOGGER;
219
        self::$logger = $logger;
220
    }
221
 
222
    public static function withoutDeduplication(): void
223
    {
224
        self::$deduplication = false;
225
    }
226
 
227
    public static function disable(): void
228
    {
229
        self::$type          = self::TYPE_NONE;
230
        self::$logger        = null;
231
        self::$deduplication = true;
232
 
233
        foreach (self::$ignoredLinks as $link => $count) {
234
            self::$ignoredLinks[$link] = 0;
235
        }
236
    }
237
 
238
    public static function ignorePackage(string $packageName): void
239
    {
240
        self::$ignoredPackages[$packageName] = true;
241
    }
242
 
243
    public static function ignoreDeprecations(string ...$links): void
244
    {
245
        foreach ($links as $link) {
246
            self::$ignoredLinks[$link] = 0;
247
        }
248
    }
249
 
250
    public static function getUniqueTriggeredDeprecationsCount(): int
251
    {
252
        return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) {
253
            return $carry + $count;
254
        }, 0);
255
    }
256
 
257
    /**
258
     * Returns each triggered deprecation link identifier and the amount of occurrences.
259
     *
260
     * @return array<string,int>
261
     */
262
    public static function getTriggeredDeprecations(): array
263
    {
264
        return self::$ignoredLinks;
265
    }
266
}