Quellcode durchsuchen

PHP 8.3 #[\Override] (#6273)

* PHP 8.3 #[\Override]
https://php.watch/versions/8.3/override-attr

With PHPStan `checkMissingOverrideMethodAttribute` https://phpstan.org/config-reference#checkmissingoverridemethodattribute

And modified the call to phpstan-next on the model of https://github.com/FreshRSS/Extensions/pull/228 (more robust than the find method, which gave some strange errors)

* Update extension example accordingly
Alexandre Alapetite vor 2 Jahren
Ursprung
Commit
350edf398c
44 geänderte Dateien mit 128 neuen und 30 gelöschten Zeilen
  1. 1 0
      app/Controllers/categoryController.php
  2. 1 0
      app/Controllers/configureController.php
  3. 1 0
      app/Controllers/entryController.php
  4. 1 0
      app/Controllers/extensionController.php
  5. 1 0
      app/Controllers/feedController.php
  6. 1 0
      app/Controllers/importExportController.php
  7. 1 0
      app/Controllers/indexController.php
  8. 1 0
      app/Controllers/javascriptController.php
  9. 1 0
      app/Controllers/statsController.php
  10. 1 0
      app/Controllers/subscriptionController.php
  11. 1 0
      app/Controllers/tagController.php
  12. 1 0
      app/Controllers/updateController.php
  13. 1 0
      app/Models/BooleanSearch.php
  14. 1 0
      app/Models/CategoryDAOSQLite.php
  15. 5 1
      app/Models/DatabaseDAOPGSQL.php
  16. 7 0
      app/Models/DatabaseDAOSQLite.php
  17. 6 0
      app/Models/EntryDAOPGSQL.php
  18. 9 0
      app/Models/EntryDAOSQLite.php
  19. 1 0
      app/Models/FeedDAOSQLite.php
  20. 1 0
      app/Models/Search.php
  21. 4 0
      app/Models/StatsDAOPGSQL.php
  22. 2 0
      app/Models/StatsDAOSQLite.php
  23. 1 0
      app/Models/TagDAOPGSQL.php
  24. 1 0
      app/Models/TagDAOSQLite.php
  25. 3 0
      cli/i18n/I18nCompletionValidator.php
  26. 3 0
      cli/i18n/I18nUsageValidator.php
  27. 1 0
      cli/i18n/I18nValue.php
  28. 1 1
      composer.json
  29. 7 7
      composer.lock
  30. 13 2
      docs/en/developers/03_Backend/05_Extensions.md
  31. 13 2
      docs/fr/developers/03_Backend/05_Extensions.md
  32. 4 0
      lib/Minz/Pdo.php
  33. 2 0
      lib/Minz/PdoMysql.php
  34. 2 0
      lib/Minz/PdoPgsql.php
  35. 2 0
      lib/Minz/PdoSqlite.php
  36. 1 1
      package-lock.json
  37. 1 1
      package.json
  38. 18 0
      phpstan-next.neon
  39. 1 0
      phpstan.neon
  40. 2 0
      tests/app/Models/LogDAOTest.php
  41. 1 0
      tests/cli/i18n/I18nCompletionValidatorTest.php
  42. 1 0
      tests/cli/i18n/I18nDataTest.php
  43. 1 0
      tests/cli/i18n/I18nUsageValidatorTest.php
  44. 0 15
      tests/phpstan-next.txt

+ 1 - 0
app/Controllers/categoryController.php

