Explorar el Código

PHPStan Level 7 for ten more files (#5327)

* PHPStan Level 7 for nine more files

* Minor syntax

* One more
Alexandre Alapetite hace 2 años
padre
commit
115724622f

+ 2 - 2
app/Controllers/indexController.php

@@ -244,9 +244,9 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
 
 	/**
 	 * This method returns a list of entries based on the Context object.
-	 * @return iterable<FreshRSS_Entry>
+	 * @return Traversable<FreshRSS_Entry>
 	 */
-	public static function listEntriesByContext(): iterable {
+	public static function listEntriesByContext(): Traversable {
 		$entryDAO = FreshRSS_Factory::createEntryDao();
 
 		$get = FreshRSS_Context::currentGet(true);

+ 2 - 2
app/Models/CategoryDAO.php

@@ -214,8 +214,8 @@ SQL;
 		}
 	}
 
-	/** @return iterable<array<string,string|int>> */
-	public function selectAll(): iterable {
+	/** @return Traversable<array<string,string|int>> */
+	public function selectAll(): Traversable {
 		$sql = 'SELECT id, name, kind, `lastUpdate`, error, attributes FROM `_category`';
 		$stm = $this->pdo->query($sql);
 		if ($stm != false) {

+ 2 - 2
app/Models/Entry.php

@@ -220,8 +220,8 @@ HTML;
 		return $content;
 	}
 
-	/** @return iterable<array{'url':string,'type'?:string,'medium'?:string,'length'?:int,'title'?:string,'description'?:string,'credit'?:string,'height'?:int,'width'?:int,'thumbnails'?:array<string>}> */
-	public function enclosures(bool $searchBodyImages = false): iterable {
+	/** @return Traversable<array{'url':string,'type'?:string,'medium'?:string,'length'?:int,'title'?:string,'description'?:string,'credit'?:string,'height'?:int,'width'?:int,'thumbnails'?:array<string>}> */
+	public function enclosures(bool $searchBodyImages = false): Traversable {
 		$attributeEnclosures = $this->attributes('enclosures');
 		if (is_array($attributeEnclosures)) {
 			// FreshRSS 1.20.1+: The enclosures are saved as attributes

+ 6 - 6
app/Models/EntryDAO.php

@@ -696,8 +696,8 @@ SQL;
 		}
 	}
 
-	/** @return iterable<array<string,string|int>> */
-	public function selectAll(): iterable {
+	/** @return Traversable<array<string,string|int>> */
+	public function selectAll(): Traversable {
 		$sql = 'SELECT id, guid, title, author, '
 			. (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
 			. ', link, date, `lastSeen`, ' . static::sqlHexEncode('hash') . ' AS hash, is_read, is_favorite, id_feed, tags, attributes '
@@ -1149,11 +1149,11 @@ SQL;
 
 	/**
 	 * @param int $id category/feed/tag ID
-	 * @return iterable<FreshRSS_Entry>
+	 * @return Traversable<FreshRSS_Entry>
 	 */
 	public function listWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
 			string $order = 'DESC', int $limit = 1, string $firstId = '',
-			?FreshRSS_BooleanSearch $filters = null, int $date_min = 0): iterable {
+			?FreshRSS_BooleanSearch $filters = null, int $date_min = 0): Traversable {
 		$stm = $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filters, $date_min);
 		if ($stm) {
 			while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
@@ -1164,9 +1164,9 @@ SQL;
 
 	/**
 	 * @param array<string> $ids
-	 * @return iterable<FreshRSS_Entry>
+	 * @return Traversable<FreshRSS_Entry>
 	 */
-	public function listByIds(array $ids, string $order = 'DESC'): iterable {
+	public function listByIds(array $ids, string $order = 'DESC'): Traversable {
 		if (count($ids) < 1) {
 			return;
 		}

+ 2 - 2
app/Models/Feed.php

@@ -480,8 +480,8 @@ class FreshRSS_Feed extends Minz_Model {
 		return $hasUniqueGuids ? $guids : $links;
 	}
 
-	/** @return iterable<FreshRSS_Entry> */
-	public function loadEntries(SimplePie $simplePie): iterable {
+	/** @return Traversable<FreshRSS_Entry> */
+	public function loadEntries(SimplePie $simplePie): Traversable {
 		$hasBadGuids = $this->attributes('hasBadGuids');
 
 		$items = $simplePie->get_items();

+ 3 - 3
app/Models/FeedDAO.php

@@ -286,8 +286,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
 		}
 	}
 
-	/** @return iterable<array<string,string|int>> */
-	public function selectAll(): iterable {
+	/** @return Traversable<array<string,string|int>> */
+	public function selectAll(): Traversable {
 		$sql = <<<'SQL'
 SELECT id, url, kind, category, name, website, description, `lastUpdate`,
 	priority, `pathEntries`, `httpAuth`, error, ttl, attributes
@@ -321,7 +321,7 @@ SQL;
 		return $feed == false ? null : $feed;
 	}
 
-	/** @return array<string>|false */
+	/** @return array<int>|false */
 	public function listFeedsIds() {
 		$sql = 'SELECT id FROM `_feed`';
 		$stm = $this->pdo->query($sql);

+ 4 - 10
app/Models/ReadingMode.php

@@ -17,9 +17,7 @@ class FreshRSS_ReadingMode {
 	 * @var string
 	 */
 	protected $title;
-	/**
-	 * @var string[]
-	 */
+	/** @var array{'c':string,'a':string,'params':array<string,mixed>} */
 	protected $urlParams;
 	/**
 	 * @var bool
@@ -28,7 +26,7 @@ class FreshRSS_ReadingMode {
 
 	/**
 	 * ReadingMode constructor.
-	 * @param array<string> $urlParams
+	 * @param array{'c':string,'a':string,'params':array<string,mixed>} $urlParams
 	 */
 	public function __construct(string $id, string $title, array $urlParams, bool $active) {
 		$this->id = $id;
@@ -60,16 +58,12 @@ class FreshRSS_ReadingMode {
 		return $this;
 	}
 
-	/**
-	 * @return array<string>
-	 */
+	/** @return array{'c':string,'a':string,'params':array<string,mixed>} */
 	public function getUrlParams(): array {
 		return $this->urlParams;
 	}
 
-	/**
-	 * @param array<string> $urlParams
-	 */
+	/** @param array{'c':string,'a':string,'params':array<string,mixed>} $urlParams */
 	public function setUrlParams(array $urlParams): FreshRSS_ReadingMode {
 		$this->urlParams = $urlParams;
 		return $this;

+ 4 - 4
app/Models/TagDAO.php

@@ -153,8 +153,8 @@ SQL;
 		}
 	}
 
-	/** @return iterable<array{'id':int,'name':string,'attributes':string}> */
-	public function selectAll(): iterable {
+	/** @return Traversable<array{'id':int,'name':string,'attributes':string}> */
+	public function selectAll(): Traversable {
 		$sql = 'SELECT id, name, attributes FROM `_tag`';
 		$stm = $this->pdo->query($sql);
 		while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
@@ -162,8 +162,8 @@ SQL;
 		}
 	}
 
-	/** @return iterable<array{'id_tag':int,'id_entry':string}> */
-	public function selectEntryTag(): iterable {
+	/** @return Traversable<array{'id_tag':int,'id_entry':string}> */
+	public function selectEntryTag(): Traversable {
 		$sql = 'SELECT id_tag, id_entry FROM `_entrytag`';
 		$stm = $this->pdo->query($sql);
 		while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {

+ 3 - 3
app/Models/Themes.php

@@ -11,7 +11,7 @@ class FreshRSS_Themes extends Minz_Model {
 	/** @return array<string> */
 	public static function getList(): array {
 		return array_values(array_diff(
-			scandir(PUBLIC_PATH . self::$themesUrl),
+			scandir(PUBLIC_PATH . self::$themesUrl) ?: [],
 			array('..', '.')
 		));
 	}
@@ -37,7 +37,7 @@ class FreshRSS_Themes extends Minz_Model {
 		if (is_dir($theme_dir)) {
 			$json_filename = $theme_dir . '/metadata.json';
 			if (file_exists($json_filename)) {
-				$content = file_get_contents($json_filename);
+				$content = file_get_contents($json_filename) ?: '';
 				$res = json_decode($content, true);
 				if ($res &&
 						!empty($res['name']) &&
@@ -75,7 +75,7 @@ class FreshRSS_Themes extends Minz_Model {
 		}
 		self::$themeIconsUrl = self::$themesUrl . $theme_id . '/icons/';
 		self::$themeIcons = is_dir(PUBLIC_PATH . self::$themeIconsUrl) ? array_fill_keys(array_diff(
-			scandir(PUBLIC_PATH . self::$themeIconsUrl),
+			scandir(PUBLIC_PATH . self::$themeIconsUrl) ?: [],
 			array('..', '.')
 		), 1) : array();
 		return $infos;

+ 2 - 2
app/Models/View.php

@@ -15,7 +15,7 @@ class FreshRSS_View extends Minz_View {
 	public $category;
 	/** @var string */
 	public $current_user;
-	/** @var array<FreshRSS_Entry> */
+	/** @var iterable<FreshRSS_Entry> */
 	public $entries;
 	/** @var FreshRSS_Entry */
 	public $entry;
@@ -49,7 +49,7 @@ class FreshRSS_View extends Minz_View {
 	public $signalError;
 
 	// Manage users
-	/** @var array<string,string|int|bool> */
+	/** @var array{'feed_count':int|false,'article_count':int|false,'database_size':int,'language':string,'mail_login':string,'enabled':bool,'is_admin':bool,'last_user_activity':string,'is_default':bool} */
 	public $details;
 	/** @var bool */
 	public $disable_aside;

+ 7 - 4
app/Services/ExportService.php

@@ -43,7 +43,7 @@ class FreshRSS_Export_Service {
 	public function generateOpml(): array {
 		$view = new FreshRSS_View();
 		$day = date('Y-m-d');
-		$view->categories = $this->category_dao->listCategories(true, true);
+		$view->categories = $this->category_dao->listCategories(true, true) ?: [];
 		$view->excludeMutedFeeds = false;
 
 		return [
@@ -67,7 +67,7 @@ class FreshRSS_Export_Service {
 	 */
 	public function generateStarredEntries(string $type): array {
 		$view = new FreshRSS_View();
-		$view->categories = $this->category_dao->listCategories(true);
+		$view->categories = $this->category_dao->listCategories(true) ?: [];
 		$day = date('Y-m-d');
 
 		$view->list_title = _t('sub.import_export.starred_list');
@@ -99,7 +99,7 @@ class FreshRSS_Export_Service {
 		}
 
 		$view = new FreshRSS_View();
-		$view->categories = $this->category_dao->listCategories(true);
+		$view->categories = $this->category_dao->listCategories(true) ?: [];
 		$view->feed = $feed;
 		$day = date('Y-m-d');
 		$filename = "feed_{$day}_" . $feed->categoryId() . '_' . $feed->id() . '.json';
@@ -127,7 +127,7 @@ class FreshRSS_Export_Service {
 	 * @return array<string,string> Keys are filenames and values are contents.
 	 */
 	public function generateAllFeedEntries(int $max_number_entries): array {
-		$feed_ids = $this->feed_dao->listFeedsIds();
+		$feed_ids = $this->feed_dao->listFeedsIds() ?: [];
 
 		$exported_files = [];
 		foreach ($feed_ids as $feed_id) {
@@ -154,6 +154,9 @@ class FreshRSS_Export_Service {
 
 		// From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly
 		$zip_file = tempnam('/tmp', 'zip');
+		if ($zip_file == false) {
+			return [$zip_filename, false];
+		}
 		$zip_archive = new ZipArchive();
 		$zip_archive->open($zip_file, ZipArchive::OVERWRITE);
 

+ 2 - 2
app/Services/ImportService.php

@@ -56,7 +56,7 @@ class FreshRSS_Import_Service {
 
 		// Get the categories by names so we can use this array to retrieve
 		// existing categories later.
-		$categories = $this->catDAO->listCategories(false);
+		$categories = $this->catDAO->listCategories(false) ?: [];
 		$categories_by_names = [];
 		foreach ($categories as $category) {
 			$categories_by_names[$category->name()] = $category;
@@ -180,7 +180,7 @@ class FreshRSS_Import_Service {
 			if (isset($feed_elt['frss:filtersActionRead'])) {
 				$feed->_filtersAction(
 					'read',
-					preg_split('/[\n\r]+/', $feed_elt['frss:filtersActionRead'])
+					preg_split('/[\n\r]+/', $feed_elt['frss:filtersActionRead']) ?: []
 				);
 			}
 

+ 1 - 0
app/views/index/reader.phtml

@@ -53,6 +53,7 @@ $MAX_TAGS_DISPLAYED = FreshRSS_Context::$user_conf->show_tags_max;
 					<?php
 						$feed = FreshRSS_CategoryDAO::findFeed($this->categories, $item->feedId());	//We most likely already have the feed object in cache
 						if ($feed == null) $feed = $item->feed();
+						if ($feed == null) continue;
 						$favoriteUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id()));
 						if ($item->isFavorite()) {
 							$favoriteUrl['params']['is_favorite'] = 0;

+ 2 - 2
app/views/user/details.phtml

@@ -28,14 +28,14 @@
 		<div class="form-group">
 			<label class="group-name"><?= _t('admin.user.feed_count') ?></label>
 			<div class="group-controls">
-				<?= format_number($this->details['feed_count']) ?>
+				<?= format_number($this->details['feed_count'] ?: 0) ?>
 			</div>
 		</div>
 
 		<div class="form-group">
 			<label class="group-name"><?= _t('admin.user.article_count') ?></label>
 			<div class="group-controls">
-				<?= format_number($this->details['article_count']) ?>
+				<?= format_number($this->details['article_count'] ?: 0) ?>
 			</div>
 		</div>
 

+ 2 - 3
cli/do-install.php

@@ -31,7 +31,7 @@ $dBparams = array(
 
 $options = getopt('', array_merge($params, $dBparams));
 
-if (!validateOptions($argv, array_merge($params, $dBparams)) || empty($options['default_user'])) {
+if (!validateOptions($argv, array_merge($params, $dBparams)) || empty($options['default_user']) || !is_string($options['default_user'])) {
 	fail('Usage: ' . basename(__FILE__) . " --default_user admin ( --auth_type form" .
 		" --environment production --base_url https://rss.example.net --allow_robots" .
 		" --language en --title FreshRSS --allow_anonymous --allow_anonymous_refresh --api_enabled" .
@@ -81,8 +81,7 @@ if (!FreshRSS_user_Controller::checkUsername($options['default_user'])) {
 }
 
 if (isset($options['auth_type']) && !in_array($options['auth_type'], array('form', 'http_auth', 'none'))) {
-	fail('FreshRSS invalid authentication method (auth_type must be one of { form, http_auth, none }): '
-		. $options['auth_type']);
+	fail('FreshRSS invalid authentication method (auth_type must be one of { form, http_auth, none })');
 }
 
 if (file_put_contents(join_path(DATA_PATH, 'config.php'),

+ 1 - 1
cli/i18n/I18nFile.php

@@ -4,7 +4,7 @@ require_once __DIR__ . '/I18nValue.php';
 
 class I18nFile {
 	/**
-	 * @return array<string,array<string,string|array<string,I18nValue>>>
+	 * @return array<string,array<string,array<string,I18nValue>>>
 	 */
 	public function load(): array {
 		$i18n = array();

+ 12 - 8
p/api/greader.php

@@ -284,7 +284,7 @@ final class GReaderAPI {
 		);
 
 		$categoryDAO = FreshRSS_Factory::createCategoryDao();
-		$categories = $categoryDAO->listCategories(true, false);
+		$categories = $categoryDAO->listCategories(true, false) ?: [];
 		foreach ($categories as $cat) {
 			$tags[] = array(
 				'id' => 'user/-/label/' . htmlspecialchars_decode($cat->name(), ENT_QUOTES),
@@ -294,7 +294,7 @@ final class GReaderAPI {
 		}
 
 		$tagDAO = FreshRSS_Factory::createTagDao();
-		$labels = $tagDAO->listTags(true);
+		$labels = $tagDAO->listTags(true) ?: [];
 		foreach ($labels as $label) {
 			$tags[] = array(
 				'id' => 'user/-/label/' . htmlspecialchars_decode($label->name(), ENT_QUOTES),
@@ -345,7 +345,7 @@ final class GReaderAPI {
 		$subscriptions = array();
 
 		$categoryDAO = FreshRSS_Factory::createCategoryDao();
-		foreach ($categoryDAO->listCategories(true, true) as $cat) {
+		foreach ($categoryDAO->listCategories(true, true) ?: [] as $cat) {
 			foreach ($cat->feeds() as $feed) {
 				$subscriptions[] = [
 					'id' => 'feed/' . $feed->id(),
@@ -418,7 +418,7 @@ final class GReaderAPI {
 					if ($action === 'subscribe') {
 						continue;
 					}
-					$feedId = $streamUrl;
+					$feedId = (int)$streamUrl;
 				} else {
 					$streamUrl = htmlspecialchars($streamUrl, ENT_COMPAT, 'UTF-8');
 					$feed = $feedDAO->searchByUrl($streamUrl);
@@ -497,7 +497,7 @@ final class GReaderAPI {
 		$feedDAO = FreshRSS_Factory::createFeedDao();
 		$feedsNewestItemUsec = $feedDAO->listFeedsNewestItemUsec();
 
-		foreach ($categoryDAO->listCategories(true, true) as $cat) {
+		foreach ($categoryDAO->listCategories(true, true) ?: [] as $cat) {
 			$catLastUpdate = 0;
 			foreach ($cat->feeds() as $feed) {
 				$lastUpdate = $feedsNewestItemUsec['f_' . $feed->id()] ?? 0;
@@ -523,7 +523,7 @@ final class GReaderAPI {
 
 		$tagDAO = FreshRSS_Factory::createTagDao();
 		$tagsNewestItemUsec = $tagDAO->listTagsNewestItemUsec();
-		foreach ($tagDAO->listTags(true) as $label) {
+		foreach ($tagDAO->listTags(true) ?: [] as $label) {
 			$lastUpdate = $tagsNewestItemUsec['t_' . $label->id()] ?? 0;
 			$unreadcounts[] = array(
 				'id' => 'user/-/label/' . htmlspecialchars_decode($label->name(), ENT_QUOTES),
@@ -554,7 +554,7 @@ final class GReaderAPI {
 			return array();
 		}
 		$catDAO = FreshRSS_Factory::createCategoryDao();
-		$categories = $catDAO->listCategories(true);
+		$categories = $catDAO->listCategories(true) ?: [];
 
 		$tagDAO = FreshRSS_Factory::createTagDao();
 		$entryIdsTagNames = $tagDAO->getEntryIdsTagNames($entries);
@@ -568,6 +568,9 @@ final class GReaderAPI {
 			}
 
 			$feed = FreshRSS_CategoryDAO::findFeed($categories, $entry->feedId());
+			if ($feed === null) {
+				continue;
+			}
 			$entry->_feed($feed);
 
 			if (isset($entryIdsTagNames['e_' . $entry->id()])) {
@@ -580,8 +583,9 @@ final class GReaderAPI {
 	}
 
 	/**
+	 * @param 'A'|'c'|'f'|'s' $type
 	 * @param string|int $streamId
-	 * @return array{string,int,int,FreshRSS_BooleanSearch}
+	 * @return array{'A'|'c'|'f'|'s'|'t',int,int,FreshRSS_BooleanSearch}
 	 */
 	private static function streamContentsFilters(string $type, $streamId,
 		string $filter_target, string $exclude_target, int $start_time, int $stop_time): array {

+ 1 - 1
tests/cli/i18n/I18nFileTest.php

@@ -15,7 +15,7 @@ class I18nFileTest extends PHPUnit\Framework\TestCase {
 		$this->assertEquals($before, $after);
 	}
 
-	/** @return array<string,string> */
+	/** @return array<string,string|false> */
 	private function computeFilesHash(): array {
 		$hashes = [];
 

+ 0 - 10
tests/phpstan-next.txt

@@ -16,27 +16,17 @@
 ./app/Models/EntryDAO.php
 ./app/Models/Feed.php
 ./app/Models/FeedDAO.php
-./app/Models/ReadingMode.php
 ./app/Models/Search.php
 ./app/Models/Share.php
 ./app/Models/StatsDAO.php
 ./app/Models/TagDAO.php
-./app/Models/Themes.php
-./app/Services/ExportService.php
-./app/Services/ImportService.php
 ./app/views/helpers/logs_pagination.phtml
-./app/views/index/reader.phtml
 ./app/views/stats/index.phtml
 ./app/views/stats/repartition.phtml
-./app/views/user/details.phtml
 ./cli/check.translation.php
-./cli/delete-user.php
-./cli/do-install.php
 ./cli/manipulate.translation.php
 ./lib/Minz/Error.php
 ./lib/Minz/Mailer.php
 ./lib/Minz/Migrator.php
 ./lib/Minz/ModelPdo.php
 ./lib/Minz/Request.php
-./p/api/greader.php
-./tests/cli/i18n/I18nFileTest.php