@@ -20,6 +20,7 @@ class DeprecationErrorHandler
2020{
2121const MODE_WEAK ='weak ' ;
2222const MODE_WEAK_VENDORS ='weak_vendors ' ;
23+ const MODE_WEAK_LAGGING_VENDORS ='weak_lagging_vendors ' ;
2324const MODE_DISABLED ='disabled ' ;
2425
2526private static $ isRegistered =false ;
@@ -30,6 +31,8 @@ class DeprecationErrorHandler
3031 * The following reporting modes are supported:
3132 * - use "weak" to hide the deprecation report but keep a global count;
3233 * - use "weak_vendors" to act as "weak" but only for vendors;
34+ * - use "weak_lagging_vendors" to act as "weak" but only for vendors that
35+ * failed to keep up with their upstream dependencies deprecations;
3336 * - use "/some-regexp/" to stop the test suite whenever a deprecation
3437 * message matches the given regular expression;
3538 * - use a number to define the upper bound of allowed deprecations,
@@ -54,7 +57,11 @@ public static function register($mode = 0)
5457if (false ===$ mode ) {
5558$ mode =getenv ('SYMFONY_DEPRECATIONS_HELPER ' );
5659 }
57- if (DeprecationErrorHandler::MODE_WEAK !==$ mode && DeprecationErrorHandler::MODE_WEAK_VENDORS !==$ mode && (!isset ($ mode [0 ]) ||'/ ' !==$ mode [0 ])) {
60+ if (!in_array ($ mode ,array (
61+ DeprecationErrorHandler::MODE_WEAK ,
62+ DeprecationErrorHandler::MODE_WEAK_VENDORS ,
63+ DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS ,
64+ ),true ) && (!isset ($ mode [0 ]) ||'/ ' !==$ mode [0 ])) {
5865$ mode =preg_match ('/^[1-9][0-9]*$/ ' ,$ mode ) ? (int )$ mode :0 ;
5966 }
6067
@@ -66,11 +73,13 @@ public static function register($mode = 0)
6673'remainingCount ' =>0 ,
6774'legacyCount ' =>0 ,
6875'otherCount ' =>0 ,
76+ 'lagging vendorCount ' =>0 ,
6977'remaining vendorCount ' =>0 ,
7078'unsilenced ' =>array (),
7179'remaining ' =>array (),
7280'legacy ' =>array (),
7381'other ' =>array (),
82+ 'lagging vendor ' =>array (),
7483'remaining vendor ' =>array (),
7584 );
7685$ deprecationHandler =function ($ type ,$ msg ,$ file ,$ line ,$ context =array ())use (&$ deprecations ,$ getMode ,$ UtilPrefix ) {
@@ -84,8 +93,9 @@ public static function register($mode = 0)
8493$ trace =debug_backtrace (true );
8594$ group ='other ' ;
8695$ isVendor =false ;
87- $ isWeak = DeprecationErrorHandler::MODE_WEAK ===$ mode || (DeprecationErrorHandler::MODE_WEAK_VENDORS ===$ mode &&$ isVendor =self ::inVendors ($ file ));
88-
96+ $ isWeak = DeprecationErrorHandler::MODE_WEAK ===$ mode ||
97+ (DeprecationErrorHandler::MODE_WEAK_VENDORS ===$ mode &&$ isVendor =self ::inVendors ($ file )) ||
98+ (DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS ===$ mode &&$ isLaggingVendor =self ::isLaggingVendor ($ trace ));
8999$ i =count ($ trace );
90100while (1 <$ i && (!isset ($ trace [--$ i ]['class ' ]) || ('ReflectionMethod ' ===$ trace [$ i ]['class ' ] ||0 ===strpos ($ trace [$ i ]['class ' ],'PHPUnit_ ' ) ||0 ===strpos ($ trace [$ i ]['class ' ],'PHPUnit \\' )))) {
91101// No-op
@@ -101,7 +111,9 @@ public static function register($mode = 0)
101111// \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest()
102112// then we need to use the serialized information to determine
103113// if the error has been triggered from vendor code.
104- $ isWeak = DeprecationErrorHandler::MODE_WEAK ===$ mode || (DeprecationErrorHandler::MODE_WEAK_VENDORS ===$ mode &&$ isVendor =isset ($ parsedMsg ['triggering_file ' ]) &&self ::inVendors ($ parsedMsg ['triggering_file ' ]));
114+ $ isWeak = DeprecationErrorHandler::MODE_WEAK ===$ mode ||
115+ (DeprecationErrorHandler::MODE_WEAK_VENDORS ===$ mode &&$ isVendor =isset ($ parsedMsg ['triggering_file ' ]) &&self ::inVendors ($ parsedMsg ['triggering_file ' ])) ||
116+ (DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS ===$ mode );// not enough information to make the right call, so let's be lenient
105117 }else {
106118$ class =isset ($ trace [$ i ]['object ' ]) ?get_class ($ trace [$ i ]['object ' ]) :$ trace [$ i ]['class ' ];
107119$ method =$ trace [$ i ]['function ' ];
@@ -118,6 +130,8 @@ public static function register($mode = 0)
118130 ||in_array ('legacy ' ,$ Test ::getGroups ($ class ,$ method ),true )
119131 ) {
120132$ group ='legacy ' ;
133+ }elseif (DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS ===$ mode &&$ isLaggingVendor ) {
134+ $ group ='lagging vendor ' ;
121135 }elseif (DeprecationErrorHandler::MODE_WEAK_VENDORS ===$ mode &&$ isVendor ) {
122136$ group ='remaining vendor ' ;
123137 }else {
@@ -192,13 +206,16 @@ public static function register($mode = 0)
192206if (DeprecationErrorHandler::MODE_WEAK_VENDORS ===$ mode ) {
193207$ groups [] ='remaining vendor ' ;
194208 }
209+ if (DeprecationErrorHandler::MODE_WEAK_LAGGING_VENDORS ===$ mode ) {
210+ $ groups [] ='lagging vendor ' ;
211+ }
195212array_push ($ groups ,'legacy ' ,'other ' );
196213
197214foreach ($ groupsas $ group ) {
198215if ($ deprecations [$ group .'Count ' ]) {
199216echo "\n" ,$ colorize (
200217sprintf ('%s deprecation notices (%d) ' ,ucfirst ($ group ),$ deprecations [$ group .'Count ' ]),
201- ' legacy ' !== $ group && 'remaining vendor ' !== $ group
218+ ! in_array ( $ group, array ( ' legacy ' , 'remaining vendor ' , ' lagging vendor ' ), true )
202219 ),"\n" ;
203220
204221uasort ($ deprecations [$ group ],$ cmp );
@@ -227,11 +244,58 @@ public static function register($mode = 0)
227244 }
228245 }
229246
230- private static function inVendors (string $ path ):bool
247+ private static function isLaggingVendor (array $ trace ):bool
248+ {
249+ $ erroringFile =$ erroringPackage =null ;
250+ foreach ($ traceas $ line ) {
251+ if (!isset ($ line ['file ' ])) {
252+ continue ;
253+ }
254+ $ file =$ line ['file ' ];
255+ if ('- ' ===$ file ) {
256+ continue ;
257+ }
258+ if (!self ::inVendors ($ file )) {
259+ return false ;
260+ }
261+ if (null !==$ erroringFile &&null !==$ erroringPackage ) {
262+ if (self ::getPackage ($ file ) !==$ erroringPackage ) {
263+ return true ;
264+ }
265+ }else {
266+ $ erroringFile =$ file ;
267+ $ erroringPackage =self ::getPackage ($ file );
268+ }
269+ }
270+
271+ return false ;
272+ }
273+
274+ /**
275+ * inVendors() should always be called prior to calling this method.
276+ */
277+ private static function getPackage (string $ path ):string
278+ {
279+ $ path =realpath ($ path ) ?:$ path ;
280+ foreach (self ::getVendors ()as $ vendorRoot ) {
281+ if (0 ===strpos ($ path ,$ vendorRoot )) {
282+ $ relativePath =substr ($ path ,strlen ($ vendorRoot ) +1 );
283+ $ vendor =strstr ($ relativePath ,DIRECTORY_SEPARATOR ,true );
284+
285+ return $ vendor .'/ ' .strstr (substr ($ relativePath ,strlen ($ vendor ) +1 ),DIRECTORY_SEPARATOR ,true );
286+ }
287+ }
288+
289+ throw new \RuntimeException ('No vendors found ' );
290+ }
291+
292+ private static function getVendors ():array
231293 {
232294/** @var string[] absolute paths to vendor directories */
233295static $ vendors ;
296+
234297if (null ===$ vendors ) {
298+ $ vendors =array ();
235299foreach (get_declared_classes ()as $ class ) {
236300if ('C ' ===$ class [0 ] &&0 ===strpos ($ class ,'ComposerAutoloaderInit ' )) {
237301$ r =new \ReflectionClass ($ class );
@@ -242,11 +306,17 @@ private static function inVendors(string $path): bool
242306 }
243307 }
244308 }
309+
310+ return $ vendors ;
311+ }
312+
313+ private static function inVendors (string $ path ):bool
314+ {
245315$ realPath =realpath ($ path );
246316if (false ===$ realPath &&'- ' !==$ path &&'Standard input code ' !==$ path ) {
247317return true ;
248318 }
249- foreach ($ vendors as $ vendor ) {
319+ foreach (self :: getVendors () as $ vendor ) {
250320if (0 ===strpos ($ realPath ,$ vendor ) &&false !==strpbrk (substr ($ realPath ,strlen ($ vendor ),1 ),'/ ' .DIRECTORY_SEPARATOR )) {
251321return true ;
252322 }