Files
nextcloud-server-mirror/apps/testing/lib/Command/StaticHunt.php
Git'Fellow 4920bd1725 refactor: add PHP 8.3 types to BC-safe class constants
PHP 8.3 allows class constants to declare a type. Adopt it where doing so
cannot break third-party apps.

Typing an inheritable constant is a hard BC break: a subclass that
redeclares it untyped fails to load with "Type of C::FOO must be
compatible with P::FOO of type string". Changes are therefore limited to
constants that cannot be redeclared by a subclass:

  - private const (not inherited)
  - final public/protected const
  - constants declared in a final class or an enum

Interface constants, trait constants, and public/protected constants in
non-final (including abstract) classes are left untyped, as is all of
lib/public (OCP) and lib/unstable (NCU).

One review-requested exception: the public OBJECT_PREFIX/OBJECT_SUFFIX
constants of the app-internal CalDAV import helpers (TextImporter,
XmlImporter) are typed as well; these classes are not public API and
have no subclasses.

Only string, int and array are used. float is avoided because it would
silently coerce an int literal and change === comparisons. No constant
name or value is modified: every changed line adds only the type token,
so runtime behaviour is unchanged.

Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com>
2026-08-11 13:52:52 +01:00

112 lines
3.3 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Testing\Command;
use OCP\App\IAppManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class StaticHunt extends Command {
private const array SKIP_REGEX = [
'@/templates/.+.php$@',
'@/ajax/.+.php$@',
'@/register_command.php$@',
];
public function __construct(
private IAppManager $appManager,
) {
parent::__construct();
}
#[\Override]
protected function configure(): void {
$this
->setName('testing:static-hunt')
->setDescription('Hunt for static properties in classes');
}
#[\Override]
protected function execute(InputInterface $input, OutputInterface $output): int {
$folders = [
'' => __DIR__ . '/../../../../lib/private/legacy',
'\\OC' => __DIR__ . '/../../../../lib/private',
'\\OC\\Core' => __DIR__ . '/../../../../core',
];
$apps = $this->appManager->getAllAppsInAppsFolders();
foreach ($apps as $app) {
$info = $this->appManager->getAppInfo($app);
if (!isset($info['namespace'])) {
continue;
}
$folders['\\OCA\\' . $info['namespace']] = $this->appManager->getAppPath($app) . '/lib';
}
$stats = [
'classes' => 0,
'properties' => 0,
];
foreach ($folders as $namespace => $folder) {
$this->scanFolder($folder, $namespace, $output, $stats);
}
$output->writeln('<info>Found ' . $stats['properties'] . ' static properties spread among ' . $stats['classes'] . ' classes</info>');
return 0;
}
private function scanFolder(string $folder, string $namespace, OutputInterface $output, array &$stats): void {
$folder = realpath($folder);
$output->writeln('Folder ' . $folder, OutputInterface::VERBOSITY_VERBOSE);
foreach ($this->recursiveGlob($folder) as $filename) {
try {
$filename = realpath($filename);
if (($namespace === '\\OC') && str_contains($filename, 'lib/private/legacy')) {
// Skip legacy in OC as it’s scanned with an empty namespace separately
continue;
}
foreach (self::SKIP_REGEX as $skipRegex) {
if (preg_match($skipRegex, $filename)) {
continue 2;
}
}
$classname = $namespace . substr(str_replace('/', '\\', substr($filename, strlen($folder))), 0, -4);
$output->writeln('Class ' . $classname, OutputInterface::VERBOSITY_VERBOSE);
if (!class_exists($classname)) {
continue;
}
$rClass = new \ReflectionClass($classname);
$staticProperties = $rClass->getStaticProperties();
if (empty($staticProperties)) {
continue;
}
$stats['classes']++;
$output->writeln('<info># ' . str_replace(\OC::$SERVERROOT, '', $filename) . " $classname</info>");
foreach ($staticProperties as $property => $value) {
$propertyObject = $rClass->getProperty($property);
$stats['properties']++;
$output->write("$propertyObject");
}
$output->writeln('');
} catch (\Throwable $t) {
$output->writeln("$t");
}
}
}
private function recursiveGlob(string $path, int $depth = 1): \Generator {
$pattern = $path . str_repeat('/*', $depth);
yield from glob($pattern . '.php');
if (!empty(glob($pattern, GLOB_ONLYDIR))) {
yield from $this->recursiveGlob($path, $depth + 1);
}
}
}