| 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 |
}
|