1111
1212namespace Symfony \Bridge \PhpUnit ;
1313
14+ use Symfony \Bridge \PhpUnit \DeprecationErrorHandler \Configuration ;
15+ use Symfony \Bridge \PhpUnit \DeprecationErrorHandler \Deprecation ;
16+
1417/**
1518 * Catch deprecation notices and print a summary report at the end of the test suite.
1619 *
1720 * @author Nicolas Grekas <p@tchwork.com>
1821 */
1922class DeprecationErrorHandler
2023{
21- const MODE_WEAK = 'weak ' ;
24+ /**
25+ * @deprecated since Symfony 4.3, use max[self]=0 instead
26+ */
2227 const MODE_WEAK_VENDORS = 'weak_vendors ' ;
28+
2329 const MODE_DISABLED = 'disabled ' ;
30+ const MODE_WEAK = 'max[total]=999999&verbose=0 ' ;
31+ const MODE_STRICT = 'max[total]=0 ' ;
2432
25- private $ mode = false ;
26- private $ resolvedMode = false ;
33+ private $ mode ;
34+ private $ configuration ;
2735 private $ deprecations = [
2836 'unsilencedCount ' => 0 ,
29- 'remainingCount ' => 0 ,
37+ 'remaining selfCount ' => 0 ,
3038 'legacyCount ' => 0 ,
3139 'otherCount ' => 0 ,
32- 'remaining vendorCount ' => 0 ,
40+ 'remaining directCount ' => 0 ,
41+ 'remaining indirectCount ' => 0 ,
3342 'unsilenced ' => [],
34- 'remaining ' => [],
43+ 'remaining self ' => [],
3544 'legacy ' => [],
3645 'other ' => [],
37- 'remaining vendor ' => [],
46+ 'remaining direct ' => [],
47+ 'remaining indirect ' => [],
3848 ];
3949
4050 private static $ isRegistered = false ;
@@ -43,13 +53,16 @@ class DeprecationErrorHandler
4353 /**
4454 * Registers and configures the deprecation handler.
4555 *
46- * The following reporting modes are supported:
47- * - use "weak" to hide the deprecation report but keep a global count;
48- * - use "weak_vendors" to fail only on deprecations triggered in your own code;
49- * - use "/some-regexp/" to stop the test suite whenever a deprecation
50- * message matches the given regular expression;
51- * - use a number to define the upper bound of allowed deprecations,
52- * making the test suite fail whenever more notices are triggered.
56+ * The mode is a query string with options:
57+ * - "disabled" to disable the deprecation handler
58+ * - "verbose" to enable/disable displaying the deprecation report
59+ * - "max" to configure the number of deprecations to allow before exiting with a non-zero
60+ * status code; it's an array with keys "total", "self", "direct" and "indirect"
61+ *
62+ * The default mode is "max[total]=0&verbose=1".
63+ *
64+ * The mode can alternatively be "/some-regexp/" to stop the test suite whenever
65+ * a deprecation message matches the given regular expression.
5366 *
5467 * @param int|string|false $mode The reporting mode, defaults to not allowing any deprecations
5568 */
@@ -108,76 +121,41 @@ public static function collectDeprecations($outputFile)
108121 */
109122 public function handleError ($ type , $ msg , $ file , $ line , $ context = [])
110123 {
111- if ((E_USER_DEPRECATED !== $ type && E_DEPRECATED !== $ type ) || self :: MODE_DISABLED === $ mode = $ this ->getMode ()) {
124+ if ((E_USER_DEPRECATED !== $ type && E_DEPRECATED !== $ type ) || ! $ this ->getConfiguration ()-> isEnabled ()) {
112125 $ ErrorHandler = self ::$ utilPrefix .'ErrorHandler ' ;
113126
114127 return $ ErrorHandler ::handleError ($ type , $ msg , $ file , $ line , $ context );
115128 }
116129
117- $ trace = debug_backtrace ();
130+ $ deprecation = new Deprecation ( $ msg , debug_backtrace (), $ file );
118131 $ group = 'other ' ;
119- $ isVendor = self ::MODE_WEAK_VENDORS === $ mode && self ::inVendors ($ file );
120132
121- $ i = \count ($ trace );
122- while (1 < $ i && (!isset ($ trace [--$ i ]['class ' ]) || ('ReflectionMethod ' === $ trace [$ i ]['class ' ] || 0 === strpos ($ trace [$ i ]['class ' ], 'PHPUnit_ ' ) || 0 === strpos ($ trace [$ i ]['class ' ], 'PHPUnit \\' )))) {
123- // No-op
124- }
125-
126- if (isset ($ trace [$ i ]['object ' ]) || isset ($ trace [$ i ]['class ' ])) {
127- if (isset ($ trace [$ i ]['class ' ]) && 0 === strpos ($ trace [$ i ]['class ' ], 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor ' )) {
128- $ parsedMsg = unserialize ($ msg );
129- $ msg = $ parsedMsg ['deprecation ' ];
130- $ class = $ parsedMsg ['class ' ];
131- $ method = $ parsedMsg ['method ' ];
132- // If the deprecation has been triggered via
133- // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
134- // then we need to use the serialized information to determine
135- // if the error has been triggered from vendor code.
136- $ isVendor = self ::MODE_WEAK_VENDORS === $ mode && isset ($ parsedMsg ['triggering_file ' ]) && self ::inVendors ($ parsedMsg ['triggering_file ' ]);
137- } else {
138- $ class = isset ($ trace [$ i ]['object ' ]) ? \get_class ($ trace [$ i ]['object ' ]) : $ trace [$ i ]['class ' ];
139- $ method = $ trace [$ i ]['function ' ];
140- }
141-
142- $ Test = self ::$ utilPrefix .'Test ' ;
133+ if ($ deprecation ->originatesFromAnObject ()) {
134+ $ class = $ deprecation ->originatingClass ();
135+ $ method = $ deprecation ->originatingMethod ();
143136
144137 if (0 !== error_reporting ()) {
145138 $ group = 'unsilenced ' ;
146- } elseif (0 === strpos ($ method , 'testLegacy ' )
147- || 0 === strpos ($ method , 'provideLegacy ' )
148- || 0 === strpos ($ method , 'getLegacy ' )
149- || strpos ($ class , '\Legacy ' )
150- || \in_array ('legacy ' , $ Test ::getGroups ($ class , $ method ), true )
151- ) {
139+ } elseif ($ deprecation ->isLegacy (self ::$ utilPrefix )) {
152140 $ group = 'legacy ' ;
153- } elseif ($ isVendor ) {
154- $ group = 'remaining vendor ' ;
141+ } elseif (! $ deprecation -> isSelf () ) {
142+ $ group = $ deprecation -> isIndirect () ? 'remaining indirect ' : ' remaining direct ' ;
155143 } else {
156- $ group = 'remaining ' ;
144+ $ group = 'remaining self ' ;
157145 }
158146
159- if (isset ($ mode [0 ]) && '/ ' === $ mode [0 ] && preg_match ($ mode , $ msg )) {
160- $ e = new \Exception ($ msg );
161- $ r = new \ReflectionProperty ($ e , 'trace ' );
162- $ r ->setAccessible (true );
163- $ r ->setValue ($ e , \array_slice ($ trace , 1 , $ i ));
164-
165- echo "\n" .ucfirst ($ group ).' deprecation triggered by ' .$ class .':: ' .$ method .': ' ;
166- echo "\n" .$ msg ;
167- echo "\nStack trace: " ;
168- echo "\n" .str_replace (' ' .getcwd ().\DIRECTORY_SEPARATOR , ' ' , $ e ->getTraceAsString ());
169- echo "\n" ;
147+ if ($ this ->getConfiguration ()->shouldDisplayStackTrace ($ msg )) {
148+ echo "\n" .ucfirst ($ group ).' ' .$ deprecation ->toString ();
170149
171150 exit (1 );
172151 }
173-
174- if ('legacy ' !== $ group && self ::MODE_WEAK !== $ mode ) {
152+ if ('legacy ' !== $ group ) {
175153 $ ref = &$ this ->deprecations [$ group ][$ msg ]['count ' ];
176154 ++$ ref ;
177155 $ ref = &$ this ->deprecations [$ group ][$ msg ][$ class .':: ' .$ method ];
178156 ++$ ref ;
179157 }
180- } elseif ( self :: MODE_WEAK !== $ mode ) {
158+ } else {
181159 $ ref = &$ this ->deprecations [$ group ][$ msg ]['count ' ];
182160 ++$ ref ;
183161 }
@@ -190,124 +168,88 @@ public function handleError($type, $msg, $file, $line, $context = [])
190168 */
191169 public function shutdown ()
192170 {
193- $ mode = $ this ->getMode ();
171+ $ configuration = $ this ->getConfiguration ();
194172
195- if (isset ( $ mode [ 0 ]) && ' / ' === $ mode [ 0 ] ) {
173+ if ($ configuration -> isInRegexMode () ) {
196174 return ;
197175 }
198176
199177 $ currErrorHandler = set_error_handler ('var_dump ' );
200178 restore_error_handler ();
201179
202180 if ($ currErrorHandler !== [$ this , 'handleError ' ]) {
203- echo "\n" , self ::colorize ('THE ERROR HANDLER HAS CHANGED! ' , true , $ mode ), "\n" ;
204- }
205-
206- $ groups = ['unsilenced ' , 'remaining ' ];
207-
208- if (self ::MODE_WEAK_VENDORS === $ mode ) {
209- $ groups [] = 'remaining vendor ' ;
181+ echo "\n" , self ::colorize ('THE ERROR HANDLER HAS CHANGED! ' , true ), "\n" ;
210182 }
211183
212- array_push ( $ groups , 'legacy ' , 'other ' ) ;
184+ $ groups = [ ' unsilenced ' , 'remaining self ' , ' remaining direct ' , ' remaining indirect ' , ' legacy ' , 'other ' ] ;
213185
214- $ this ->displayDeprecations ($ groups , $ mode );
186+ $ this ->displayDeprecations ($ groups , $ configuration );
215187
216188 // store failing status
217- $ isFailing = self :: MODE_WEAK !== $ mode && $ mode < $ this -> deprecations [ ' unsilencedCount ' ] + $ this ->deprecations [ ' remainingCount ' ] + $ this -> deprecations [ ' otherCount ' ] ;
189+ $ isFailing = ! $ configuration -> tolerates ( $ this ->deprecations ) ;
218190
219191 // reset deprecations array
220192 foreach ($ this ->deprecations as $ group => $ arrayOrInt ) {
221193 $ this ->deprecations [$ group ] = \is_int ($ arrayOrInt ) ? 0 : [];
222194 }
223195
224- register_shutdown_function (function () use ($ isFailing , $ groups , $ mode ) {
196+ register_shutdown_function (function () use ($ isFailing , $ groups , $ configuration ) {
225197 foreach ($ this ->deprecations as $ group => $ arrayOrInt ) {
226198 if (0 < (\is_int ($ arrayOrInt ) ? $ arrayOrInt : \count ($ arrayOrInt ))) {
227199 echo "Shutdown-time deprecations: \n" ;
228200 break ;
229201 }
230202 }
231203
232- $ this ->displayDeprecations ($ groups , $ mode );
204+ $ this ->displayDeprecations ($ groups , $ configuration );
233205
234- if ($ isFailing || self :: MODE_WEAK !== $ mode && $ mode < $ this -> deprecations [ ' unsilencedCount ' ] + $ this ->deprecations [ ' remainingCount ' ] + $ this -> deprecations [ ' otherCount ' ] ) {
206+ if ($ isFailing || ! $ configuration -> tolerates ( $ this ->deprecations ) ) {
235207 exit (1 );
236208 }
237209 });
238210 }
239211
240- private function getMode ()
212+ private function getConfiguration ()
241213 {
242- if (false !== $ this ->resolvedMode ) {
243- return $ this ->resolvedMode ;
214+ if (null !== $ this ->configuration ) {
215+ return $ this ->configuration ;
244216 }
245-
246217 if (false === $ mode = $ this ->mode ) {
247218 $ mode = getenv ('SYMFONY_DEPRECATIONS_HELPER ' );
248219 }
249-
250- if (self ::MODE_DISABLED !== $ mode
251- && self ::MODE_WEAK !== $ mode
252- && self ::MODE_WEAK_VENDORS !== $ mode
253- && (!isset ($ mode [0 ]) || '/ ' !== $ mode [0 ])
254- ) {
255- $ mode = preg_match ('/^[1-9][0-9]*$/ ' , $ mode ) ? (int ) $ mode : 0 ;
220+ if ('strict ' === $ mode ) {
221+ return $ this ->configuration = Configuration::inStrictMode ();
256222 }
257-
258- return $ this ->resolvedMode = $ mode ;
259- }
260-
261- /**
262- * @param string $path
263- *
264- * @return bool
265- */
266- private static function inVendors ($ path )
267- {
268- /** @var string[] absolute paths to vendor directories */
269- static $ vendors ;
270-
271- if (null === $ vendors ) {
272- foreach (get_declared_classes () as $ class ) {
273- if ('C ' !== $ class [0 ] || 0 !== strpos ($ class , 'ComposerAutoloaderInit ' )) {
274- continue ;
275- }
276-
277- $ r = new \ReflectionClass ($ class );
278- $ v = \dirname (\dirname ($ r ->getFileName ()));
279-
280- if (file_exists ($ v .'/composer/installed.json ' )) {
281- $ vendors [] = $ v ;
282- }
283- }
223+ if (self ::MODE_DISABLED === $ mode ) {
224+ return $ this ->configuration = Configuration::inDisabledMode ();
284225 }
285-
286- $ realPath = realpath ($ path );
287-
288- if (false === $ realPath && '- ' !== $ path && 'Standard input code ' !== $ path ) {
289- return true ;
226+ if ('weak ' === $ mode ) {
227+ return $ this ->configuration = Configuration::inWeakMode ();
228+ }
229+ if (self ::MODE_WEAK_VENDORS === $ mode ) {
230+ echo sprintf ('Setting SYMFONY_DEPRECATIONS_HELPER to "%s" is deprecated in favor of "max[self]=0" ' , $ mode ).PHP_EOL ;
231+ exit (1 );
232+ }
233+ if (isset ($ mode [0 ]) && '/ ' === $ mode [0 ]) {
234+ return $ this ->configuration = Configuration::fromRegex ($ mode );
290235 }
291236
292- foreach ($ vendors as $ vendor ) {
293- if (0 === strpos ($ realPath , $ vendor ) && false !== strpbrk (substr ($ realPath , \strlen ($ vendor ), 1 ), '/ ' .\DIRECTORY_SEPARATOR )) {
294- return true ;
295- }
237+ if (preg_match ('/^[1-9][0-9]*$/ ' , (string ) $ mode )) {
238+ return $ this ->configuration = Configuration::fromNumber ($ mode );
296239 }
297240
298- return false ;
241+ return $ this -> configuration = Configuration:: fromUrlEncodedString (( string ) $ mode ) ;
299242 }
300243
301244 /**
302245 * @param string $str
303246 * @param bool $red
304- * @param mixed $mode
305247 *
306248 * @return string
307249 */
308- private static function colorize ($ str , $ red, $ mode )
250+ private static function colorize ($ str , $ red )
309251 {
310- if (!self ::hasColorSupport () || self :: MODE_WEAK === $ mode ) {
252+ if (!self ::hasColorSupport ()) {
311253 return $ str ;
312254 }
313255
@@ -317,36 +259,36 @@ private static function colorize($str, $red, $mode)
317259 }
318260
319261 /**
320- * @param string[] $groups
321- * @param mixed $mode
262+ * @param string[] $groups
263+ * @param Configuration $configuration
322264 */
323- private function displayDeprecations ($ groups , $ mode )
265+ private function displayDeprecations ($ groups , $ configuration )
324266 {
325267 $ cmp = function ($ a , $ b ) {
326268 return $ b ['count ' ] - $ a ['count ' ];
327269 };
328270
329271 foreach ($ groups as $ group ) {
330- if (!$ this ->deprecations [$ group .'Count ' ]) {
331- continue ;
332- }
272+ if ($ this ->deprecations [$ group .'Count ' ]) {
273+ echo "\n" , self ::colorize (
274+ sprintf ('%s deprecation notices (%d) ' , ucfirst ($ group ), $ this ->deprecations [$ group .'Count ' ]),
275+ 'legacy ' !== $ group && 'remaining indirect ' !== $ group
276+ ), "\n" ;
333277
334- echo "\n" , self ::colorize (
335- sprintf ('%s deprecation notices (%d) ' , ucfirst ($ group ), $ this ->deprecations [$ group .'Count ' ]),
336- 'legacy ' !== $ group && 'remaining vendor ' !== $ group ,
337- $ mode
338- ), "\n" ;
339-
340- uasort ($ this ->deprecations [$ group ], $ cmp );
278+ if (!$ configuration ->verboseOutput ()) {
279+ continue ;
280+ }
281+ uasort ($ this ->deprecations [$ group ], $ cmp );
341282
342- foreach ($ this ->deprecations [$ group ] as $ msg => $ notices ) {
343- echo "\n " , $ notices ['count ' ], 'x: ' , $ msg , "\n" ;
283+ foreach ($ this ->deprecations [$ group ] as $ msg => $ notices ) {
284+ echo "\n " , $ notices ['count ' ], 'x: ' , $ msg , "\n" ;
344285
345- arsort ($ notices );
286+ arsort ($ notices );
346287
347- foreach ($ notices as $ method => $ count ) {
348- if ('count ' !== $ method ) {
349- echo ' ' , $ count , 'x in ' , preg_replace ('/(.*) \\\\(.*?::.*?)$/ ' , '$2 from $1 ' , $ method ), "\n" ;
288+ foreach ($ notices as $ method => $ count ) {
289+ if ('count ' !== $ method ) {
290+ echo ' ' , $ count , 'x in ' , preg_replace ('/(.*) \\\\(.*?::.*?)$/ ' , '$2 from $1 ' , $ method ), "\n" ;
291+ }
350292 }
351293 }
352294 }
0 commit comments