From cef1aa91dba2604dfcd598e6a354aa8c58d334e8 Mon Sep 17 00:00:00 2001 From: Spine Date: Fri, 11 Jul 2025 09:38:56 +0000 Subject: [PATCH] deploy some stricter phpstan rules --- app/Forum.php | 7 +- app/Json/Better/SingleSeeded.php | 2 +- app/Json/Better/Transcode.php | 2 +- app/Json/TGroup.php | 2 +- app/Manager/Request.php | 12 +- app/Request.php | 4 +- app/User/Bookmark.php | 2 +- app/User/UserLink.php | 4 +- app/UserMatch/MatchCandidate.php | 4 +- classes/sphinxql.class.php | 2 +- composer.json | 9 +- composer.lock | 124 +++++++++++++++++--- lib/util.php | 4 +- misc/phpstan.neon | 43 ++++++- sections/login/login.php | 2 +- sections/tools/development/process_info.php | 14 ++- sections/tools/managers/rate_limit.php | 2 +- tests/phpunit/DnuTest.php | 2 +- tests/phpunit/RequestTest.php | 1 - tests/phpunit/WikiTest.php | 1 - tests/phpunit/search/UserAdvancedTest.php | 1 - 21 files changed, 191 insertions(+), 53 deletions(-) diff --git a/app/Forum.php b/app/Forum.php index fdf7da43c..ca97a15b2 100644 --- a/app/Forum.php +++ b/app/Forum.php @@ -260,9 +260,7 @@ class Forum extends BaseObject { ); } - /** - * @deprecated - */ + #[\Deprecated] public function threadCount(): int { $toc = $this->tableOfContentsForum(); return $toc ? current($toc)['threadCount'] : 0; @@ -283,9 +281,8 @@ class Forum extends BaseObject { * - int 'LastPostAuthorID' User id of author of most recent post * - int 'stickyCount' Number of sticky posts * - int 'threadCount' Total number of threads in forum - * - * @deprecated */ + #[\Deprecated] public function tableOfContentsForum(int $page = 1): array { $key = sprintf(self::CACHE_TOC_FORUM, $this->id); $forumToc = null; diff --git a/app/Json/Better/SingleSeeded.php b/app/Json/Better/SingleSeeded.php index 440fe89af..2a874fb50 100644 --- a/app/Json/Better/SingleSeeded.php +++ b/app/Json/Better/SingleSeeded.php @@ -13,7 +13,7 @@ class SingleSeeded extends \Gazelle\Json { fn ($torrent) => [ 'torrentId' => $torrent->id(), 'groupId' => $torrent->groupId(), - 'artistInfo' => $this->artistPayload($torrent->group()), + 'artistInfo' => static::artistPayload($torrent->group()), 'groupName' => $torrent->group()->name(), 'groupYear' => $torrent->group()->year(), 'downloadUrl' => "torrents.php?action=download&id={$torrent->id()}&torrent_pass={$this->user->announceKey()}", diff --git a/app/Json/Better/Transcode.php b/app/Json/Better/Transcode.php index 66d810a9d..14dcb176b 100644 --- a/app/Json/Better/Transcode.php +++ b/app/Json/Better/Transcode.php @@ -20,7 +20,7 @@ class Transcode extends \Gazelle\Json { $payload[] = [ 'torrentId' => $torrent->id(), 'groupId' => $tgroup->id(), - 'artistInfo' => $this->artistPayload($torrent->group()), + 'artistInfo' => static::artistPayload($torrent->group()), 'groupName' => $tgroup->name(), 'groupYear' => $tgroup->year(), 'downloadUrl' => "torrents.php?action=download&id={$torrent->id()}&torrent_pass={$this->announceKey}", diff --git a/app/Json/TGroup.php b/app/Json/TGroup.php index ff0fac79f..03ecb2769 100644 --- a/app/Json/TGroup.php +++ b/app/Json/TGroup.php @@ -11,7 +11,7 @@ class TGroup extends \Gazelle\Json { public function tgroupPayload(): array { $tgroup = $this->tgroup; - $musicInfo = $this->artistPayload($tgroup); + $musicInfo = static::artistPayload($tgroup); return [ 'wikiBody' => \Text::full_format($tgroup->description()), diff --git a/app/Manager/Request.php b/app/Manager/Request.php index 80dd01c9d..a66108dae 100644 --- a/app/Manager/Request.php +++ b/app/Manager/Request.php @@ -236,9 +236,9 @@ class Request extends \Gazelle\BaseManager { i.log_score, i.need_log, i.need_cue, i.need_checksum, i.artist_title_ts, i.tag, - string_to_array(i.encoding_str::text, '|'), - string_to_array(i.format_str::text, '|'), - string_to_array(i.media_str::text, '|') + i.encoding_str, + i.format_str, + i.media_str ) when matched then update set @@ -251,9 +251,9 @@ class Request extends \Gazelle\BaseManager { log_score = i.log_score, need_log = i.need_log, need_cue = i.need_cue, need_checksum = i.need_checksum, artist_title_ts = i.artist_title_ts, tag = i.tag, - encoding_str = string_to_array(i.encoding_str::text, '|'), - format_str = string_to_array(i.format_str::text, '|'), - media_str = string_to_array(i.media_str::text, '|') + encoding_str = i.encoding_str, + format_str = i.format_str, + media_str = i.media_str "); } } diff --git a/app/Request.php b/app/Request.php index 8f0bda2d5..8bd8a9be3 100644 --- a/app/Request.php +++ b/app/Request.php @@ -176,8 +176,8 @@ class Request extends BaseObject implements CategoryHasArtist { ); $info['logCue'] = new Request\LogCue( needLogChecksum: (bool)$info['checksum'], - needCue: strpos($info['log_cue'], 'Cue') !== false, - needLog: strpos($info['log_cue'], 'Log') !== false, + needCue: str_contains($info['log_cue'], 'Cue'), + needLog: str_contains($info['log_cue'], 'Log'), minScore: (int)preg_replace('/\D+/', '', $info['log_cue']), ); diff --git a/app/User/Bookmark.php b/app/User/Bookmark.php index c9e26bdd6..ca64b26f7 100644 --- a/app/User/Bookmark.php +++ b/app/User/Bookmark.php @@ -154,7 +154,7 @@ class Bookmark extends \Gazelle\BaseUser { * Check if a request is bookmarked by a user */ public function isRequestBookmarked(Request $request): bool { - return in_array($request, $this->allBookmarks('request')); + return in_array($request->id, $this->allBookmarks('request')); } /** diff --git a/app/User/UserLink.php b/app/User/UserLink.php index 7bc1a92f7..6d1d2f9fb 100644 --- a/app/User/UserLink.php +++ b/app/User/UserLink.php @@ -21,11 +21,11 @@ class UserLink extends \Gazelle\BaseUser { } public function groupId(\Gazelle\User $user): ?int { - $id = (int)self::$db->scalar(" + $id = self::$db->scalar(" SELECT GroupID FROM users_dupes WHERE UserID = ? ", $user->id ); - return $id ? (int)$id : null; + return is_null($id) ? null : (int)$id; } public function info(): array { diff --git a/app/UserMatch/MatchCandidate.php b/app/UserMatch/MatchCandidate.php index 22caa5564..850d2c094 100644 --- a/app/UserMatch/MatchCandidate.php +++ b/app/UserMatch/MatchCandidate.php @@ -194,8 +194,8 @@ class MatchCandidate { continue; } - $end = $end ?? $start; - $otherEnd = $otherEnd ?? $otherStart; + $end ??= $start; + $otherEnd ??= $otherStart; /* possible cases: * any two dates are very close diff --git a/classes/sphinxql.class.php b/classes/sphinxql.class.php index 6a4ff1995..144172ab9 100644 --- a/classes/sphinxql.class.php +++ b/classes/sphinxql.class.php @@ -62,7 +62,7 @@ class Sphinxql extends mysqli { global $Debug; $Debug->mark("Connecting to Sphinx server $this->Ident"); for ($Attempt = 0; $Attempt < 3; $Attempt++) { - parent::__construct($this->Server, '', '', '', $this->Port, $this->Socket); + parent::__construct($this->Server, '', '', '', $this->Port, $this->Socket); /** @phpstan-ignore-line kittens were drowned for this */ if (!$this->connect_errno) { $this->Connected = true; break; diff --git a/composer.json b/composer.json index 8fd664e1c..f1f51f3fd 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,10 @@ "classmap": ["classes/"] }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "phpstan/extension-installer": true + } }, "require": { "php": "^8.4", @@ -43,10 +46,12 @@ "brianium/paratest": "^7.9", "phpmd/phpmd": "@stable", "phpstan/phpstan": "^2.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpcov": "^11", "phpunit/php-code-coverage": "^12", "phpunit/phpunit": "^12", - "rector/rector": "^2.0", + "rector/rector": "^2.1", "squizlabs/php_codesniffer": "^3.12" } } diff --git a/composer.lock b/composer.lock index f194dfa0a..e1a3f1ed1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "84254745c0f3ef05c1cec3c81b293d32", + "content-hash": "a1aca354806f57bc6c77d2d16bea3a75", "packages": [ { "name": "bacon/bacon-qr-code", @@ -3787,6 +3787,54 @@ ], "time": "2023-12-11T08:22:20+00:00" }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, { "name": "phpstan/phpstan", "version": "2.1.17", @@ -3845,6 +3893,54 @@ ], "time": "2025-05-21T20:55:28+00:00" }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", + "reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4" + }, + "time": "2025-03-18T11:42:40+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "12.3.1", @@ -4243,16 +4339,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.2.6", + "version": "12.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "638644c62a58f04974da115f98981c9b48564021" + "reference": "8b1348b254e5959acaf1539c6bd790515fb49414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/638644c62a58f04974da115f98981c9b48564021", - "reference": "638644c62a58f04974da115f98981c9b48564021", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8b1348b254e5959acaf1539c6bd790515fb49414", + "reference": "8b1348b254e5959acaf1539c6bd790515fb49414", "shasum": "" }, "require": { @@ -4262,7 +4358,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.3", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", @@ -4320,7 +4416,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.2.7" }, "funding": [ { @@ -4344,20 +4440,20 @@ "type": "tidelift" } ], - "time": "2025-07-04T06:00:16+00:00" + "time": "2025-07-11T04:11:13+00:00" }, { "name": "rector/rector", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "d513dea45a94394b660e15c155d1fa27826f8e30" + "reference": "d0917c069bb0d9bb06ed111cf052510f609015a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/d513dea45a94394b660e15c155d1fa27826f8e30", - "reference": "d513dea45a94394b660e15c155d1fa27826f8e30", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/d0917c069bb0d9bb06ed111cf052510f609015a4", + "reference": "d0917c069bb0d9bb06ed111cf052510f609015a4", "shasum": "" }, "require": { @@ -4396,7 +4492,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.1.0" + "source": "https://github.com/rectorphp/rector/tree/2.1.1" }, "funding": [ { @@ -4404,7 +4500,7 @@ "type": "github" } ], - "time": "2025-06-24T20:26:57+00:00" + "time": "2025-07-10T11:31:31+00:00" }, { "name": "sebastian/cli-parser", diff --git a/lib/util.php b/lib/util.php index 5050eadab..14177952f 100644 --- a/lib/util.php +++ b/lib/util.php @@ -549,7 +549,7 @@ function array_key_filter_and_map(string $prefix, array $list, bool $cast = true if (!str_starts_with($k, $prefix)) { continue; } - $key = $cast ? (int)explode('-', (string)$k, 2)[1] : explode('-', (string)$k, 2)[1]; + $key = $cast ? (int)explode('-', $k, 2)[1] : explode('-', $k, 2)[1]; $out[$key] = $v; } return $out; @@ -831,7 +831,7 @@ function check_paranoia(string $Property, string|array $Paranoia, int $UserClass function httpProxy(): ?string { $proxy = getenv('HTTP_PROXY'); if ($proxy !== false) { - return (string)$proxy; + return $proxy; } elseif (HTTP_PROXY != false) { return HTTP_PROXY; } diff --git a/misc/phpstan.neon b/misc/phpstan.neon index cda94ec5b..969270ccf 100644 --- a/misc/phpstan.neon +++ b/misc/phpstan.neon @@ -12,18 +12,47 @@ parameters: checkMissingCallableSignature: true checkTooWideReturnTypesInProtectedAndPublicMethods: true polluteScopeWithAlwaysIterableForeach: false + polluteScopeWithBlock: false polluteScopeWithLoopInitialAssignments: false rememberPossiblyImpureFunctionValues: true reportAlwaysTrueInLastCondition: true reportAnyTypeWideningInVarTag: true reportMaybesInMethodSignatures: true + reportMaybesInPropertyPhpDocTypes: true + reportPossiblyNonexistentConstantArrayOffset: true + reportPossiblyNonexistentGeneralArrayOffset: true reportStaticMethodSignatures: true reportWrongPhpDocTypeInVarTag: true + + strictRules: + booleansInConditions: false + booleansInLoopConditions: false + closureUsesThis: true + disallowedBacktick: true + disallowedEmpty: false + disallowedImplicitArrayCreation: false + disallowedLooseComparison: false + disallowedShortTernary: false + dynamicCallOnStaticMethod: true + illegalConstructorMethodCall: true + matchingInheritedMethodNames: true + noVariableVariables: false + numericOperandsInArithmeticOperators: false + overwriteVariablesWithLoop: false + requireParentConstructorCall: false + strictArrayFilter: false + strictFunctionCalls: false + switchConditionsMatchingType: true + uselessCast: true + errorFormat: table + bootstrapFiles: - ../lib/config.php + scanFiles: - ../lib/bootstrap.php + paths: - ../lib/util.php - ../app @@ -32,8 +61,12 @@ parameters: - ../misc - ../tests - ../sections + parallel: - maximumNumberOfProcesses: 1 + jobSize: 20 + maximumNumberOfProcesses: 12 + minimumNumberOfJobsPerProcess: 2 + dynamicConstantNames: - AJAX - BITCOIN_DONATION_XYZPUB @@ -76,6 +109,14 @@ parameters: ignoreErrors: + - + identifier: offsetAccess.notFound + + - + identifier: staticMethod.dynamicCall + paths: + - ../tests/phpunit/* + - message: '#^Cannot unset property \S+ because it might have hooks in a subclass\.$#' reportUnmatched: false diff --git a/sections/login/login.php b/sections/login/login.php index bddf9f527..6e82fcc1a 100644 --- a/sections/login/login.php +++ b/sections/login/login.php @@ -62,7 +62,7 @@ if (!empty($_POST['username']) && !empty($_POST['password'])) { 'useragent' => $context->useragent(), ]); - (new SessionCookie(SessionCookie::encode($user, $current['SessionID']))) + new SessionCookie(SessionCookie::encode($user, $current['SessionID'])) ->emit((int)$login->persistent() * (time() + 60 * 60 * 24 * 90)); header("Location: index.php"); exit; diff --git a/sections/tools/development/process_info.php b/sections/tools/development/process_info.php index 07be3270a..a46212656 100644 --- a/sections/tools/development/process_info.php +++ b/sections/tools/development/process_info.php @@ -14,12 +14,14 @@ if (!$Viewer->permitted('admin_site_debug')) { $proc = []; if (preg_match('/.*\/(.*)/', PHP_BINARY, $match, PREG_UNMATCHED_AS_NULL)) { $binary = $match[1]; - $ps = trim(`ps -C {$binary} -o pid --no-header`); - $pidList = explode("\n", $ps); - foreach ($pidList as $pid) { - $p = $Cache->get_value("php_$pid"); - if ($p !== false) { - $proc[$pid] = $p; + $ps = shell_exec("ps -C {$binary} -o pid --no-header"); + if ($ps !== false && !is_null($ps)) { + $pidList = explode("\n", trim($ps)); + foreach ($pidList as $pid) { + $p = $Cache->get_value("php_$pid"); + if ($p !== false) { + $proc[$pid] = $p; + } } } } diff --git a/sections/tools/managers/rate_limit.php b/sections/tools/managers/rate_limit.php index a9122c07c..69e76e8ef 100644 --- a/sections/tools/managers/rate_limit.php +++ b/sections/tools/managers/rate_limit.php @@ -34,7 +34,7 @@ if ($_POST) { echo $Twig->render('admin/rate-limiting.twig', [ 'class_list' => new Manager\User()->classList(), - 'priv_list' => new Manager\Privilege()->privilegeList(), + 'priv_list' => new Manager\Privilege()::privilegeList(), 'rate_list' => $limiter->list(), 'viewer' => $Viewer, ]); diff --git a/tests/phpunit/DnuTest.php b/tests/phpunit/DnuTest.php index 993d6b5c2..e28b54683 100644 --- a/tests/phpunit/DnuTest.php +++ b/tests/phpunit/DnuTest.php @@ -8,7 +8,7 @@ use GazelleUnitTest\Helper; class DnuTest extends TestCase { protected User $user; - public function setup(): void { + public function setUp(): void { $this->user = Helper::makeUser('dnu.' . randomString(10), 'dnu'); } diff --git a/tests/phpunit/RequestTest.php b/tests/phpunit/RequestTest.php index 68f00b3c1..229990ac9 100644 --- a/tests/phpunit/RequestTest.php +++ b/tests/phpunit/RequestTest.php @@ -613,7 +613,6 @@ class RequestTest extends TestCase { $this->userList['admin'], 'phpunit request json', ); - $artistMan = new Manager\Artist(); $this->request->artistRole()->set( [ARTIST_MAIN => ['phpunit req ' . randomString(6)]], $this->userList['user'], diff --git a/tests/phpunit/WikiTest.php b/tests/phpunit/WikiTest.php index 9ddb4166c..b8ba929de 100644 --- a/tests/phpunit/WikiTest.php +++ b/tests/phpunit/WikiTest.php @@ -125,7 +125,6 @@ class WikiTest extends TestCase { public function testWikiAlias(): void { $manager = new Manager\Wiki(); $title = 'phpunit title ' . randomString(6); - $alias = Wiki::normalizeAlias($title); $article = $manager->create( $title, 'wiki body', diff --git a/tests/phpunit/search/UserAdvancedTest.php b/tests/phpunit/search/UserAdvancedTest.php index bdffdc1d9..1b6aa94cc 100644 --- a/tests/phpunit/search/UserAdvancedTest.php +++ b/tests/phpunit/search/UserAdvancedTest.php @@ -63,7 +63,6 @@ class UserAdvancedTest extends TestCase { #[DataProvider('dataDate')] public function testSearchUserDate(string $name, string $compare, string $expected): void { - $search = new Search\User(''); $this->assertEquals( $expected, new Search\User('')->date('f', $compare),