From 00cd5df294c875ea1e00ab2f645a338a6bd92c8e Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 17 Dec 2025 10:11:18 +0100 Subject: [PATCH] Use native PHP #[Deprecated] (#8325) https://php.watch/versions/8.4/Deprecated And enfore it with PHPUnit + PHPStan. Especially useful for extensions. --- app/Controllers/indexController.php | 6 +- app/Models/Context.php | 9 +++ app/Models/Entry.php | 6 +- app/Models/Feed.php | 2 +- app/Models/SimplePieCustom.php | 4 +- composer.json | 3 +- composer.lock | 109 ++++++++++++++++++++-------- lib/Minz/ActionController.php | 6 +- lib/Minz/Configuration.php | 42 ++++++----- lib/Minz/Request.php | 2 +- lib/Minz/Session.php | 2 +- lib/Minz/View.php | 5 +- lib/lib_rss.php | 8 +- phpstan.dist.neon | 4 + tests/app/Models/SearchTest.php | 4 +- 15 files changed, 140 insertions(+), 72 deletions(-) diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 7def8e781..732e4248a 100644 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -227,8 +227,8 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController { /** * This action displays the RSS feed of FreshRSS. - * @deprecated See user query RSS sharing instead */ + #[Deprecated('See user query RSS sharing instead')] public function rssAction(): void { $allow_anonymous = FreshRSS_Context::systemConf()->allow_anonymous; @@ -262,9 +262,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController { header('Content-Type: application/rss+xml; charset=utf-8'); } - /** - * @deprecated See user query OPML sharing instead - */ + #[Deprecated('See user query OPML sharing instead')] public function opmlAction(): void { $allow_anonymous = FreshRSS_Context::systemConf()->allow_anonymous; diff --git a/app/Models/Context.php b/app/Models/Context.php index cc1b77026..a8feb7dbd 100644 --- a/app/Models/Context.php +++ b/app/Models/Context.php @@ -57,11 +57,13 @@ final class FreshRSS_Context { /** * @access private * @deprecated Will be made `private`; use `FreshRSS_Context::systemConf()` instead. + * @internal */ public static ?FreshRSS_SystemConfiguration $system_conf = null; /** * @access private * @deprecated Will be made `private`; use `FreshRSS_Context::userConf()` instead. + * @internal */ public static ?FreshRSS_UserConfiguration $user_conf = null; @@ -186,6 +188,13 @@ final class FreshRSS_Context { FreshRSS_Context::$user_conf = null; } + /** + * @internal + */ + public static function setUserConf(?FreshRSS_UserConfiguration $user_conf): void { + FreshRSS_Context::$user_conf = $user_conf; + } + /** @return array where the key is the category ID */ public static function categories(): array { if (empty(self::$categories)) { diff --git a/app/Models/Entry.php b/app/Models/Entry.php index fa12ceb66..9d9f880fd 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -160,7 +160,8 @@ class FreshRSS_Entry extends Minz_Model { } return $title; } - /** @deprecated */ + + #[Deprecated('Use authors() instead')] public function author(): string { return $this->authors(true); } @@ -540,7 +541,8 @@ HTML; $this->hash = ''; $this->title = trim($value); } - /** @deprecated */ + + #[Deprecated('Use _authors() instead')] public function _author(string $value): void { $this->_authors($value); } diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 5cf82b718..19ff4fa81 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -40,7 +40,7 @@ class FreshRSS_Feed extends Minz_Model { public const PRIORITY_CATEGORY = 0; public const PRIORITY_FEED = -5; public const PRIORITY_HIDDEN = -10; - /** @deprecated use PRIORITY_HIDDEN instead */ + #[Deprecated('Use PRIORITY_HIDDEN instead')] public const PRIORITY_ARCHIVED = -10; public const TTL_DEFAULT = 0; diff --git a/app/Models/SimplePieCustom.php b/app/Models/SimplePieCustom.php index c3593e4a2..44e5b030e 100644 --- a/app/Models/SimplePieCustom.php +++ b/app/Models/SimplePieCustom.php @@ -13,8 +13,8 @@ final class FreshRSS_SimplePieCustom extends \SimplePie\SimplePie $limits = FreshRSS_Context::systemConf()->limits; $this->get_registry()->register(\SimplePie\File::class, FreshRSS_SimplePieFetch::class); $this->set_useragent(FRESHRSS_USERAGENT); - $this->set_cache_name_function('sha1'); - $this->set_cache_location(CACHE_PATH); + $this->set_cache_name_function('sha1'); // @phpstan-ignore method.deprecated + $this->set_cache_location(CACHE_PATH); // @phpstan-ignore method.deprecated $this->set_cache_duration($limits['cache_duration'], $limits['cache_duration_min'], $limits['cache_duration_max']); $this->enable_order_by_date(false); diff --git a/composer.json b/composer.json index 290b1f347..88ce232c5 100644 --- a/composer.json +++ b/composer.json @@ -58,6 +58,7 @@ "ext-tokenizer": "*", "ext-xmlwriter": "*", "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^10", @@ -70,7 +71,7 @@ "phpcbf": "phpcbf . -p -s", "phpstan": "phpstan analyse --memory-limit 512M .", "phpstan-next": "phpstan analyse --memory-limit 512M -c phpstan-next.neon .", - "phpunit": "phpunit --bootstrap ./tests/bootstrap.php --display-notices --display-phpunit-deprecations ./tests", + "phpunit": "phpunit --bootstrap ./tests/bootstrap.php --display-notices --display-deprecations --display-phpunit-deprecations ./tests", "translations": "cli/manipulate.translation.php --action format && cli/check.translation.php --generate-readme", "test": [ "@php-lint", diff --git a/composer.lock b/composer.lock index 0cb229a5b..24e1d8fbb 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": "3a4fbd6ff5c1cb410b629030fe0571e7", + "content-hash": "a9fa7dc52d7ae1c05b7c889cb59881db", "packages": [], "packages-dev": [ { @@ -69,16 +69,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -121,9 +121,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -245,11 +245,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.32", + "version": "2.1.33", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", - "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", "shasum": "" }, "require": { @@ -294,20 +294,67 @@ "type": "github" } ], - "time": "2025-11-11T15:18:17+00:00" + "time": "2025-12-05T10:24:31+00:00" }, { - "name": "phpstan/phpstan-phpunit", - "version": "2.0.8", + "name": "phpstan/phpstan-deprecation-rules", + "version": "2.0.3", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "2fe9fbeceaf76dd1ebaa7bbbb25e2fb5e59db2fe" + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "468e02c9176891cc901143da118f09dc9505fc2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/2fe9fbeceaf76dd1ebaa7bbbb25e2fb5e59db2fe", - "reference": "2fe9fbeceaf76dd1ebaa7bbbb25e2fb5e59db2fe", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f", + "reference": "468e02c9176891cc901143da118f09dc9505fc2f", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.15" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "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": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3" + }, + "time": "2025-05-14T10:56:57+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "2.0.10", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "8d61a5854e7497d95bc85188e13537e99bd7aae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/8d61a5854e7497d95bc85188e13537e99bd7aae7", + "reference": "8d61a5854e7497d95bc85188e13537e99bd7aae7", "shasum": "" }, "require": { @@ -345,9 +392,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.8" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.10" }, - "time": "2025-11-11T07:55:22+00:00" + "time": "2025-12-06T11:15:39+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -720,16 +767,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.58", + "version": "10.5.60", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca" + "reference": "f2e26f52f80ef77832e359205f216eeac00e320c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca", - "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c", + "reference": "f2e26f52f80ef77832e359205f216eeac00e320c", "shasum": "" }, "require": { @@ -801,7 +848,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.60" }, "funding": [ { @@ -825,7 +872,7 @@ "type": "tidelift" } ], - "time": "2025-09-28T12:04:46+00:00" + "time": "2025-12-06T07:50:42+00:00" }, { "name": "sebastian/cli-parser", @@ -1861,16 +1908,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -1899,7 +1946,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -1907,7 +1954,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], diff --git a/lib/Minz/ActionController.php b/lib/Minz/ActionController.php index 9549a146f..eb6e8477d 100644 --- a/lib/Minz/ActionController.php +++ b/lib/Minz/ActionController.php @@ -27,6 +27,8 @@ abstract class Minz_ActionController { * Gives the possibility to override the default view model type. * @var class-string * @deprecated Use constructor with view type instead + * @access private + * @internal */ public static string $defaultViewType = Minz_View::class; @@ -43,8 +45,8 @@ abstract class Minz_ActionController { $view = null; } } - if ($view === null && class_exists(self::$defaultViewType)) { - $view = new self::$defaultViewType(); + if ($view === null && class_exists(self::$defaultViewType)) { /// @phpstan-ignore staticProperty.deprecated + $view = new self::$defaultViewType(); // @phpstan-ignore staticProperty.deprecated if (!($view instanceof Minz_View)) { $view = null; } diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index e0480b414..45988f4a9 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -26,7 +26,7 @@ class Minz_Configuration { * @param string $namespace the name of the current configuration * @param string $config_filename the filename of the configuration * @param string $default_filename a filename containing default values for the configuration - * @param Minz_ConfigurationSetterInterface $configuration_setter an optional helper to set values in configuration + * @param Minz_ConfigurationSetterInterface $configuration_setter an optional helper to set values in configuration @deprecated * @throws Minz_FileNotExistException */ public static function register(string $namespace, string $config_filename, ?string $default_filename = null, @@ -102,7 +102,7 @@ class Minz_Configuration { * @param string $namespace the name of the current configuration. * @param string $config_filename the file containing configuration values. * @param string $default_filename the file containing default values, null by default. - * @param Minz_ConfigurationSetterInterface $configuration_setter an optional helper to set values in configuration + * @param Minz_ConfigurationSetterInterface $configuration_setter an optional helper to set values in configuration @deprecated * @throws Minz_FileNotExistException */ final private function __construct(string $namespace, string $config_filename, ?string $default_filename = null, @@ -138,6 +138,7 @@ class Minz_Configuration { } } + #[Deprecated] public function configurationSetter(): ?Minz_ConfigurationSetterInterface { return $this->configuration_setter; } @@ -155,13 +156,12 @@ class Minz_Configuration { * @param string $key the name of the param. * @param mixed $default default value to return if key does not exist. * @return array|mixed value corresponding to the key. - * @access private - * @deprecated Use `attribute*()` methods instead. */ + #[Deprecated('Use `attribute*()` methods instead.')] public function param(string $key, mixed $default = null): mixed { if (isset($this->data[$key])) { return $this->data[$key]; - } elseif (!is_null($default)) { + } elseif ($default !== null) { return $default; } else { Minz_Log::warning($key . ' does not exist in configuration'); @@ -170,13 +170,14 @@ class Minz_Configuration { } /** - * A wrapper for param(). * @return array|mixed - * @access private - * @deprecated */ public function __get(string $key): mixed { - return $this->param($key); + if (isset($this->data[$key])) { + return $this->data[$key]; + } + Minz_Log::warning($key . ' does not exist in configuration'); + return null; } /** @@ -184,13 +185,12 @@ class Minz_Configuration { * * @param string $key the param name to set. * @param mixed $value the value to set. If null, the key is removed from the configuration. - * @access private - * @deprecated Use `_attribute()` instead. */ + #[Deprecated('Use `_attribute()` instead.')] public function _param(string $key, mixed $value = null): void { if ($this->configuration_setter !== null && $this->configuration_setter->support($key)) { $this->configuration_setter->handle($this->data, $key, $value); - } elseif (isset($this->data[$key]) && is_null($value)) { + } elseif (isset($this->data[$key]) && $value === null) { unset($this->data[$key]); } elseif ($value !== null) { $this->data[$key] = $value; @@ -198,12 +198,16 @@ class Minz_Configuration { } /** - * A wrapper for _param(). - * @access private - * @deprecated + * {@see Minz_Configuration::_attribute()} instead. + * @param string $key the param name to set. + * @param mixed $value the value to set. If null, the key is removed. */ public function __set(string $key, mixed $value): void { - $this->_param($key, $value); + if ($value === null) { + unset($this->data[$key]); + } else { + $this->data[$key] = $value; + } } /** @@ -266,6 +270,10 @@ class Minz_Configuration { * @param array|mixed|null $value Value, not HTML-encoded */ public function _attribute(string $key, $value = null): void { - self::_param($key, $value); + if (isset($this->data[$key]) && $value === null) { + unset($this->data[$key]); + } elseif ($value !== null) { + $this->data[$key] = $value; + } } } diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 8667c6442..cfb309ff6 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -42,8 +42,8 @@ class Minz_Request { * @param mixed $default default value, if no parameter is given * @param bool $specialchars `true` to return special characters, `false` (default) to XML-encode them * @return mixed value of the parameter - * @deprecated use typed versions instead */ + #[Deprecated('Use typed versions instead')] public static function param(string $key, mixed $default = false, bool $specialchars = false): mixed { if (isset(self::$params[$key])) { $p = self::$params[$key]; diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php index 2f4058905..d9eab6434 100644 --- a/lib/Minz/Session.php +++ b/lib/Minz/Session.php @@ -61,8 +61,8 @@ class Minz_Session { * @param string $p the parameter to retrieve * @param mixed|false $default the default value if the parameter doesn’t exist * @return mixed|false the value of the session variable, false if doesn’t exist - * @deprecated Use typed versions instead */ + #[Deprecated('Use typed versions instead')] public static function param(string $p, $default = false): mixed { return $_SESSION[$p] ?? $default; } diff --git a/lib/Minz/View.php b/lib/Minz/View.php index 65573c7bd..eebd7ee92 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -39,8 +39,9 @@ class Minz_View { } /** - * @deprecated Change the view file based on controller and action. + * Change the view file based on controller and action. */ + #[Deprecated('Use Minz_View::_path() instead.')] public function change_view(string $controller_name, string $action_name): void { Minz_Log::warning('Minz_View::change_view is deprecated, it will be removed in a future version. Please use Minz_View::_path instead.'); $this->_path($controller_name . '/' . $action_name . '.phtml'); @@ -170,9 +171,9 @@ class Minz_View { /** * Choose if we want to use the layout or not. - * @deprecated Please use the `_layout` function instead. * @param bool $use true if we want to use the layout, false else */ + #[Deprecated('Use Minz_View::_layout() instead.')] public function _useLayout(bool $use): void { Minz_Log::warning('Minz_View::_useLayout is deprecated, it will be removed in a future version. Please use Minz_View::_layout instead.'); if ($use) { diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 7636ee225..8bc06cd36 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -307,16 +307,12 @@ function invalidateHttpCache(string $username = ''): bool { return FreshRSS_UserDAO::ctouch($username); } -/** - * @deprecated Use {@see Minz_Request::connectionRemoteAddress()} instead. - */ +#[Deprecated('Use Minz_Request::connectionRemoteAddress() instead.')] function connectionRemoteAddress(): string { return Minz_Request::connectionRemoteAddress(); } -/** - * @deprecated Use {@see FreshRSS_http_Util::checkTrustedIP()} instead. - */ +#[Deprecated('Use FreshRSS_http_Util::checkTrustedIP() instead.')] function checkTrustedIP(): bool { return FreshRSS_http_Util::checkTrustedIP(); } diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 4cde7f8ab..0132b7c4a 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -57,7 +57,11 @@ parameters: - 'Minz_Exception' ignoreErrors: - '#Only booleans are allowed in (a negated boolean|a ternary operator condition|an elseif condition|an if condition|&&|\|\|), (bool|false|int(<[0-9, max]+>)?|true|null|\|)+ given.*#' + - + message: '#Access to deprecated#' + path: app/Models/Context.php includes: + - vendor/phpstan/phpstan-deprecation-rules/rules.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - vendor/phpstan/phpstan-strict-rules/rules.neon diff --git a/tests/app/Models/SearchTest.php b/tests/app/Models/SearchTest.php index 990598dff..fad5d4768 100644 --- a/tests/app/Models/SearchTest.php +++ b/tests/app/Models/SearchTest.php @@ -251,7 +251,7 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { $previousUserConf = FreshRSS_Context::hasUserConf() ? FreshRSS_Context::userConf() : null; $newUserConf = $previousUserConf instanceof FreshRSS_UserConfiguration ? clone $previousUserConf : clone FreshRSS_UserConfiguration::default(); $newUserConf->queries = $queries; - FreshRSS_Context::$user_conf = $newUserConf; + FreshRSS_Context::setUserConf($newUserConf); try { $search = new FreshRSS_BooleanSearch($input); @@ -259,7 +259,7 @@ final class SearchTest extends \PHPUnit\Framework\TestCase { self::assertSame($expectedResult[0], trim($actualSql)); self::assertSame($expectedResult[1], $actualValues); } finally { - FreshRSS_Context::$user_conf = $previousUserConf; + FreshRSS_Context::setUserConf($previousUserConf); } }