diff --git a/.phan/config.php b/.phan/config.php index 036ca52d990..9eeb36c8caf 100644 --- a/.phan/config.php +++ b/.phan/config.php @@ -30,6 +30,8 @@ $cfg['file_list'] = array_merge( class_exists( PEAR::class ) ? [] : [ '.phan/stubs/mail.php' ], defined( 'PASSWORD_ARGON2ID' ) ? [] : [ '.phan/stubs/password.php' ], class_exists( ValueError::class ) ? [] : [ '.phan/stubs/ValueError.php' ], + class_exists( Socket::class ) ? [] : [ '.phan/stubs/Socket.php' ], + class_exists( AllowDynamicProperties::class ) ? [] : [ '.phan/stubs/AllowDynamicProperties.php' ], [ // This makes constants and globals known to Phan before processing all other files. // You can check the parser order with --dump-parsed-file-list @@ -51,6 +53,20 @@ $cfg['exclude_file_list'] = array_merge( ] ); +if ( PHP_VERSION_ID >= 80000 ) { + // Exclude PHP 8.0 polyfills if PHP 8.0+ is running + $cfg['exclude_file_list'] = array_merge( + $cfg['exclude_file_list'], + [ + 'vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ] + ); +} + $cfg['analyzed_file_extensions'] = array_merge( $cfg['analyzed_file_extensions'] ?? [ 'php' ], [ 'inc' ] diff --git a/.phan/stubs/AllowDynamicProperties.php b/.phan/stubs/AllowDynamicProperties.php new file mode 100644 index 00000000000..d8062c8eb21 --- /dev/null +++ b/.phan/stubs/AllowDynamicProperties.php @@ -0,0 +1,6 @@ +` root node to the `` wrapper while ( $root->firstChild ) { $node = $root->firstChild; - // @phan-suppress-next-line PhanUndeclaredProperty False positive + '@phan-var \DOMElement $node'; /** @var \DOMElement $node */ if ( !$titleNode && $node->nodeType === XML_ELEMENT_NODE && $node->tagName === 'title' ) { // Remember the first encountered `` node $titleNode = $node; diff --git a/includes/ResourceLoader/VueComponentParser.php b/includes/ResourceLoader/VueComponentParser.php index 329e1ab3abf..7696f645be2 100644 --- a/includes/ResourceLoader/VueComponentParser.php +++ b/includes/ResourceLoader/VueComponentParser.php @@ -91,7 +91,7 @@ class VueComponentParser { $template = $this->getTemplateHtml( $html, $options['minifyTemplate'] ?? false ); return [ - 'script' => trim( $nodes['script']->nodeValue ), + 'script' => trim( $nodes['script']->nodeValue ?? '' ), 'template' => $template, 'style' => $styleData ? $styleData['style'] : null, 'styleLang' => $styleData ? $styleData['lang'] : null @@ -160,7 +160,7 @@ class VueComponentParser { * @throws Exception If an invalid language is used, or if the 'scoped' attribute is set. */ private function getStyleAndLang( DOMElement $styleNode ): array { - $style = trim( $styleNode->nodeValue ); + $style = trim( $styleNode->nodeValue ?? '' ); $styleLang = $styleNode->hasAttribute( 'lang' ) ? $styleNode->getAttribute( 'lang' ) : 'css'; if ( $styleLang !== 'css' && $styleLang !== 'less' ) { diff --git a/includes/debug/logger/monolog/LegacyHandler.php b/includes/debug/logger/monolog/LegacyHandler.php index a8483c5ca48..5243d6260f3 100644 --- a/includes/debug/logger/monolog/LegacyHandler.php +++ b/includes/debug/logger/monolog/LegacyHandler.php @@ -24,6 +24,7 @@ use LogicException; use MediaWiki\Logger\LegacyLogger; use Monolog\Handler\AbstractProcessingHandler; use Monolog\Logger; +use Socket; use UnexpectedValueException; /** @@ -62,7 +63,7 @@ class LegacyHandler extends AbstractProcessingHandler { /** * Log sink - * @var resource|null + * @var Socket|resource|null */ protected $sink; @@ -142,7 +143,6 @@ class LegacyHandler extends AbstractProcessingHandler { $domain = AF_INET; } - // @phan-suppress-next-line PhanTypeMismatchProperty False positive caused by PHP 8.0 resource transition $this->sink = socket_create( $domain, SOCK_DGRAM, SOL_UDP ); } else { @@ -216,7 +216,6 @@ class LegacyHandler extends AbstractProcessingHandler { } socket_sendto( - // @phan-suppress-next-line PhanTypeMismatchArgumentInternal False positive caused by PHP 8.0 transition $this->sink, $text, strlen( $text ), @@ -233,9 +232,7 @@ class LegacyHandler extends AbstractProcessingHandler { public function close(): void { if ( $this->sink ) { if ( $this->useUdp() ) { - // @phan-suppress-next-line PhanTypeMismatchArgumentInternal socket_close( $this->sink ); - } else { fclose( $this->sink ); } diff --git a/includes/libs/objectcache/MemcachedPeclBagOStuff.php b/includes/libs/objectcache/MemcachedPeclBagOStuff.php index dc1d1a5ae5f..bf4ee1671bd 100644 --- a/includes/libs/objectcache/MemcachedPeclBagOStuff.php +++ b/includes/libs/objectcache/MemcachedPeclBagOStuff.php @@ -208,7 +208,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff { $result = $this->client->set( $routeKey, $value, $this->fixExpiry( $exptime ) ); ScopedCallback::consume( $noReplyScope ); - return ( $result === false && $this->client->getResultCode() === Memcached::RES_NOTSTORED ) + return ( !$result && $this->client->getResultCode() === Memcached::RES_NOTSTORED ) // "Not stored" is always used as the mcrouter response with AllAsyncRoute ? true : $this->checkResult( $key, $result ); @@ -235,7 +235,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff { $result = $this->client->delete( $routeKey ); ScopedCallback::consume( $noReplyScope ); - return ( $result === false && $this->client->getResultCode() === Memcached::RES_NOTFOUND ) + return ( !$result && $this->client->getResultCode() === Memcached::RES_NOTFOUND ) // "Not found" is counted as success in our interface ? true : $this->checkResult( $key, $result ); @@ -379,7 +379,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff { // The PECL implementation uses multi-key "get"/"gets"; no need to pipeline. // T257003: avoid Memcached::GET_EXTENDED; no tokens are needed and that requires "gets" // https://github.com/libmemcached/libmemcached/blob/eda2becbec24363f56115fa5d16d38a2d1f54775/libmemcached/get.cc#L272 - $resByRouteKey = $this->client->getMulti( $routeKeys ) ?: []; + $resByRouteKey = $this->client->getMulti( $routeKeys ); if ( is_array( $resByRouteKey ) ) { $res = []; @@ -390,7 +390,8 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff { $res = false; } - return $this->checkResult( false, $res ); + $res = $this->checkResult( false, $res ); + return $res !== false ? $res : []; } protected function doSetMulti( array $data, $exptime = 0, $flags = 0 ) { diff --git a/includes/libs/rdbms/database/DatabaseMysqli.php b/includes/libs/rdbms/database/DatabaseMysqli.php index 8dd3fa468fc..6b40e2e588b 100644 --- a/includes/libs/rdbms/database/DatabaseMysqli.php +++ b/includes/libs/rdbms/database/DatabaseMysqli.php @@ -196,7 +196,7 @@ class DatabaseMysqli extends DatabaseMysqlBase { */ public function lastErrno() { if ( $this->conn instanceof mysqli ) { - return (int)$this->conn->errno; + return $this->conn->errno; } else { return mysqli_connect_errno(); } diff --git a/includes/password/BcryptPassword.php b/includes/password/BcryptPassword.php index 95492fb3b52..86409325a5c 100644 --- a/includes/password/BcryptPassword.php +++ b/includes/password/BcryptPassword.php @@ -77,7 +77,7 @@ class BcryptPassword extends ParameterizedPassword { $hash = crypt( $password, sprintf( '$2y$%02d$%s', (int)$this->params['rounds'], $this->args[0] ) ); - if ( !is_string( $hash ) || strlen( $hash ) <= 13 ) { + if ( strlen( $hash ) <= 13 ) { throw new PasswordError( 'Error when hashing password.' ); } diff --git a/includes/password/MWOldPassword.php b/includes/password/MWOldPassword.php index 497034668fa..db6906eac64 100644 --- a/includes/password/MWOldPassword.php +++ b/includes/password/MWOldPassword.php @@ -49,7 +49,7 @@ class MWOldPassword extends ParameterizedPassword { $this->hash = md5( $plaintext ); } - if ( !is_string( $this->hash ) || strlen( $this->hash ) < 32 ) { + if ( strlen( $this->hash ) < 32 ) { throw new PasswordError( 'Error when hashing password.' ); } } diff --git a/includes/password/MWSaltedPassword.php b/includes/password/MWSaltedPassword.php index bcb7f282fd7..f0f994768d7 100644 --- a/includes/password/MWSaltedPassword.php +++ b/includes/password/MWSaltedPassword.php @@ -45,7 +45,7 @@ class MWSaltedPassword extends ParameterizedPassword { $this->hash = md5( $this->args[0] . '-' . md5( $plaintext ) ); - if ( !is_string( $this->hash ) || strlen( $this->hash ) < 32 ) { + if ( strlen( $this->hash ) < 32 ) { throw new PasswordError( 'Error when hashing password.' ); } } diff --git a/includes/password/Pbkdf2PasswordUsingHashExtension.php b/includes/password/Pbkdf2PasswordUsingHashExtension.php index 3c00049664a..09c05d230b8 100644 --- a/includes/password/Pbkdf2PasswordUsingHashExtension.php +++ b/includes/password/Pbkdf2PasswordUsingHashExtension.php @@ -44,6 +44,8 @@ class Pbkdf2PasswordUsingHashExtension extends AbstractPbkdf2Password { int $length ): string { $hash = hash_pbkdf2( $digestAlgo, $password, $salt, $rounds, $length, true ); + // Note: this check is unnecessary in PHP 8.0+, since the warning cases + // were all converted to exceptions if ( !is_string( $hash ) ) { throw new PasswordError( 'Error when hashing password.' ); } diff --git a/includes/preferences/DefaultPreferencesFactory.php b/includes/preferences/DefaultPreferencesFactory.php index 6b7cdf80343..e62ab5c7298 100644 --- a/includes/preferences/DefaultPreferencesFactory.php +++ b/includes/preferences/DefaultPreferencesFactory.php @@ -1505,8 +1505,10 @@ class DefaultPreferencesFactory implements PreferencesFactory { $thumbNamespaces ) ); - $defaultThumbNamespacesFormatted = (array)array_intersect_key( $thumbNamespacesFormatted, [ NS_FILE => 1 ] ); - $extraThumbNamespacesFormatted = (array)array_diff_key( $thumbNamespacesFormatted, [ NS_FILE => 1 ] ); + $defaultThumbNamespacesFormatted = + array_intersect_key( $thumbNamespacesFormatted, [ NS_FILE => 1 ] ) ?? []; + $extraThumbNamespacesFormatted = + array_diff_key( $thumbNamespacesFormatted, [ NS_FILE => 1 ] ); if ( $extraThumbNamespacesFormatted ) { $defaultPreferences['search-thumbnail-extra-namespaces'] = [ 'type' => 'toggle', diff --git a/includes/search/searchwidgets/FullSearchResultWidget.php b/includes/search/searchwidgets/FullSearchResultWidget.php index cad08823e81..2392f060021 100644 --- a/includes/search/searchwidgets/FullSearchResultWidget.php +++ b/includes/search/searchwidgets/FullSearchResultWidget.php @@ -399,7 +399,7 @@ class FullSearchResultWidget implements SearchResultWidget { // we'll only deal with width from now on since conventions for // standard sizes have formed around width; height will simply // follow according to aspect ratio - $rescaledWidth = round( $rescaleCoefficient * $thumbnail->getWidth() ); + $rescaledWidth = (int)round( $rescaleCoefficient * $thumbnail->getWidth() ); // we'll also be looking at $wgThumbLimits to ensure that we pick // from within the predefined list of sizes diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index 8681236e8f2..4885c0a53e2 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -824,7 +824,7 @@ class SpecialWatchlist extends ChangesListSpecialPage { ] ) ); asort( $hours ); - $select = new XmlSelect( 'days', 'days', (float)( $selectedHours / 24 ) ); + $select = new XmlSelect( 'days', 'days', $selectedHours / 24 ); foreach ( $hours as $value ) { if ( $value < 24 ) { diff --git a/includes/xml/Xml.php b/includes/xml/Xml.php index fcdedf771cd..7d4f01ddc57 100644 --- a/includes/xml/Xml.php +++ b/includes/xml/Xml.php @@ -183,7 +183,7 @@ class Xml { $timestamp = MWTimestamp::getInstance(); $thisMonth = intval( $timestamp->format( 'n' ) ); $thisYear = intval( $timestamp->format( 'Y' ) ); - if ( intval( $encMonth ) > $thisMonth ) { + if ( $encMonth > $thisMonth ) { $thisYear--; } $encYear = $thisYear;