Skip to content

Commit d1e6f3e

Browse files
authored
Update to PHP 8.5 (#4470)
2 parents 6d0b0a1 + 5c94efd commit d1e6f3e

21 files changed

Lines changed: 578 additions & 184 deletions

.github/workflows/run-checks-tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-22.04
1010
strategy:
1111
matrix:
12-
php-versions: ['8.3']
12+
php-versions: ['8.5']
1313
composer-prefered-dependencies: ['--prefer-lowest', '']
1414
fail-fast: false
1515
steps:

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
}
1717
],
1818
"require": {
19-
"php": "^8.3",
19+
"php": "^8.5",
2020
"ext-tokenizer": "*",
2121
"friendsofphp/php-cs-fixer": "^3.58.1",
2222
"nette/utils": "^4.0",

ecs.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
use Shopsys\CodingStandards\Sniffs\General\ForbiddenExitSniff;
101101
use Shopsys\CodingStandards\Sniffs\General\ForbiddenSuperGlobalSniff;
102102
use Shopsys\CodingStandards\Sniffs\General\ObjectIsCreatedByFactorySniff;
103+
use Shopsys\CodingStandards\Sniffs\General\RequireOverrideAttributeOnPropertySniff;
103104
use Shopsys\CodingStandards\Sniffs\General\RequireOverrideAttributeSniff;
104105
use Shopsys\CodingStandards\Sniffs\General\ValidVariableNameSniff;
105106
use SlevomatCodingStandard\Sniffs\Arrays\TrailingArrayCommaSniff;
@@ -254,6 +255,7 @@
254255
DisallowEmptySniff::class,
255256
ParentCallSpacingSniff::class,
256257
UselessIfConditionWithReturnSniff::class,
258+
RequireOverrideAttributeOnPropertySniff::class,
257259
RequireOverrideAttributeSniff::class,
258260
ParameterTypeHintSniff::class,
259261
ReturnTypeHintSniff::class,

phpunit.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,8 @@
2525
<directory>tests/Unit</directory>
2626
</testsuite>
2727
</testsuites>
28+
<php>
29+
<ini name="error_reporting" value="24575"/>
30+
</php>
2831
<source/>
2932
</phpunit>
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shopsys\CodingStandards\Sniffs\General;
6+
7+
use Override;
8+
use PHP_CodeSniffer\Files\File;
9+
use PHP_CodeSniffer\Sniffs\Sniff;
10+
use ReflectionClass;
11+
use SlevomatCodingStandard\Helpers\NamespaceHelper;
12+
use SlevomatCodingStandard\Helpers\UseStatementHelper;
13+
14+
use const T_ANON_CLASS;
15+
16+
abstract class AbstractRequireOverrideAttributeSniff implements Sniff
17+
{
18+
/**
19+
* @return array<int|string>
20+
*/
21+
#[Override]
22+
public function register(): array
23+
{
24+
return [T_CLASS];
25+
}
26+
27+
/**
28+
* @param int $stackPtr
29+
* @throws \ReflectionException
30+
*/
31+
#[Override]
32+
public function process(File $phpcsFile, $stackPtr): void
33+
{
34+
$tokens = $phpcsFile->getTokens();
35+
36+
if ($tokens[$stackPtr]['code'] === T_ANON_CLASS) {
37+
return;
38+
}
39+
40+
$classNamesToCheck = $this->getInterfaceNames($phpcsFile, $stackPtr);
41+
$parentClassName = $this->getParentClassName($phpcsFile, $stackPtr);
42+
43+
if ($parentClassName !== null) {
44+
$classNamesToCheck[] = $parentClassName;
45+
}
46+
47+
foreach ($classNamesToCheck as $className) {
48+
$this->processClassMembers($phpcsFile, $stackPtr, new ReflectionClass($className));
49+
}
50+
}
51+
52+
abstract protected function processClassMembers(
53+
File $phpcsFile,
54+
int $classPtr,
55+
ReflectionClass $parentClass,
56+
): void;
57+
58+
protected function getParentClassName(File $phpcsFile, int $stackPtr): ?string
59+
{
60+
$tokens = $phpcsFile->getTokens();
61+
$extendsPtr = $phpcsFile->findNext(T_EXTENDS, $stackPtr, $tokens[$stackPtr]['scope_opener']);
62+
63+
if ($extendsPtr === false) {
64+
return null;
65+
}
66+
67+
$parentClassPtr = $phpcsFile->findNext(T_STRING, $extendsPtr);
68+
69+
if ($parentClassPtr === false) {
70+
return null;
71+
}
72+
73+
$parentClassName = $tokens[$parentClassPtr]['content'];
74+
75+
return $this->resolveClassName($phpcsFile, $parentClassName, $parentClassPtr);
76+
}
77+
78+
/**
79+
* @return array<string>
80+
*/
81+
protected function getInterfaceNames(File $phpcsFile, int $stackPtr): array
82+
{
83+
$tokens = $phpcsFile->getTokens();
84+
$interfaceNames = [];
85+
86+
$implementsPtr = $phpcsFile->findNext(T_IMPLEMENTS, $stackPtr, $tokens[$stackPtr]['scope_opener']);
87+
88+
if ($implementsPtr === false) {
89+
return $interfaceNames;
90+
}
91+
92+
$currentPtr = $implementsPtr;
93+
$classOpenPtr = $tokens[$stackPtr]['scope_opener'];
94+
95+
while (($currentPtr = $phpcsFile->findNext(T_STRING, $currentPtr + 1, $classOpenPtr)) !== false) {
96+
$interfaceName = $tokens[$currentPtr]['content'];
97+
$resolvedName = $this->resolveClassName($phpcsFile, $interfaceName, $currentPtr);
98+
99+
if ($resolvedName !== null) {
100+
$interfaceNames[] = $resolvedName;
101+
}
102+
}
103+
104+
return $interfaceNames;
105+
}
106+
107+
protected function resolveClassName(File $phpcsFile, string $className, int $position): ?string
108+
{
109+
$allUseStatements = UseStatementHelper::getFileUseStatements($phpcsFile);
110+
111+
foreach ($allUseStatements as $useStatements) {
112+
// PHP use statements are case-insensitive
113+
$lowerClassName = strtolower($className);
114+
115+
if (isset($useStatements[$lowerClassName])) {
116+
return $useStatements[$lowerClassName]->getFullyQualifiedTypeName();
117+
}
118+
}
119+
120+
$namespace = NamespaceHelper::findCurrentNamespaceName($phpcsFile, $position);
121+
122+
return $namespace ? $namespace . '\\' . $className : $className;
123+
}
124+
125+
/**
126+
* Adds the #[Override] attribute before the given token pointer and a `use Override;` statement if needed.
127+
*/
128+
protected function applyOverrideAttributeFix(File $phpcsFile, int $searchFromPtr): void
129+
{
130+
$tokens = $phpcsFile->getTokens();
131+
$phpcsFile->fixer->beginChangeset();
132+
133+
$previousLinePtr = $phpcsFile->findPrevious(T_WHITESPACE, $searchFromPtr, null, false, PHP_EOL);
134+
135+
// Get indentation from the next line
136+
$indent = '';
137+
138+
if ($previousLinePtr !== false && isset($tokens[$previousLinePtr + 1])) {
139+
$nextToken = $tokens[$previousLinePtr + 1];
140+
141+
if ($nextToken['code'] === T_WHITESPACE) {
142+
$indent = $nextToken['content'];
143+
}
144+
}
145+
146+
// Add the #[Override] attribute
147+
$phpcsFile->fixer->addContentBefore($previousLinePtr + 1, $indent . '#[Override]' . PHP_EOL);
148+
149+
// Add use statement if needed
150+
if (!$this->hasOverrideUseStatement($phpcsFile)) {
151+
$this->addUseStatement($phpcsFile, 'Override');
152+
}
153+
154+
$phpcsFile->fixer->endChangeset();
155+
}
156+
157+
protected function hasOverrideUseStatement(File $phpcsFile): bool
158+
{
159+
$allUseStatements = UseStatementHelper::getFileUseStatements($phpcsFile);
160+
161+
foreach ($allUseStatements as $useStatements) {
162+
// Use statements are stored with lowercase keys
163+
if (isset($useStatements['override'])) {
164+
return true;
165+
}
166+
}
167+
168+
return false;
169+
}
170+
171+
protected function addUseStatement(File $phpcsFile, string $className): void
172+
{
173+
// First try to find existing use statements
174+
$usePtr = $phpcsFile->findNext(T_USE, 0);
175+
176+
if ($usePtr !== false) {
177+
// There are existing use statements, add before the first one
178+
$previousLinePtr = $phpcsFile->findPrevious(T_WHITESPACE, $usePtr, null, false, PHP_EOL);
179+
$phpcsFile->fixer->addContentBefore($previousLinePtr + 1, 'use ' . $className . ';' . PHP_EOL);
180+
181+
return;
182+
}
183+
184+
// No existing use statements, find the namespace and add after it
185+
$namespacePtr = $phpcsFile->findNext(T_NAMESPACE, 0);
186+
187+
if ($namespacePtr === false) {
188+
return; // No namespace found, can't add use statement
189+
}
190+
191+
// Find the semicolon after the namespace declaration
192+
$semicolonPtr = $phpcsFile->findNext(T_SEMICOLON, $namespacePtr);
193+
194+
if ($semicolonPtr === false) {
195+
return;
196+
}
197+
198+
// Add the use statement after the namespace
199+
$phpcsFile->fixer->addContentBefore($semicolonPtr + 1, PHP_EOL . PHP_EOL . 'use ' . $className . ';');
200+
}
201+
}

src/Sniffs/General/ForbiddenDumpSniff.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Shopsys\CodingStandards\Sniffs\General;
66

7+
use Override;
78
use PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\ForbiddenFunctionsSniff;
89

910
final class ForbiddenDumpSniff extends ForbiddenFunctionsSniff
@@ -17,6 +18,7 @@ final class ForbiddenDumpSniff extends ForbiddenFunctionsSniff
1718
* @var string[]|null[]
1819
* @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint
1920
*/
21+
#[Override]
2022
public $forbiddenFunctions = [
2123
'd' => null,
2224
'dump' => null,

src/Sniffs/General/ForbiddenExitSniff.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Shopsys\CodingStandards\Sniffs\General;
66

7+
use Override;
78
use PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\ForbiddenFunctionsSniff;
89

910
final class ForbiddenExitSniff extends ForbiddenFunctionsSniff
@@ -17,6 +18,7 @@ final class ForbiddenExitSniff extends ForbiddenFunctionsSniff
1718
* @var string[]|null[]
1819
* @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint
1920
*/
21+
#[Override]
2022
public $forbiddenFunctions = [
2123
'exit' => null,
2224
];

0 commit comments

Comments
 (0)