@@ -12,6 +12,7 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
 	 * underlying framework.
 	 *
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess()) {
 			Minz_Error::error(403);

+ 1 - 0
app/Controllers/configureController.php

@@ -10,6 +10,7 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
 	 * the common boilerplate for every action. It is triggered by the
 	 * underlying framework.
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess()) {
 			Minz_Error::error(403);

+ 1 - 0
app/Controllers/entryController.php

@@ -16,6 +16,7 @@ class FreshRSS_entry_Controller extends FreshRSS_ActionController {
 	 * the common boilerplate for every action. It is triggered by the
 	 * underlying framework.
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess()) {
 			Minz_Error::error(403);

+ 1 - 0
app/Controllers/extensionController.php

@@ -10,6 +10,7 @@ class FreshRSS_extension_Controller extends FreshRSS_ActionController {
 	 * the common boiler plate for every action. It is triggered by the
 	 * underlying framework.
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess()) {
 			Minz_Error::error(403);

+ 1 - 0
app/Controllers/feedController.php

@@ -10,6 +10,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
 	 * the common boiler plate for every action. It is triggered by the
 	 * underlying framework.
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess()) {
 			// Token is useful in the case that anonymous refresh is forbidden

+ 1 - 0
app/Controllers/importExportController.php

@@ -15,6 +15,7 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
 	 * the common boilerplate for every action. It is triggered by the
 	 * underlying framework.
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess()) {
 			Minz_Error::error(403);

+ 1 - 0
app/Controllers/indexController.php

@@ -6,6 +6,7 @@ declare(strict_types=1);
  */
 class FreshRSS_index_Controller extends FreshRSS_ActionController {
 
+	#[\Override]
 	public function firstAction(): void {
 		$this->view->html_url = Minz_Url::display(['c' => 'index', 'a' => 'index'], 'html', 'root');
 	}

+ 1 - 0
app/Controllers/javascriptController.php

@@ -12,6 +12,7 @@ class FreshRSS_javascript_Controller extends FreshRSS_ActionController {
 		parent::__construct(FreshRSS_ViewJavascript::class);
 	}
 
+	#[\Override]
 	public function firstAction(): void {
 		$this->view->_layout(null);
 	}

+ 1 - 0
app/Controllers/statsController.php

@@ -20,6 +20,7 @@ class FreshRSS_stats_Controller extends FreshRSS_ActionController {
 	 * the common boilerplate for every action. It is triggered by the
 	 * underlying framework.
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess()) {
 			Minz_Error::error(403);

+ 1 - 0
app/Controllers/subscriptionController.php

@@ -10,6 +10,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
 	 * the common boilerplate for every action. It is triggered by the
 	 * underlying framework.
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess()) {
 			Minz_Error::error(403);

+ 1 - 0
app/Controllers/tagController.php

@@ -16,6 +16,7 @@ class FreshRSS_tag_Controller extends FreshRSS_ActionController {
 	 * the common boilerplate for every action. It is triggered by the
 	 * underlying framework.
 	 */
+	#[\Override]
 	public function firstAction(): void {
 		// If ajax request, we do not print layout
 		$this->ajax = Minz_Request::paramBoolean('ajax');

+ 1 - 0
app/Controllers/updateController.php

@@ -113,6 +113,7 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
 		return $return == 0 ? true : 'Git error: ' . $line;
 	}
 
+	#[\Override]
 	public function firstAction(): void {
 		if (!FreshRSS_Auth::hasAccess('admin')) {
 			Minz_Error::error(403);

+ 1 - 0
app/Models/BooleanSearch.php

@@ -291,6 +291,7 @@ class FreshRSS_BooleanSearch {
 		$this->searches[] = $search;
 	}
 
+	#[\Override]
 	public function __toString(): string {
 		return $this->getRawInput();
 	}

+ 1 - 0
app/Models/CategoryDAOSQLite.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 class FreshRSS_CategoryDAOSQLite extends FreshRSS_CategoryDAO {
 
 	/** @param array<int|string> $errorInfo */
+	#[\Override]
 	protected function autoUpdateDb(array $errorInfo): bool {
 		if ($tableInfo = $this->pdo->query("PRAGMA table_info('category')")) {
 			$columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1);

+ 5 - 1
app/Models/DatabaseDAOPGSQL.php

@@ -10,6 +10,7 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite {
 	public const UNDEFINED_COLUMN = '42703';
 	public const UNDEFINED_TABLE = '42P01';
 
+	#[\Override]
 	public function tablesAreCorrect(): bool {
 		$db = FreshRSS_Context::systemConf()->db;
 		$sql = 'SELECT * FROM pg_catalog.pg_tables where tableowner=:tableowner';
@@ -34,6 +35,7 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite {
 	}
 
 	/** @return array<array<string,string|int|bool|null>> */
+	#[\Override]
 	public function getSchema(string $table): array {
 		$sql = <<<'SQL'
 SELECT column_name AS field, data_type AS type, column_default AS default, is_nullable AS null
@@ -47,6 +49,7 @@ SQL;
 	 * @param array<string,string|int|bool|null> $dao
 	 * @return array{'name':string,'type':string,'notnull':bool,'default':mixed}
 	 */
+	#[\Override]
 	public function daoToSchema(array $dao): array {
 		return [
 			'name' => (string)($dao['field']),
@@ -56,6 +59,7 @@ SQL;
 		];
 	}
 
+	#[\Override]
 	public function size(bool $all = false): int {
 		if ($all) {
 			$db = FreshRSS_Context::systemConf()->db;
@@ -75,7 +79,7 @@ SQL;
 		return (int)($res[0] ?? -1);
 	}
 
-
+	#[\Override]
 	public function optimize(): bool {
 		$ok = true;
 		$tables = ['category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag'];

+ 7 - 0
app/Models/DatabaseDAOSQLite.php

@@ -6,6 +6,7 @@ declare(strict_types=1);
  */
 class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
 
+	#[\Override]
 	public function tablesAreCorrect(): bool {
 		$sql = 'SELECT name FROM sqlite_master WHERE type="table"';
 		$stm = $this->pdo->query($sql);
@@ -30,18 +31,21 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
 	}
 
 	/** @return array<array<string,string|int|bool|null>> */
+	#[\Override]
 	public function getSchema(string $table): array {
 		$sql = 'PRAGMA table_info(' . $table . ')';
 		$stm = $this->pdo->query($sql);
 		return $stm ? $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];
 	}
 
+	#[\Override]
 	public function entryIsCorrect(): bool {
 		return $this->checkTable('entry', [
 			'id', 'guid', 'title', 'author', 'content', 'link', 'date', 'lastSeen', 'hash', 'is_read', 'is_favorite', 'id_feed', 'tags',
 		]);
 	}
 
+	#[\Override]
 	public function entrytmpIsCorrect(): bool {
 		return $this->checkTable('entrytmp', [
 			'id', 'guid', 'title', 'author', 'content', 'link', 'date', 'lastSeen', 'hash', 'is_read', 'is_favorite', 'id_feed', 'tags'
@@ -52,6 +56,7 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
 	 * @param array<string,string|int|bool|null> $dao
 	 * @return array{'name':string,'type':string,'notnull':bool,'default':mixed}
 	 */
+	#[\Override]
 	public function daoToSchema(array $dao): array {
 		return [
 			'name'    => (string)$dao['name'],
@@ -61,6 +66,7 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
 		];
 	}
 
+	#[\Override]
 	public function size(bool $all = false): int {
 		$sum = 0;
 		if ($all) {
@@ -73,6 +79,7 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
 		return $sum;
 	}
 
+	#[\Override]
 	public function optimize(): bool {
 		$ok = $this->pdo->exec('VACUUM') !== false;
 		if (!$ok) {

+ 6 - 0
app/Models/EntryDAOPGSQL.php

@@ -3,23 +3,28 @@ declare(strict_types=1);
 
 class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
 
+	#[\Override]
 	public static function hasNativeHex(): bool {
 		return true;
 	}
 
+	#[\Override]
 	public static function sqlHexDecode(string $x): string {
 		return 'decode(' . $x . ", 'hex')";
 	}
 
+	#[\Override]
 	public static function sqlHexEncode(string $x): string {
 		return 'encode(' . $x . ", 'hex')";
 	}
 
+	#[\Override]
 	public static function sqlIgnoreConflict(string $sql): string {
 		return rtrim($sql, ' ;') . ' ON CONFLICT DO NOTHING';
 	}
 
 	/** @param array<string|int> $errorInfo */
+	#[\Override]
 	protected function autoUpdateDb(array $errorInfo): bool {
 		if (isset($errorInfo[0])) {
 			if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) {
@@ -34,6 +39,7 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
 		return false;
 	}
 
+	#[\Override]
 	public function commitNewEntries(): bool {
 		//TODO: Update to PostgreSQL 9.5+ syntax with ON CONFLICT DO NOTHING
 		$sql = 'DO $$

+ 9 - 0
app/Models/EntryDAOSQLite.php

@@ -3,27 +3,33 @@ declare(strict_types=1);
 
 class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
 
+	#[\Override]
 	public static function isCompressed(): bool {
 		return false;
 	}
 
+	#[\Override]
 	public static function hasNativeHex(): bool {
 		return false;
 	}
 
+	#[\Override]
 	protected static function sqlConcat(string $s1, string $s2): string {
 		return $s1 . '||' . $s2;
 	}
 
+	#[\Override]
 	public static function sqlHexDecode(string $x): string {
 		return $x;
 	}
 
+	#[\Override]
 	public static function sqlIgnoreConflict(string $sql): string {
 		return str_replace('INSERT INTO ', 'INSERT OR IGNORE INTO ', $sql);
 	}
 
 	/** @param array<string|int> $errorInfo */
+	#[\Override]
 	protected function autoUpdateDb(array $errorInfo): bool {
 		if ($tableInfo = $this->pdo->query("PRAGMA table_info('entry')")) {
 			$columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1) ?: [];
@@ -36,6 +42,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
 		return false;
 	}
 
+	#[\Override]
 	public function commitNewEntries(): bool {
 		$sql = <<<'SQL'
 DROP TABLE IF EXISTS `tmp`;
@@ -74,6 +81,7 @@ SQL;
 	 * @param bool $is_read
 	 * @return int|false affected rows
 	 */
+	#[\Override]
 	public function markRead($ids, bool $is_read = true) {
 		FreshRSS_UserDAO::touch();
 		if (is_array($ids)) {	//Many IDs at once (used by API)
@@ -119,6 +127,7 @@ SQL;
 	 * @param string $idMax max article ID
 	 * @return int|false affected rows
 	 */
+	#[\Override]
 	public function markReadTag($id = 0, string $idMax = '0', ?FreshRSS_BooleanSearch $filters = null, int $state = 0, bool $is_read = true) {
 		FreshRSS_UserDAO::touch();
 		if ($idMax == 0) {

+ 1 - 0
app/Models/FeedDAOSQLite.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO {
 
 	/** @param array<int|string> $errorInfo */
+	#[\Override]
 	protected function autoUpdateDb(array $errorInfo): bool {
 		if ($tableInfo = $this->pdo->query("PRAGMA table_info('feed')")) {
 			$columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1);

+ 1 - 0
app/Models/Search.php

@@ -107,6 +107,7 @@ class FreshRSS_Search {
 		$this->parseSearch($input);
 	}
 
+	#[\Override]
 	public function __toString(): string {
 		return $this->getRawInput();
 	}

+ 4 - 0
app/Models/StatsDAOPGSQL.php

@@ -9,6 +9,7 @@ class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO {
 	 * @param int $feed id
 	 * @return array<int,int>
 	 */
+	#[\Override]
 	public function calculateEntryRepartitionPerFeedPerHour(?int $feed = null): array {
 		return $this->calculateEntryRepartitionPerFeedPerPeriod('hour', $feed);
 	}
@@ -17,6 +18,7 @@ class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO {
 	 * Calculates the number of article per day of week per feed
 	 * @return array<int,int>
 	 */
+	#[\Override]
 	public function calculateEntryRepartitionPerFeedPerDayOfWeek(?int $feed = null): array {
 		return $this->calculateEntryRepartitionPerFeedPerPeriod('day', $feed);
 	}
@@ -25,6 +27,7 @@ class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO {
 	 * Calculates the number of article per month per feed
 	 * @return array<int,int>
 	 */
+	#[\Override]
 	public function calculateEntryRepartitionPerFeedPerMonth(?int $feed = null): array {
 		return $this->calculateEntryRepartitionPerFeedPerPeriod('month', $feed);
 	}
@@ -34,6 +37,7 @@ class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO {
 	 * @param string $period format string to use for grouping
 	 * @return array<int,int>
 	 */
+	#[\Override]
 	protected function calculateEntryRepartitionPerFeedPerPeriod(string $period, ?int $feed = null): array {
 		$restrict = '';
 		if ($feed) {

+ 2 - 0
app/Models/StatsDAOSQLite.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
 
+	#[\Override]
 	protected function sqlFloor(string $s): string {
 		return "CAST(($s) AS INT)";
 	}
@@ -10,6 +11,7 @@ class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
 	/**
 	 * @return array<int,int>
 	 */
+	#[\Override]
 	protected function calculateEntryRepartitionPerFeedPerPeriod(string $period, ?int $feed = null): array {
 		if ($feed) {
 			$restrict = "WHERE e.id_feed = {$feed}";

+ 1 - 0
app/Models/TagDAOPGSQL.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 class FreshRSS_TagDAOPGSQL extends FreshRSS_TagDAO {
 
+	#[\Override]
 	public function sqlIgnore(): string {
 		return '';	//TODO
 	}

+ 1 - 0
app/Models/TagDAOSQLite.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 
 class FreshRSS_TagDAOSQLite extends FreshRSS_TagDAO {
 
+	#[\Override]
 	public function sqlIgnore(): string {
 		return 'OR IGNORE';
 	}

+ 3 - 0
cli/i18n/I18nCompletionValidator.php

@@ -22,6 +22,7 @@ class I18nCompletionValidator implements I18nValidatorInterface {
 		$this->language = $language;
 	}
 
+	#[\Override]
 	public function displayReport(): string {
 		if ($this->passEntries > $this->totalEntries) {
 			throw new \RuntimeException('The number of translated strings cannot be higher than the number of strings');
@@ -32,10 +33,12 @@ class I18nCompletionValidator implements I18nValidatorInterface {
 		return sprintf('Translation is %5.1f%% complete.', $this->passEntries / $this->totalEntries * 100) . PHP_EOL;
 	}
 
+	#[\Override]
 	public function displayResult(): string {
 		return $this->result;
 	}
 
+	#[\Override]
 	public function validate(): bool {
 		foreach ($this->reference as $file => $data) {
 			foreach ($data as $refKey => $refValue) {

+ 3 - 0
cli/i18n/I18nUsageValidator.php

@@ -22,6 +22,7 @@ class I18nUsageValidator implements I18nValidatorInterface {
 		$this->reference = $reference;
 	}
 
+	#[\Override]
 	public function displayReport(): string {
 		if ($this->failedEntries > $this->totalEntries) {
 			throw new \RuntimeException('The number of unused strings cannot be higher than the number of strings');
@@ -32,10 +33,12 @@ class I18nUsageValidator implements I18nValidatorInterface {
 		return sprintf('%5.1f%% of translation keys are unused.', $this->failedEntries / $this->totalEntries * 100) . PHP_EOL;
 	}
 
+	#[\Override]
 	public function displayResult(): string {
 		return $this->result;
 	}
 
+	#[\Override]
 	public function validate(): bool {
 		foreach ($this->reference as $file => $data) {
 			foreach ($data as $key => $value) {

+ 1 - 0
cli/i18n/I18nValue.php

@@ -66,6 +66,7 @@ class I18nValue {
 		}
 	}
 
+	#[\Override]
 	public function __toString(): string {
 		if ($this->state === null) {
 			return $this->value;

+ 1 - 1
composer.json

@@ -69,7 +69,7 @@
         "phpcs": "phpcs . -s",
         "phpcbf": "phpcbf . -p -s",
         "phpstan": "phpstan analyse --memory-limit 512M .",
-        "phpstan-next": "phpstan analyse --level 9 --memory-limit 512M $(find . -type d -name 'vendor' -prune -o -name '*.php' -o -name '*.phtml' | grep -Fxvf ./tests/phpstan-next.txt | sort | paste -s -)",
+        "phpstan-next": "phpstan analyse --memory-limit 512M -c phpstan-next.neon .",
         "phpunit": "phpunit --bootstrap ./tests/bootstrap.php --verbose ./tests",
         "translations": "cli/manipulate.translation.php -a format",
         "test": [

+ 7 - 7
composer.lock

@@ -428,21 +428,21 @@
         },
         {
             "name": "phpstan/phpstan-strict-rules",
-            "version": "1.5.2",
+            "version": "1.5.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpstan/phpstan-strict-rules.git",
-                "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542"
+                "reference": "568210bd301f94a0d4b1e5a0808c374c1b9cf11b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542",
-                "reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542",
+                "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/568210bd301f94a0d4b1e5a0808c374c1b9cf11b",
+                "reference": "568210bd301f94a0d4b1e5a0808c374c1b9cf11b",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.2 || ^8.0",
-                "phpstan/phpstan": "^1.10.34"
+                "phpstan/phpstan": "^1.10.60"
             },
             "require-dev": {
                 "nikic/php-parser": "^4.13.0",
@@ -471,9 +471,9 @@
             "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/1.5.2"
+                "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.3"
             },
-            "time": "2023-10-30T14:35:06+00:00"
+            "time": "2024-04-06T07:43:25+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",

+ 13 - 2
docs/en/developers/03_Backend/05_Extensions.md

@@ -132,15 +132,26 @@ The `Minz_Extension` abstract class defines another set of methods that should n
 You can register at the FreshRSS event system in an extensions `init()` method, to manipulate data when some of the core functions are executed.
 
 ```php
-class HelloWorldExtension extends Minz_Extension
+final class HelloWorldExtension extends Minz_Extension
 {
+	#[\Override]
 	public function init(): void {
 		$this->registerHook('entry_before_display', [$this, 'renderEntry']);
+		$this->registerHook('check_url_before_add', [self::class, 'checkUrl']);
 	}
+
 	public function renderEntry(FreshRSS_Entry $entry): FreshRSS_Entry {
-		$entry->_content('<h1>Hello World</h1>' . $entry->content());
+		$message = $this->getUserConfigurationValue('message');
+		$entry->_content("<h1>{$message}</h1>" . $entry->content());
 		return $entry;
 	}
+
+	public static function checkUrlBeforeAdd(string $url): string {
+		if (str_starts_with($url, 'https://')) {
+			return $url;
+		}
+		return null;
+	}
 }
 ```
 

+ 13 - 2
docs/fr/developers/03_Backend/05_Extensions.md

@@ -188,15 +188,26 @@ You can register at the FreshRSS event system in an extensions `init()`
 method, to manipulate data when some of the core functions are executed.
 
 ```php
-class HelloWorldExtension extends Minz_Extension
+final class HelloWorldExtension extends Minz_Extension
 {
+	#[\Override]
 	public function init(): void {
 		$this->registerHook('entry_before_display', [$this, 'renderEntry']);
+		$this->registerHook('check_url_before_add', [self::class, 'checkUrl']);
 	}
+
 	public function renderEntry(FreshRSS_Entry $entry): FreshRSS_Entry {
-		$entry->_content('<h1>Hello World</h1>' . $entry->content());
+		$message = $this->getUserConfigurationValue('message');
+		$entry->_content("<h1>{$message}</h1>" . $entry->content());
 		return $entry;
 	}
+
+	public static function checkUrlBeforeAdd(string $url): string {
+		if (str_starts_with($url, 'https://')) {
+			return $url;
+		}
+		return null;
+	}
 }
 ```
 

+ 4 - 0
lib/Minz/Pdo.php

@@ -43,6 +43,7 @@ abstract class Minz_Pdo extends PDO {
 	 * @return string|false
 	 * @throws PDOException if the attribute `PDO::ATTR_ERRMODE` is set to `PDO::ERRMODE_EXCEPTION`
 	 */
+	#[\Override]
 	#[\ReturnTypeWillChange]
 	public function lastInsertId($name = null) {
 		if ($name != null) {
@@ -59,6 +60,7 @@ abstract class Minz_Pdo extends PDO {
 	 * @throws PDOException if the attribute `PDO::ATTR_ERRMODE` is set to `PDO::ERRMODE_EXCEPTION`
 	 * @phpstan-ignore-next-line
 	 */
+	#[\Override]
 	#[\ReturnTypeWillChange]
 	public function prepare($query, $options = []) {
 		$query = $this->preSql($query);
@@ -72,6 +74,7 @@ abstract class Minz_Pdo extends PDO {
 	 * @throws PDOException if the attribute `PDO::ATTR_ERRMODE` is set to `PDO::ERRMODE_EXCEPTION`
 	 * @phpstan-ignore-next-line
 	 */
+	#[\Override]
 	#[\ReturnTypeWillChange]
 	public function exec($statement) {
 		$statement = $this->preSql($statement);
@@ -83,6 +86,7 @@ abstract class Minz_Pdo extends PDO {
 	 * @throws PDOException if the attribute `PDO::ATTR_ERRMODE` is set to `PDO::ERRMODE_EXCEPTION`
 	 * @phpstan-ignore-next-line
 	 */
+	#[\Override]
 	#[\ReturnTypeWillChange]
 	public function query(string $query, ?int $fetch_mode = null, ...$fetch_mode_args) {
 		$query = $this->preSql($query);

+ 2 - 0
lib/Minz/PdoMysql.php

@@ -16,6 +16,7 @@ class Minz_PdoMysql extends Minz_Pdo {
 		$this->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
 	}
 
+	#[\Override]
 	public function dbType(): string {
 		return 'mysql';
 	}
@@ -25,6 +26,7 @@ class Minz_PdoMysql extends Minz_Pdo {
 	 * @return string|false
 	 * @throws PDOException if the attribute `PDO::ATTR_ERRMODE` is set to `PDO::ERRMODE_EXCEPTION`
 	 */
+	#[\Override]
 	#[\ReturnTypeWillChange]
 	public function lastInsertId($name = null) {
 		return parent::lastInsertId();	//We discard the name, only used by PostgreSQL

+ 2 - 0
lib/Minz/PdoPgsql.php

@@ -16,10 +16,12 @@ class Minz_PdoPgsql extends Minz_Pdo {
 		$this->exec("SET NAMES 'UTF8';");
 	}
 
+	#[\Override]
 	public function dbType(): string {
 		return 'pgsql';
 	}
 
+	#[\Override]
 	protected function preSql(string $statement): string {
 		$statement = parent::preSql($statement);
 		return str_replace(array('`', ' LIKE '), array('"', ' ILIKE '), $statement);

+ 2 - 0
lib/Minz/PdoSqlite.php

@@ -16,6 +16,7 @@ class Minz_PdoSqlite extends Minz_Pdo {
 		$this->exec('PRAGMA foreign_keys = ON;');
 	}
 
+	#[\Override]
 	public function dbType(): string {
 		return 'sqlite';
 	}
@@ -25,6 +26,7 @@ class Minz_PdoSqlite extends Minz_Pdo {
 	 * @return string|false
 	 * @throws PDOException if the attribute `PDO::ATTR_ERRMODE` is set to `PDO::ERRMODE_EXCEPTION`
 	 */
+	#[\Override]
 	#[\ReturnTypeWillChange]
 	public function lastInsertId($name = null) {
 		return parent::lastInsertId();	//We discard the name, only used by PostgreSQL

+ 1 - 1
package-lock.json

@@ -14,7 +14,7 @@
         "eslint-plugin-promise": "^6.1.1",
         "markdownlint-cli": "^0.39.0",
         "rtlcss": "^4.1.1",
-        "sass": "^1.72.0",
+        "sass": "^1.74.1",
         "stylelint": "^15.11.0",
         "stylelint-config-recommended-scss": "^13.1.0",
         "stylelint-order": "^6.0.4",

+ 1 - 1
package.json

@@ -40,7 +40,7 @@
     "eslint-plugin-promise": "^6.1.1",
     "markdownlint-cli": "^0.39.0",
     "rtlcss": "^4.1.1",
-    "sass": "^1.72.0",
+    "sass": "^1.74.1",
     "stylelint": "^15.11.0",
     "stylelint-config-recommended-scss": "^13.1.0",
     "stylelint-order": "^6.0.4",

+ 18 - 0
phpstan-next.neon

@@ -0,0 +1,18 @@
+includes:
+	- phpstan.neon
+
+parameters:
+	level: 9
+	excludePaths:
+		analyse:
+			# TODO: Update files below and remove them from this list
+			- app/Controllers/configureController.php
+			- app/Controllers/importExportController.php
+			- app/Models/DatabaseDAO.php
+			- app/Models/Entry.php
+			- app/Models/Feed.php
+			- app/Services/ImportService.php
+			- app/views/configure/archiving.phtml
+			- app/views/helpers/feed/update.phtml
+			- lib/Minz/Helper.php
+			- lib/Minz/Request.php

+ 1 - 0
phpstan.neon

@@ -35,6 +35,7 @@ parameters:
 		- STDOUT
 		- TMP_PATH
 		- USERS_PATH
+	checkMissingOverrideMethodAttribute: true
 	reportMaybesInPropertyPhpDocTypes: false
 	treatPhpDocTypesAsCertain: false
 	strictRules:

+ 2 - 0
tests/app/Models/LogDAOTest.php

@@ -10,6 +10,7 @@ class LogDAOTest extends TestCase {
 
 	private string $logPath;
 
+	#[\Override]
 	protected function setUp(): void {
 		$this->logDAO = new FreshRSS_LogDAO();
 		$this->logPath = FreshRSS_LogDAO::logPath(self::LOG_FILE_TEST);
@@ -36,6 +37,7 @@ class LogDAOTest extends TestCase {
 		self::assertStringContainsString('', file_get_contents($this->logPath) ?: '');
 	}
 
+	#[\Override]
 	protected function tearDown(): void {
 		unlink($this->logPath);
 	}

+ 1 - 0
tests/cli/i18n/I18nCompletionValidatorTest.php

@@ -7,6 +7,7 @@ class I18nCompletionValidatorTest extends PHPUnit\Framework\TestCase {
 	/** @var I18nValue&PHPUnit\Framework\MockObject\MockObject */
 	private $value;
 
+	#[\Override]
 	public function setUp(): void {
 		$this->value = $this->getMockBuilder(I18nValue::class)
 			->disableOriginalConstructor()

+ 1 - 0
tests/cli/i18n/I18nDataTest.php

@@ -9,6 +9,7 @@ class I18nDataTest extends PHPUnit\Framework\TestCase {
 	/** @var I18nValue&PHPUnit\Framework\MockObject\MockObject */
 	private $value;
 
+	#[\Override]
 	public function setUp(): void {
 		$this->value = $this->getMockBuilder(I18nValue::class)
 			->disableOriginalConstructor()

+ 1 - 0
tests/cli/i18n/I18nUsageValidatorTest.php

@@ -7,6 +7,7 @@ class I18nUsageValidatorTest extends PHPUnit\Framework\TestCase {
 
 	private I18nValue $value;
 
+	#[\Override]
 	public function setUp(): void {
 		$this->value = $this->getMockBuilder(I18nValue::class)
 			->disableOriginalConstructor()

+ 0 - 15
tests/phpstan-next.txt

@@ -1,15 +0,0 @@
-# List of files, which are not yet passing PHPStan level 9 https://phpstan.org/user-guide/rule-levels
-# Used for automated tests to avoid regressions in files already passing that level.
-# Can be regenerated with something like:
-# find . -type d -name 'vendor' -prune -o -name '*.php' -exec sh -c 'vendor/bin/phpstan analyse --level 9 --memory-limit 512M {} >/dev/null 2>/dev/null || echo {}' \;
-
-./app/Controllers/configureController.php
-./app/Controllers/importExportController.php
-./app/Models/DatabaseDAO.php
-./app/Models/Entry.php
-./app/Models/Feed.php
-./app/Services/ImportService.php
-./app/views/configure/archiving.phtml
-./app/views/helpers/feed/update.phtml
-./lib/Minz/Helper.php
-./lib/Minz/Request.php