From a29304c89fa5f68227a8b6221f97a88d8c83d510 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 17 Sep 2026 12:03:31 +0200 Subject: [PATCH 1/4] feat(phpstan): import strict rules Signed-off-by: Carl Schwan --- phpstan.neon | 6 ++++ vendor-bin/phpstan/composer.json | 3 +- vendor-bin/phpstan/composer.lock | 54 +++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index e746422c4..7b7223e9d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,8 @@ # SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors # SPDX-License-Identifier: AGPL-3.0-or-later +includes: + - vendor-bin/phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon + parameters: level: 10 phpVersion: @@ -9,6 +12,9 @@ parameters: - lib - templates - tests + strictRules: + allRules: false + strictFunctionCalls: true excludePaths: - tests/stubs scanDirectories: diff --git a/vendor-bin/phpstan/composer.json b/vendor-bin/phpstan/composer.json index 5111129c6..602e266b1 100644 --- a/vendor-bin/phpstan/composer.json +++ b/vendor-bin/phpstan/composer.json @@ -1,5 +1,6 @@ { "require-dev": { - "phpstan/phpstan": "^2.2" + "phpstan/phpstan": "^2.2", + "phpstan/phpstan-strict-rules": "^2.0" } } diff --git a/vendor-bin/phpstan/composer.lock b/vendor-bin/phpstan/composer.lock index 53d98e585..b2bf85268 100644 --- a/vendor-bin/phpstan/composer.lock +++ b/vendor-bin/phpstan/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": "a51b3f39b291c98954d42def89bce21e", + "content-hash": "1e46f1af4fc47e311cc3d5016311ba28", "packages": [], "packages-dev": [ { @@ -70,6 +70,58 @@ } ], "time": "2026-08-22T07:38:16+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.12", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "2bc5ae19ae965663b62ac907ee6342c3903ec93b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/2bc5ae19ae965663b62ac907ee6342c3903ec93b", + "reference": "2bc5ae19ae965663b62ac907ee6342c3903ec93b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.52" + }, + "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", + "shipmonk/name-collision-detector": "^2.1" + }, + "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", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.12" + }, + "time": "2026-07-19T07:24:06+00:00" } ], "aliases": [], From 7d88be0b0a28789aa609adfc7f456d1e2148729f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 17 Sep 2026 12:04:41 +0200 Subject: [PATCH 2/4] fix(phpstan): Setup strictFunctionCalls Signed-off-by: Carl Schwan --- lib/ACL/Rule.php | 2 +- lib/Folder/FolderManager.php | 4 ++-- lib/Mount/MountProvider.php | 2 +- tests/ACL/ACLManagerTest.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ACL/Rule.php b/lib/ACL/Rule.php index 874be5a8d..125602216 100644 --- a/lib/ACL/Rule.php +++ b/lib/ACL/Rule.php @@ -140,7 +140,7 @@ public static function xmlDeserialize(Reader $reader): Rule { throw new \RuntimeException(self::MAPPING_TYPE . ' is not a string.'); } - if (!in_array($mappingType, ['user', 'group', 'circle'])) { + if (!in_array($mappingType, ['user', 'group', 'circle'], true)) { throw new \RuntimeException(self::MAPPING_TYPE . ' does not have a valid value.'); } diff --git a/lib/Folder/FolderManager.php b/lib/Folder/FolderManager.php index 46853da25..4ecaf45fc 100644 --- a/lib/Folder/FolderManager.php +++ b/lib/Folder/FolderManager.php @@ -1536,12 +1536,12 @@ public function updateOverwriteHomeFolders(): void { $appIdsList = $this->appConfig->getValueArray('files', 'overwrites_home_folders'); if ($this->hasHomeFolderOverwriteMount()) { - if (!in_array(Application::APP_ID, $appIdsList)) { + if (!in_array(Application::APP_ID, $appIdsList, true)) { $appIdsList[] = Application::APP_ID; $this->appConfig->setValueArray('files', 'overwrites_home_folders', $appIdsList); } } else { - if (in_array(Application::APP_ID, $appIdsList)) { + if (in_array(Application::APP_ID, $appIdsList, true)) { $appIdsList = array_values(array_filter($appIdsList, fn ($v): bool => $v !== Application::APP_ID)); $this->appConfig->setValueArray('files', 'overwrites_home_folders', $appIdsList); } diff --git a/lib/Mount/MountProvider.php b/lib/Mount/MountProvider.php index a84777dc2..60227986f 100644 --- a/lib/Mount/MountProvider.php +++ b/lib/Mount/MountProvider.php @@ -72,7 +72,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader): array { return array_map(function (FolderDefinitionWithPermissions $folder) use ($user, $loader, $conflicts, $aclManager, $rootRules): IMountPoint { // check for existing files in the user home and rename them if needed $originalFolderName = $folder->mountPoint; - if (in_array($originalFolderName, $conflicts)) { + if (in_array($originalFolderName, $conflicts, true)) { /** @var IStorage $userStorage */ $userStorage = $this->mountProviderCollection->getHomeMountForUser($user)->getStorage(); $userCache = $userStorage->getCache(); diff --git a/tests/ACL/ACLManagerTest.php b/tests/ACL/ACLManagerTest.php index c78382e11..f526119e1 100644 --- a/tests/ACL/ACLManagerTest.php +++ b/tests/ACL/ACLManagerTest.php @@ -48,7 +48,7 @@ protected function setUp(): void { /** @var string[] $paths */ $this->requestedPaths = array_values(array_merge($this->requestedPaths, $paths)); $rules = array_fill_keys($paths, []); - $actualRules = array_filter($this->rules, fn (string $path): bool => array_search($path, $paths) !== false, ARRAY_FILTER_USE_KEY); + $actualRules = array_filter($this->rules, fn (string $path): bool => array_search($path, $paths, true) !== false, ARRAY_FILTER_USE_KEY); return array_merge($rules, $actualRules); }); From 878a29cecdcf7cca0ffaf0c82d3007808aca677b Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 17 Sep 2026 12:07:36 +0200 Subject: [PATCH 3/4] fix(phpstan): Enable a few more strict rules Signed-off-by: Carl Schwan --- lib/ACL/ACLManager.php | 2 +- lib/ACL/RuleManager.php | 8 ++++---- lib/Command/Create.php | 2 +- lib/Command/Rename.php | 2 +- lib/Service/ApplicationService.php | 2 +- phpstan.neon | 3 +++ 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/ACL/ACLManager.php b/lib/ACL/ACLManager.php index 3aad0ad80..539708c27 100644 --- a/lib/ACL/ACLManager.php +++ b/lib/ACL/ACLManager.php @@ -64,7 +64,7 @@ private function getRules(int $storageId, array $paths, bool $cache = true): arr $nonCachedPaths = array_filter($paths, fn (string $path): bool => !isset($rules[$path])); - if (!empty($nonCachedPaths)) { + if ($nonCachedPaths !== []) { $newRules = $this->ruleManager->getRulesForFilesByPath($this->user, $storageId, $nonCachedPaths); foreach ($newRules as $path => $rulesForPath) { if ($cache) { diff --git a/lib/ACL/RuleManager.php b/lib/ACL/RuleManager.php index 268ec0b12..d1f635fd4 100644 --- a/lib/ACL/RuleManager.php +++ b/lib/ACL/RuleManager.php @@ -29,7 +29,7 @@ public function __construct( * @param array{mapping_type?: 'user'|'group'|'dummy'|'circle'|null, mapping_id: string, fileid: int|string, mask: int|string, permissions: int|string} $data */ private function createRule(array $data): ?Rule { - if (empty($data['mapping_type'])) { + if (!isset($data['mapping_type'])) { return null; } $mapping = $this->userMappingManager->mappingFromId($data['mapping_type'], $data['mapping_id']); @@ -116,7 +116,7 @@ public function getRulesForFilesByPath(IUser $user, int $storageId, array $fileP */ public function getRulesForFilesByIds(IUser $user, array $fileIds): array { $userMappings = $this->userMappingManager->getMappingsForUser($user); - if (empty($userMappings)) { + if ($userMappings === []) { return []; } @@ -144,7 +144,7 @@ public function getRulesForFilesByIds(IUser $user, array $fileIds): array { */ public function getRulesForFilesByParent(IUser $user, int $storageId, int $parentId): array { $userMappings = $this->userMappingManager->getMappingsForUser($user); - if (empty($userMappings)) { + if ($userMappings === []) { return []; } @@ -312,7 +312,7 @@ public function getAllRulesForPrefix(int $storageId, string $prefix): array { */ public function getRulesForPrefix(IUser $user, int $storageId, string $prefix): array { $userMappings = $this->userMappingManager->getMappingsForUser($user); - if (empty($userMappings)) { + if ($userMappings === []) { return []; } diff --git a/lib/Command/Create.php b/lib/Command/Create.php index a8c56c029..31f84dfec 100644 --- a/lib/Command/Create.php +++ b/lib/Command/Create.php @@ -40,7 +40,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $name = $this->folderManager->trimMountpoint($name); // Check if the folder name is valid - if (empty($name)) { + if ($name === '') { $output->writeln('Folder name cannot be empty'); return 1; } diff --git a/lib/Command/Rename.php b/lib/Command/Rename.php index dccf76309..7bff3675e 100644 --- a/lib/Command/Rename.php +++ b/lib/Command/Rename.php @@ -38,7 +38,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Check if the new name is valid $name = $this->folderManager->trimMountpoint($name); - if (empty($name)) { + if ($name === '') { $output->writeln('Folder name cannot be empty'); return 1; } diff --git a/lib/Service/ApplicationService.php b/lib/Service/ApplicationService.php index a5cffcae2..ebdd5b2e6 100644 --- a/lib/Service/ApplicationService.php +++ b/lib/Service/ApplicationService.php @@ -24,6 +24,6 @@ public function __construct( public function checkAppsInstalled(): bool { $diffApps = array_diff(Application::APPS_USE_GROUPFOLDERS, $this->appManager->getEnabledApps()); - return empty($diffApps); + return $diffApps === []; } } diff --git a/phpstan.neon b/phpstan.neon index 7b7223e9d..50bad73ae 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -15,6 +15,9 @@ parameters: strictRules: allRules: false strictFunctionCalls: true + disallowedLooseComparison: true + disallowedEmpty: true + disallowedImplicitArrayCreation: true excludePaths: - tests/stubs scanDirectories: From a1391568e6c514fe9be73fae93a835dda06f756a Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 17 Sep 2026 12:09:27 +0200 Subject: [PATCH 4/4] fix(phpstan): Fix method.nameCase issues Signed-off-by: Carl Schwan --- lib/Folder/FolderManager.php | 2 +- lib/Mount/GroupMountPoint.php | 2 +- lib/Service/FoldersFilter.php | 4 ++-- lib/Versions/VersionsBackend.php | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/Folder/FolderManager.php b/lib/Folder/FolderManager.php index 4ecaf45fc..9f394115a 100644 --- a/lib/Folder/FolderManager.php +++ b/lib/Folder/FolderManager.php @@ -528,7 +528,7 @@ public function canManageACL(int $folderId, IUser $user, bool $excludeAdmins = f } private function computeCanManageACL(int $folderId, IUser $user, bool $excludeAdmins): bool { - $userId = $user->getUId(); + $userId = $user->getUID(); if (!$excludeAdmins && $this->groupManager->isAdmin($userId)) { return true; } diff --git a/lib/Mount/GroupMountPoint.php b/lib/Mount/GroupMountPoint.php index c3493be4d..a6e56b663 100644 --- a/lib/Mount/GroupMountPoint.php +++ b/lib/Mount/GroupMountPoint.php @@ -54,7 +54,7 @@ public function getSourcePath(): string { $storage = $this->storage; if ($storage && $storage->instanceOfStorage(Jail::class)) { /** @var Jail $storage */ - return $storage->getUnJailedPath(''); + return $storage->getUnjailedPath(''); } return ''; diff --git a/lib/Service/FoldersFilter.php b/lib/Service/FoldersFilter.php index 6bfba5585..b0e12fd03 100644 --- a/lib/Service/FoldersFilter.php +++ b/lib/Service/FoldersFilter.php @@ -35,10 +35,10 @@ public function getForApiUser(array $folders): array { return array_filter($folders, function (array $folder) use ($user): bool { foreach ($folder['manage'] as $manager) { if ($manager['type'] === 'group') { - if ($this->groupManager->isInGroup($user->getUid(), $manager['id'])) { + if ($this->groupManager->isInGroup($user->getUID(), $manager['id'])) { return true; } - } elseif ($manager['id'] === $user->getUid()) { + } elseif ($manager['id'] === $user->getUID()) { return true; } } diff --git a/lib/Versions/VersionsBackend.php b/lib/Versions/VersionsBackend.php index 2d6f58a15..c232f7f8e 100644 --- a/lib/Versions/VersionsBackend.php +++ b/lib/Versions/VersionsBackend.php @@ -112,7 +112,7 @@ public function getVersionsForFile(IUser $user, FileInfo $file): array { throw new RuntimeException('Failed to get id of file.'); } $versionEntity->setFileId($fileId); - $versionEntity->setTimestamp($file->getMTime()); + $versionEntity->setTimestamp($file->getMtime()); $versionEntity->setSize($file->getSize()); $versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype())); $versionEntity->setDecodedMetadata([]); @@ -135,7 +135,7 @@ public function getVersionsForFile(IUser $user, FileInfo $file): array { // This means that the name of some versions is the exact mtime of the next version. This behavior is now fixed. // To prevent occasional conflicts between the last version and the current one, we decrement the last version mtime. $mtime = (int)$version->getName(); - if ($mtime === $file->getMTime()) { + if ($mtime === $file->getMtime()) { $versionEntity->setTimestamp($mtime - 1); $version->move($version->getParent()->getPath() . '/' . ($mtime - 1)); } else { @@ -427,7 +427,7 @@ public function createVersionEntity(File $file): null { $versionEntity->setFileId($fileId); $versionEntity->setTimestamp($timestamp); $versionEntity->setSize($file->getSize()); - $versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype())); + $versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimeType())); $versionEntity->setDecodedMetadata([]); $this->groupVersionsMapper->insert($versionEntity); } @@ -494,7 +494,7 @@ public function importVersionsForFile(IUser $user, Node $source, Node $target, a $versionEntity->setFileId($target->getId()); $versionEntity->setTimestamp($version->getTimestamp()); $versionEntity->setSize($version->getSize()); - $versionEntity->setMimetype($this->mimeTypeLoader->getId($version->getMimetype())); + $versionEntity->setMimetype($this->mimeTypeLoader->getId($version->getMimeType())); if ($version instanceof IMetadataVersion) { $versionEntity->setDecodedMetadata($version->getMetadata()); }